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 CompletionDetailAlignment, CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings,
57 HideMouseMode, ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61 render_breadcrumb_text,
62};
63pub use git::blame::BlameRenderer;
64pub use hover_popover::hover_markdown_style;
65pub use inlays::Inlay;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
71 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
72 ToPoint,
73};
74pub use split::SplittableEditor;
75pub use text::Bias;
76
77use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
78use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
79use anyhow::{Context as _, Result, anyhow, bail};
80use blink_manager::BlinkManager;
81use buffer_diff::DiffHunkStatus;
82use client::{Collaborator, ParticipantIndex, parse_zed_link};
83use clock::ReplicaId;
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use collections::{BTreeMap, HashMap, HashSet, VecDeque};
89use convert_case::{Case, Casing};
90use dap::TelemetrySpawnLocation;
91use display_map::*;
92use edit_prediction_types::{
93 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionGranularity,
94 SuggestionDisplayType,
95};
96use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
97use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
98use futures::{
99 FutureExt, StreamExt as _,
100 future::{self, Shared, join},
101 stream::FuturesUnordered,
102};
103use fuzzy::{StringMatch, StringMatchCandidate};
104use git::blame::{GitBlame, GlobalBlameRenderer};
105use gpui::{
106 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
107 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
108 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
109 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
110 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
111 Render, ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextRun,
112 TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
113 WeakEntity, WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative,
114 size,
115};
116use hover_links::{HoverLink, HoveredLinkState, find_file};
117use hover_popover::{HoverState, hide_hover};
118use indent_guides::ActiveIndentGuidesState;
119use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
120use itertools::{Either, Itertools};
121use language::{
122 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
123 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
124 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
125 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
126 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
127 TreeSitterOptions, WordsQuery,
128 language_settings::{
129 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
130 all_language_settings, language_settings,
131 },
132 point_from_lsp, point_to_lsp, text_diff_with_options,
133};
134use linked_editing_ranges::refresh_linked_ranges;
135use lsp::{
136 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
137 LanguageServerId,
138};
139use lsp_colors::LspColorData;
140use markdown::Markdown;
141use mouse_context_menu::MouseContextMenu;
142use movement::TextLayoutDetails;
143use multi_buffer::{
144 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
145};
146use parking_lot::Mutex;
147use persistence::DB;
148use project::{
149 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
150 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
151 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
152 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
153 debugger::{
154 breakpoint_store::{
155 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
156 BreakpointStore, BreakpointStoreEvent,
157 },
158 session::{Session, SessionEvent},
159 },
160 git_store::GitStoreEvent,
161 lsp_store::{
162 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
163 OpenLspBufferHandle,
164 },
165 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
166};
167use rand::seq::SliceRandom;
168use regex::Regex;
169use rpc::{ErrorCode, ErrorExt, proto::PeerId};
170use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
171use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
172use serde::{Deserialize, Serialize};
173use settings::{
174 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
175 update_settings_file,
176};
177use smallvec::{SmallVec, smallvec};
178use snippet::Snippet;
179use std::{
180 any::{Any, TypeId},
181 borrow::Cow,
182 cell::{OnceCell, RefCell},
183 cmp::{self, Ordering, Reverse},
184 collections::hash_map,
185 iter::{self, Peekable},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 sync::Arc,
192 time::{Duration, Instant},
193};
194use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
196use theme::{
197 AccentColors, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::{CollapseDirection, SearchEvent},
212};
213
214use crate::{
215 code_context_menus::CompletionsMenuSource,
216 editor_settings::MultiCursorModifier,
217 hover_links::{find_url, find_url_from_range},
218 inlays::{
219 InlineValueCache,
220 inlay_hints::{LspInlayHintData, inlay_hint_settings},
221 },
222 scroll::{ScrollOffset, ScrollPixelOffset},
223 selections_collection::resolve_selections_wrapping_blocks,
224 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
225};
226
227pub const FILE_HEADER_HEIGHT: u32 = 2;
228pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
229const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
230const MAX_LINE_LEN: usize = 1024;
231const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
232const MAX_SELECTION_HISTORY_LEN: usize = 1024;
233pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
234#[doc(hidden)]
235pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
236pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
237
238pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
241pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
242
243pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
244pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
245pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
246
247pub type RenderDiffHunkControlsFn = Arc<
248 dyn Fn(
249 u32,
250 &DiffHunkStatus,
251 Range<Anchor>,
252 bool,
253 Pixels,
254 &Entity<Editor>,
255 &mut Window,
256 &mut App,
257 ) -> AnyElement,
258>;
259
260enum ReportEditorEvent {
261 Saved { auto_saved: bool },
262 EditorOpened,
263 Closed,
264}
265
266impl ReportEditorEvent {
267 pub fn event_type(&self) -> &'static str {
268 match self {
269 Self::Saved { .. } => "Editor Saved",
270 Self::EditorOpened => "Editor Opened",
271 Self::Closed => "Editor Closed",
272 }
273 }
274}
275
276pub enum ActiveDebugLine {}
277pub enum DebugStackFrameLine {}
278enum DocumentHighlightRead {}
279enum DocumentHighlightWrite {}
280enum InputComposition {}
281pub enum PendingInput {}
282enum SelectedTextHighlight {}
283
284pub enum ConflictsOuter {}
285pub enum ConflictsOurs {}
286pub enum ConflictsTheirs {}
287pub enum ConflictsOursMarker {}
288pub enum ConflictsTheirsMarker {}
289
290pub struct HunkAddedColor;
291pub struct HunkRemovedColor;
292
293#[derive(Debug, Copy, Clone, PartialEq, Eq)]
294pub enum Navigated {
295 Yes,
296 No,
297}
298
299impl Navigated {
300 pub fn from_bool(yes: bool) -> Navigated {
301 if yes { Navigated::Yes } else { Navigated::No }
302 }
303}
304
305#[derive(Debug, Clone, PartialEq, Eq)]
306enum DisplayDiffHunk {
307 Folded {
308 display_row: DisplayRow,
309 },
310 Unfolded {
311 is_created_file: bool,
312 diff_base_byte_range: Range<usize>,
313 display_row_range: Range<DisplayRow>,
314 multi_buffer_range: Range<Anchor>,
315 status: DiffHunkStatus,
316 word_diffs: Vec<Range<MultiBufferOffset>>,
317 },
318}
319
320pub enum HideMouseCursorOrigin {
321 TypingAction,
322 MovementAction,
323}
324
325pub fn init(cx: &mut App) {
326 cx.set_global(GlobalBlameRenderer(Arc::new(())));
327
328 workspace::register_project_item::<Editor>(cx);
329 workspace::FollowableViewRegistry::register::<Editor>(cx);
330 workspace::register_serializable_item::<Editor>(cx);
331
332 cx.observe_new(
333 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
334 workspace.register_action(Editor::new_file);
335 workspace.register_action(Editor::new_file_split);
336 workspace.register_action(Editor::new_file_vertical);
337 workspace.register_action(Editor::new_file_horizontal);
338 workspace.register_action(Editor::cancel_language_server_work);
339 workspace.register_action(Editor::toggle_focus);
340 },
341 )
342 .detach();
343
344 cx.on_action(move |_: &workspace::NewFile, cx| {
345 let app_state = workspace::AppState::global(cx);
346 if let Some(app_state) = app_state.upgrade() {
347 workspace::open_new(
348 Default::default(),
349 app_state,
350 cx,
351 |workspace, window, cx| {
352 Editor::new_file(workspace, &Default::default(), window, cx)
353 },
354 )
355 .detach();
356 }
357 })
358 .on_action(move |_: &workspace::NewWindow, cx| {
359 let app_state = workspace::AppState::global(cx);
360 if let Some(app_state) = app_state.upgrade() {
361 workspace::open_new(
362 Default::default(),
363 app_state,
364 cx,
365 |workspace, window, cx| {
366 cx.activate(true);
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373}
374
375pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
376 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
377}
378
379pub trait DiagnosticRenderer {
380 fn render_group(
381 &self,
382 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
383 buffer_id: BufferId,
384 snapshot: EditorSnapshot,
385 editor: WeakEntity<Editor>,
386 language_registry: Option<Arc<LanguageRegistry>>,
387 cx: &mut App,
388 ) -> Vec<BlockProperties<Anchor>>;
389
390 fn render_hover(
391 &self,
392 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
393 range: Range<Point>,
394 buffer_id: BufferId,
395 language_registry: Option<Arc<LanguageRegistry>>,
396 cx: &mut App,
397 ) -> Option<Entity<markdown::Markdown>>;
398
399 fn open_link(
400 &self,
401 editor: &mut Editor,
402 link: SharedString,
403 window: &mut Window,
404 cx: &mut Context<Editor>,
405 );
406}
407
408pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
409
410impl GlobalDiagnosticRenderer {
411 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
412 cx.try_global::<Self>().map(|g| g.0.clone())
413 }
414}
415
416impl gpui::Global for GlobalDiagnosticRenderer {}
417pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
418 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
419}
420
421pub struct SearchWithinRange;
422
423trait InvalidationRegion {
424 fn ranges(&self) -> &[Range<Anchor>];
425}
426
427#[derive(Clone, Debug, PartialEq)]
428pub enum SelectPhase {
429 Begin {
430 position: DisplayPoint,
431 add: bool,
432 click_count: usize,
433 },
434 BeginColumnar {
435 position: DisplayPoint,
436 reset: bool,
437 mode: ColumnarMode,
438 goal_column: u32,
439 },
440 Extend {
441 position: DisplayPoint,
442 click_count: usize,
443 },
444 Update {
445 position: DisplayPoint,
446 goal_column: u32,
447 scroll_delta: gpui::Point<f32>,
448 },
449 End,
450}
451
452#[derive(Clone, Debug, PartialEq)]
453pub enum ColumnarMode {
454 FromMouse,
455 FromSelection,
456}
457
458#[derive(Clone, Debug)]
459pub enum SelectMode {
460 Character,
461 Word(Range<Anchor>),
462 Line(Range<Anchor>),
463 All,
464}
465
466#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
467pub enum SizingBehavior {
468 /// The editor will layout itself using `size_full` and will include the vertical
469 /// scroll margin as requested by user settings.
470 #[default]
471 Default,
472 /// The editor will layout itself using `size_full`, but will not have any
473 /// vertical overscroll.
474 ExcludeOverscrollMargin,
475 /// The editor will request a vertical size according to its content and will be
476 /// layouted without a vertical scroll margin.
477 SizeByContent,
478}
479
480#[derive(Clone, PartialEq, Eq, Debug)]
481pub enum EditorMode {
482 SingleLine,
483 AutoHeight {
484 min_lines: usize,
485 max_lines: Option<usize>,
486 },
487 Full {
488 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
489 scale_ui_elements_with_buffer_font_size: bool,
490 /// When set to `true`, the editor will render a background for the active line.
491 show_active_line_background: bool,
492 /// Determines the sizing behavior for this editor
493 sizing_behavior: SizingBehavior,
494 },
495 Minimap {
496 parent: WeakEntity<Editor>,
497 },
498}
499
500impl EditorMode {
501 pub fn full() -> Self {
502 Self::Full {
503 scale_ui_elements_with_buffer_font_size: true,
504 show_active_line_background: true,
505 sizing_behavior: SizingBehavior::Default,
506 }
507 }
508
509 #[inline]
510 pub fn is_full(&self) -> bool {
511 matches!(self, Self::Full { .. })
512 }
513
514 #[inline]
515 pub fn is_single_line(&self) -> bool {
516 matches!(self, Self::SingleLine { .. })
517 }
518
519 #[inline]
520 fn is_minimap(&self) -> bool {
521 matches!(self, Self::Minimap { .. })
522 }
523}
524
525#[derive(Copy, Clone, Debug)]
526pub enum SoftWrap {
527 /// Prefer not to wrap at all.
528 ///
529 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
530 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
531 GitDiff,
532 /// Prefer a single line generally, unless an overly long line is encountered.
533 None,
534 /// Soft wrap lines that exceed the editor width.
535 EditorWidth,
536 /// Soft wrap lines at the preferred line length.
537 Column(u32),
538 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
539 Bounded(u32),
540}
541
542#[derive(Clone)]
543pub struct EditorStyle {
544 pub background: Hsla,
545 pub border: Hsla,
546 pub local_player: PlayerColor,
547 pub text: TextStyle,
548 pub scrollbar_width: Pixels,
549 pub syntax: Arc<SyntaxTheme>,
550 pub status: StatusColors,
551 pub inlay_hints_style: HighlightStyle,
552 pub edit_prediction_styles: EditPredictionStyles,
553 pub unnecessary_code_fade: f32,
554 pub show_underlines: bool,
555}
556
557impl Default for EditorStyle {
558 fn default() -> Self {
559 Self {
560 background: Hsla::default(),
561 border: Hsla::default(),
562 local_player: PlayerColor::default(),
563 text: TextStyle::default(),
564 scrollbar_width: Pixels::default(),
565 syntax: Default::default(),
566 // HACK: Status colors don't have a real default.
567 // We should look into removing the status colors from the editor
568 // style and retrieve them directly from the theme.
569 status: StatusColors::dark(),
570 inlay_hints_style: HighlightStyle::default(),
571 edit_prediction_styles: EditPredictionStyles {
572 insertion: HighlightStyle::default(),
573 whitespace: HighlightStyle::default(),
574 },
575 unnecessary_code_fade: Default::default(),
576 show_underlines: true,
577 }
578 }
579}
580
581pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
582 let show_background = language_settings::language_settings(None, None, cx)
583 .inlay_hints
584 .show_background;
585
586 let mut style = cx.theme().syntax().get("hint");
587
588 if style.color.is_none() {
589 style.color = Some(cx.theme().status().hint);
590 }
591
592 if !show_background {
593 style.background_color = None;
594 return style;
595 }
596
597 if style.background_color.is_none() {
598 style.background_color = Some(cx.theme().status().hint_background);
599 }
600
601 style
602}
603
604pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
605 EditPredictionStyles {
606 insertion: HighlightStyle {
607 color: Some(cx.theme().status().predictive),
608 ..HighlightStyle::default()
609 },
610 whitespace: HighlightStyle {
611 background_color: Some(cx.theme().status().created_background),
612 ..HighlightStyle::default()
613 },
614 }
615}
616
617type CompletionId = usize;
618
619pub(crate) enum EditDisplayMode {
620 TabAccept,
621 DiffPopover,
622 Inline,
623}
624
625enum EditPrediction {
626 Edit {
627 edits: Vec<(Range<Anchor>, Arc<str>)>,
628 edit_preview: Option<EditPreview>,
629 display_mode: EditDisplayMode,
630 snapshot: BufferSnapshot,
631 },
632 /// Move to a specific location in the active editor
633 MoveWithin {
634 target: Anchor,
635 snapshot: BufferSnapshot,
636 },
637 /// Move to a specific location in a different editor (not the active one)
638 MoveOutside {
639 target: language::Anchor,
640 snapshot: BufferSnapshot,
641 },
642}
643
644struct EditPredictionState {
645 inlay_ids: Vec<InlayId>,
646 completion: EditPrediction,
647 completion_id: Option<SharedString>,
648 invalidation_range: Option<Range<Anchor>>,
649}
650
651enum EditPredictionSettings {
652 Disabled,
653 Enabled {
654 show_in_menu: bool,
655 preview_requires_modifier: bool,
656 },
657}
658
659enum EditPredictionHighlight {}
660
661#[derive(Debug, Clone)]
662struct InlineDiagnostic {
663 message: SharedString,
664 group_id: usize,
665 is_primary: bool,
666 start: Point,
667 severity: lsp::DiagnosticSeverity,
668}
669
670pub enum MenuEditPredictionsPolicy {
671 Never,
672 ByProvider,
673}
674
675pub enum EditPredictionPreview {
676 /// Modifier is not pressed
677 Inactive { released_too_fast: bool },
678 /// Modifier pressed
679 Active {
680 since: Instant,
681 previous_scroll_position: Option<ScrollAnchor>,
682 },
683}
684
685impl EditPredictionPreview {
686 pub fn released_too_fast(&self) -> bool {
687 match self {
688 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
689 EditPredictionPreview::Active { .. } => false,
690 }
691 }
692
693 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
694 if let EditPredictionPreview::Active {
695 previous_scroll_position,
696 ..
697 } = self
698 {
699 *previous_scroll_position = scroll_position;
700 }
701 }
702}
703
704pub struct ContextMenuOptions {
705 pub min_entries_visible: usize,
706 pub max_entries_visible: usize,
707 pub placement: Option<ContextMenuPlacement>,
708}
709
710#[derive(Debug, Clone, PartialEq, Eq)]
711pub enum ContextMenuPlacement {
712 Above,
713 Below,
714}
715
716#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
717struct EditorActionId(usize);
718
719impl EditorActionId {
720 pub fn post_inc(&mut self) -> Self {
721 let answer = self.0;
722
723 *self = Self(answer + 1);
724
725 Self(answer)
726 }
727}
728
729// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
730// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
731
732type BackgroundHighlight = (
733 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
734 Arc<[Range<Anchor>]>,
735);
736type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
737
738#[derive(Default)]
739struct ScrollbarMarkerState {
740 scrollbar_size: Size<Pixels>,
741 dirty: bool,
742 markers: Arc<[PaintQuad]>,
743 pending_refresh: Option<Task<Result<()>>>,
744}
745
746impl ScrollbarMarkerState {
747 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
748 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
749 }
750}
751
752#[derive(Clone, Copy, PartialEq, Eq)]
753pub enum MinimapVisibility {
754 Disabled,
755 Enabled {
756 /// The configuration currently present in the users settings.
757 setting_configuration: bool,
758 /// Whether to override the currently set visibility from the users setting.
759 toggle_override: bool,
760 },
761}
762
763impl MinimapVisibility {
764 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
765 if mode.is_full() {
766 Self::Enabled {
767 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
768 toggle_override: false,
769 }
770 } else {
771 Self::Disabled
772 }
773 }
774
775 fn hidden(&self) -> Self {
776 match *self {
777 Self::Enabled {
778 setting_configuration,
779 ..
780 } => Self::Enabled {
781 setting_configuration,
782 toggle_override: setting_configuration,
783 },
784 Self::Disabled => Self::Disabled,
785 }
786 }
787
788 fn disabled(&self) -> bool {
789 matches!(*self, Self::Disabled)
790 }
791
792 fn settings_visibility(&self) -> bool {
793 match *self {
794 Self::Enabled {
795 setting_configuration,
796 ..
797 } => setting_configuration,
798 _ => false,
799 }
800 }
801
802 fn visible(&self) -> bool {
803 match *self {
804 Self::Enabled {
805 setting_configuration,
806 toggle_override,
807 } => setting_configuration ^ toggle_override,
808 _ => false,
809 }
810 }
811
812 fn toggle_visibility(&self) -> Self {
813 match *self {
814 Self::Enabled {
815 toggle_override,
816 setting_configuration,
817 } => Self::Enabled {
818 setting_configuration,
819 toggle_override: !toggle_override,
820 },
821 Self::Disabled => Self::Disabled,
822 }
823 }
824}
825
826#[derive(Debug, Clone, Copy, PartialEq, Eq)]
827pub enum BufferSerialization {
828 All,
829 NonDirtyBuffers,
830}
831
832impl BufferSerialization {
833 fn new(restore_unsaved_buffers: bool) -> Self {
834 if restore_unsaved_buffers {
835 Self::All
836 } else {
837 Self::NonDirtyBuffers
838 }
839 }
840}
841
842#[derive(Clone, Debug)]
843struct RunnableTasks {
844 templates: Vec<(TaskSourceKind, TaskTemplate)>,
845 offset: multi_buffer::Anchor,
846 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
847 column: u32,
848 // Values of all named captures, including those starting with '_'
849 extra_variables: HashMap<String, String>,
850 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
851 context_range: Range<BufferOffset>,
852}
853
854impl RunnableTasks {
855 fn resolve<'a>(
856 &'a self,
857 cx: &'a task::TaskContext,
858 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
859 self.templates.iter().filter_map(|(kind, template)| {
860 template
861 .resolve_task(&kind.to_id_base(), cx)
862 .map(|task| (kind.clone(), task))
863 })
864 }
865}
866
867#[derive(Clone)]
868pub struct ResolvedTasks {
869 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
870 position: Anchor,
871}
872
873/// Addons allow storing per-editor state in other crates (e.g. Vim)
874pub trait Addon: 'static {
875 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
876
877 fn render_buffer_header_controls(
878 &self,
879 _: &ExcerptInfo,
880 _: &Window,
881 _: &App,
882 ) -> Option<AnyElement> {
883 None
884 }
885
886 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
887 None
888 }
889
890 fn to_any(&self) -> &dyn std::any::Any;
891
892 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
893 None
894 }
895}
896
897struct ChangeLocation {
898 current: Option<Vec<Anchor>>,
899 original: Vec<Anchor>,
900}
901impl ChangeLocation {
902 fn locations(&self) -> &[Anchor] {
903 self.current.as_ref().unwrap_or(&self.original)
904 }
905}
906
907/// A set of caret positions, registered when the editor was edited.
908pub struct ChangeList {
909 changes: Vec<ChangeLocation>,
910 /// Currently "selected" change.
911 position: Option<usize>,
912}
913
914impl ChangeList {
915 pub fn new() -> Self {
916 Self {
917 changes: Vec::new(),
918 position: None,
919 }
920 }
921
922 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
923 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
924 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
925 if self.changes.is_empty() {
926 return None;
927 }
928
929 let prev = self.position.unwrap_or(self.changes.len());
930 let next = if direction == Direction::Prev {
931 prev.saturating_sub(count)
932 } else {
933 (prev + count).min(self.changes.len() - 1)
934 };
935 self.position = Some(next);
936 self.changes.get(next).map(|change| change.locations())
937 }
938
939 /// Adds a new change to the list, resetting the change list position.
940 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
941 self.position.take();
942 if let Some(last) = self.changes.last_mut()
943 && group
944 {
945 last.current = Some(new_positions)
946 } else {
947 self.changes.push(ChangeLocation {
948 original: new_positions,
949 current: None,
950 });
951 }
952 }
953
954 pub fn last(&self) -> Option<&[Anchor]> {
955 self.changes.last().map(|change| change.locations())
956 }
957
958 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
959 self.changes.last().map(|change| change.original.as_slice())
960 }
961
962 pub fn invert_last_group(&mut self) {
963 if let Some(last) = self.changes.last_mut()
964 && let Some(current) = last.current.as_mut()
965 {
966 mem::swap(&mut last.original, current);
967 }
968 }
969}
970
971#[derive(Clone)]
972struct InlineBlamePopoverState {
973 scroll_handle: ScrollHandle,
974 commit_message: Option<ParsedCommitMessage>,
975 markdown: Entity<Markdown>,
976}
977
978struct InlineBlamePopover {
979 position: gpui::Point<Pixels>,
980 hide_task: Option<Task<()>>,
981 popover_bounds: Option<Bounds<Pixels>>,
982 popover_state: InlineBlamePopoverState,
983 keyboard_grace: bool,
984}
985
986enum SelectionDragState {
987 /// State when no drag related activity is detected.
988 None,
989 /// State when the mouse is down on a selection that is about to be dragged.
990 ReadyToDrag {
991 selection: Selection<Anchor>,
992 click_position: gpui::Point<Pixels>,
993 mouse_down_time: Instant,
994 },
995 /// State when the mouse is dragging the selection in the editor.
996 Dragging {
997 selection: Selection<Anchor>,
998 drop_cursor: Selection<Anchor>,
999 hide_drop_cursor: bool,
1000 },
1001}
1002
1003enum ColumnarSelectionState {
1004 FromMouse {
1005 selection_tail: Anchor,
1006 display_point: Option<DisplayPoint>,
1007 },
1008 FromSelection {
1009 selection_tail: Anchor,
1010 },
1011}
1012
1013/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1014/// a breakpoint on them.
1015#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1016struct PhantomBreakpointIndicator {
1017 display_row: DisplayRow,
1018 /// There's a small debounce between hovering over the line and showing the indicator.
1019 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1020 is_active: bool,
1021 collides_with_existing_breakpoint: bool,
1022}
1023
1024/// Represents a diff review button indicator that shows up when hovering over lines in the gutter
1025/// in diff view mode.
1026#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1027pub(crate) struct PhantomDiffReviewIndicator {
1028 pub display_row: DisplayRow,
1029 /// There's a small debounce between hovering over the line and showing the indicator.
1030 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1031 pub is_active: bool,
1032}
1033
1034/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1035///
1036/// See the [module level documentation](self) for more information.
1037pub struct Editor {
1038 focus_handle: FocusHandle,
1039 last_focused_descendant: Option<WeakFocusHandle>,
1040 /// The text buffer being edited
1041 buffer: Entity<MultiBuffer>,
1042 /// Map of how text in the buffer should be displayed.
1043 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1044 pub display_map: Entity<DisplayMap>,
1045 placeholder_display_map: Option<Entity<DisplayMap>>,
1046 pub selections: SelectionsCollection,
1047 pub scroll_manager: ScrollManager,
1048 /// When inline assist editors are linked, they all render cursors because
1049 /// typing enters text into each of them, even the ones that aren't focused.
1050 pub(crate) show_cursor_when_unfocused: bool,
1051 columnar_selection_state: Option<ColumnarSelectionState>,
1052 add_selections_state: Option<AddSelectionsState>,
1053 select_next_state: Option<SelectNextState>,
1054 select_prev_state: Option<SelectNextState>,
1055 selection_history: SelectionHistory,
1056 defer_selection_effects: bool,
1057 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1058 autoclose_regions: Vec<AutocloseRegion>,
1059 snippet_stack: InvalidationStack<SnippetState>,
1060 select_syntax_node_history: SelectSyntaxNodeHistory,
1061 ime_transaction: Option<TransactionId>,
1062 pub diagnostics_max_severity: DiagnosticSeverity,
1063 active_diagnostics: ActiveDiagnostic,
1064 show_inline_diagnostics: bool,
1065 inline_diagnostics_update: Task<()>,
1066 inline_diagnostics_enabled: bool,
1067 diagnostics_enabled: bool,
1068 word_completions_enabled: bool,
1069 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1070 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1071 hard_wrap: Option<usize>,
1072 project: Option<Entity<Project>>,
1073 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1074 completion_provider: Option<Rc<dyn CompletionProvider>>,
1075 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1076 blink_manager: Entity<BlinkManager>,
1077 show_cursor_names: bool,
1078 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1079 pub show_local_selections: bool,
1080 mode: EditorMode,
1081 show_breadcrumbs: bool,
1082 show_gutter: bool,
1083 show_scrollbars: ScrollbarAxes,
1084 minimap_visibility: MinimapVisibility,
1085 offset_content: bool,
1086 disable_expand_excerpt_buttons: bool,
1087 delegate_expand_excerpts: bool,
1088 show_line_numbers: Option<bool>,
1089 use_relative_line_numbers: Option<bool>,
1090 show_git_diff_gutter: Option<bool>,
1091 show_code_actions: Option<bool>,
1092 show_runnables: Option<bool>,
1093 show_breakpoints: Option<bool>,
1094 show_diff_review_button: bool,
1095 show_wrap_guides: Option<bool>,
1096 show_indent_guides: Option<bool>,
1097 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1098 highlight_order: usize,
1099 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1100 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1101 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1102 scrollbar_marker_state: ScrollbarMarkerState,
1103 active_indent_guides_state: ActiveIndentGuidesState,
1104 nav_history: Option<ItemNavHistory>,
1105 context_menu: RefCell<Option<CodeContextMenu>>,
1106 context_menu_options: Option<ContextMenuOptions>,
1107 mouse_context_menu: Option<MouseContextMenu>,
1108 completion_tasks: Vec<(CompletionId, Task<()>)>,
1109 inline_blame_popover: Option<InlineBlamePopover>,
1110 inline_blame_popover_show_task: Option<Task<()>>,
1111 signature_help_state: SignatureHelpState,
1112 auto_signature_help: Option<bool>,
1113 find_all_references_task_sources: Vec<Anchor>,
1114 next_completion_id: CompletionId,
1115 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1116 code_actions_task: Option<Task<Result<()>>>,
1117 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1118 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1119 document_highlights_task: Option<Task<()>>,
1120 linked_editing_range_task: Option<Task<Option<()>>>,
1121 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1122 pending_rename: Option<RenameState>,
1123 searchable: bool,
1124 cursor_shape: CursorShape,
1125 /// Whether the cursor is offset one character to the left when something is
1126 /// selected (needed for vim visual mode)
1127 cursor_offset_on_selection: bool,
1128 current_line_highlight: Option<CurrentLineHighlight>,
1129 pub collapse_matches: bool,
1130 autoindent_mode: Option<AutoindentMode>,
1131 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1132 input_enabled: bool,
1133 use_modal_editing: bool,
1134 read_only: bool,
1135 leader_id: Option<CollaboratorId>,
1136 remote_id: Option<ViewId>,
1137 pub hover_state: HoverState,
1138 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1139 prev_pressure_stage: Option<PressureStage>,
1140 gutter_hovered: bool,
1141 hovered_link_state: Option<HoveredLinkState>,
1142 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1143 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1144 active_edit_prediction: Option<EditPredictionState>,
1145 /// Used to prevent flickering as the user types while the menu is open
1146 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1147 edit_prediction_settings: EditPredictionSettings,
1148 edit_predictions_hidden_for_vim_mode: bool,
1149 show_edit_predictions_override: Option<bool>,
1150 show_completions_on_input_override: Option<bool>,
1151 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1152 edit_prediction_preview: EditPredictionPreview,
1153 edit_prediction_indent_conflict: bool,
1154 edit_prediction_requires_modifier_in_indent_conflict: bool,
1155 next_inlay_id: usize,
1156 next_color_inlay_id: usize,
1157 _subscriptions: Vec<Subscription>,
1158 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1159 gutter_dimensions: GutterDimensions,
1160 style: Option<EditorStyle>,
1161 text_style_refinement: Option<TextStyleRefinement>,
1162 next_editor_action_id: EditorActionId,
1163 editor_actions: Rc<
1164 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1165 >,
1166 use_autoclose: bool,
1167 use_auto_surround: bool,
1168 auto_replace_emoji_shortcode: bool,
1169 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1170 show_git_blame_gutter: bool,
1171 show_git_blame_inline: bool,
1172 show_git_blame_inline_delay_task: Option<Task<()>>,
1173 git_blame_inline_enabled: bool,
1174 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1175 buffer_serialization: Option<BufferSerialization>,
1176 show_selection_menu: Option<bool>,
1177 blame: Option<Entity<GitBlame>>,
1178 blame_subscription: Option<Subscription>,
1179 custom_context_menu: Option<
1180 Box<
1181 dyn 'static
1182 + Fn(
1183 &mut Self,
1184 DisplayPoint,
1185 &mut Window,
1186 &mut Context<Self>,
1187 ) -> Option<Entity<ui::ContextMenu>>,
1188 >,
1189 >,
1190 last_bounds: Option<Bounds<Pixels>>,
1191 last_position_map: Option<Rc<PositionMap>>,
1192 expect_bounds_change: Option<Bounds<Pixels>>,
1193 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1194 tasks_update_task: Option<Task<()>>,
1195 breakpoint_store: Option<Entity<BreakpointStore>>,
1196 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1197 pub(crate) gutter_diff_review_indicator: (Option<PhantomDiffReviewIndicator>, Option<Task<()>>),
1198 hovered_diff_hunk_row: Option<DisplayRow>,
1199 pull_diagnostics_task: Task<()>,
1200 pull_diagnostics_background_task: Task<()>,
1201 in_project_search: bool,
1202 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1203 breadcrumb_header: Option<String>,
1204 focused_block: Option<FocusedBlock>,
1205 next_scroll_position: NextScrollCursorCenterTopBottom,
1206 addons: HashMap<TypeId, Box<dyn Addon>>,
1207 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1208 load_diff_task: Option<Shared<Task<()>>>,
1209 /// Whether we are temporarily displaying a diff other than git's
1210 temporary_diff_override: bool,
1211 selection_mark_mode: bool,
1212 toggle_fold_multiple_buffers: Task<()>,
1213 _scroll_cursor_center_top_bottom_task: Task<()>,
1214 serialize_selections: Task<()>,
1215 serialize_folds: Task<()>,
1216 mouse_cursor_hidden: bool,
1217 minimap: Option<Entity<Self>>,
1218 hide_mouse_mode: HideMouseMode,
1219 pub change_list: ChangeList,
1220 inline_value_cache: InlineValueCache,
1221 number_deleted_lines: bool,
1222
1223 selection_drag_state: SelectionDragState,
1224 colors: Option<LspColorData>,
1225 post_scroll_update: Task<()>,
1226 refresh_colors_task: Task<()>,
1227 inlay_hints: Option<LspInlayHintData>,
1228 folding_newlines: Task<()>,
1229 select_next_is_case_sensitive: Option<bool>,
1230 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1231 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1232 accent_data: Option<AccentData>,
1233 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1234}
1235
1236#[derive(Debug, PartialEq)]
1237struct AccentData {
1238 colors: AccentColors,
1239 overrides: Vec<SharedString>,
1240}
1241
1242fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1243 if debounce_ms > 0 {
1244 Some(Duration::from_millis(debounce_ms))
1245 } else {
1246 None
1247 }
1248}
1249
1250#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1251enum NextScrollCursorCenterTopBottom {
1252 #[default]
1253 Center,
1254 Top,
1255 Bottom,
1256}
1257
1258impl NextScrollCursorCenterTopBottom {
1259 fn next(&self) -> Self {
1260 match self {
1261 Self::Center => Self::Top,
1262 Self::Top => Self::Bottom,
1263 Self::Bottom => Self::Center,
1264 }
1265 }
1266}
1267
1268#[derive(Clone)]
1269pub struct EditorSnapshot {
1270 pub mode: EditorMode,
1271 show_gutter: bool,
1272 offset_content: bool,
1273 show_line_numbers: Option<bool>,
1274 number_deleted_lines: bool,
1275 show_git_diff_gutter: Option<bool>,
1276 show_code_actions: Option<bool>,
1277 show_runnables: Option<bool>,
1278 show_breakpoints: Option<bool>,
1279 git_blame_gutter_max_author_length: Option<usize>,
1280 pub display_snapshot: DisplaySnapshot,
1281 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1282 is_focused: bool,
1283 scroll_anchor: ScrollAnchor,
1284 ongoing_scroll: OngoingScroll,
1285 current_line_highlight: CurrentLineHighlight,
1286 gutter_hovered: bool,
1287}
1288
1289#[derive(Default, Debug, Clone, Copy)]
1290pub struct GutterDimensions {
1291 pub left_padding: Pixels,
1292 pub right_padding: Pixels,
1293 pub width: Pixels,
1294 pub margin: Pixels,
1295 pub git_blame_entries_width: Option<Pixels>,
1296}
1297
1298impl GutterDimensions {
1299 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1300 Self {
1301 margin: Self::default_gutter_margin(font_id, font_size, cx),
1302 ..Default::default()
1303 }
1304 }
1305
1306 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1307 -cx.text_system().descent(font_id, font_size)
1308 }
1309 /// The full width of the space taken up by the gutter.
1310 pub fn full_width(&self) -> Pixels {
1311 self.margin + self.width
1312 }
1313
1314 /// The width of the space reserved for the fold indicators,
1315 /// use alongside 'justify_end' and `gutter_width` to
1316 /// right align content with the line numbers
1317 pub fn fold_area_width(&self) -> Pixels {
1318 self.margin + self.right_padding
1319 }
1320}
1321
1322struct CharacterDimensions {
1323 em_width: Pixels,
1324 em_advance: Pixels,
1325 line_height: Pixels,
1326}
1327
1328#[derive(Debug)]
1329pub struct RemoteSelection {
1330 pub replica_id: ReplicaId,
1331 pub selection: Selection<Anchor>,
1332 pub cursor_shape: CursorShape,
1333 pub collaborator_id: CollaboratorId,
1334 pub line_mode: bool,
1335 pub user_name: Option<SharedString>,
1336 pub color: PlayerColor,
1337}
1338
1339#[derive(Clone, Debug)]
1340struct SelectionHistoryEntry {
1341 selections: Arc<[Selection<Anchor>]>,
1342 select_next_state: Option<SelectNextState>,
1343 select_prev_state: Option<SelectNextState>,
1344 add_selections_state: Option<AddSelectionsState>,
1345}
1346
1347#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1348enum SelectionHistoryMode {
1349 #[default]
1350 Normal,
1351 Undoing,
1352 Redoing,
1353 Skipping,
1354}
1355
1356#[derive(Clone, PartialEq, Eq, Hash)]
1357struct HoveredCursor {
1358 replica_id: ReplicaId,
1359 selection_id: usize,
1360}
1361
1362#[derive(Debug)]
1363/// SelectionEffects controls the side-effects of updating the selection.
1364///
1365/// The default behaviour does "what you mostly want":
1366/// - it pushes to the nav history if the cursor moved by >10 lines
1367/// - it re-triggers completion requests
1368/// - it scrolls to fit
1369///
1370/// You might want to modify these behaviours. For example when doing a "jump"
1371/// like go to definition, we always want to add to nav history; but when scrolling
1372/// in vim mode we never do.
1373///
1374/// Similarly, you might want to disable scrolling if you don't want the viewport to
1375/// move.
1376#[derive(Clone)]
1377pub struct SelectionEffects {
1378 nav_history: Option<bool>,
1379 completions: bool,
1380 scroll: Option<Autoscroll>,
1381}
1382
1383impl Default for SelectionEffects {
1384 fn default() -> Self {
1385 Self {
1386 nav_history: None,
1387 completions: true,
1388 scroll: Some(Autoscroll::fit()),
1389 }
1390 }
1391}
1392impl SelectionEffects {
1393 pub fn scroll(scroll: Autoscroll) -> Self {
1394 Self {
1395 scroll: Some(scroll),
1396 ..Default::default()
1397 }
1398 }
1399
1400 pub fn no_scroll() -> Self {
1401 Self {
1402 scroll: None,
1403 ..Default::default()
1404 }
1405 }
1406
1407 pub fn completions(self, completions: bool) -> Self {
1408 Self {
1409 completions,
1410 ..self
1411 }
1412 }
1413
1414 pub fn nav_history(self, nav_history: bool) -> Self {
1415 Self {
1416 nav_history: Some(nav_history),
1417 ..self
1418 }
1419 }
1420}
1421
1422struct DeferredSelectionEffectsState {
1423 changed: bool,
1424 effects: SelectionEffects,
1425 old_cursor_position: Anchor,
1426 history_entry: SelectionHistoryEntry,
1427}
1428
1429#[derive(Default)]
1430struct SelectionHistory {
1431 #[allow(clippy::type_complexity)]
1432 selections_by_transaction:
1433 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1434 mode: SelectionHistoryMode,
1435 undo_stack: VecDeque<SelectionHistoryEntry>,
1436 redo_stack: VecDeque<SelectionHistoryEntry>,
1437}
1438
1439impl SelectionHistory {
1440 #[track_caller]
1441 fn insert_transaction(
1442 &mut self,
1443 transaction_id: TransactionId,
1444 selections: Arc<[Selection<Anchor>]>,
1445 ) {
1446 if selections.is_empty() {
1447 log::error!(
1448 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1449 std::panic::Location::caller()
1450 );
1451 return;
1452 }
1453 self.selections_by_transaction
1454 .insert(transaction_id, (selections, None));
1455 }
1456
1457 #[allow(clippy::type_complexity)]
1458 fn transaction(
1459 &self,
1460 transaction_id: TransactionId,
1461 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1462 self.selections_by_transaction.get(&transaction_id)
1463 }
1464
1465 #[allow(clippy::type_complexity)]
1466 fn transaction_mut(
1467 &mut self,
1468 transaction_id: TransactionId,
1469 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1470 self.selections_by_transaction.get_mut(&transaction_id)
1471 }
1472
1473 fn push(&mut self, entry: SelectionHistoryEntry) {
1474 if !entry.selections.is_empty() {
1475 match self.mode {
1476 SelectionHistoryMode::Normal => {
1477 self.push_undo(entry);
1478 self.redo_stack.clear();
1479 }
1480 SelectionHistoryMode::Undoing => self.push_redo(entry),
1481 SelectionHistoryMode::Redoing => self.push_undo(entry),
1482 SelectionHistoryMode::Skipping => {}
1483 }
1484 }
1485 }
1486
1487 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1488 if self
1489 .undo_stack
1490 .back()
1491 .is_none_or(|e| e.selections != entry.selections)
1492 {
1493 self.undo_stack.push_back(entry);
1494 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1495 self.undo_stack.pop_front();
1496 }
1497 }
1498 }
1499
1500 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1501 if self
1502 .redo_stack
1503 .back()
1504 .is_none_or(|e| e.selections != entry.selections)
1505 {
1506 self.redo_stack.push_back(entry);
1507 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1508 self.redo_stack.pop_front();
1509 }
1510 }
1511 }
1512}
1513
1514#[derive(Clone, Copy)]
1515pub struct RowHighlightOptions {
1516 pub autoscroll: bool,
1517 pub include_gutter: bool,
1518}
1519
1520impl Default for RowHighlightOptions {
1521 fn default() -> Self {
1522 Self {
1523 autoscroll: Default::default(),
1524 include_gutter: true,
1525 }
1526 }
1527}
1528
1529struct RowHighlight {
1530 index: usize,
1531 range: Range<Anchor>,
1532 color: Hsla,
1533 options: RowHighlightOptions,
1534 type_id: TypeId,
1535}
1536
1537#[derive(Clone, Debug)]
1538struct AddSelectionsState {
1539 groups: Vec<AddSelectionsGroup>,
1540}
1541
1542#[derive(Clone, Debug)]
1543struct AddSelectionsGroup {
1544 above: bool,
1545 stack: Vec<usize>,
1546}
1547
1548#[derive(Clone)]
1549struct SelectNextState {
1550 query: AhoCorasick,
1551 wordwise: bool,
1552 done: bool,
1553}
1554
1555impl std::fmt::Debug for SelectNextState {
1556 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1557 f.debug_struct(std::any::type_name::<Self>())
1558 .field("wordwise", &self.wordwise)
1559 .field("done", &self.done)
1560 .finish()
1561 }
1562}
1563
1564#[derive(Debug)]
1565struct AutocloseRegion {
1566 selection_id: usize,
1567 range: Range<Anchor>,
1568 pair: BracketPair,
1569}
1570
1571#[derive(Debug)]
1572struct SnippetState {
1573 ranges: Vec<Vec<Range<Anchor>>>,
1574 active_index: usize,
1575 choices: Vec<Option<Vec<String>>>,
1576}
1577
1578#[doc(hidden)]
1579pub struct RenameState {
1580 pub range: Range<Anchor>,
1581 pub old_name: Arc<str>,
1582 pub editor: Entity<Editor>,
1583 block_id: CustomBlockId,
1584}
1585
1586struct InvalidationStack<T>(Vec<T>);
1587
1588struct RegisteredEditPredictionDelegate {
1589 provider: Arc<dyn EditPredictionDelegateHandle>,
1590 _subscription: Subscription,
1591}
1592
1593#[derive(Debug, PartialEq, Eq)]
1594pub struct ActiveDiagnosticGroup {
1595 pub active_range: Range<Anchor>,
1596 pub active_message: String,
1597 pub group_id: usize,
1598 pub blocks: HashSet<CustomBlockId>,
1599}
1600
1601#[derive(Debug, PartialEq, Eq)]
1602
1603pub(crate) enum ActiveDiagnostic {
1604 None,
1605 All,
1606 Group(ActiveDiagnosticGroup),
1607}
1608
1609#[derive(Serialize, Deserialize, Clone, Debug)]
1610pub struct ClipboardSelection {
1611 /// The number of bytes in this selection.
1612 pub len: usize,
1613 /// Whether this was a full-line selection.
1614 pub is_entire_line: bool,
1615 /// The indentation of the first line when this content was originally copied.
1616 pub first_line_indent: u32,
1617 #[serde(default)]
1618 pub file_path: Option<PathBuf>,
1619 #[serde(default)]
1620 pub line_range: Option<RangeInclusive<u32>>,
1621}
1622
1623impl ClipboardSelection {
1624 pub fn for_buffer(
1625 len: usize,
1626 is_entire_line: bool,
1627 range: Range<Point>,
1628 buffer: &MultiBufferSnapshot,
1629 project: Option<&Entity<Project>>,
1630 cx: &App,
1631 ) -> Self {
1632 let first_line_indent = buffer
1633 .indent_size_for_line(MultiBufferRow(range.start.row))
1634 .len;
1635
1636 let file_path = util::maybe!({
1637 let project = project?.read(cx);
1638 let file = buffer.file_at(range.start)?;
1639 let project_path = ProjectPath {
1640 worktree_id: file.worktree_id(cx),
1641 path: file.path().clone(),
1642 };
1643 project.absolute_path(&project_path, cx)
1644 });
1645
1646 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1647
1648 Self {
1649 len,
1650 is_entire_line,
1651 first_line_indent,
1652 file_path,
1653 line_range,
1654 }
1655 }
1656}
1657
1658// selections, scroll behavior, was newest selection reversed
1659type SelectSyntaxNodeHistoryState = (
1660 Box<[Selection<MultiBufferOffset>]>,
1661 SelectSyntaxNodeScrollBehavior,
1662 bool,
1663);
1664
1665#[derive(Default)]
1666struct SelectSyntaxNodeHistory {
1667 stack: Vec<SelectSyntaxNodeHistoryState>,
1668 // disable temporarily to allow changing selections without losing the stack
1669 pub disable_clearing: bool,
1670}
1671
1672impl SelectSyntaxNodeHistory {
1673 pub fn try_clear(&mut self) {
1674 if !self.disable_clearing {
1675 self.stack.clear();
1676 }
1677 }
1678
1679 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1680 self.stack.push(selection);
1681 }
1682
1683 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1684 self.stack.pop()
1685 }
1686}
1687
1688enum SelectSyntaxNodeScrollBehavior {
1689 CursorTop,
1690 FitSelection,
1691 CursorBottom,
1692}
1693
1694#[derive(Debug)]
1695pub(crate) struct NavigationData {
1696 cursor_anchor: Anchor,
1697 cursor_position: Point,
1698 scroll_anchor: ScrollAnchor,
1699 scroll_top_row: u32,
1700}
1701
1702#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1703pub enum GotoDefinitionKind {
1704 Symbol,
1705 Declaration,
1706 Type,
1707 Implementation,
1708}
1709
1710pub enum FormatTarget {
1711 Buffers(HashSet<Entity<Buffer>>),
1712 Ranges(Vec<Range<MultiBufferPoint>>),
1713}
1714
1715pub(crate) struct FocusedBlock {
1716 id: BlockId,
1717 focus_handle: WeakFocusHandle,
1718}
1719
1720#[derive(Clone, Debug)]
1721enum JumpData {
1722 MultiBufferRow {
1723 row: MultiBufferRow,
1724 line_offset_from_top: u32,
1725 },
1726 MultiBufferPoint {
1727 excerpt_id: ExcerptId,
1728 position: Point,
1729 anchor: text::Anchor,
1730 line_offset_from_top: u32,
1731 },
1732}
1733
1734pub enum MultibufferSelectionMode {
1735 First,
1736 All,
1737}
1738
1739#[derive(Clone, Copy, Debug, Default)]
1740pub struct RewrapOptions {
1741 pub override_language_settings: bool,
1742 pub preserve_existing_whitespace: bool,
1743}
1744
1745impl Editor {
1746 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1747 let buffer = cx.new(|cx| Buffer::local("", cx));
1748 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1749 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1750 }
1751
1752 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1753 let buffer = cx.new(|cx| Buffer::local("", cx));
1754 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1755 Self::new(EditorMode::full(), buffer, None, window, cx)
1756 }
1757
1758 pub fn auto_height(
1759 min_lines: usize,
1760 max_lines: usize,
1761 window: &mut Window,
1762 cx: &mut Context<Self>,
1763 ) -> Self {
1764 let buffer = cx.new(|cx| Buffer::local("", cx));
1765 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1766 Self::new(
1767 EditorMode::AutoHeight {
1768 min_lines,
1769 max_lines: Some(max_lines),
1770 },
1771 buffer,
1772 None,
1773 window,
1774 cx,
1775 )
1776 }
1777
1778 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1779 /// The editor grows as tall as needed to fit its content.
1780 pub fn auto_height_unbounded(
1781 min_lines: usize,
1782 window: &mut Window,
1783 cx: &mut Context<Self>,
1784 ) -> Self {
1785 let buffer = cx.new(|cx| Buffer::local("", cx));
1786 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1787 Self::new(
1788 EditorMode::AutoHeight {
1789 min_lines,
1790 max_lines: None,
1791 },
1792 buffer,
1793 None,
1794 window,
1795 cx,
1796 )
1797 }
1798
1799 pub fn for_buffer(
1800 buffer: Entity<Buffer>,
1801 project: Option<Entity<Project>>,
1802 window: &mut Window,
1803 cx: &mut Context<Self>,
1804 ) -> Self {
1805 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1806 Self::new(EditorMode::full(), buffer, project, window, cx)
1807 }
1808
1809 pub fn for_multibuffer(
1810 buffer: Entity<MultiBuffer>,
1811 project: Option<Entity<Project>>,
1812 window: &mut Window,
1813 cx: &mut Context<Self>,
1814 ) -> Self {
1815 Self::new(EditorMode::full(), buffer, project, window, cx)
1816 }
1817
1818 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1819 let mut clone = Self::new(
1820 self.mode.clone(),
1821 self.buffer.clone(),
1822 self.project.clone(),
1823 window,
1824 cx,
1825 );
1826 self.display_map.update(cx, |display_map, cx| {
1827 let snapshot = display_map.snapshot(cx);
1828 clone.display_map.update(cx, |display_map, cx| {
1829 display_map.set_state(&snapshot, cx);
1830 });
1831 });
1832 clone.folds_did_change(cx);
1833 clone.selections.clone_state(&self.selections);
1834 clone.scroll_manager.clone_state(&self.scroll_manager);
1835 clone.searchable = self.searchable;
1836 clone.read_only = self.read_only;
1837 clone
1838 }
1839
1840 pub fn new(
1841 mode: EditorMode,
1842 buffer: Entity<MultiBuffer>,
1843 project: Option<Entity<Project>>,
1844 window: &mut Window,
1845 cx: &mut Context<Self>,
1846 ) -> Self {
1847 Editor::new_internal(mode, buffer, project, None, window, cx)
1848 }
1849
1850 pub fn sticky_headers(
1851 &self,
1852 style: &EditorStyle,
1853 cx: &App,
1854 ) -> Option<Vec<OutlineItem<Anchor>>> {
1855 let multi_buffer = self.buffer().read(cx);
1856 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1857 let multi_buffer_visible_start = self
1858 .scroll_manager
1859 .anchor()
1860 .anchor
1861 .to_point(&multi_buffer_snapshot);
1862 let max_row = multi_buffer_snapshot.max_point().row;
1863
1864 let start_row = (multi_buffer_visible_start.row).min(max_row);
1865 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1866
1867 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1868 let outline_items = buffer
1869 .outline_items_containing(
1870 Point::new(start_row, 0)..Point::new(end_row, 0),
1871 true,
1872 Some(style.syntax.as_ref()),
1873 )
1874 .into_iter()
1875 .map(|outline_item| OutlineItem {
1876 depth: outline_item.depth,
1877 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1878 source_range_for_text: Anchor::range_in_buffer(
1879 *excerpt_id,
1880 outline_item.source_range_for_text,
1881 ),
1882 text: outline_item.text,
1883 highlight_ranges: outline_item.highlight_ranges,
1884 name_ranges: outline_item.name_ranges,
1885 body_range: outline_item
1886 .body_range
1887 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1888 annotation_range: outline_item
1889 .annotation_range
1890 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1891 });
1892 return Some(outline_items.collect());
1893 }
1894
1895 None
1896 }
1897
1898 fn new_internal(
1899 mode: EditorMode,
1900 multi_buffer: Entity<MultiBuffer>,
1901 project: Option<Entity<Project>>,
1902 display_map: Option<Entity<DisplayMap>>,
1903 window: &mut Window,
1904 cx: &mut Context<Self>,
1905 ) -> Self {
1906 debug_assert!(
1907 display_map.is_none() || mode.is_minimap(),
1908 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1909 );
1910
1911 let full_mode = mode.is_full();
1912 let is_minimap = mode.is_minimap();
1913 let diagnostics_max_severity = if full_mode {
1914 EditorSettings::get_global(cx)
1915 .diagnostics_max_severity
1916 .unwrap_or(DiagnosticSeverity::Hint)
1917 } else {
1918 DiagnosticSeverity::Off
1919 };
1920 let style = window.text_style();
1921 let font_size = style.font_size.to_pixels(window.rem_size());
1922 let editor = cx.entity().downgrade();
1923 let fold_placeholder = FoldPlaceholder {
1924 constrain_width: false,
1925 render: Arc::new(move |fold_id, fold_range, cx| {
1926 let editor = editor.clone();
1927 div()
1928 .id(fold_id)
1929 .bg(cx.theme().colors().ghost_element_background)
1930 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1931 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1932 .rounded_xs()
1933 .size_full()
1934 .cursor_pointer()
1935 .child("⋯")
1936 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1937 .on_click(move |_, _window, cx| {
1938 editor
1939 .update(cx, |editor, cx| {
1940 editor.unfold_ranges(
1941 &[fold_range.start..fold_range.end],
1942 true,
1943 false,
1944 cx,
1945 );
1946 cx.stop_propagation();
1947 })
1948 .ok();
1949 })
1950 .into_any()
1951 }),
1952 merge_adjacent: true,
1953 ..FoldPlaceholder::default()
1954 };
1955 let display_map = display_map.unwrap_or_else(|| {
1956 cx.new(|cx| {
1957 DisplayMap::new(
1958 multi_buffer.clone(),
1959 style.font(),
1960 font_size,
1961 None,
1962 FILE_HEADER_HEIGHT,
1963 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1964 fold_placeholder,
1965 diagnostics_max_severity,
1966 cx,
1967 )
1968 })
1969 });
1970
1971 let selections = SelectionsCollection::new();
1972
1973 let blink_manager = cx.new(|cx| {
1974 let mut blink_manager = BlinkManager::new(
1975 CURSOR_BLINK_INTERVAL,
1976 |cx| EditorSettings::get_global(cx).cursor_blink,
1977 cx,
1978 );
1979 if is_minimap {
1980 blink_manager.disable(cx);
1981 }
1982 blink_manager
1983 });
1984
1985 let soft_wrap_mode_override =
1986 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1987
1988 let mut project_subscriptions = Vec::new();
1989 if full_mode && let Some(project) = project.as_ref() {
1990 project_subscriptions.push(cx.subscribe_in(
1991 project,
1992 window,
1993 |editor, _, event, window, cx| match event {
1994 project::Event::RefreshCodeLens => {
1995 // we always query lens with actions, without storing them, always refreshing them
1996 }
1997 project::Event::RefreshInlayHints {
1998 server_id,
1999 request_id,
2000 } => {
2001 editor.refresh_inlay_hints(
2002 InlayHintRefreshReason::RefreshRequested {
2003 server_id: *server_id,
2004 request_id: *request_id,
2005 },
2006 cx,
2007 );
2008 }
2009 project::Event::LanguageServerRemoved(..) => {
2010 if editor.tasks_update_task.is_none() {
2011 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2012 }
2013 editor.registered_buffers.clear();
2014 editor.register_visible_buffers(cx);
2015 }
2016 project::Event::LanguageServerAdded(..) => {
2017 if editor.tasks_update_task.is_none() {
2018 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2019 }
2020 }
2021 project::Event::SnippetEdit(id, snippet_edits) => {
2022 // todo(lw): Non singletons
2023 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2024 let snapshot = buffer.read(cx).snapshot();
2025 let focus_handle = editor.focus_handle(cx);
2026 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2027 for (range, snippet) in snippet_edits {
2028 let buffer_range =
2029 language::range_from_lsp(*range).to_offset(&snapshot);
2030 editor
2031 .insert_snippet(
2032 &[MultiBufferOffset(buffer_range.start)
2033 ..MultiBufferOffset(buffer_range.end)],
2034 snippet.clone(),
2035 window,
2036 cx,
2037 )
2038 .ok();
2039 }
2040 }
2041 }
2042 }
2043 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2044 let buffer_id = *buffer_id;
2045 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2046 editor.register_buffer(buffer_id, cx);
2047 editor.update_lsp_data(Some(buffer_id), window, cx);
2048 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2049 refresh_linked_ranges(editor, window, cx);
2050 editor.refresh_code_actions(window, cx);
2051 editor.refresh_document_highlights(cx);
2052 }
2053 }
2054
2055 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2056 let Some(workspace) = editor.workspace() else {
2057 return;
2058 };
2059 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2060 else {
2061 return;
2062 };
2063
2064 if active_editor.entity_id() == cx.entity_id() {
2065 let entity_id = cx.entity_id();
2066 workspace.update(cx, |this, cx| {
2067 this.panes_mut()
2068 .iter_mut()
2069 .filter(|pane| pane.entity_id() != entity_id)
2070 .for_each(|p| {
2071 p.update(cx, |pane, _| {
2072 pane.nav_history_mut().rename_item(
2073 entity_id,
2074 project_path.clone(),
2075 abs_path.clone().into(),
2076 );
2077 })
2078 });
2079 });
2080
2081 Self::open_transaction_for_hidden_buffers(
2082 workspace,
2083 transaction.clone(),
2084 "Rename".to_string(),
2085 window,
2086 cx,
2087 );
2088 }
2089 }
2090
2091 project::Event::WorkspaceEditApplied(transaction) => {
2092 let Some(workspace) = editor.workspace() else {
2093 return;
2094 };
2095 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2096 else {
2097 return;
2098 };
2099
2100 if active_editor.entity_id() == cx.entity_id() {
2101 Self::open_transaction_for_hidden_buffers(
2102 workspace,
2103 transaction.clone(),
2104 "LSP Edit".to_string(),
2105 window,
2106 cx,
2107 );
2108 }
2109 }
2110
2111 _ => {}
2112 },
2113 ));
2114 if let Some(task_inventory) = project
2115 .read(cx)
2116 .task_store()
2117 .read(cx)
2118 .task_inventory()
2119 .cloned()
2120 {
2121 project_subscriptions.push(cx.observe_in(
2122 &task_inventory,
2123 window,
2124 |editor, _, window, cx| {
2125 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2126 },
2127 ));
2128 };
2129
2130 project_subscriptions.push(cx.subscribe_in(
2131 &project.read(cx).breakpoint_store(),
2132 window,
2133 |editor, _, event, window, cx| match event {
2134 BreakpointStoreEvent::ClearDebugLines => {
2135 editor.clear_row_highlights::<ActiveDebugLine>();
2136 editor.refresh_inline_values(cx);
2137 }
2138 BreakpointStoreEvent::SetDebugLine => {
2139 if editor.go_to_active_debug_line(window, cx) {
2140 cx.stop_propagation();
2141 }
2142
2143 editor.refresh_inline_values(cx);
2144 }
2145 _ => {}
2146 },
2147 ));
2148 let git_store = project.read(cx).git_store().clone();
2149 let project = project.clone();
2150 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2151 if let GitStoreEvent::RepositoryAdded = event {
2152 this.load_diff_task = Some(
2153 update_uncommitted_diff_for_buffer(
2154 cx.entity(),
2155 &project,
2156 this.buffer.read(cx).all_buffers(),
2157 this.buffer.clone(),
2158 cx,
2159 )
2160 .shared(),
2161 );
2162 }
2163 }));
2164 }
2165
2166 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2167
2168 let inlay_hint_settings =
2169 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2170 let focus_handle = cx.focus_handle();
2171 if !is_minimap {
2172 cx.on_focus(&focus_handle, window, Self::handle_focus)
2173 .detach();
2174 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2175 .detach();
2176 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2177 .detach();
2178 cx.on_blur(&focus_handle, window, Self::handle_blur)
2179 .detach();
2180 cx.observe_pending_input(window, Self::observe_pending_input)
2181 .detach();
2182 }
2183
2184 let show_indent_guides =
2185 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2186 Some(false)
2187 } else {
2188 None
2189 };
2190
2191 let breakpoint_store = match (&mode, project.as_ref()) {
2192 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2193 _ => None,
2194 };
2195
2196 let mut code_action_providers = Vec::new();
2197 let mut load_uncommitted_diff = None;
2198 if let Some(project) = project.clone() {
2199 load_uncommitted_diff = Some(
2200 update_uncommitted_diff_for_buffer(
2201 cx.entity(),
2202 &project,
2203 multi_buffer.read(cx).all_buffers(),
2204 multi_buffer.clone(),
2205 cx,
2206 )
2207 .shared(),
2208 );
2209 code_action_providers.push(Rc::new(project) as Rc<_>);
2210 }
2211
2212 let mut editor = Self {
2213 focus_handle,
2214 show_cursor_when_unfocused: false,
2215 last_focused_descendant: None,
2216 buffer: multi_buffer.clone(),
2217 display_map: display_map.clone(),
2218 placeholder_display_map: None,
2219 selections,
2220 scroll_manager: ScrollManager::new(cx),
2221 columnar_selection_state: None,
2222 add_selections_state: None,
2223 select_next_state: None,
2224 select_prev_state: None,
2225 selection_history: SelectionHistory::default(),
2226 defer_selection_effects: false,
2227 deferred_selection_effects_state: None,
2228 autoclose_regions: Vec::new(),
2229 snippet_stack: InvalidationStack::default(),
2230 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2231 ime_transaction: None,
2232 active_diagnostics: ActiveDiagnostic::None,
2233 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2234 inline_diagnostics_update: Task::ready(()),
2235 inline_diagnostics: Vec::new(),
2236 soft_wrap_mode_override,
2237 diagnostics_max_severity,
2238 hard_wrap: None,
2239 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2240 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2241 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2242 project,
2243 blink_manager: blink_manager.clone(),
2244 show_local_selections: true,
2245 show_scrollbars: ScrollbarAxes {
2246 horizontal: full_mode,
2247 vertical: full_mode,
2248 },
2249 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2250 offset_content: !matches!(mode, EditorMode::SingleLine),
2251 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2252 show_gutter: full_mode,
2253 show_line_numbers: (!full_mode).then_some(false),
2254 use_relative_line_numbers: None,
2255 disable_expand_excerpt_buttons: !full_mode,
2256 delegate_expand_excerpts: false,
2257 show_git_diff_gutter: None,
2258 show_code_actions: None,
2259 show_runnables: None,
2260 show_breakpoints: None,
2261 show_diff_review_button: false,
2262 show_wrap_guides: None,
2263 show_indent_guides,
2264 buffers_with_disabled_indent_guides: HashSet::default(),
2265 highlight_order: 0,
2266 highlighted_rows: HashMap::default(),
2267 background_highlights: HashMap::default(),
2268 gutter_highlights: HashMap::default(),
2269 scrollbar_marker_state: ScrollbarMarkerState::default(),
2270 active_indent_guides_state: ActiveIndentGuidesState::default(),
2271 nav_history: None,
2272 context_menu: RefCell::new(None),
2273 context_menu_options: None,
2274 mouse_context_menu: None,
2275 completion_tasks: Vec::new(),
2276 inline_blame_popover: None,
2277 inline_blame_popover_show_task: None,
2278 signature_help_state: SignatureHelpState::default(),
2279 auto_signature_help: None,
2280 find_all_references_task_sources: Vec::new(),
2281 next_completion_id: 0,
2282 next_inlay_id: 0,
2283 code_action_providers,
2284 available_code_actions: None,
2285 code_actions_task: None,
2286 quick_selection_highlight_task: None,
2287 debounced_selection_highlight_task: None,
2288 document_highlights_task: None,
2289 linked_editing_range_task: None,
2290 pending_rename: None,
2291 searchable: !is_minimap,
2292 cursor_shape: EditorSettings::get_global(cx)
2293 .cursor_shape
2294 .unwrap_or_default(),
2295 cursor_offset_on_selection: false,
2296 current_line_highlight: None,
2297 autoindent_mode: Some(AutoindentMode::EachLine),
2298 collapse_matches: false,
2299 workspace: None,
2300 input_enabled: !is_minimap,
2301 use_modal_editing: full_mode,
2302 read_only: is_minimap,
2303 use_autoclose: true,
2304 use_auto_surround: true,
2305 auto_replace_emoji_shortcode: false,
2306 jsx_tag_auto_close_enabled_in_any_buffer: false,
2307 leader_id: None,
2308 remote_id: None,
2309 hover_state: HoverState::default(),
2310 pending_mouse_down: None,
2311 prev_pressure_stage: None,
2312 hovered_link_state: None,
2313 edit_prediction_provider: None,
2314 active_edit_prediction: None,
2315 stale_edit_prediction_in_menu: None,
2316 edit_prediction_preview: EditPredictionPreview::Inactive {
2317 released_too_fast: false,
2318 },
2319 inline_diagnostics_enabled: full_mode,
2320 diagnostics_enabled: full_mode,
2321 word_completions_enabled: full_mode,
2322 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2323 gutter_hovered: false,
2324 pixel_position_of_newest_cursor: None,
2325 last_bounds: None,
2326 last_position_map: None,
2327 expect_bounds_change: None,
2328 gutter_dimensions: GutterDimensions::default(),
2329 style: None,
2330 show_cursor_names: false,
2331 hovered_cursors: HashMap::default(),
2332 next_editor_action_id: EditorActionId::default(),
2333 editor_actions: Rc::default(),
2334 edit_predictions_hidden_for_vim_mode: false,
2335 show_edit_predictions_override: None,
2336 show_completions_on_input_override: None,
2337 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2338 edit_prediction_settings: EditPredictionSettings::Disabled,
2339 edit_prediction_indent_conflict: false,
2340 edit_prediction_requires_modifier_in_indent_conflict: true,
2341 custom_context_menu: None,
2342 show_git_blame_gutter: false,
2343 show_git_blame_inline: false,
2344 show_selection_menu: None,
2345 show_git_blame_inline_delay_task: None,
2346 git_blame_inline_enabled: full_mode
2347 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2348 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2349 buffer_serialization: is_minimap.not().then(|| {
2350 BufferSerialization::new(
2351 ProjectSettings::get_global(cx)
2352 .session
2353 .restore_unsaved_buffers,
2354 )
2355 }),
2356 blame: None,
2357 blame_subscription: None,
2358 tasks: BTreeMap::default(),
2359
2360 breakpoint_store,
2361 gutter_breakpoint_indicator: (None, None),
2362 gutter_diff_review_indicator: (None, None),
2363 hovered_diff_hunk_row: None,
2364 _subscriptions: (!is_minimap)
2365 .then(|| {
2366 vec![
2367 cx.observe(&multi_buffer, Self::on_buffer_changed),
2368 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2369 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2370 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2371 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2372 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2373 cx.observe_window_activation(window, |editor, window, cx| {
2374 let active = window.is_window_active();
2375 editor.blink_manager.update(cx, |blink_manager, cx| {
2376 if active {
2377 blink_manager.enable(cx);
2378 } else {
2379 blink_manager.disable(cx);
2380 }
2381 });
2382 if active {
2383 editor.show_mouse_cursor(cx);
2384 }
2385 }),
2386 ]
2387 })
2388 .unwrap_or_default(),
2389 tasks_update_task: None,
2390 pull_diagnostics_task: Task::ready(()),
2391 pull_diagnostics_background_task: Task::ready(()),
2392 colors: None,
2393 refresh_colors_task: Task::ready(()),
2394 inlay_hints: None,
2395 next_color_inlay_id: 0,
2396 post_scroll_update: Task::ready(()),
2397 linked_edit_ranges: Default::default(),
2398 in_project_search: false,
2399 previous_search_ranges: None,
2400 breadcrumb_header: None,
2401 focused_block: None,
2402 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2403 addons: HashMap::default(),
2404 registered_buffers: HashMap::default(),
2405 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2406 selection_mark_mode: false,
2407 toggle_fold_multiple_buffers: Task::ready(()),
2408 serialize_selections: Task::ready(()),
2409 serialize_folds: Task::ready(()),
2410 text_style_refinement: None,
2411 load_diff_task: load_uncommitted_diff,
2412 temporary_diff_override: false,
2413 mouse_cursor_hidden: false,
2414 minimap: None,
2415 hide_mouse_mode: EditorSettings::get_global(cx)
2416 .hide_mouse
2417 .unwrap_or_default(),
2418 change_list: ChangeList::new(),
2419 mode,
2420 selection_drag_state: SelectionDragState::None,
2421 folding_newlines: Task::ready(()),
2422 lookup_key: None,
2423 select_next_is_case_sensitive: None,
2424 applicable_language_settings: HashMap::default(),
2425 accent_data: None,
2426 fetched_tree_sitter_chunks: HashMap::default(),
2427 number_deleted_lines: false,
2428 };
2429
2430 if is_minimap {
2431 return editor;
2432 }
2433
2434 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2435 editor.accent_data = editor.fetch_accent_data(cx);
2436
2437 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2438 editor
2439 ._subscriptions
2440 .push(cx.observe(breakpoints, |_, _, cx| {
2441 cx.notify();
2442 }));
2443 }
2444 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2445 editor._subscriptions.extend(project_subscriptions);
2446
2447 editor._subscriptions.push(cx.subscribe_in(
2448 &cx.entity(),
2449 window,
2450 |editor, _, e: &EditorEvent, window, cx| match e {
2451 EditorEvent::ScrollPositionChanged { local, .. } => {
2452 if *local {
2453 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2454 editor.inline_blame_popover.take();
2455 let new_anchor = editor.scroll_manager.anchor();
2456 let snapshot = editor.snapshot(window, cx);
2457 editor.update_restoration_data(cx, move |data| {
2458 data.scroll_position = (
2459 new_anchor.top_row(snapshot.buffer_snapshot()),
2460 new_anchor.offset,
2461 );
2462 });
2463
2464 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2465 cx.background_executor()
2466 .timer(Duration::from_millis(50))
2467 .await;
2468 editor
2469 .update_in(cx, |editor, window, cx| {
2470 editor.register_visible_buffers(cx);
2471 editor.refresh_colors_for_visible_range(None, window, cx);
2472 editor.refresh_inlay_hints(
2473 InlayHintRefreshReason::NewLinesShown,
2474 cx,
2475 );
2476 editor.colorize_brackets(false, cx);
2477 })
2478 .ok();
2479 });
2480 }
2481 }
2482 EditorEvent::Edited { .. } => {
2483 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2484 .map(|vim_mode| vim_mode.0)
2485 .unwrap_or(false);
2486 if !vim_mode {
2487 let display_map = editor.display_snapshot(cx);
2488 let selections = editor.selections.all_adjusted_display(&display_map);
2489 let pop_state = editor
2490 .change_list
2491 .last()
2492 .map(|previous| {
2493 previous.len() == selections.len()
2494 && previous.iter().enumerate().all(|(ix, p)| {
2495 p.to_display_point(&display_map).row()
2496 == selections[ix].head().row()
2497 })
2498 })
2499 .unwrap_or(false);
2500 let new_positions = selections
2501 .into_iter()
2502 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2503 .collect();
2504 editor
2505 .change_list
2506 .push_to_change_list(pop_state, new_positions);
2507 }
2508 }
2509 _ => (),
2510 },
2511 ));
2512
2513 if let Some(dap_store) = editor
2514 .project
2515 .as_ref()
2516 .map(|project| project.read(cx).dap_store())
2517 {
2518 let weak_editor = cx.weak_entity();
2519
2520 editor
2521 ._subscriptions
2522 .push(
2523 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2524 let session_entity = cx.entity();
2525 weak_editor
2526 .update(cx, |editor, cx| {
2527 editor._subscriptions.push(
2528 cx.subscribe(&session_entity, Self::on_debug_session_event),
2529 );
2530 })
2531 .ok();
2532 }),
2533 );
2534
2535 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2536 editor
2537 ._subscriptions
2538 .push(cx.subscribe(&session, Self::on_debug_session_event));
2539 }
2540 }
2541
2542 // skip adding the initial selection to selection history
2543 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2544 editor.end_selection(window, cx);
2545 editor.selection_history.mode = SelectionHistoryMode::Normal;
2546
2547 editor.scroll_manager.show_scrollbars(window, cx);
2548 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2549
2550 if full_mode {
2551 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2552 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2553
2554 if editor.git_blame_inline_enabled {
2555 editor.start_git_blame_inline(false, window, cx);
2556 }
2557
2558 editor.go_to_active_debug_line(window, cx);
2559
2560 editor.minimap =
2561 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2562 editor.colors = Some(LspColorData::new(cx));
2563 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2564
2565 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2566 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2567 }
2568 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2569 }
2570
2571 editor
2572 }
2573
2574 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2575 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2576 }
2577
2578 pub fn deploy_mouse_context_menu(
2579 &mut self,
2580 position: gpui::Point<Pixels>,
2581 context_menu: Entity<ContextMenu>,
2582 window: &mut Window,
2583 cx: &mut Context<Self>,
2584 ) {
2585 self.mouse_context_menu = Some(MouseContextMenu::new(
2586 self,
2587 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2588 context_menu,
2589 window,
2590 cx,
2591 ));
2592 }
2593
2594 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2595 self.mouse_context_menu
2596 .as_ref()
2597 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2598 }
2599
2600 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2601 if self
2602 .selections
2603 .pending_anchor()
2604 .is_some_and(|pending_selection| {
2605 let snapshot = self.buffer().read(cx).snapshot(cx);
2606 pending_selection.range().includes(range, &snapshot)
2607 })
2608 {
2609 return true;
2610 }
2611
2612 self.selections
2613 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2614 .into_iter()
2615 .any(|selection| {
2616 // This is needed to cover a corner case, if we just check for an existing
2617 // selection in the fold range, having a cursor at the start of the fold
2618 // marks it as selected. Non-empty selections don't cause this.
2619 let length = selection.end - selection.start;
2620 length > 0
2621 })
2622 }
2623
2624 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2625 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2626 }
2627
2628 fn key_context_internal(
2629 &self,
2630 has_active_edit_prediction: bool,
2631 window: &mut Window,
2632 cx: &mut App,
2633 ) -> KeyContext {
2634 let mut key_context = KeyContext::new_with_defaults();
2635 key_context.add("Editor");
2636 let mode = match self.mode {
2637 EditorMode::SingleLine => "single_line",
2638 EditorMode::AutoHeight { .. } => "auto_height",
2639 EditorMode::Minimap { .. } => "minimap",
2640 EditorMode::Full { .. } => "full",
2641 };
2642
2643 if EditorSettings::jupyter_enabled(cx) {
2644 key_context.add("jupyter");
2645 }
2646
2647 key_context.set("mode", mode);
2648 if self.pending_rename.is_some() {
2649 key_context.add("renaming");
2650 }
2651
2652 if let Some(snippet_stack) = self.snippet_stack.last() {
2653 key_context.add("in_snippet");
2654
2655 if snippet_stack.active_index > 0 {
2656 key_context.add("has_previous_tabstop");
2657 }
2658
2659 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2660 key_context.add("has_next_tabstop");
2661 }
2662 }
2663
2664 match self.context_menu.borrow().as_ref() {
2665 Some(CodeContextMenu::Completions(menu)) => {
2666 if menu.visible() {
2667 key_context.add("menu");
2668 key_context.add("showing_completions");
2669 }
2670 }
2671 Some(CodeContextMenu::CodeActions(menu)) => {
2672 if menu.visible() {
2673 key_context.add("menu");
2674 key_context.add("showing_code_actions")
2675 }
2676 }
2677 None => {}
2678 }
2679
2680 if self.signature_help_state.has_multiple_signatures() {
2681 key_context.add("showing_signature_help");
2682 }
2683
2684 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2685 if !self.focus_handle(cx).contains_focused(window, cx)
2686 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2687 {
2688 for addon in self.addons.values() {
2689 addon.extend_key_context(&mut key_context, cx)
2690 }
2691 }
2692
2693 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2694 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2695 Some(
2696 file.full_path(cx)
2697 .extension()?
2698 .to_string_lossy()
2699 .to_lowercase(),
2700 )
2701 }) {
2702 key_context.set("extension", extension);
2703 }
2704 } else {
2705 key_context.add("multibuffer");
2706 }
2707
2708 if has_active_edit_prediction {
2709 if self.edit_prediction_in_conflict() {
2710 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2711 } else {
2712 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2713 key_context.add("copilot_suggestion");
2714 }
2715 }
2716
2717 if self.selection_mark_mode {
2718 key_context.add("selection_mode");
2719 }
2720
2721 let disjoint = self.selections.disjoint_anchors();
2722 let snapshot = self.snapshot(window, cx);
2723 let snapshot = snapshot.buffer_snapshot();
2724 if self.mode == EditorMode::SingleLine
2725 && let [selection] = disjoint
2726 && selection.start == selection.end
2727 && selection.end.to_offset(snapshot) == snapshot.len()
2728 {
2729 key_context.add("end_of_input");
2730 }
2731
2732 if self.has_any_expanded_diff_hunks(cx) {
2733 key_context.add("diffs_expanded");
2734 }
2735
2736 key_context
2737 }
2738
2739 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2740 self.last_bounds.as_ref()
2741 }
2742
2743 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2744 if self.mouse_cursor_hidden {
2745 self.mouse_cursor_hidden = false;
2746 cx.notify();
2747 }
2748 }
2749
2750 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2751 let hide_mouse_cursor = match origin {
2752 HideMouseCursorOrigin::TypingAction => {
2753 matches!(
2754 self.hide_mouse_mode,
2755 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2756 )
2757 }
2758 HideMouseCursorOrigin::MovementAction => {
2759 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2760 }
2761 };
2762 if self.mouse_cursor_hidden != hide_mouse_cursor {
2763 self.mouse_cursor_hidden = hide_mouse_cursor;
2764 cx.notify();
2765 }
2766 }
2767
2768 pub fn edit_prediction_in_conflict(&self) -> bool {
2769 if !self.show_edit_predictions_in_menu() {
2770 return false;
2771 }
2772
2773 let showing_completions = self
2774 .context_menu
2775 .borrow()
2776 .as_ref()
2777 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2778
2779 showing_completions
2780 || self.edit_prediction_requires_modifier()
2781 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2782 // bindings to insert tab characters.
2783 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2784 }
2785
2786 pub fn accept_edit_prediction_keybind(
2787 &self,
2788 granularity: EditPredictionGranularity,
2789 window: &mut Window,
2790 cx: &mut App,
2791 ) -> AcceptEditPredictionBinding {
2792 let key_context = self.key_context_internal(true, window, cx);
2793 let in_conflict = self.edit_prediction_in_conflict();
2794
2795 let bindings =
2796 match granularity {
2797 EditPredictionGranularity::Word => window
2798 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2799 EditPredictionGranularity::Line => window
2800 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2801 EditPredictionGranularity::Full => {
2802 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2803 }
2804 };
2805
2806 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2807 !in_conflict
2808 || binding
2809 .keystrokes()
2810 .first()
2811 .is_some_and(|keystroke| keystroke.modifiers().modified())
2812 }))
2813 }
2814
2815 pub fn new_file(
2816 workspace: &mut Workspace,
2817 _: &workspace::NewFile,
2818 window: &mut Window,
2819 cx: &mut Context<Workspace>,
2820 ) {
2821 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2822 "Failed to create buffer",
2823 window,
2824 cx,
2825 |e, _, _| match e.error_code() {
2826 ErrorCode::RemoteUpgradeRequired => Some(format!(
2827 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2828 e.error_tag("required").unwrap_or("the latest version")
2829 )),
2830 _ => None,
2831 },
2832 );
2833 }
2834
2835 pub fn new_in_workspace(
2836 workspace: &mut Workspace,
2837 window: &mut Window,
2838 cx: &mut Context<Workspace>,
2839 ) -> Task<Result<Entity<Editor>>> {
2840 let project = workspace.project().clone();
2841 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2842
2843 cx.spawn_in(window, async move |workspace, cx| {
2844 let buffer = create.await?;
2845 workspace.update_in(cx, |workspace, window, cx| {
2846 let editor =
2847 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2848 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2849 editor
2850 })
2851 })
2852 }
2853
2854 fn new_file_vertical(
2855 workspace: &mut Workspace,
2856 _: &workspace::NewFileSplitVertical,
2857 window: &mut Window,
2858 cx: &mut Context<Workspace>,
2859 ) {
2860 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2861 }
2862
2863 fn new_file_horizontal(
2864 workspace: &mut Workspace,
2865 _: &workspace::NewFileSplitHorizontal,
2866 window: &mut Window,
2867 cx: &mut Context<Workspace>,
2868 ) {
2869 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2870 }
2871
2872 fn new_file_split(
2873 workspace: &mut Workspace,
2874 action: &workspace::NewFileSplit,
2875 window: &mut Window,
2876 cx: &mut Context<Workspace>,
2877 ) {
2878 Self::new_file_in_direction(workspace, action.0, window, cx)
2879 }
2880
2881 fn new_file_in_direction(
2882 workspace: &mut Workspace,
2883 direction: SplitDirection,
2884 window: &mut Window,
2885 cx: &mut Context<Workspace>,
2886 ) {
2887 let project = workspace.project().clone();
2888 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2889
2890 cx.spawn_in(window, async move |workspace, cx| {
2891 let buffer = create.await?;
2892 workspace.update_in(cx, move |workspace, window, cx| {
2893 workspace.split_item(
2894 direction,
2895 Box::new(
2896 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2897 ),
2898 window,
2899 cx,
2900 )
2901 })?;
2902 anyhow::Ok(())
2903 })
2904 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2905 match e.error_code() {
2906 ErrorCode::RemoteUpgradeRequired => Some(format!(
2907 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2908 e.error_tag("required").unwrap_or("the latest version")
2909 )),
2910 _ => None,
2911 }
2912 });
2913 }
2914
2915 pub fn leader_id(&self) -> Option<CollaboratorId> {
2916 self.leader_id
2917 }
2918
2919 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2920 &self.buffer
2921 }
2922
2923 pub fn project(&self) -> Option<&Entity<Project>> {
2924 self.project.as_ref()
2925 }
2926
2927 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2928 self.workspace.as_ref()?.0.upgrade()
2929 }
2930
2931 /// Returns the workspace serialization ID if this editor should be serialized.
2932 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2933 self.workspace
2934 .as_ref()
2935 .filter(|_| self.should_serialize_buffer())
2936 .and_then(|workspace| workspace.1)
2937 }
2938
2939 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2940 self.buffer().read(cx).title(cx)
2941 }
2942
2943 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2944 let git_blame_gutter_max_author_length = self
2945 .render_git_blame_gutter(cx)
2946 .then(|| {
2947 if let Some(blame) = self.blame.as_ref() {
2948 let max_author_length =
2949 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2950 Some(max_author_length)
2951 } else {
2952 None
2953 }
2954 })
2955 .flatten();
2956
2957 EditorSnapshot {
2958 mode: self.mode.clone(),
2959 show_gutter: self.show_gutter,
2960 offset_content: self.offset_content,
2961 show_line_numbers: self.show_line_numbers,
2962 number_deleted_lines: self.number_deleted_lines,
2963 show_git_diff_gutter: self.show_git_diff_gutter,
2964 show_code_actions: self.show_code_actions,
2965 show_runnables: self.show_runnables,
2966 show_breakpoints: self.show_breakpoints,
2967 git_blame_gutter_max_author_length,
2968 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2969 placeholder_display_snapshot: self
2970 .placeholder_display_map
2971 .as_ref()
2972 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2973 scroll_anchor: self.scroll_manager.anchor(),
2974 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2975 is_focused: self.focus_handle.is_focused(window),
2976 current_line_highlight: self
2977 .current_line_highlight
2978 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2979 gutter_hovered: self.gutter_hovered,
2980 }
2981 }
2982
2983 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2984 self.buffer.read(cx).language_at(point, cx)
2985 }
2986
2987 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2988 self.buffer.read(cx).read(cx).file_at(point).cloned()
2989 }
2990
2991 pub fn active_excerpt(
2992 &self,
2993 cx: &App,
2994 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2995 self.buffer
2996 .read(cx)
2997 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2998 }
2999
3000 pub fn mode(&self) -> &EditorMode {
3001 &self.mode
3002 }
3003
3004 pub fn set_mode(&mut self, mode: EditorMode) {
3005 self.mode = mode;
3006 }
3007
3008 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3009 self.collaboration_hub.as_deref()
3010 }
3011
3012 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3013 self.collaboration_hub = Some(hub);
3014 }
3015
3016 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3017 self.in_project_search = in_project_search;
3018 }
3019
3020 pub fn set_custom_context_menu(
3021 &mut self,
3022 f: impl 'static
3023 + Fn(
3024 &mut Self,
3025 DisplayPoint,
3026 &mut Window,
3027 &mut Context<Self>,
3028 ) -> Option<Entity<ui::ContextMenu>>,
3029 ) {
3030 self.custom_context_menu = Some(Box::new(f))
3031 }
3032
3033 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3034 self.completion_provider = provider;
3035 }
3036
3037 #[cfg(any(test, feature = "test-support"))]
3038 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3039 self.completion_provider.clone()
3040 }
3041
3042 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3043 self.semantics_provider.clone()
3044 }
3045
3046 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3047 self.semantics_provider = provider;
3048 }
3049
3050 pub fn set_edit_prediction_provider<T>(
3051 &mut self,
3052 provider: Option<Entity<T>>,
3053 window: &mut Window,
3054 cx: &mut Context<Self>,
3055 ) where
3056 T: EditPredictionDelegate,
3057 {
3058 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3059 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3060 if this.focus_handle.is_focused(window) {
3061 this.update_visible_edit_prediction(window, cx);
3062 }
3063 }),
3064 provider: Arc::new(provider),
3065 });
3066 self.update_edit_prediction_settings(cx);
3067 self.refresh_edit_prediction(false, false, window, cx);
3068 }
3069
3070 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3071 self.placeholder_display_map
3072 .as_ref()
3073 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3074 }
3075
3076 pub fn set_placeholder_text(
3077 &mut self,
3078 placeholder_text: &str,
3079 window: &mut Window,
3080 cx: &mut Context<Self>,
3081 ) {
3082 let multibuffer = cx
3083 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3084
3085 let style = window.text_style();
3086
3087 self.placeholder_display_map = Some(cx.new(|cx| {
3088 DisplayMap::new(
3089 multibuffer,
3090 style.font(),
3091 style.font_size.to_pixels(window.rem_size()),
3092 None,
3093 FILE_HEADER_HEIGHT,
3094 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3095 Default::default(),
3096 DiagnosticSeverity::Off,
3097 cx,
3098 )
3099 }));
3100 cx.notify();
3101 }
3102
3103 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3104 self.cursor_shape = cursor_shape;
3105
3106 // Disrupt blink for immediate user feedback that the cursor shape has changed
3107 self.blink_manager.update(cx, BlinkManager::show_cursor);
3108
3109 cx.notify();
3110 }
3111
3112 pub fn cursor_shape(&self) -> CursorShape {
3113 self.cursor_shape
3114 }
3115
3116 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3117 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3118 }
3119
3120 pub fn set_current_line_highlight(
3121 &mut self,
3122 current_line_highlight: Option<CurrentLineHighlight>,
3123 ) {
3124 self.current_line_highlight = current_line_highlight;
3125 }
3126
3127 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3128 self.collapse_matches = collapse_matches;
3129 }
3130
3131 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3132 if self.collapse_matches {
3133 return range.start..range.start;
3134 }
3135 range.clone()
3136 }
3137
3138 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3139 self.display_map.read(cx).clip_at_line_ends
3140 }
3141
3142 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3143 if self.display_map.read(cx).clip_at_line_ends != clip {
3144 self.display_map
3145 .update(cx, |map, _| map.clip_at_line_ends = clip);
3146 }
3147 }
3148
3149 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3150 self.input_enabled = input_enabled;
3151 }
3152
3153 pub fn set_edit_predictions_hidden_for_vim_mode(
3154 &mut self,
3155 hidden: bool,
3156 window: &mut Window,
3157 cx: &mut Context<Self>,
3158 ) {
3159 if hidden != self.edit_predictions_hidden_for_vim_mode {
3160 self.edit_predictions_hidden_for_vim_mode = hidden;
3161 if hidden {
3162 self.update_visible_edit_prediction(window, cx);
3163 } else {
3164 self.refresh_edit_prediction(true, false, window, cx);
3165 }
3166 }
3167 }
3168
3169 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3170 self.menu_edit_predictions_policy = value;
3171 }
3172
3173 pub fn set_autoindent(&mut self, autoindent: bool) {
3174 if autoindent {
3175 self.autoindent_mode = Some(AutoindentMode::EachLine);
3176 } else {
3177 self.autoindent_mode = None;
3178 }
3179 }
3180
3181 pub fn capability(&self, cx: &App) -> Capability {
3182 if self.read_only {
3183 Capability::ReadOnly
3184 } else {
3185 self.buffer.read(cx).capability()
3186 }
3187 }
3188
3189 pub fn read_only(&self, cx: &App) -> bool {
3190 self.read_only || self.buffer.read(cx).read_only()
3191 }
3192
3193 pub fn set_read_only(&mut self, read_only: bool) {
3194 self.read_only = read_only;
3195 }
3196
3197 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3198 self.use_autoclose = autoclose;
3199 }
3200
3201 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3202 self.use_auto_surround = auto_surround;
3203 }
3204
3205 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3206 self.auto_replace_emoji_shortcode = auto_replace;
3207 }
3208
3209 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3210 self.buffer_serialization = should_serialize.then(|| {
3211 BufferSerialization::new(
3212 ProjectSettings::get_global(cx)
3213 .session
3214 .restore_unsaved_buffers,
3215 )
3216 })
3217 }
3218
3219 fn should_serialize_buffer(&self) -> bool {
3220 self.buffer_serialization.is_some()
3221 }
3222
3223 pub fn toggle_edit_predictions(
3224 &mut self,
3225 _: &ToggleEditPrediction,
3226 window: &mut Window,
3227 cx: &mut Context<Self>,
3228 ) {
3229 if self.show_edit_predictions_override.is_some() {
3230 self.set_show_edit_predictions(None, window, cx);
3231 } else {
3232 let show_edit_predictions = !self.edit_predictions_enabled();
3233 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3234 }
3235 }
3236
3237 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3238 self.show_completions_on_input_override = show_completions_on_input;
3239 }
3240
3241 pub fn set_show_edit_predictions(
3242 &mut self,
3243 show_edit_predictions: Option<bool>,
3244 window: &mut Window,
3245 cx: &mut Context<Self>,
3246 ) {
3247 self.show_edit_predictions_override = show_edit_predictions;
3248 self.update_edit_prediction_settings(cx);
3249
3250 if let Some(false) = show_edit_predictions {
3251 self.discard_edit_prediction(false, cx);
3252 } else {
3253 self.refresh_edit_prediction(false, true, window, cx);
3254 }
3255 }
3256
3257 fn edit_predictions_disabled_in_scope(
3258 &self,
3259 buffer: &Entity<Buffer>,
3260 buffer_position: language::Anchor,
3261 cx: &App,
3262 ) -> bool {
3263 let snapshot = buffer.read(cx).snapshot();
3264 let settings = snapshot.settings_at(buffer_position, cx);
3265
3266 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3267 return false;
3268 };
3269
3270 scope.override_name().is_some_and(|scope_name| {
3271 settings
3272 .edit_predictions_disabled_in
3273 .iter()
3274 .any(|s| s == scope_name)
3275 })
3276 }
3277
3278 pub fn set_use_modal_editing(&mut self, to: bool) {
3279 self.use_modal_editing = to;
3280 }
3281
3282 pub fn use_modal_editing(&self) -> bool {
3283 self.use_modal_editing
3284 }
3285
3286 fn selections_did_change(
3287 &mut self,
3288 local: bool,
3289 old_cursor_position: &Anchor,
3290 effects: SelectionEffects,
3291 window: &mut Window,
3292 cx: &mut Context<Self>,
3293 ) {
3294 window.invalidate_character_coordinates();
3295
3296 // Copy selections to primary selection buffer
3297 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3298 if local {
3299 let selections = self
3300 .selections
3301 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3302 let buffer_handle = self.buffer.read(cx).read(cx);
3303
3304 let mut text = String::new();
3305 for (index, selection) in selections.iter().enumerate() {
3306 let text_for_selection = buffer_handle
3307 .text_for_range(selection.start..selection.end)
3308 .collect::<String>();
3309
3310 text.push_str(&text_for_selection);
3311 if index != selections.len() - 1 {
3312 text.push('\n');
3313 }
3314 }
3315
3316 if !text.is_empty() {
3317 cx.write_to_primary(ClipboardItem::new_string(text));
3318 }
3319 }
3320
3321 let selection_anchors = self.selections.disjoint_anchors_arc();
3322
3323 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3324 self.buffer.update(cx, |buffer, cx| {
3325 buffer.set_active_selections(
3326 &selection_anchors,
3327 self.selections.line_mode(),
3328 self.cursor_shape,
3329 cx,
3330 )
3331 });
3332 }
3333 let display_map = self
3334 .display_map
3335 .update(cx, |display_map, cx| display_map.snapshot(cx));
3336 let buffer = display_map.buffer_snapshot();
3337 if self.selections.count() == 1 {
3338 self.add_selections_state = None;
3339 }
3340 self.select_next_state = None;
3341 self.select_prev_state = None;
3342 self.select_syntax_node_history.try_clear();
3343 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3344 self.snippet_stack.invalidate(&selection_anchors, buffer);
3345 self.take_rename(false, window, cx);
3346
3347 let newest_selection = self.selections.newest_anchor();
3348 let new_cursor_position = newest_selection.head();
3349 let selection_start = newest_selection.start;
3350
3351 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3352 self.push_to_nav_history(
3353 *old_cursor_position,
3354 Some(new_cursor_position.to_point(buffer)),
3355 false,
3356 effects.nav_history == Some(true),
3357 cx,
3358 );
3359 }
3360
3361 if local {
3362 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3363 self.register_buffer(buffer_id, cx);
3364 }
3365
3366 let mut context_menu = self.context_menu.borrow_mut();
3367 let completion_menu = match context_menu.as_ref() {
3368 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3369 Some(CodeContextMenu::CodeActions(_)) => {
3370 *context_menu = None;
3371 None
3372 }
3373 None => None,
3374 };
3375 let completion_position = completion_menu.map(|menu| menu.initial_position);
3376 drop(context_menu);
3377
3378 if effects.completions
3379 && let Some(completion_position) = completion_position
3380 {
3381 let start_offset = selection_start.to_offset(buffer);
3382 let position_matches = start_offset == completion_position.to_offset(buffer);
3383 let continue_showing = if let Some((snap, ..)) =
3384 buffer.point_to_buffer_offset(completion_position)
3385 && !snap.capability.editable()
3386 {
3387 false
3388 } else if position_matches {
3389 if self.snippet_stack.is_empty() {
3390 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3391 == Some(CharKind::Word)
3392 } else {
3393 // Snippet choices can be shown even when the cursor is in whitespace.
3394 // Dismissing the menu with actions like backspace is handled by
3395 // invalidation regions.
3396 true
3397 }
3398 } else {
3399 false
3400 };
3401
3402 if continue_showing {
3403 self.open_or_update_completions_menu(None, None, false, window, cx);
3404 } else {
3405 self.hide_context_menu(window, cx);
3406 }
3407 }
3408
3409 hide_hover(self, cx);
3410
3411 if old_cursor_position.to_display_point(&display_map).row()
3412 != new_cursor_position.to_display_point(&display_map).row()
3413 {
3414 self.available_code_actions.take();
3415 }
3416 self.refresh_code_actions(window, cx);
3417 self.refresh_document_highlights(cx);
3418 refresh_linked_ranges(self, window, cx);
3419
3420 self.refresh_selected_text_highlights(false, window, cx);
3421 self.refresh_matching_bracket_highlights(window, cx);
3422 self.update_visible_edit_prediction(window, cx);
3423 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3424 self.inline_blame_popover.take();
3425 if self.git_blame_inline_enabled {
3426 self.start_inline_blame_timer(window, cx);
3427 }
3428 }
3429
3430 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3431 cx.emit(EditorEvent::SelectionsChanged { local });
3432
3433 let selections = &self.selections.disjoint_anchors_arc();
3434 if selections.len() == 1 {
3435 cx.emit(SearchEvent::ActiveMatchChanged)
3436 }
3437 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3438 let inmemory_selections = selections
3439 .iter()
3440 .map(|s| {
3441 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3442 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3443 })
3444 .collect();
3445 self.update_restoration_data(cx, |data| {
3446 data.selections = inmemory_selections;
3447 });
3448
3449 if WorkspaceSettings::get(None, cx).restore_on_startup
3450 != RestoreOnStartupBehavior::EmptyTab
3451 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3452 {
3453 let snapshot = self.buffer().read(cx).snapshot(cx);
3454 let selections = selections.clone();
3455 let background_executor = cx.background_executor().clone();
3456 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3457 self.serialize_selections = cx.background_spawn(async move {
3458 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3459 let db_selections = selections
3460 .iter()
3461 .map(|selection| {
3462 (
3463 selection.start.to_offset(&snapshot).0,
3464 selection.end.to_offset(&snapshot).0,
3465 )
3466 })
3467 .collect();
3468
3469 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3470 .await
3471 .with_context(|| {
3472 format!(
3473 "persisting editor selections for editor {editor_id}, \
3474 workspace {workspace_id:?}"
3475 )
3476 })
3477 .log_err();
3478 });
3479 }
3480 }
3481
3482 cx.notify();
3483 }
3484
3485 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3486 use text::ToOffset as _;
3487 use text::ToPoint as _;
3488
3489 if self.mode.is_minimap()
3490 || WorkspaceSettings::get(None, cx).restore_on_startup
3491 == RestoreOnStartupBehavior::EmptyTab
3492 {
3493 return;
3494 }
3495
3496 if !self.buffer().read(cx).is_singleton() {
3497 return;
3498 }
3499
3500 let display_snapshot = self
3501 .display_map
3502 .update(cx, |display_map, cx| display_map.snapshot(cx));
3503 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3504 return;
3505 };
3506 let inmemory_folds = display_snapshot
3507 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3508 .map(|fold| {
3509 fold.range.start.text_anchor.to_point(&snapshot)
3510 ..fold.range.end.text_anchor.to_point(&snapshot)
3511 })
3512 .collect();
3513 self.update_restoration_data(cx, |data| {
3514 data.folds = inmemory_folds;
3515 });
3516
3517 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3518 return;
3519 };
3520 let background_executor = cx.background_executor().clone();
3521 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3522 const FINGERPRINT_LEN: usize = 32;
3523 let db_folds = display_snapshot
3524 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3525 .map(|fold| {
3526 let start = fold.range.start.text_anchor.to_offset(&snapshot);
3527 let end = fold.range.end.text_anchor.to_offset(&snapshot);
3528
3529 // Extract fingerprints - content at fold boundaries for validation on restore
3530 // Both fingerprints must be INSIDE the fold to avoid capturing surrounding
3531 // content that might change independently.
3532 // start_fp: first min(32, fold_len) bytes of fold content
3533 // end_fp: last min(32, fold_len) bytes of fold content
3534 // Clip to character boundaries to handle multibyte UTF-8 characters.
3535 let fold_len = end - start;
3536 let start_fp_end = snapshot
3537 .clip_offset(start + std::cmp::min(FINGERPRINT_LEN, fold_len), Bias::Left);
3538 let start_fp: String = snapshot.text_for_range(start..start_fp_end).collect();
3539 let end_fp_start = snapshot
3540 .clip_offset(end.saturating_sub(FINGERPRINT_LEN).max(start), Bias::Right);
3541 let end_fp: String = snapshot.text_for_range(end_fp_start..end).collect();
3542
3543 (start, end, start_fp, end_fp)
3544 })
3545 .collect::<Vec<_>>();
3546 self.serialize_folds = cx.background_spawn(async move {
3547 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3548 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3549 .await
3550 .with_context(|| {
3551 format!(
3552 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3553 )
3554 })
3555 .log_err();
3556 });
3557 }
3558
3559 pub fn sync_selections(
3560 &mut self,
3561 other: Entity<Editor>,
3562 cx: &mut Context<Self>,
3563 ) -> gpui::Subscription {
3564 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3565 if !other_selections.is_empty() {
3566 self.selections
3567 .change_with(&self.display_snapshot(cx), |selections| {
3568 selections.select_anchors(other_selections);
3569 });
3570 }
3571
3572 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3573 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3574 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3575 if other_selections.is_empty() {
3576 return;
3577 }
3578 let snapshot = this.display_snapshot(cx);
3579 this.selections.change_with(&snapshot, |selections| {
3580 selections.select_anchors(other_selections);
3581 });
3582 }
3583 });
3584
3585 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3586 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3587 let these_selections = this.selections.disjoint_anchors().to_vec();
3588 if these_selections.is_empty() {
3589 return;
3590 }
3591 other.update(cx, |other_editor, cx| {
3592 let snapshot = other_editor.display_snapshot(cx);
3593 other_editor
3594 .selections
3595 .change_with(&snapshot, |selections| {
3596 selections.select_anchors(these_selections);
3597 })
3598 });
3599 }
3600 });
3601
3602 Subscription::join(other_subscription, this_subscription)
3603 }
3604
3605 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3606 if self.buffer().read(cx).is_singleton() {
3607 return;
3608 }
3609 let snapshot = self.buffer.read(cx).snapshot(cx);
3610 let buffer_ids: HashSet<BufferId> = self
3611 .selections
3612 .disjoint_anchor_ranges()
3613 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3614 .collect();
3615 for buffer_id in buffer_ids {
3616 self.unfold_buffer(buffer_id, cx);
3617 }
3618 }
3619
3620 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3621 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3622 /// effects of selection change occur at the end of the transaction.
3623 pub fn change_selections<R>(
3624 &mut self,
3625 effects: SelectionEffects,
3626 window: &mut Window,
3627 cx: &mut Context<Self>,
3628 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3629 ) -> R {
3630 let snapshot = self.display_snapshot(cx);
3631 if let Some(state) = &mut self.deferred_selection_effects_state {
3632 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3633 state.effects.completions = effects.completions;
3634 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3635 let (changed, result) = self.selections.change_with(&snapshot, change);
3636 state.changed |= changed;
3637 return result;
3638 }
3639 let mut state = DeferredSelectionEffectsState {
3640 changed: false,
3641 effects,
3642 old_cursor_position: self.selections.newest_anchor().head(),
3643 history_entry: SelectionHistoryEntry {
3644 selections: self.selections.disjoint_anchors_arc(),
3645 select_next_state: self.select_next_state.clone(),
3646 select_prev_state: self.select_prev_state.clone(),
3647 add_selections_state: self.add_selections_state.clone(),
3648 },
3649 };
3650 let (changed, result) = self.selections.change_with(&snapshot, change);
3651 state.changed = state.changed || changed;
3652 if self.defer_selection_effects {
3653 self.deferred_selection_effects_state = Some(state);
3654 } else {
3655 self.apply_selection_effects(state, window, cx);
3656 }
3657 result
3658 }
3659
3660 /// Defers the effects of selection change, so that the effects of multiple calls to
3661 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3662 /// to selection history and the state of popovers based on selection position aren't
3663 /// erroneously updated.
3664 pub fn with_selection_effects_deferred<R>(
3665 &mut self,
3666 window: &mut Window,
3667 cx: &mut Context<Self>,
3668 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3669 ) -> R {
3670 let already_deferred = self.defer_selection_effects;
3671 self.defer_selection_effects = true;
3672 let result = update(self, window, cx);
3673 if !already_deferred {
3674 self.defer_selection_effects = false;
3675 if let Some(state) = self.deferred_selection_effects_state.take() {
3676 self.apply_selection_effects(state, window, cx);
3677 }
3678 }
3679 result
3680 }
3681
3682 fn apply_selection_effects(
3683 &mut self,
3684 state: DeferredSelectionEffectsState,
3685 window: &mut Window,
3686 cx: &mut Context<Self>,
3687 ) {
3688 if state.changed {
3689 self.selection_history.push(state.history_entry);
3690
3691 if let Some(autoscroll) = state.effects.scroll {
3692 self.request_autoscroll(autoscroll, cx);
3693 }
3694
3695 let old_cursor_position = &state.old_cursor_position;
3696
3697 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3698
3699 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3700 self.show_signature_help(&ShowSignatureHelp, window, cx);
3701 }
3702 }
3703 }
3704
3705 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3706 where
3707 I: IntoIterator<Item = (Range<S>, T)>,
3708 S: ToOffset,
3709 T: Into<Arc<str>>,
3710 {
3711 if self.read_only(cx) {
3712 return;
3713 }
3714
3715 self.buffer
3716 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3717 }
3718
3719 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3720 where
3721 I: IntoIterator<Item = (Range<S>, T)>,
3722 S: ToOffset,
3723 T: Into<Arc<str>>,
3724 {
3725 if self.read_only(cx) {
3726 return;
3727 }
3728
3729 self.buffer.update(cx, |buffer, cx| {
3730 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3731 });
3732 }
3733
3734 pub fn edit_with_block_indent<I, S, T>(
3735 &mut self,
3736 edits: I,
3737 original_indent_columns: Vec<Option<u32>>,
3738 cx: &mut Context<Self>,
3739 ) where
3740 I: IntoIterator<Item = (Range<S>, T)>,
3741 S: ToOffset,
3742 T: Into<Arc<str>>,
3743 {
3744 if self.read_only(cx) {
3745 return;
3746 }
3747
3748 self.buffer.update(cx, |buffer, cx| {
3749 buffer.edit(
3750 edits,
3751 Some(AutoindentMode::Block {
3752 original_indent_columns,
3753 }),
3754 cx,
3755 )
3756 });
3757 }
3758
3759 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3760 self.hide_context_menu(window, cx);
3761
3762 match phase {
3763 SelectPhase::Begin {
3764 position,
3765 add,
3766 click_count,
3767 } => self.begin_selection(position, add, click_count, window, cx),
3768 SelectPhase::BeginColumnar {
3769 position,
3770 goal_column,
3771 reset,
3772 mode,
3773 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3774 SelectPhase::Extend {
3775 position,
3776 click_count,
3777 } => self.extend_selection(position, click_count, window, cx),
3778 SelectPhase::Update {
3779 position,
3780 goal_column,
3781 scroll_delta,
3782 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3783 SelectPhase::End => self.end_selection(window, cx),
3784 }
3785 }
3786
3787 fn extend_selection(
3788 &mut self,
3789 position: DisplayPoint,
3790 click_count: usize,
3791 window: &mut Window,
3792 cx: &mut Context<Self>,
3793 ) {
3794 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3795 let tail = self
3796 .selections
3797 .newest::<MultiBufferOffset>(&display_map)
3798 .tail();
3799 let click_count = click_count.max(match self.selections.select_mode() {
3800 SelectMode::Character => 1,
3801 SelectMode::Word(_) => 2,
3802 SelectMode::Line(_) => 3,
3803 SelectMode::All => 4,
3804 });
3805 self.begin_selection(position, false, click_count, window, cx);
3806
3807 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3808
3809 let current_selection = match self.selections.select_mode() {
3810 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3811 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3812 };
3813
3814 let mut pending_selection = self
3815 .selections
3816 .pending_anchor()
3817 .cloned()
3818 .expect("extend_selection not called with pending selection");
3819
3820 if pending_selection
3821 .start
3822 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3823 == Ordering::Greater
3824 {
3825 pending_selection.start = current_selection.start;
3826 }
3827 if pending_selection
3828 .end
3829 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3830 == Ordering::Less
3831 {
3832 pending_selection.end = current_selection.end;
3833 pending_selection.reversed = true;
3834 }
3835
3836 let mut pending_mode = self.selections.pending_mode().unwrap();
3837 match &mut pending_mode {
3838 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3839 _ => {}
3840 }
3841
3842 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3843 SelectionEffects::scroll(Autoscroll::fit())
3844 } else {
3845 SelectionEffects::no_scroll()
3846 };
3847
3848 self.change_selections(effects, window, cx, |s| {
3849 s.set_pending(pending_selection.clone(), pending_mode);
3850 s.set_is_extending(true);
3851 });
3852 }
3853
3854 fn begin_selection(
3855 &mut self,
3856 position: DisplayPoint,
3857 add: bool,
3858 click_count: usize,
3859 window: &mut Window,
3860 cx: &mut Context<Self>,
3861 ) {
3862 if !self.focus_handle.is_focused(window) {
3863 self.last_focused_descendant = None;
3864 window.focus(&self.focus_handle, cx);
3865 }
3866
3867 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3868 let buffer = display_map.buffer_snapshot();
3869 let position = display_map.clip_point(position, Bias::Left);
3870
3871 let start;
3872 let end;
3873 let mode;
3874 let mut auto_scroll;
3875 match click_count {
3876 1 => {
3877 start = buffer.anchor_before(position.to_point(&display_map));
3878 end = start;
3879 mode = SelectMode::Character;
3880 auto_scroll = true;
3881 }
3882 2 => {
3883 let position = display_map
3884 .clip_point(position, Bias::Left)
3885 .to_offset(&display_map, Bias::Left);
3886 let (range, _) = buffer.surrounding_word(position, None);
3887 start = buffer.anchor_before(range.start);
3888 end = buffer.anchor_before(range.end);
3889 mode = SelectMode::Word(start..end);
3890 auto_scroll = true;
3891 }
3892 3 => {
3893 let position = display_map
3894 .clip_point(position, Bias::Left)
3895 .to_point(&display_map);
3896 let line_start = display_map.prev_line_boundary(position).0;
3897 let next_line_start = buffer.clip_point(
3898 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3899 Bias::Left,
3900 );
3901 start = buffer.anchor_before(line_start);
3902 end = buffer.anchor_before(next_line_start);
3903 mode = SelectMode::Line(start..end);
3904 auto_scroll = true;
3905 }
3906 _ => {
3907 start = buffer.anchor_before(MultiBufferOffset(0));
3908 end = buffer.anchor_before(buffer.len());
3909 mode = SelectMode::All;
3910 auto_scroll = false;
3911 }
3912 }
3913 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3914
3915 let point_to_delete: Option<usize> = {
3916 let selected_points: Vec<Selection<Point>> =
3917 self.selections.disjoint_in_range(start..end, &display_map);
3918
3919 if !add || click_count > 1 {
3920 None
3921 } else if !selected_points.is_empty() {
3922 Some(selected_points[0].id)
3923 } else {
3924 let clicked_point_already_selected =
3925 self.selections.disjoint_anchors().iter().find(|selection| {
3926 selection.start.to_point(buffer) == start.to_point(buffer)
3927 || selection.end.to_point(buffer) == end.to_point(buffer)
3928 });
3929
3930 clicked_point_already_selected.map(|selection| selection.id)
3931 }
3932 };
3933
3934 let selections_count = self.selections.count();
3935 let effects = if auto_scroll {
3936 SelectionEffects::default()
3937 } else {
3938 SelectionEffects::no_scroll()
3939 };
3940
3941 self.change_selections(effects, window, cx, |s| {
3942 if let Some(point_to_delete) = point_to_delete {
3943 s.delete(point_to_delete);
3944
3945 if selections_count == 1 {
3946 s.set_pending_anchor_range(start..end, mode);
3947 }
3948 } else {
3949 if !add {
3950 s.clear_disjoint();
3951 }
3952
3953 s.set_pending_anchor_range(start..end, mode);
3954 }
3955 });
3956 }
3957
3958 fn begin_columnar_selection(
3959 &mut self,
3960 position: DisplayPoint,
3961 goal_column: u32,
3962 reset: bool,
3963 mode: ColumnarMode,
3964 window: &mut Window,
3965 cx: &mut Context<Self>,
3966 ) {
3967 if !self.focus_handle.is_focused(window) {
3968 self.last_focused_descendant = None;
3969 window.focus(&self.focus_handle, cx);
3970 }
3971
3972 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3973
3974 if reset {
3975 let pointer_position = display_map
3976 .buffer_snapshot()
3977 .anchor_before(position.to_point(&display_map));
3978
3979 self.change_selections(
3980 SelectionEffects::scroll(Autoscroll::newest()),
3981 window,
3982 cx,
3983 |s| {
3984 s.clear_disjoint();
3985 s.set_pending_anchor_range(
3986 pointer_position..pointer_position,
3987 SelectMode::Character,
3988 );
3989 },
3990 );
3991 };
3992
3993 let tail = self.selections.newest::<Point>(&display_map).tail();
3994 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3995 self.columnar_selection_state = match mode {
3996 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3997 selection_tail: selection_anchor,
3998 display_point: if reset {
3999 if position.column() != goal_column {
4000 Some(DisplayPoint::new(position.row(), goal_column))
4001 } else {
4002 None
4003 }
4004 } else {
4005 None
4006 },
4007 }),
4008 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
4009 selection_tail: selection_anchor,
4010 }),
4011 };
4012
4013 if !reset {
4014 self.select_columns(position, goal_column, &display_map, window, cx);
4015 }
4016 }
4017
4018 fn update_selection(
4019 &mut self,
4020 position: DisplayPoint,
4021 goal_column: u32,
4022 scroll_delta: gpui::Point<f32>,
4023 window: &mut Window,
4024 cx: &mut Context<Self>,
4025 ) {
4026 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
4027
4028 if self.columnar_selection_state.is_some() {
4029 self.select_columns(position, goal_column, &display_map, window, cx);
4030 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4031 let buffer = display_map.buffer_snapshot();
4032 let head;
4033 let tail;
4034 let mode = self.selections.pending_mode().unwrap();
4035 match &mode {
4036 SelectMode::Character => {
4037 head = position.to_point(&display_map);
4038 tail = pending.tail().to_point(buffer);
4039 }
4040 SelectMode::Word(original_range) => {
4041 let offset = display_map
4042 .clip_point(position, Bias::Left)
4043 .to_offset(&display_map, Bias::Left);
4044 let original_range = original_range.to_offset(buffer);
4045
4046 let head_offset = if buffer.is_inside_word(offset, None)
4047 || original_range.contains(&offset)
4048 {
4049 let (word_range, _) = buffer.surrounding_word(offset, None);
4050 if word_range.start < original_range.start {
4051 word_range.start
4052 } else {
4053 word_range.end
4054 }
4055 } else {
4056 offset
4057 };
4058
4059 head = head_offset.to_point(buffer);
4060 if head_offset <= original_range.start {
4061 tail = original_range.end.to_point(buffer);
4062 } else {
4063 tail = original_range.start.to_point(buffer);
4064 }
4065 }
4066 SelectMode::Line(original_range) => {
4067 let original_range = original_range.to_point(display_map.buffer_snapshot());
4068
4069 let position = display_map
4070 .clip_point(position, Bias::Left)
4071 .to_point(&display_map);
4072 let line_start = display_map.prev_line_boundary(position).0;
4073 let next_line_start = buffer.clip_point(
4074 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4075 Bias::Left,
4076 );
4077
4078 if line_start < original_range.start {
4079 head = line_start
4080 } else {
4081 head = next_line_start
4082 }
4083
4084 if head <= original_range.start {
4085 tail = original_range.end;
4086 } else {
4087 tail = original_range.start;
4088 }
4089 }
4090 SelectMode::All => {
4091 return;
4092 }
4093 };
4094
4095 if head < tail {
4096 pending.start = buffer.anchor_before(head);
4097 pending.end = buffer.anchor_before(tail);
4098 pending.reversed = true;
4099 } else {
4100 pending.start = buffer.anchor_before(tail);
4101 pending.end = buffer.anchor_before(head);
4102 pending.reversed = false;
4103 }
4104
4105 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4106 s.set_pending(pending.clone(), mode);
4107 });
4108 } else {
4109 log::error!("update_selection dispatched with no pending selection");
4110 return;
4111 }
4112
4113 self.apply_scroll_delta(scroll_delta, window, cx);
4114 cx.notify();
4115 }
4116
4117 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4118 self.columnar_selection_state.take();
4119 if let Some(pending_mode) = self.selections.pending_mode() {
4120 let selections = self
4121 .selections
4122 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4123 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4124 s.select(selections);
4125 s.clear_pending();
4126 if s.is_extending() {
4127 s.set_is_extending(false);
4128 } else {
4129 s.set_select_mode(pending_mode);
4130 }
4131 });
4132 }
4133 }
4134
4135 fn select_columns(
4136 &mut self,
4137 head: DisplayPoint,
4138 goal_column: u32,
4139 display_map: &DisplaySnapshot,
4140 window: &mut Window,
4141 cx: &mut Context<Self>,
4142 ) {
4143 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4144 return;
4145 };
4146
4147 let tail = match columnar_state {
4148 ColumnarSelectionState::FromMouse {
4149 selection_tail,
4150 display_point,
4151 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4152 ColumnarSelectionState::FromSelection { selection_tail } => {
4153 selection_tail.to_display_point(display_map)
4154 }
4155 };
4156
4157 let start_row = cmp::min(tail.row(), head.row());
4158 let end_row = cmp::max(tail.row(), head.row());
4159 let start_column = cmp::min(tail.column(), goal_column);
4160 let end_column = cmp::max(tail.column(), goal_column);
4161 let reversed = start_column < tail.column();
4162
4163 let selection_ranges = (start_row.0..=end_row.0)
4164 .map(DisplayRow)
4165 .filter_map(|row| {
4166 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4167 || start_column <= display_map.line_len(row))
4168 && !display_map.is_block_line(row)
4169 {
4170 let start = display_map
4171 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4172 .to_point(display_map);
4173 let end = display_map
4174 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4175 .to_point(display_map);
4176 if reversed {
4177 Some(end..start)
4178 } else {
4179 Some(start..end)
4180 }
4181 } else {
4182 None
4183 }
4184 })
4185 .collect::<Vec<_>>();
4186 if selection_ranges.is_empty() {
4187 return;
4188 }
4189
4190 let ranges = match columnar_state {
4191 ColumnarSelectionState::FromMouse { .. } => {
4192 let mut non_empty_ranges = selection_ranges
4193 .iter()
4194 .filter(|selection_range| selection_range.start != selection_range.end)
4195 .peekable();
4196 if non_empty_ranges.peek().is_some() {
4197 non_empty_ranges.cloned().collect()
4198 } else {
4199 selection_ranges
4200 }
4201 }
4202 _ => selection_ranges,
4203 };
4204
4205 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4206 s.select_ranges(ranges);
4207 });
4208 cx.notify();
4209 }
4210
4211 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4212 self.selections
4213 .all_adjusted(snapshot)
4214 .iter()
4215 .any(|selection| !selection.is_empty())
4216 }
4217
4218 pub fn has_pending_nonempty_selection(&self) -> bool {
4219 let pending_nonempty_selection = match self.selections.pending_anchor() {
4220 Some(Selection { start, end, .. }) => start != end,
4221 None => false,
4222 };
4223
4224 pending_nonempty_selection
4225 || (self.columnar_selection_state.is_some()
4226 && self.selections.disjoint_anchors().len() > 1)
4227 }
4228
4229 pub fn has_pending_selection(&self) -> bool {
4230 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4231 }
4232
4233 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4234 self.selection_mark_mode = false;
4235 self.selection_drag_state = SelectionDragState::None;
4236
4237 if self.dismiss_menus_and_popups(true, window, cx) {
4238 cx.notify();
4239 return;
4240 }
4241 if self.clear_expanded_diff_hunks(cx) {
4242 cx.notify();
4243 return;
4244 }
4245 if self.show_git_blame_gutter {
4246 self.show_git_blame_gutter = false;
4247 cx.notify();
4248 return;
4249 }
4250
4251 if self.mode.is_full()
4252 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4253 {
4254 cx.notify();
4255 return;
4256 }
4257
4258 cx.propagate();
4259 }
4260
4261 pub fn dismiss_menus_and_popups(
4262 &mut self,
4263 is_user_requested: bool,
4264 window: &mut Window,
4265 cx: &mut Context<Self>,
4266 ) -> bool {
4267 let mut dismissed = false;
4268
4269 dismissed |= self.take_rename(false, window, cx).is_some();
4270 dismissed |= self.hide_blame_popover(true, cx);
4271 dismissed |= hide_hover(self, cx);
4272 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4273 dismissed |= self.hide_context_menu(window, cx).is_some();
4274 dismissed |= self.mouse_context_menu.take().is_some();
4275 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4276 dismissed |= self.snippet_stack.pop().is_some();
4277
4278 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4279 self.dismiss_diagnostics(cx);
4280 dismissed = true;
4281 }
4282
4283 dismissed
4284 }
4285
4286 fn linked_editing_ranges_for(
4287 &self,
4288 selection: Range<text::Anchor>,
4289 cx: &App,
4290 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4291 if self.linked_edit_ranges.is_empty() {
4292 return None;
4293 }
4294 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4295 selection.end.buffer_id.and_then(|end_buffer_id| {
4296 if selection.start.buffer_id != Some(end_buffer_id) {
4297 return None;
4298 }
4299 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4300 let snapshot = buffer.read(cx).snapshot();
4301 self.linked_edit_ranges
4302 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4303 .map(|ranges| (ranges, snapshot, buffer))
4304 })?;
4305 use text::ToOffset as TO;
4306 // find offset from the start of current range to current cursor position
4307 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4308
4309 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4310 let start_difference = start_offset - start_byte_offset;
4311 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4312 let end_difference = end_offset - start_byte_offset;
4313 // Current range has associated linked ranges.
4314 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4315 for range in linked_ranges.iter() {
4316 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4317 let end_offset = start_offset + end_difference;
4318 let start_offset = start_offset + start_difference;
4319 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4320 continue;
4321 }
4322 if self.selections.disjoint_anchor_ranges().any(|s| {
4323 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4324 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4325 {
4326 return false;
4327 }
4328 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4329 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4330 }) {
4331 continue;
4332 }
4333 let start = buffer_snapshot.anchor_after(start_offset);
4334 let end = buffer_snapshot.anchor_after(end_offset);
4335 linked_edits
4336 .entry(buffer.clone())
4337 .or_default()
4338 .push(start..end);
4339 }
4340 Some(linked_edits)
4341 }
4342
4343 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4344 let text: Arc<str> = text.into();
4345
4346 if self.read_only(cx) {
4347 return;
4348 }
4349
4350 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4351
4352 self.unfold_buffers_with_selections(cx);
4353
4354 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4355 let mut bracket_inserted = false;
4356 let mut edits = Vec::new();
4357 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4358 let mut new_selections = Vec::with_capacity(selections.len());
4359 let mut new_autoclose_regions = Vec::new();
4360 let snapshot = self.buffer.read(cx).read(cx);
4361 let mut clear_linked_edit_ranges = false;
4362 let mut all_selections_read_only = true;
4363 let mut has_adjacent_edits = false;
4364 let mut in_adjacent_group = false;
4365
4366 let mut regions = self
4367 .selections_with_autoclose_regions(selections, &snapshot)
4368 .peekable();
4369
4370 while let Some((selection, autoclose_region)) = regions.next() {
4371 if snapshot
4372 .point_to_buffer_point(selection.head())
4373 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4374 {
4375 continue;
4376 }
4377 if snapshot
4378 .point_to_buffer_point(selection.tail())
4379 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4380 {
4381 // note, ideally we'd clip the tail to the closest writeable region towards the head
4382 continue;
4383 }
4384 all_selections_read_only = false;
4385
4386 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4387 // Determine if the inserted text matches the opening or closing
4388 // bracket of any of this language's bracket pairs.
4389 let mut bracket_pair = None;
4390 let mut is_bracket_pair_start = false;
4391 let mut is_bracket_pair_end = false;
4392 if !text.is_empty() {
4393 let mut bracket_pair_matching_end = None;
4394 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4395 // and they are removing the character that triggered IME popup.
4396 for (pair, enabled) in scope.brackets() {
4397 if !pair.close && !pair.surround {
4398 continue;
4399 }
4400
4401 if enabled && pair.start.ends_with(text.as_ref()) {
4402 let prefix_len = pair.start.len() - text.len();
4403 let preceding_text_matches_prefix = prefix_len == 0
4404 || (selection.start.column >= (prefix_len as u32)
4405 && snapshot.contains_str_at(
4406 Point::new(
4407 selection.start.row,
4408 selection.start.column - (prefix_len as u32),
4409 ),
4410 &pair.start[..prefix_len],
4411 ));
4412 if preceding_text_matches_prefix {
4413 bracket_pair = Some(pair.clone());
4414 is_bracket_pair_start = true;
4415 break;
4416 }
4417 }
4418 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4419 {
4420 // take first bracket pair matching end, but don't break in case a later bracket
4421 // pair matches start
4422 bracket_pair_matching_end = Some(pair.clone());
4423 }
4424 }
4425 if let Some(end) = bracket_pair_matching_end
4426 && bracket_pair.is_none()
4427 {
4428 bracket_pair = Some(end);
4429 is_bracket_pair_end = true;
4430 }
4431 }
4432
4433 if let Some(bracket_pair) = bracket_pair {
4434 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4435 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4436 let auto_surround =
4437 self.use_auto_surround && snapshot_settings.use_auto_surround;
4438 if selection.is_empty() {
4439 if is_bracket_pair_start {
4440 // If the inserted text is a suffix of an opening bracket and the
4441 // selection is preceded by the rest of the opening bracket, then
4442 // insert the closing bracket.
4443 let following_text_allows_autoclose = snapshot
4444 .chars_at(selection.start)
4445 .next()
4446 .is_none_or(|c| scope.should_autoclose_before(c));
4447
4448 let preceding_text_allows_autoclose = selection.start.column == 0
4449 || snapshot
4450 .reversed_chars_at(selection.start)
4451 .next()
4452 .is_none_or(|c| {
4453 bracket_pair.start != bracket_pair.end
4454 || !snapshot
4455 .char_classifier_at(selection.start)
4456 .is_word(c)
4457 });
4458
4459 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4460 && bracket_pair.start.len() == 1
4461 {
4462 let target = bracket_pair.start.chars().next().unwrap();
4463 let mut byte_offset = 0u32;
4464 let current_line_count = snapshot
4465 .reversed_chars_at(selection.start)
4466 .take_while(|&c| c != '\n')
4467 .filter(|c| {
4468 byte_offset += c.len_utf8() as u32;
4469 if *c != target {
4470 return false;
4471 }
4472
4473 let point = Point::new(
4474 selection.start.row,
4475 selection.start.column.saturating_sub(byte_offset),
4476 );
4477
4478 let is_enabled = snapshot
4479 .language_scope_at(point)
4480 .and_then(|scope| {
4481 scope
4482 .brackets()
4483 .find(|(pair, _)| {
4484 pair.start == bracket_pair.start
4485 })
4486 .map(|(_, enabled)| enabled)
4487 })
4488 .unwrap_or(true);
4489
4490 let is_delimiter = snapshot
4491 .language_scope_at(Point::new(
4492 point.row,
4493 point.column + 1,
4494 ))
4495 .and_then(|scope| {
4496 scope
4497 .brackets()
4498 .find(|(pair, _)| {
4499 pair.start == bracket_pair.start
4500 })
4501 .map(|(_, enabled)| !enabled)
4502 })
4503 .unwrap_or(false);
4504
4505 is_enabled && !is_delimiter
4506 })
4507 .count();
4508 current_line_count % 2 == 1
4509 } else {
4510 false
4511 };
4512
4513 if autoclose
4514 && bracket_pair.close
4515 && following_text_allows_autoclose
4516 && preceding_text_allows_autoclose
4517 && !is_closing_quote
4518 {
4519 let anchor = snapshot.anchor_before(selection.end);
4520 new_selections.push((selection.map(|_| anchor), text.len()));
4521 new_autoclose_regions.push((
4522 anchor,
4523 text.len(),
4524 selection.id,
4525 bracket_pair.clone(),
4526 ));
4527 edits.push((
4528 selection.range(),
4529 format!("{}{}", text, bracket_pair.end).into(),
4530 ));
4531 bracket_inserted = true;
4532 continue;
4533 }
4534 }
4535
4536 if let Some(region) = autoclose_region {
4537 // If the selection is followed by an auto-inserted closing bracket,
4538 // then don't insert that closing bracket again; just move the selection
4539 // past the closing bracket.
4540 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4541 && text.as_ref() == region.pair.end.as_str()
4542 && snapshot.contains_str_at(region.range.end, text.as_ref());
4543 if should_skip {
4544 let anchor = snapshot.anchor_after(selection.end);
4545 new_selections
4546 .push((selection.map(|_| anchor), region.pair.end.len()));
4547 continue;
4548 }
4549 }
4550
4551 let always_treat_brackets_as_autoclosed = snapshot
4552 .language_settings_at(selection.start, cx)
4553 .always_treat_brackets_as_autoclosed;
4554 if always_treat_brackets_as_autoclosed
4555 && is_bracket_pair_end
4556 && snapshot.contains_str_at(selection.end, text.as_ref())
4557 {
4558 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4559 // and the inserted text is a closing bracket and the selection is followed
4560 // by the closing bracket then move the selection past the closing bracket.
4561 let anchor = snapshot.anchor_after(selection.end);
4562 new_selections.push((selection.map(|_| anchor), text.len()));
4563 continue;
4564 }
4565 }
4566 // If an opening bracket is 1 character long and is typed while
4567 // text is selected, then surround that text with the bracket pair.
4568 else if auto_surround
4569 && bracket_pair.surround
4570 && is_bracket_pair_start
4571 && bracket_pair.start.chars().count() == 1
4572 {
4573 edits.push((selection.start..selection.start, text.clone()));
4574 edits.push((
4575 selection.end..selection.end,
4576 bracket_pair.end.as_str().into(),
4577 ));
4578 bracket_inserted = true;
4579 new_selections.push((
4580 Selection {
4581 id: selection.id,
4582 start: snapshot.anchor_after(selection.start),
4583 end: snapshot.anchor_before(selection.end),
4584 reversed: selection.reversed,
4585 goal: selection.goal,
4586 },
4587 0,
4588 ));
4589 continue;
4590 }
4591 }
4592 }
4593
4594 if self.auto_replace_emoji_shortcode
4595 && selection.is_empty()
4596 && text.as_ref().ends_with(':')
4597 && let Some(possible_emoji_short_code) =
4598 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4599 && !possible_emoji_short_code.is_empty()
4600 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4601 {
4602 let emoji_shortcode_start = Point::new(
4603 selection.start.row,
4604 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4605 );
4606
4607 // Remove shortcode from buffer
4608 edits.push((
4609 emoji_shortcode_start..selection.start,
4610 "".to_string().into(),
4611 ));
4612 new_selections.push((
4613 Selection {
4614 id: selection.id,
4615 start: snapshot.anchor_after(emoji_shortcode_start),
4616 end: snapshot.anchor_before(selection.start),
4617 reversed: selection.reversed,
4618 goal: selection.goal,
4619 },
4620 0,
4621 ));
4622
4623 // Insert emoji
4624 let selection_start_anchor = snapshot.anchor_after(selection.start);
4625 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4626 edits.push((selection.start..selection.end, emoji.to_string().into()));
4627
4628 continue;
4629 }
4630
4631 let next_is_adjacent = regions
4632 .peek()
4633 .is_some_and(|(next, _)| selection.end == next.start);
4634
4635 // If not handling any auto-close operation, then just replace the selected
4636 // text with the given input and move the selection to the end of the
4637 // newly inserted text.
4638 let anchor = if in_adjacent_group || next_is_adjacent {
4639 // After edits the right bias would shift those anchor to the next visible fragment
4640 // but we want to resolve to the previous one
4641 snapshot.anchor_before(selection.end)
4642 } else {
4643 snapshot.anchor_after(selection.end)
4644 };
4645
4646 if !self.linked_edit_ranges.is_empty() {
4647 let start_anchor = snapshot.anchor_before(selection.start);
4648
4649 let is_word_char = text.chars().next().is_none_or(|char| {
4650 let classifier = snapshot
4651 .char_classifier_at(start_anchor.to_offset(&snapshot))
4652 .scope_context(Some(CharScopeContext::LinkedEdit));
4653 classifier.is_word(char)
4654 });
4655
4656 if is_word_char {
4657 if let Some(ranges) = self
4658 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4659 {
4660 for (buffer, edits) in ranges {
4661 linked_edits
4662 .entry(buffer.clone())
4663 .or_default()
4664 .extend(edits.into_iter().map(|range| (range, text.clone())));
4665 }
4666 }
4667 } else {
4668 clear_linked_edit_ranges = true;
4669 }
4670 }
4671
4672 new_selections.push((selection.map(|_| anchor), 0));
4673 edits.push((selection.start..selection.end, text.clone()));
4674
4675 has_adjacent_edits |= next_is_adjacent;
4676 in_adjacent_group = next_is_adjacent;
4677 }
4678
4679 if all_selections_read_only {
4680 return;
4681 }
4682
4683 drop(regions);
4684 drop(snapshot);
4685
4686 self.transact(window, cx, |this, window, cx| {
4687 if clear_linked_edit_ranges {
4688 this.linked_edit_ranges.clear();
4689 }
4690 let initial_buffer_versions =
4691 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4692
4693 this.buffer.update(cx, |buffer, cx| {
4694 if has_adjacent_edits {
4695 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4696 } else {
4697 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4698 }
4699 });
4700 for (buffer, edits) in linked_edits {
4701 buffer.update(cx, |buffer, cx| {
4702 let snapshot = buffer.snapshot();
4703 let edits = edits
4704 .into_iter()
4705 .map(|(range, text)| {
4706 use text::ToPoint as TP;
4707 let end_point = TP::to_point(&range.end, &snapshot);
4708 let start_point = TP::to_point(&range.start, &snapshot);
4709 (start_point..end_point, text)
4710 })
4711 .sorted_by_key(|(range, _)| range.start);
4712 buffer.edit(edits, None, cx);
4713 })
4714 }
4715 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4716 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4717 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4718 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4719 new_anchor_selections,
4720 &map,
4721 )
4722 .zip(new_selection_deltas)
4723 .map(|(selection, delta)| Selection {
4724 id: selection.id,
4725 start: selection.start + delta,
4726 end: selection.end + delta,
4727 reversed: selection.reversed,
4728 goal: SelectionGoal::None,
4729 })
4730 .collect::<Vec<_>>();
4731
4732 let mut i = 0;
4733 for (position, delta, selection_id, pair) in new_autoclose_regions {
4734 let position = position.to_offset(map.buffer_snapshot()) + delta;
4735 let start = map.buffer_snapshot().anchor_before(position);
4736 let end = map.buffer_snapshot().anchor_after(position);
4737 while let Some(existing_state) = this.autoclose_regions.get(i) {
4738 match existing_state
4739 .range
4740 .start
4741 .cmp(&start, map.buffer_snapshot())
4742 {
4743 Ordering::Less => i += 1,
4744 Ordering::Greater => break,
4745 Ordering::Equal => {
4746 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4747 Ordering::Less => i += 1,
4748 Ordering::Equal => break,
4749 Ordering::Greater => break,
4750 }
4751 }
4752 }
4753 }
4754 this.autoclose_regions.insert(
4755 i,
4756 AutocloseRegion {
4757 selection_id,
4758 range: start..end,
4759 pair,
4760 },
4761 );
4762 }
4763
4764 let had_active_edit_prediction = this.has_active_edit_prediction();
4765 this.change_selections(
4766 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4767 window,
4768 cx,
4769 |s| s.select(new_selections),
4770 );
4771
4772 if !bracket_inserted
4773 && let Some(on_type_format_task) =
4774 this.trigger_on_type_formatting(text.to_string(), window, cx)
4775 {
4776 on_type_format_task.detach_and_log_err(cx);
4777 }
4778
4779 let editor_settings = EditorSettings::get_global(cx);
4780 if bracket_inserted
4781 && (editor_settings.auto_signature_help
4782 || editor_settings.show_signature_help_after_edits)
4783 {
4784 this.show_signature_help(&ShowSignatureHelp, window, cx);
4785 }
4786
4787 let trigger_in_words =
4788 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4789 if this.hard_wrap.is_some() {
4790 let latest: Range<Point> = this.selections.newest(&map).range();
4791 if latest.is_empty()
4792 && this
4793 .buffer()
4794 .read(cx)
4795 .snapshot(cx)
4796 .line_len(MultiBufferRow(latest.start.row))
4797 == latest.start.column
4798 {
4799 this.rewrap_impl(
4800 RewrapOptions {
4801 override_language_settings: true,
4802 preserve_existing_whitespace: true,
4803 },
4804 cx,
4805 )
4806 }
4807 }
4808 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4809 refresh_linked_ranges(this, window, cx);
4810 this.refresh_edit_prediction(true, false, window, cx);
4811 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4812 });
4813 }
4814
4815 fn find_possible_emoji_shortcode_at_position(
4816 snapshot: &MultiBufferSnapshot,
4817 position: Point,
4818 ) -> Option<String> {
4819 let mut chars = Vec::new();
4820 let mut found_colon = false;
4821 for char in snapshot.reversed_chars_at(position).take(100) {
4822 // Found a possible emoji shortcode in the middle of the buffer
4823 if found_colon {
4824 if char.is_whitespace() {
4825 chars.reverse();
4826 return Some(chars.iter().collect());
4827 }
4828 // If the previous character is not a whitespace, we are in the middle of a word
4829 // and we only want to complete the shortcode if the word is made up of other emojis
4830 let mut containing_word = String::new();
4831 for ch in snapshot
4832 .reversed_chars_at(position)
4833 .skip(chars.len() + 1)
4834 .take(100)
4835 {
4836 if ch.is_whitespace() {
4837 break;
4838 }
4839 containing_word.push(ch);
4840 }
4841 let containing_word = containing_word.chars().rev().collect::<String>();
4842 if util::word_consists_of_emojis(containing_word.as_str()) {
4843 chars.reverse();
4844 return Some(chars.iter().collect());
4845 }
4846 }
4847
4848 if char.is_whitespace() || !char.is_ascii() {
4849 return None;
4850 }
4851 if char == ':' {
4852 found_colon = true;
4853 } else {
4854 chars.push(char);
4855 }
4856 }
4857 // Found a possible emoji shortcode at the beginning of the buffer
4858 chars.reverse();
4859 Some(chars.iter().collect())
4860 }
4861
4862 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4863 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4864 self.transact(window, cx, |this, window, cx| {
4865 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4866 let selections = this
4867 .selections
4868 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4869 let multi_buffer = this.buffer.read(cx);
4870 let buffer = multi_buffer.snapshot(cx);
4871 selections
4872 .iter()
4873 .map(|selection| {
4874 let start_point = selection.start.to_point(&buffer);
4875 let mut existing_indent =
4876 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4877 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4878 let start = selection.start;
4879 let end = selection.end;
4880 let selection_is_empty = start == end;
4881 let language_scope = buffer.language_scope_at(start);
4882 let (delimiter, newline_config) = if let Some(language) = &language_scope {
4883 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
4884 &buffer,
4885 start..end,
4886 language,
4887 )
4888 || NewlineConfig::insert_extra_newline_tree_sitter(
4889 &buffer,
4890 start..end,
4891 );
4892
4893 let mut newline_config = NewlineConfig::Newline {
4894 additional_indent: IndentSize::spaces(0),
4895 extra_line_additional_indent: if needs_extra_newline {
4896 Some(IndentSize::spaces(0))
4897 } else {
4898 None
4899 },
4900 prevent_auto_indent: false,
4901 };
4902
4903 let comment_delimiter = maybe!({
4904 if !selection_is_empty {
4905 return None;
4906 }
4907
4908 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4909 return None;
4910 }
4911
4912 return comment_delimiter_for_newline(
4913 &start_point,
4914 &buffer,
4915 language,
4916 );
4917 });
4918
4919 let doc_delimiter = maybe!({
4920 if !selection_is_empty {
4921 return None;
4922 }
4923
4924 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4925 return None;
4926 }
4927
4928 return documentation_delimiter_for_newline(
4929 &start_point,
4930 &buffer,
4931 language,
4932 &mut newline_config,
4933 );
4934 });
4935
4936 let list_delimiter = maybe!({
4937 if !selection_is_empty {
4938 return None;
4939 }
4940
4941 if !multi_buffer.language_settings(cx).extend_list_on_newline {
4942 return None;
4943 }
4944
4945 return list_delimiter_for_newline(
4946 &start_point,
4947 &buffer,
4948 language,
4949 &mut newline_config,
4950 );
4951 });
4952
4953 (
4954 comment_delimiter.or(doc_delimiter).or(list_delimiter),
4955 newline_config,
4956 )
4957 } else {
4958 (
4959 None,
4960 NewlineConfig::Newline {
4961 additional_indent: IndentSize::spaces(0),
4962 extra_line_additional_indent: None,
4963 prevent_auto_indent: false,
4964 },
4965 )
4966 };
4967
4968 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
4969 NewlineConfig::ClearCurrentLine => {
4970 let row_start =
4971 buffer.point_to_offset(Point::new(start_point.row, 0));
4972 (row_start, String::new(), false)
4973 }
4974 NewlineConfig::UnindentCurrentLine { continuation } => {
4975 let row_start =
4976 buffer.point_to_offset(Point::new(start_point.row, 0));
4977 let tab_size = buffer.language_settings_at(start, cx).tab_size;
4978 let tab_size_indent = IndentSize::spaces(tab_size.get());
4979 let reduced_indent =
4980 existing_indent.with_delta(Ordering::Less, tab_size_indent);
4981 let mut new_text = String::new();
4982 new_text.extend(reduced_indent.chars());
4983 new_text.push_str(continuation);
4984 (row_start, new_text, true)
4985 }
4986 NewlineConfig::Newline {
4987 additional_indent,
4988 extra_line_additional_indent,
4989 prevent_auto_indent,
4990 } => {
4991 let capacity_for_delimiter =
4992 delimiter.as_deref().map(str::len).unwrap_or_default();
4993 let extra_line_len = extra_line_additional_indent
4994 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
4995 .unwrap_or(0);
4996 let mut new_text = String::with_capacity(
4997 1 + capacity_for_delimiter
4998 + existing_indent.len as usize
4999 + additional_indent.len as usize
5000 + extra_line_len,
5001 );
5002 new_text.push('\n');
5003 new_text.extend(existing_indent.chars());
5004 new_text.extend(additional_indent.chars());
5005 if let Some(delimiter) = &delimiter {
5006 new_text.push_str(delimiter);
5007 }
5008 if let Some(extra_indent) = extra_line_additional_indent {
5009 new_text.push('\n');
5010 new_text.extend(existing_indent.chars());
5011 new_text.extend(extra_indent.chars());
5012 }
5013 (start, new_text, *prevent_auto_indent)
5014 }
5015 };
5016
5017 let anchor = buffer.anchor_after(end);
5018 let new_selection = selection.map(|_| anchor);
5019 (
5020 ((edit_start..end, new_text), prevent_auto_indent),
5021 (newline_config.has_extra_line(), new_selection),
5022 )
5023 })
5024 .unzip()
5025 };
5026
5027 let mut auto_indent_edits = Vec::new();
5028 let mut edits = Vec::new();
5029 for (edit, prevent_auto_indent) in edits_with_flags {
5030 if prevent_auto_indent {
5031 edits.push(edit);
5032 } else {
5033 auto_indent_edits.push(edit);
5034 }
5035 }
5036 if !edits.is_empty() {
5037 this.edit(edits, cx);
5038 }
5039 if !auto_indent_edits.is_empty() {
5040 this.edit_with_autoindent(auto_indent_edits, cx);
5041 }
5042
5043 let buffer = this.buffer.read(cx).snapshot(cx);
5044 let new_selections = selection_info
5045 .into_iter()
5046 .map(|(extra_newline_inserted, new_selection)| {
5047 let mut cursor = new_selection.end.to_point(&buffer);
5048 if extra_newline_inserted {
5049 cursor.row -= 1;
5050 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5051 }
5052 new_selection.map(|_| cursor)
5053 })
5054 .collect();
5055
5056 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5057 this.refresh_edit_prediction(true, false, window, cx);
5058 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5059 task.detach_and_log_err(cx);
5060 }
5061 });
5062 }
5063
5064 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5065 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5066
5067 let buffer = self.buffer.read(cx);
5068 let snapshot = buffer.snapshot(cx);
5069
5070 let mut edits = Vec::new();
5071 let mut rows = Vec::new();
5072
5073 for (rows_inserted, selection) in self
5074 .selections
5075 .all_adjusted(&self.display_snapshot(cx))
5076 .into_iter()
5077 .enumerate()
5078 {
5079 let cursor = selection.head();
5080 let row = cursor.row;
5081
5082 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5083
5084 let newline = "\n".to_string();
5085 edits.push((start_of_line..start_of_line, newline));
5086
5087 rows.push(row + rows_inserted as u32);
5088 }
5089
5090 self.transact(window, cx, |editor, window, cx| {
5091 editor.edit(edits, cx);
5092
5093 editor.change_selections(Default::default(), window, cx, |s| {
5094 let mut index = 0;
5095 s.move_cursors_with(|map, _, _| {
5096 let row = rows[index];
5097 index += 1;
5098
5099 let point = Point::new(row, 0);
5100 let boundary = map.next_line_boundary(point).1;
5101 let clipped = map.clip_point(boundary, Bias::Left);
5102
5103 (clipped, SelectionGoal::None)
5104 });
5105 });
5106
5107 let mut indent_edits = Vec::new();
5108 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5109 for row in rows {
5110 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5111 for (row, indent) in indents {
5112 if indent.len == 0 {
5113 continue;
5114 }
5115
5116 let text = match indent.kind {
5117 IndentKind::Space => " ".repeat(indent.len as usize),
5118 IndentKind::Tab => "\t".repeat(indent.len as usize),
5119 };
5120 let point = Point::new(row.0, 0);
5121 indent_edits.push((point..point, text));
5122 }
5123 }
5124 editor.edit(indent_edits, cx);
5125 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5126 format.detach_and_log_err(cx);
5127 }
5128 });
5129 }
5130
5131 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5132 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5133
5134 let buffer = self.buffer.read(cx);
5135 let snapshot = buffer.snapshot(cx);
5136
5137 let mut edits = Vec::new();
5138 let mut rows = Vec::new();
5139 let mut rows_inserted = 0;
5140
5141 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5142 let cursor = selection.head();
5143 let row = cursor.row;
5144
5145 let point = Point::new(row + 1, 0);
5146 let start_of_line = snapshot.clip_point(point, Bias::Left);
5147
5148 let newline = "\n".to_string();
5149 edits.push((start_of_line..start_of_line, newline));
5150
5151 rows_inserted += 1;
5152 rows.push(row + rows_inserted);
5153 }
5154
5155 self.transact(window, cx, |editor, window, cx| {
5156 editor.edit(edits, cx);
5157
5158 editor.change_selections(Default::default(), window, cx, |s| {
5159 let mut index = 0;
5160 s.move_cursors_with(|map, _, _| {
5161 let row = rows[index];
5162 index += 1;
5163
5164 let point = Point::new(row, 0);
5165 let boundary = map.next_line_boundary(point).1;
5166 let clipped = map.clip_point(boundary, Bias::Left);
5167
5168 (clipped, SelectionGoal::None)
5169 });
5170 });
5171
5172 let mut indent_edits = Vec::new();
5173 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5174 for row in rows {
5175 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5176 for (row, indent) in indents {
5177 if indent.len == 0 {
5178 continue;
5179 }
5180
5181 let text = match indent.kind {
5182 IndentKind::Space => " ".repeat(indent.len as usize),
5183 IndentKind::Tab => "\t".repeat(indent.len as usize),
5184 };
5185 let point = Point::new(row.0, 0);
5186 indent_edits.push((point..point, text));
5187 }
5188 }
5189 editor.edit(indent_edits, cx);
5190 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5191 format.detach_and_log_err(cx);
5192 }
5193 });
5194 }
5195
5196 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5197 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5198 original_indent_columns: Vec::new(),
5199 });
5200 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5201 }
5202
5203 fn insert_with_autoindent_mode(
5204 &mut self,
5205 text: &str,
5206 autoindent_mode: Option<AutoindentMode>,
5207 window: &mut Window,
5208 cx: &mut Context<Self>,
5209 ) {
5210 if self.read_only(cx) {
5211 return;
5212 }
5213
5214 let text: Arc<str> = text.into();
5215 self.transact(window, cx, |this, window, cx| {
5216 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5217 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5218 let anchors = {
5219 let snapshot = buffer.read(cx);
5220 old_selections
5221 .iter()
5222 .map(|s| {
5223 let anchor = snapshot.anchor_after(s.head());
5224 s.map(|_| anchor)
5225 })
5226 .collect::<Vec<_>>()
5227 };
5228 buffer.edit(
5229 old_selections
5230 .iter()
5231 .map(|s| (s.start..s.end, text.clone())),
5232 autoindent_mode,
5233 cx,
5234 );
5235 anchors
5236 });
5237
5238 this.change_selections(Default::default(), window, cx, |s| {
5239 s.select_anchors(selection_anchors);
5240 });
5241
5242 cx.notify();
5243 });
5244 }
5245
5246 fn trigger_completion_on_input(
5247 &mut self,
5248 text: &str,
5249 trigger_in_words: bool,
5250 window: &mut Window,
5251 cx: &mut Context<Self>,
5252 ) {
5253 let completions_source = self
5254 .context_menu
5255 .borrow()
5256 .as_ref()
5257 .and_then(|menu| match menu {
5258 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5259 CodeContextMenu::CodeActions(_) => None,
5260 });
5261
5262 match completions_source {
5263 Some(CompletionsMenuSource::Words { .. }) => {
5264 self.open_or_update_completions_menu(
5265 Some(CompletionsMenuSource::Words {
5266 ignore_threshold: false,
5267 }),
5268 None,
5269 trigger_in_words,
5270 window,
5271 cx,
5272 );
5273 }
5274 _ => self.open_or_update_completions_menu(
5275 None,
5276 Some(text.to_owned()).filter(|x| !x.is_empty()),
5277 true,
5278 window,
5279 cx,
5280 ),
5281 }
5282 }
5283
5284 /// If any empty selections is touching the start of its innermost containing autoclose
5285 /// region, expand it to select the brackets.
5286 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5287 let selections = self
5288 .selections
5289 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5290 let buffer = self.buffer.read(cx).read(cx);
5291 let new_selections = self
5292 .selections_with_autoclose_regions(selections, &buffer)
5293 .map(|(mut selection, region)| {
5294 if !selection.is_empty() {
5295 return selection;
5296 }
5297
5298 if let Some(region) = region {
5299 let mut range = region.range.to_offset(&buffer);
5300 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5301 range.start -= region.pair.start.len();
5302 if buffer.contains_str_at(range.start, ®ion.pair.start)
5303 && buffer.contains_str_at(range.end, ®ion.pair.end)
5304 {
5305 range.end += region.pair.end.len();
5306 selection.start = range.start;
5307 selection.end = range.end;
5308
5309 return selection;
5310 }
5311 }
5312 }
5313
5314 let always_treat_brackets_as_autoclosed = buffer
5315 .language_settings_at(selection.start, cx)
5316 .always_treat_brackets_as_autoclosed;
5317
5318 if !always_treat_brackets_as_autoclosed {
5319 return selection;
5320 }
5321
5322 if let Some(scope) = buffer.language_scope_at(selection.start) {
5323 for (pair, enabled) in scope.brackets() {
5324 if !enabled || !pair.close {
5325 continue;
5326 }
5327
5328 if buffer.contains_str_at(selection.start, &pair.end) {
5329 let pair_start_len = pair.start.len();
5330 if buffer.contains_str_at(
5331 selection.start.saturating_sub_usize(pair_start_len),
5332 &pair.start,
5333 ) {
5334 selection.start -= pair_start_len;
5335 selection.end += pair.end.len();
5336
5337 return selection;
5338 }
5339 }
5340 }
5341 }
5342
5343 selection
5344 })
5345 .collect();
5346
5347 drop(buffer);
5348 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5349 selections.select(new_selections)
5350 });
5351 }
5352
5353 /// Iterate the given selections, and for each one, find the smallest surrounding
5354 /// autoclose region. This uses the ordering of the selections and the autoclose
5355 /// regions to avoid repeated comparisons.
5356 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5357 &'a self,
5358 selections: impl IntoIterator<Item = Selection<D>>,
5359 buffer: &'a MultiBufferSnapshot,
5360 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5361 let mut i = 0;
5362 let mut regions = self.autoclose_regions.as_slice();
5363 selections.into_iter().map(move |selection| {
5364 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5365
5366 let mut enclosing = None;
5367 while let Some(pair_state) = regions.get(i) {
5368 if pair_state.range.end.to_offset(buffer) < range.start {
5369 regions = ®ions[i + 1..];
5370 i = 0;
5371 } else if pair_state.range.start.to_offset(buffer) > range.end {
5372 break;
5373 } else {
5374 if pair_state.selection_id == selection.id {
5375 enclosing = Some(pair_state);
5376 }
5377 i += 1;
5378 }
5379 }
5380
5381 (selection, enclosing)
5382 })
5383 }
5384
5385 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5386 fn invalidate_autoclose_regions(
5387 &mut self,
5388 mut selections: &[Selection<Anchor>],
5389 buffer: &MultiBufferSnapshot,
5390 ) {
5391 self.autoclose_regions.retain(|state| {
5392 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5393 return false;
5394 }
5395
5396 let mut i = 0;
5397 while let Some(selection) = selections.get(i) {
5398 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5399 selections = &selections[1..];
5400 continue;
5401 }
5402 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5403 break;
5404 }
5405 if selection.id == state.selection_id {
5406 return true;
5407 } else {
5408 i += 1;
5409 }
5410 }
5411 false
5412 });
5413 }
5414
5415 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5416 let offset = position.to_offset(buffer);
5417 let (word_range, kind) =
5418 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5419 if offset > word_range.start && kind == Some(CharKind::Word) {
5420 Some(
5421 buffer
5422 .text_for_range(word_range.start..offset)
5423 .collect::<String>(),
5424 )
5425 } else {
5426 None
5427 }
5428 }
5429
5430 pub fn visible_excerpts(
5431 &self,
5432 lsp_related_only: bool,
5433 cx: &mut Context<Editor>,
5434 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5435 let project = self.project().cloned();
5436 let multi_buffer = self.buffer().read(cx);
5437 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5438 let multi_buffer_visible_start = self
5439 .scroll_manager
5440 .anchor()
5441 .anchor
5442 .to_point(&multi_buffer_snapshot);
5443 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5444 multi_buffer_visible_start
5445 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5446 Bias::Left,
5447 );
5448 multi_buffer_snapshot
5449 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5450 .into_iter()
5451 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5452 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5453 if !lsp_related_only {
5454 return Some((
5455 excerpt_id,
5456 (
5457 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5458 buffer.version().clone(),
5459 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5460 ),
5461 ));
5462 }
5463
5464 let project = project.as_ref()?.read(cx);
5465 let buffer_file = project::File::from_dyn(buffer.file())?;
5466 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5467 let worktree_entry = buffer_worktree
5468 .read(cx)
5469 .entry_for_id(buffer_file.project_entry_id()?)?;
5470 if worktree_entry.is_ignored {
5471 None
5472 } else {
5473 Some((
5474 excerpt_id,
5475 (
5476 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5477 buffer.version().clone(),
5478 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5479 ),
5480 ))
5481 }
5482 })
5483 .collect()
5484 }
5485
5486 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5487 TextLayoutDetails {
5488 text_system: window.text_system().clone(),
5489 editor_style: self.style.clone().unwrap(),
5490 rem_size: window.rem_size(),
5491 scroll_anchor: self.scroll_manager.anchor(),
5492 visible_rows: self.visible_line_count(),
5493 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5494 }
5495 }
5496
5497 fn trigger_on_type_formatting(
5498 &self,
5499 input: String,
5500 window: &mut Window,
5501 cx: &mut Context<Self>,
5502 ) -> Option<Task<Result<()>>> {
5503 if input.chars().count() != 1 {
5504 return None;
5505 }
5506
5507 let project = self.project()?;
5508 let position = self.selections.newest_anchor().head();
5509 let (buffer, buffer_position) = self
5510 .buffer
5511 .read(cx)
5512 .text_anchor_for_position(position, cx)?;
5513
5514 let settings = language_settings::language_settings(
5515 buffer
5516 .read(cx)
5517 .language_at(buffer_position)
5518 .map(|l| l.name()),
5519 buffer.read(cx).file(),
5520 cx,
5521 );
5522 if !settings.use_on_type_format {
5523 return None;
5524 }
5525
5526 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5527 // hence we do LSP request & edit on host side only — add formats to host's history.
5528 let push_to_lsp_host_history = true;
5529 // If this is not the host, append its history with new edits.
5530 let push_to_client_history = project.read(cx).is_via_collab();
5531
5532 let on_type_formatting = project.update(cx, |project, cx| {
5533 project.on_type_format(
5534 buffer.clone(),
5535 buffer_position,
5536 input,
5537 push_to_lsp_host_history,
5538 cx,
5539 )
5540 });
5541 Some(cx.spawn_in(window, async move |editor, cx| {
5542 if let Some(transaction) = on_type_formatting.await? {
5543 if push_to_client_history {
5544 buffer.update(cx, |buffer, _| {
5545 buffer.push_transaction(transaction, Instant::now());
5546 buffer.finalize_last_transaction();
5547 });
5548 }
5549 editor.update(cx, |editor, cx| {
5550 editor.refresh_document_highlights(cx);
5551 })?;
5552 }
5553 Ok(())
5554 }))
5555 }
5556
5557 pub fn show_word_completions(
5558 &mut self,
5559 _: &ShowWordCompletions,
5560 window: &mut Window,
5561 cx: &mut Context<Self>,
5562 ) {
5563 self.open_or_update_completions_menu(
5564 Some(CompletionsMenuSource::Words {
5565 ignore_threshold: true,
5566 }),
5567 None,
5568 false,
5569 window,
5570 cx,
5571 );
5572 }
5573
5574 pub fn show_completions(
5575 &mut self,
5576 _: &ShowCompletions,
5577 window: &mut Window,
5578 cx: &mut Context<Self>,
5579 ) {
5580 self.open_or_update_completions_menu(None, None, false, window, cx);
5581 }
5582
5583 fn open_or_update_completions_menu(
5584 &mut self,
5585 requested_source: Option<CompletionsMenuSource>,
5586 trigger: Option<String>,
5587 trigger_in_words: bool,
5588 window: &mut Window,
5589 cx: &mut Context<Self>,
5590 ) {
5591 if self.pending_rename.is_some() {
5592 return;
5593 }
5594
5595 let completions_source = self
5596 .context_menu
5597 .borrow()
5598 .as_ref()
5599 .and_then(|menu| match menu {
5600 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5601 CodeContextMenu::CodeActions(_) => None,
5602 });
5603
5604 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5605
5606 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5607 // inserted and selected. To handle that case, the start of the selection is used so that
5608 // the menu starts with all choices.
5609 let position = self
5610 .selections
5611 .newest_anchor()
5612 .start
5613 .bias_right(&multibuffer_snapshot);
5614 if position.diff_base_anchor.is_some() {
5615 return;
5616 }
5617 let buffer_position = multibuffer_snapshot.anchor_before(position);
5618 let Some(buffer) = buffer_position
5619 .text_anchor
5620 .buffer_id
5621 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5622 else {
5623 return;
5624 };
5625 let buffer_snapshot = buffer.read(cx).snapshot();
5626
5627 let menu_is_open = matches!(
5628 self.context_menu.borrow().as_ref(),
5629 Some(CodeContextMenu::Completions(_))
5630 );
5631
5632 let language = buffer_snapshot
5633 .language_at(buffer_position.text_anchor)
5634 .map(|language| language.name());
5635
5636 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5637 let completion_settings = language_settings.completions.clone();
5638
5639 let show_completions_on_input = self
5640 .show_completions_on_input_override
5641 .unwrap_or(language_settings.show_completions_on_input);
5642 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5643 return;
5644 }
5645
5646 let query: Option<Arc<String>> =
5647 Self::completion_query(&multibuffer_snapshot, buffer_position)
5648 .map(|query| query.into());
5649
5650 drop(multibuffer_snapshot);
5651
5652 // Hide the current completions menu when query is empty. Without this, cached
5653 // completions from before the trigger char may be reused (#32774).
5654 if query.is_none() && menu_is_open {
5655 self.hide_context_menu(window, cx);
5656 }
5657
5658 let mut ignore_word_threshold = false;
5659 let provider = match requested_source {
5660 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5661 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5662 ignore_word_threshold = ignore_threshold;
5663 None
5664 }
5665 Some(CompletionsMenuSource::SnippetChoices)
5666 | Some(CompletionsMenuSource::SnippetsOnly) => {
5667 log::error!("bug: SnippetChoices requested_source is not handled");
5668 None
5669 }
5670 };
5671
5672 let sort_completions = provider
5673 .as_ref()
5674 .is_some_and(|provider| provider.sort_completions());
5675
5676 let filter_completions = provider
5677 .as_ref()
5678 .is_none_or(|provider| provider.filter_completions());
5679
5680 let was_snippets_only = matches!(
5681 completions_source,
5682 Some(CompletionsMenuSource::SnippetsOnly)
5683 );
5684
5685 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5686 if filter_completions {
5687 menu.filter(
5688 query.clone().unwrap_or_default(),
5689 buffer_position.text_anchor,
5690 &buffer,
5691 provider.clone(),
5692 window,
5693 cx,
5694 );
5695 }
5696 // When `is_incomplete` is false, no need to re-query completions when the current query
5697 // is a suffix of the initial query.
5698 let was_complete = !menu.is_incomplete;
5699 if was_complete && !was_snippets_only {
5700 // If the new query is a suffix of the old query (typing more characters) and
5701 // the previous result was complete, the existing completions can be filtered.
5702 //
5703 // Note that snippet completions are always complete.
5704 let query_matches = match (&menu.initial_query, &query) {
5705 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5706 (None, _) => true,
5707 _ => false,
5708 };
5709 if query_matches {
5710 let position_matches = if menu.initial_position == position {
5711 true
5712 } else {
5713 let snapshot = self.buffer.read(cx).read(cx);
5714 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5715 };
5716 if position_matches {
5717 return;
5718 }
5719 }
5720 }
5721 };
5722
5723 let Anchor {
5724 excerpt_id: buffer_excerpt_id,
5725 text_anchor: buffer_position,
5726 ..
5727 } = buffer_position;
5728
5729 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5730 buffer_snapshot.surrounding_word(buffer_position, None)
5731 {
5732 let word_to_exclude = buffer_snapshot
5733 .text_for_range(word_range.clone())
5734 .collect::<String>();
5735 (
5736 buffer_snapshot.anchor_before(word_range.start)
5737 ..buffer_snapshot.anchor_after(buffer_position),
5738 Some(word_to_exclude),
5739 )
5740 } else {
5741 (buffer_position..buffer_position, None)
5742 };
5743
5744 let show_completion_documentation = buffer_snapshot
5745 .settings_at(buffer_position, cx)
5746 .show_completion_documentation;
5747
5748 // The document can be large, so stay in reasonable bounds when searching for words,
5749 // otherwise completion pop-up might be slow to appear.
5750 const WORD_LOOKUP_ROWS: u32 = 5_000;
5751 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5752 let min_word_search = buffer_snapshot.clip_point(
5753 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5754 Bias::Left,
5755 );
5756 let max_word_search = buffer_snapshot.clip_point(
5757 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5758 Bias::Right,
5759 );
5760 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5761 ..buffer_snapshot.point_to_offset(max_word_search);
5762
5763 let skip_digits = query
5764 .as_ref()
5765 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5766
5767 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5768 trigger.as_ref().is_none_or(|trigger| {
5769 provider.is_completion_trigger(
5770 &buffer,
5771 position.text_anchor,
5772 trigger,
5773 trigger_in_words,
5774 cx,
5775 )
5776 })
5777 });
5778
5779 let provider_responses = if let Some(provider) = &provider
5780 && load_provider_completions
5781 {
5782 let trigger_character =
5783 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5784 let completion_context = CompletionContext {
5785 trigger_kind: match &trigger_character {
5786 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5787 None => CompletionTriggerKind::INVOKED,
5788 },
5789 trigger_character,
5790 };
5791
5792 provider.completions(
5793 buffer_excerpt_id,
5794 &buffer,
5795 buffer_position,
5796 completion_context,
5797 window,
5798 cx,
5799 )
5800 } else {
5801 Task::ready(Ok(Vec::new()))
5802 };
5803
5804 let load_word_completions = if !self.word_completions_enabled {
5805 false
5806 } else if requested_source
5807 == Some(CompletionsMenuSource::Words {
5808 ignore_threshold: true,
5809 })
5810 {
5811 true
5812 } else {
5813 load_provider_completions
5814 && completion_settings.words != WordsCompletionMode::Disabled
5815 && (ignore_word_threshold || {
5816 let words_min_length = completion_settings.words_min_length;
5817 // check whether word has at least `words_min_length` characters
5818 let query_chars = query.iter().flat_map(|q| q.chars());
5819 query_chars.take(words_min_length).count() == words_min_length
5820 })
5821 };
5822
5823 let mut words = if load_word_completions {
5824 cx.background_spawn({
5825 let buffer_snapshot = buffer_snapshot.clone();
5826 async move {
5827 buffer_snapshot.words_in_range(WordsQuery {
5828 fuzzy_contents: None,
5829 range: word_search_range,
5830 skip_digits,
5831 })
5832 }
5833 })
5834 } else {
5835 Task::ready(BTreeMap::default())
5836 };
5837
5838 let snippets = if let Some(provider) = &provider
5839 && provider.show_snippets()
5840 && let Some(project) = self.project()
5841 {
5842 let char_classifier = buffer_snapshot
5843 .char_classifier_at(buffer_position)
5844 .scope_context(Some(CharScopeContext::Completion));
5845 project.update(cx, |project, cx| {
5846 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5847 })
5848 } else {
5849 Task::ready(Ok(CompletionResponse {
5850 completions: Vec::new(),
5851 display_options: Default::default(),
5852 is_incomplete: false,
5853 }))
5854 };
5855
5856 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5857
5858 let id = post_inc(&mut self.next_completion_id);
5859 let task = cx.spawn_in(window, async move |editor, cx| {
5860 let Ok(()) = editor.update(cx, |this, _| {
5861 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5862 }) else {
5863 return;
5864 };
5865
5866 // TODO: Ideally completions from different sources would be selectively re-queried, so
5867 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5868 let mut completions = Vec::new();
5869 let mut is_incomplete = false;
5870 let mut display_options: Option<CompletionDisplayOptions> = None;
5871 if let Some(provider_responses) = provider_responses.await.log_err()
5872 && !provider_responses.is_empty()
5873 {
5874 for response in provider_responses {
5875 completions.extend(response.completions);
5876 is_incomplete = is_incomplete || response.is_incomplete;
5877 match display_options.as_mut() {
5878 None => {
5879 display_options = Some(response.display_options);
5880 }
5881 Some(options) => options.merge(&response.display_options),
5882 }
5883 }
5884 if completion_settings.words == WordsCompletionMode::Fallback {
5885 words = Task::ready(BTreeMap::default());
5886 }
5887 }
5888 let display_options = display_options.unwrap_or_default();
5889
5890 let mut words = words.await;
5891 if let Some(word_to_exclude) = &word_to_exclude {
5892 words.remove(word_to_exclude);
5893 }
5894 for lsp_completion in &completions {
5895 words.remove(&lsp_completion.new_text);
5896 }
5897 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5898 replace_range: word_replace_range.clone(),
5899 new_text: word.clone(),
5900 label: CodeLabel::plain(word, None),
5901 match_start: None,
5902 snippet_deduplication_key: None,
5903 icon_path: None,
5904 documentation: None,
5905 source: CompletionSource::BufferWord {
5906 word_range,
5907 resolved: false,
5908 },
5909 insert_text_mode: Some(InsertTextMode::AS_IS),
5910 confirm: None,
5911 }));
5912
5913 completions.extend(
5914 snippets
5915 .await
5916 .into_iter()
5917 .flat_map(|response| response.completions),
5918 );
5919
5920 let menu = if completions.is_empty() {
5921 None
5922 } else {
5923 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5924 let languages = editor
5925 .workspace
5926 .as_ref()
5927 .and_then(|(workspace, _)| workspace.upgrade())
5928 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5929 let menu = CompletionsMenu::new(
5930 id,
5931 requested_source.unwrap_or(if load_provider_completions {
5932 CompletionsMenuSource::Normal
5933 } else {
5934 CompletionsMenuSource::SnippetsOnly
5935 }),
5936 sort_completions,
5937 show_completion_documentation,
5938 position,
5939 query.clone(),
5940 is_incomplete,
5941 buffer.clone(),
5942 completions.into(),
5943 editor
5944 .context_menu()
5945 .borrow_mut()
5946 .as_ref()
5947 .map(|menu| menu.primary_scroll_handle()),
5948 display_options,
5949 snippet_sort_order,
5950 languages,
5951 language,
5952 cx,
5953 );
5954
5955 let query = if filter_completions { query } else { None };
5956 let matches_task = menu.do_async_filtering(
5957 query.unwrap_or_default(),
5958 buffer_position,
5959 &buffer,
5960 cx,
5961 );
5962 (menu, matches_task)
5963 }) else {
5964 return;
5965 };
5966
5967 let matches = matches_task.await;
5968
5969 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5970 // Newer menu already set, so exit.
5971 if let Some(CodeContextMenu::Completions(prev_menu)) =
5972 editor.context_menu.borrow().as_ref()
5973 && prev_menu.id > id
5974 {
5975 return;
5976 };
5977
5978 // Only valid to take prev_menu because either the new menu is immediately set
5979 // below, or the menu is hidden.
5980 if let Some(CodeContextMenu::Completions(prev_menu)) =
5981 editor.context_menu.borrow_mut().take()
5982 {
5983 let position_matches =
5984 if prev_menu.initial_position == menu.initial_position {
5985 true
5986 } else {
5987 let snapshot = editor.buffer.read(cx).read(cx);
5988 prev_menu.initial_position.to_offset(&snapshot)
5989 == menu.initial_position.to_offset(&snapshot)
5990 };
5991 if position_matches {
5992 // Preserve markdown cache before `set_filter_results` because it will
5993 // try to populate the documentation cache.
5994 menu.preserve_markdown_cache(prev_menu);
5995 }
5996 };
5997
5998 menu.set_filter_results(matches, provider, window, cx);
5999 }) else {
6000 return;
6001 };
6002
6003 menu.visible().then_some(menu)
6004 };
6005
6006 editor
6007 .update_in(cx, |editor, window, cx| {
6008 if editor.focus_handle.is_focused(window)
6009 && let Some(menu) = menu
6010 {
6011 *editor.context_menu.borrow_mut() =
6012 Some(CodeContextMenu::Completions(menu));
6013
6014 crate::hover_popover::hide_hover(editor, cx);
6015 if editor.show_edit_predictions_in_menu() {
6016 editor.update_visible_edit_prediction(window, cx);
6017 } else {
6018 editor.discard_edit_prediction(false, cx);
6019 }
6020
6021 cx.notify();
6022 return;
6023 }
6024
6025 if editor.completion_tasks.len() <= 1 {
6026 // If there are no more completion tasks and the last menu was empty, we should hide it.
6027 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6028 // If it was already hidden and we don't show edit predictions in the menu,
6029 // we should also show the edit prediction when available.
6030 if was_hidden && editor.show_edit_predictions_in_menu() {
6031 editor.update_visible_edit_prediction(window, cx);
6032 }
6033 }
6034 })
6035 .ok();
6036 });
6037
6038 self.completion_tasks.push((id, task));
6039 }
6040
6041 #[cfg(feature = "test-support")]
6042 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6043 let menu = self.context_menu.borrow();
6044 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6045 let completions = menu.completions.borrow();
6046 Some(completions.to_vec())
6047 } else {
6048 None
6049 }
6050 }
6051
6052 pub fn with_completions_menu_matching_id<R>(
6053 &self,
6054 id: CompletionId,
6055 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6056 ) -> R {
6057 let mut context_menu = self.context_menu.borrow_mut();
6058 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6059 return f(None);
6060 };
6061 if completions_menu.id != id {
6062 return f(None);
6063 }
6064 f(Some(completions_menu))
6065 }
6066
6067 pub fn confirm_completion(
6068 &mut self,
6069 action: &ConfirmCompletion,
6070 window: &mut Window,
6071 cx: &mut Context<Self>,
6072 ) -> Option<Task<Result<()>>> {
6073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6074 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6075 }
6076
6077 pub fn confirm_completion_insert(
6078 &mut self,
6079 _: &ConfirmCompletionInsert,
6080 window: &mut Window,
6081 cx: &mut Context<Self>,
6082 ) -> Option<Task<Result<()>>> {
6083 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6084 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6085 }
6086
6087 pub fn confirm_completion_replace(
6088 &mut self,
6089 _: &ConfirmCompletionReplace,
6090 window: &mut Window,
6091 cx: &mut Context<Self>,
6092 ) -> Option<Task<Result<()>>> {
6093 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6094 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6095 }
6096
6097 pub fn compose_completion(
6098 &mut self,
6099 action: &ComposeCompletion,
6100 window: &mut Window,
6101 cx: &mut Context<Self>,
6102 ) -> Option<Task<Result<()>>> {
6103 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6104 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6105 }
6106
6107 fn do_completion(
6108 &mut self,
6109 item_ix: Option<usize>,
6110 intent: CompletionIntent,
6111 window: &mut Window,
6112 cx: &mut Context<Editor>,
6113 ) -> Option<Task<Result<()>>> {
6114 use language::ToOffset as _;
6115
6116 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6117 else {
6118 return None;
6119 };
6120
6121 let candidate_id = {
6122 let entries = completions_menu.entries.borrow();
6123 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6124 if self.show_edit_predictions_in_menu() {
6125 self.discard_edit_prediction(true, cx);
6126 }
6127 mat.candidate_id
6128 };
6129
6130 let completion = completions_menu
6131 .completions
6132 .borrow()
6133 .get(candidate_id)?
6134 .clone();
6135 cx.stop_propagation();
6136
6137 let buffer_handle = completions_menu.buffer.clone();
6138
6139 let CompletionEdit {
6140 new_text,
6141 snippet,
6142 replace_range,
6143 } = process_completion_for_edit(
6144 &completion,
6145 intent,
6146 &buffer_handle,
6147 &completions_menu.initial_position.text_anchor,
6148 cx,
6149 );
6150
6151 let buffer = buffer_handle.read(cx);
6152 let snapshot = self.buffer.read(cx).snapshot(cx);
6153 let newest_anchor = self.selections.newest_anchor();
6154 let replace_range_multibuffer = {
6155 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6156 excerpt.map_range_from_buffer(replace_range.clone())
6157 };
6158 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6159 return None;
6160 }
6161
6162 let old_text = buffer
6163 .text_for_range(replace_range.clone())
6164 .collect::<String>();
6165 let lookbehind = newest_anchor
6166 .start
6167 .text_anchor
6168 .to_offset(buffer)
6169 .saturating_sub(replace_range.start.0);
6170 let lookahead = replace_range
6171 .end
6172 .0
6173 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6174 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6175 let suffix = &old_text[lookbehind.min(old_text.len())..];
6176
6177 let selections = self
6178 .selections
6179 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6180 let mut ranges = Vec::new();
6181 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6182
6183 for selection in &selections {
6184 let range = if selection.id == newest_anchor.id {
6185 replace_range_multibuffer.clone()
6186 } else {
6187 let mut range = selection.range();
6188
6189 // if prefix is present, don't duplicate it
6190 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6191 range.start = range.start.saturating_sub_usize(lookbehind);
6192
6193 // if suffix is also present, mimic the newest cursor and replace it
6194 if selection.id != newest_anchor.id
6195 && snapshot.contains_str_at(range.end, suffix)
6196 {
6197 range.end += lookahead;
6198 }
6199 }
6200 range
6201 };
6202
6203 ranges.push(range.clone());
6204
6205 if !self.linked_edit_ranges.is_empty() {
6206 let start_anchor = snapshot.anchor_before(range.start);
6207 let end_anchor = snapshot.anchor_after(range.end);
6208 if let Some(ranges) = self
6209 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6210 {
6211 for (buffer, edits) in ranges {
6212 linked_edits
6213 .entry(buffer.clone())
6214 .or_default()
6215 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6216 }
6217 }
6218 }
6219 }
6220
6221 let common_prefix_len = old_text
6222 .chars()
6223 .zip(new_text.chars())
6224 .take_while(|(a, b)| a == b)
6225 .map(|(a, _)| a.len_utf8())
6226 .sum::<usize>();
6227
6228 cx.emit(EditorEvent::InputHandled {
6229 utf16_range_to_replace: None,
6230 text: new_text[common_prefix_len..].into(),
6231 });
6232
6233 self.transact(window, cx, |editor, window, cx| {
6234 if let Some(mut snippet) = snippet {
6235 snippet.text = new_text.to_string();
6236 editor
6237 .insert_snippet(&ranges, snippet, window, cx)
6238 .log_err();
6239 } else {
6240 editor.buffer.update(cx, |multi_buffer, cx| {
6241 let auto_indent = match completion.insert_text_mode {
6242 Some(InsertTextMode::AS_IS) => None,
6243 _ => editor.autoindent_mode.clone(),
6244 };
6245 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6246 multi_buffer.edit(edits, auto_indent, cx);
6247 });
6248 }
6249 for (buffer, edits) in linked_edits {
6250 buffer.update(cx, |buffer, cx| {
6251 let snapshot = buffer.snapshot();
6252 let edits = edits
6253 .into_iter()
6254 .map(|(range, text)| {
6255 use text::ToPoint as TP;
6256 let end_point = TP::to_point(&range.end, &snapshot);
6257 let start_point = TP::to_point(&range.start, &snapshot);
6258 (start_point..end_point, text)
6259 })
6260 .sorted_by_key(|(range, _)| range.start);
6261 buffer.edit(edits, None, cx);
6262 })
6263 }
6264
6265 editor.refresh_edit_prediction(true, false, window, cx);
6266 });
6267 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6268
6269 let show_new_completions_on_confirm = completion
6270 .confirm
6271 .as_ref()
6272 .is_some_and(|confirm| confirm(intent, window, cx));
6273 if show_new_completions_on_confirm {
6274 self.open_or_update_completions_menu(None, None, false, window, cx);
6275 }
6276
6277 let provider = self.completion_provider.as_ref()?;
6278
6279 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6280 let command = lsp_store.as_ref().and_then(|lsp_store| {
6281 let CompletionSource::Lsp {
6282 lsp_completion,
6283 server_id,
6284 ..
6285 } = &completion.source
6286 else {
6287 return None;
6288 };
6289 let lsp_command = lsp_completion.command.as_ref()?;
6290 let available_commands = lsp_store
6291 .read(cx)
6292 .lsp_server_capabilities
6293 .get(server_id)
6294 .and_then(|server_capabilities| {
6295 server_capabilities
6296 .execute_command_provider
6297 .as_ref()
6298 .map(|options| options.commands.as_slice())
6299 })?;
6300 if available_commands.contains(&lsp_command.command) {
6301 Some(CodeAction {
6302 server_id: *server_id,
6303 range: language::Anchor::MIN..language::Anchor::MIN,
6304 lsp_action: LspAction::Command(lsp_command.clone()),
6305 resolved: false,
6306 })
6307 } else {
6308 None
6309 }
6310 });
6311
6312 drop(completion);
6313 let apply_edits = provider.apply_additional_edits_for_completion(
6314 buffer_handle.clone(),
6315 completions_menu.completions.clone(),
6316 candidate_id,
6317 true,
6318 cx,
6319 );
6320
6321 let editor_settings = EditorSettings::get_global(cx);
6322 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6323 // After the code completion is finished, users often want to know what signatures are needed.
6324 // so we should automatically call signature_help
6325 self.show_signature_help(&ShowSignatureHelp, window, cx);
6326 }
6327
6328 Some(cx.spawn_in(window, async move |editor, cx| {
6329 apply_edits.await?;
6330
6331 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6332 let title = command.lsp_action.title().to_owned();
6333 let project_transaction = lsp_store
6334 .update(cx, |lsp_store, cx| {
6335 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6336 })
6337 .await
6338 .context("applying post-completion command")?;
6339 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6340 Self::open_project_transaction(
6341 &editor,
6342 workspace.downgrade(),
6343 project_transaction,
6344 title,
6345 cx,
6346 )
6347 .await?;
6348 }
6349 }
6350
6351 Ok(())
6352 }))
6353 }
6354
6355 pub fn toggle_code_actions(
6356 &mut self,
6357 action: &ToggleCodeActions,
6358 window: &mut Window,
6359 cx: &mut Context<Self>,
6360 ) {
6361 let quick_launch = action.quick_launch;
6362 let mut context_menu = self.context_menu.borrow_mut();
6363 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6364 if code_actions.deployed_from == action.deployed_from {
6365 // Toggle if we're selecting the same one
6366 *context_menu = None;
6367 cx.notify();
6368 return;
6369 } else {
6370 // Otherwise, clear it and start a new one
6371 *context_menu = None;
6372 cx.notify();
6373 }
6374 }
6375 drop(context_menu);
6376 let snapshot = self.snapshot(window, cx);
6377 let deployed_from = action.deployed_from.clone();
6378 let action = action.clone();
6379 self.completion_tasks.clear();
6380 self.discard_edit_prediction(false, cx);
6381
6382 let multibuffer_point = match &action.deployed_from {
6383 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6384 DisplayPoint::new(*row, 0).to_point(&snapshot)
6385 }
6386 _ => self
6387 .selections
6388 .newest::<Point>(&snapshot.display_snapshot)
6389 .head(),
6390 };
6391 let Some((buffer, buffer_row)) = snapshot
6392 .buffer_snapshot()
6393 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6394 .and_then(|(buffer_snapshot, range)| {
6395 self.buffer()
6396 .read(cx)
6397 .buffer(buffer_snapshot.remote_id())
6398 .map(|buffer| (buffer, range.start.row))
6399 })
6400 else {
6401 return;
6402 };
6403 let buffer_id = buffer.read(cx).remote_id();
6404 let tasks = self
6405 .tasks
6406 .get(&(buffer_id, buffer_row))
6407 .map(|t| Arc::new(t.to_owned()));
6408
6409 if !self.focus_handle.is_focused(window) {
6410 return;
6411 }
6412 let project = self.project.clone();
6413
6414 let code_actions_task = match deployed_from {
6415 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6416 _ => self.code_actions(buffer_row, window, cx),
6417 };
6418
6419 let runnable_task = match deployed_from {
6420 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6421 _ => {
6422 let mut task_context_task = Task::ready(None);
6423 if let Some(tasks) = &tasks
6424 && let Some(project) = project
6425 {
6426 task_context_task =
6427 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6428 }
6429
6430 cx.spawn_in(window, {
6431 let buffer = buffer.clone();
6432 async move |editor, cx| {
6433 let task_context = task_context_task.await;
6434
6435 let resolved_tasks =
6436 tasks
6437 .zip(task_context.clone())
6438 .map(|(tasks, task_context)| ResolvedTasks {
6439 templates: tasks.resolve(&task_context).collect(),
6440 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6441 multibuffer_point.row,
6442 tasks.column,
6443 )),
6444 });
6445 let debug_scenarios = editor
6446 .update(cx, |editor, cx| {
6447 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6448 })?
6449 .await;
6450 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6451 }
6452 })
6453 }
6454 };
6455
6456 cx.spawn_in(window, async move |editor, cx| {
6457 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6458 let code_actions = code_actions_task.await;
6459 let spawn_straight_away = quick_launch
6460 && resolved_tasks
6461 .as_ref()
6462 .is_some_and(|tasks| tasks.templates.len() == 1)
6463 && code_actions
6464 .as_ref()
6465 .is_none_or(|actions| actions.is_empty())
6466 && debug_scenarios.is_empty();
6467
6468 editor.update_in(cx, |editor, window, cx| {
6469 crate::hover_popover::hide_hover(editor, cx);
6470 let actions = CodeActionContents::new(
6471 resolved_tasks,
6472 code_actions,
6473 debug_scenarios,
6474 task_context.unwrap_or_default(),
6475 );
6476
6477 // Don't show the menu if there are no actions available
6478 if actions.is_empty() {
6479 cx.notify();
6480 return Task::ready(Ok(()));
6481 }
6482
6483 *editor.context_menu.borrow_mut() =
6484 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6485 buffer,
6486 actions,
6487 selected_item: Default::default(),
6488 scroll_handle: UniformListScrollHandle::default(),
6489 deployed_from,
6490 }));
6491 cx.notify();
6492 if spawn_straight_away
6493 && let Some(task) = editor.confirm_code_action(
6494 &ConfirmCodeAction { item_ix: Some(0) },
6495 window,
6496 cx,
6497 )
6498 {
6499 return task;
6500 }
6501
6502 Task::ready(Ok(()))
6503 })
6504 })
6505 .detach_and_log_err(cx);
6506 }
6507
6508 fn debug_scenarios(
6509 &mut self,
6510 resolved_tasks: &Option<ResolvedTasks>,
6511 buffer: &Entity<Buffer>,
6512 cx: &mut App,
6513 ) -> Task<Vec<task::DebugScenario>> {
6514 maybe!({
6515 let project = self.project()?;
6516 let dap_store = project.read(cx).dap_store();
6517 let mut scenarios = vec![];
6518 let resolved_tasks = resolved_tasks.as_ref()?;
6519 let buffer = buffer.read(cx);
6520 let language = buffer.language()?;
6521 let file = buffer.file();
6522 let debug_adapter = language_settings(language.name().into(), file, cx)
6523 .debuggers
6524 .first()
6525 .map(SharedString::from)
6526 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6527
6528 dap_store.update(cx, |dap_store, cx| {
6529 for (_, task) in &resolved_tasks.templates {
6530 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6531 task.original_task().clone(),
6532 debug_adapter.clone().into(),
6533 task.display_label().to_owned().into(),
6534 cx,
6535 );
6536 scenarios.push(maybe_scenario);
6537 }
6538 });
6539 Some(cx.background_spawn(async move {
6540 futures::future::join_all(scenarios)
6541 .await
6542 .into_iter()
6543 .flatten()
6544 .collect::<Vec<_>>()
6545 }))
6546 })
6547 .unwrap_or_else(|| Task::ready(vec![]))
6548 }
6549
6550 fn code_actions(
6551 &mut self,
6552 buffer_row: u32,
6553 window: &mut Window,
6554 cx: &mut Context<Self>,
6555 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6556 let mut task = self.code_actions_task.take();
6557 cx.spawn_in(window, async move |editor, cx| {
6558 while let Some(prev_task) = task {
6559 prev_task.await.log_err();
6560 task = editor
6561 .update(cx, |this, _| this.code_actions_task.take())
6562 .ok()?;
6563 }
6564
6565 editor
6566 .update(cx, |editor, cx| {
6567 editor
6568 .available_code_actions
6569 .clone()
6570 .and_then(|(location, code_actions)| {
6571 let snapshot = location.buffer.read(cx).snapshot();
6572 let point_range = location.range.to_point(&snapshot);
6573 let point_range = point_range.start.row..=point_range.end.row;
6574 if point_range.contains(&buffer_row) {
6575 Some(code_actions)
6576 } else {
6577 None
6578 }
6579 })
6580 })
6581 .ok()
6582 .flatten()
6583 })
6584 }
6585
6586 pub fn confirm_code_action(
6587 &mut self,
6588 action: &ConfirmCodeAction,
6589 window: &mut Window,
6590 cx: &mut Context<Self>,
6591 ) -> Option<Task<Result<()>>> {
6592 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6593
6594 let actions_menu =
6595 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6596 menu
6597 } else {
6598 return None;
6599 };
6600
6601 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6602 let action = actions_menu.actions.get(action_ix)?;
6603 let title = action.label();
6604 let buffer = actions_menu.buffer;
6605 let workspace = self.workspace()?;
6606
6607 match action {
6608 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6609 workspace.update(cx, |workspace, cx| {
6610 workspace.schedule_resolved_task(
6611 task_source_kind,
6612 resolved_task,
6613 false,
6614 window,
6615 cx,
6616 );
6617
6618 Some(Task::ready(Ok(())))
6619 })
6620 }
6621 CodeActionsItem::CodeAction {
6622 excerpt_id,
6623 action,
6624 provider,
6625 } => {
6626 let apply_code_action =
6627 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6628 let workspace = workspace.downgrade();
6629 Some(cx.spawn_in(window, async move |editor, cx| {
6630 let project_transaction = apply_code_action.await?;
6631 Self::open_project_transaction(
6632 &editor,
6633 workspace,
6634 project_transaction,
6635 title,
6636 cx,
6637 )
6638 .await
6639 }))
6640 }
6641 CodeActionsItem::DebugScenario(scenario) => {
6642 let context = actions_menu.actions.context;
6643
6644 workspace.update(cx, |workspace, cx| {
6645 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6646 workspace.start_debug_session(
6647 scenario,
6648 context,
6649 Some(buffer),
6650 None,
6651 window,
6652 cx,
6653 );
6654 });
6655 Some(Task::ready(Ok(())))
6656 }
6657 }
6658 }
6659
6660 fn open_transaction_for_hidden_buffers(
6661 workspace: Entity<Workspace>,
6662 transaction: ProjectTransaction,
6663 title: String,
6664 window: &mut Window,
6665 cx: &mut Context<Self>,
6666 ) {
6667 if transaction.0.is_empty() {
6668 return;
6669 }
6670
6671 let edited_buffers_already_open = {
6672 let other_editors: Vec<Entity<Editor>> = workspace
6673 .read(cx)
6674 .panes()
6675 .iter()
6676 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6677 .filter(|editor| editor.entity_id() != cx.entity_id())
6678 .collect();
6679
6680 transaction.0.keys().all(|buffer| {
6681 other_editors.iter().any(|editor| {
6682 let multi_buffer = editor.read(cx).buffer();
6683 multi_buffer.read(cx).is_singleton()
6684 && multi_buffer
6685 .read(cx)
6686 .as_singleton()
6687 .map_or(false, |singleton| {
6688 singleton.entity_id() == buffer.entity_id()
6689 })
6690 })
6691 })
6692 };
6693 if !edited_buffers_already_open {
6694 let workspace = workspace.downgrade();
6695 cx.defer_in(window, move |_, window, cx| {
6696 cx.spawn_in(window, async move |editor, cx| {
6697 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6698 .await
6699 .ok()
6700 })
6701 .detach();
6702 });
6703 }
6704 }
6705
6706 pub async fn open_project_transaction(
6707 editor: &WeakEntity<Editor>,
6708 workspace: WeakEntity<Workspace>,
6709 transaction: ProjectTransaction,
6710 title: String,
6711 cx: &mut AsyncWindowContext,
6712 ) -> Result<()> {
6713 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6714 cx.update(|_, cx| {
6715 entries.sort_unstable_by_key(|(buffer, _)| {
6716 buffer.read(cx).file().map(|f| f.path().clone())
6717 });
6718 })?;
6719 if entries.is_empty() {
6720 return Ok(());
6721 }
6722
6723 // If the project transaction's edits are all contained within this editor, then
6724 // avoid opening a new editor to display them.
6725
6726 if let [(buffer, transaction)] = &*entries {
6727 let excerpt = editor.update(cx, |editor, cx| {
6728 editor
6729 .buffer()
6730 .read(cx)
6731 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6732 })?;
6733 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6734 && excerpted_buffer == *buffer
6735 {
6736 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6737 let excerpt_range = excerpt_range.to_offset(buffer);
6738 buffer
6739 .edited_ranges_for_transaction::<usize>(transaction)
6740 .all(|range| {
6741 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6742 })
6743 });
6744
6745 if all_edits_within_excerpt {
6746 return Ok(());
6747 }
6748 }
6749 }
6750
6751 let mut ranges_to_highlight = Vec::new();
6752 let excerpt_buffer = cx.new(|cx| {
6753 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6754 for (buffer_handle, transaction) in &entries {
6755 let edited_ranges = buffer_handle
6756 .read(cx)
6757 .edited_ranges_for_transaction::<Point>(transaction)
6758 .collect::<Vec<_>>();
6759 let (ranges, _) = multibuffer.set_excerpts_for_path(
6760 PathKey::for_buffer(buffer_handle, cx),
6761 buffer_handle.clone(),
6762 edited_ranges,
6763 multibuffer_context_lines(cx),
6764 cx,
6765 );
6766
6767 ranges_to_highlight.extend(ranges);
6768 }
6769 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6770 multibuffer
6771 });
6772
6773 workspace.update_in(cx, |workspace, window, cx| {
6774 let project = workspace.project().clone();
6775 let editor =
6776 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6777 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6778 editor.update(cx, |editor, cx| {
6779 editor.highlight_background::<Self>(
6780 &ranges_to_highlight,
6781 |_, theme| theme.colors().editor_highlighted_line_background,
6782 cx,
6783 );
6784 });
6785 })?;
6786
6787 Ok(())
6788 }
6789
6790 pub fn clear_code_action_providers(&mut self) {
6791 self.code_action_providers.clear();
6792 self.available_code_actions.take();
6793 }
6794
6795 pub fn add_code_action_provider(
6796 &mut self,
6797 provider: Rc<dyn CodeActionProvider>,
6798 window: &mut Window,
6799 cx: &mut Context<Self>,
6800 ) {
6801 if self
6802 .code_action_providers
6803 .iter()
6804 .any(|existing_provider| existing_provider.id() == provider.id())
6805 {
6806 return;
6807 }
6808
6809 self.code_action_providers.push(provider);
6810 self.refresh_code_actions(window, cx);
6811 }
6812
6813 pub fn remove_code_action_provider(
6814 &mut self,
6815 id: Arc<str>,
6816 window: &mut Window,
6817 cx: &mut Context<Self>,
6818 ) {
6819 self.code_action_providers
6820 .retain(|provider| provider.id() != id);
6821 self.refresh_code_actions(window, cx);
6822 }
6823
6824 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6825 !self.code_action_providers.is_empty()
6826 && EditorSettings::get_global(cx).toolbar.code_actions
6827 }
6828
6829 pub fn has_available_code_actions(&self) -> bool {
6830 self.available_code_actions
6831 .as_ref()
6832 .is_some_and(|(_, actions)| !actions.is_empty())
6833 }
6834
6835 fn render_inline_code_actions(
6836 &self,
6837 icon_size: ui::IconSize,
6838 display_row: DisplayRow,
6839 is_active: bool,
6840 cx: &mut Context<Self>,
6841 ) -> AnyElement {
6842 let show_tooltip = !self.context_menu_visible();
6843 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6844 .icon_size(icon_size)
6845 .shape(ui::IconButtonShape::Square)
6846 .icon_color(ui::Color::Hidden)
6847 .toggle_state(is_active)
6848 .when(show_tooltip, |this| {
6849 this.tooltip({
6850 let focus_handle = self.focus_handle.clone();
6851 move |_window, cx| {
6852 Tooltip::for_action_in(
6853 "Toggle Code Actions",
6854 &ToggleCodeActions {
6855 deployed_from: None,
6856 quick_launch: false,
6857 },
6858 &focus_handle,
6859 cx,
6860 )
6861 }
6862 })
6863 })
6864 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6865 window.focus(&editor.focus_handle(cx), cx);
6866 editor.toggle_code_actions(
6867 &crate::actions::ToggleCodeActions {
6868 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6869 display_row,
6870 )),
6871 quick_launch: false,
6872 },
6873 window,
6874 cx,
6875 );
6876 }))
6877 .into_any_element()
6878 }
6879
6880 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6881 &self.context_menu
6882 }
6883
6884 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6885 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6886 cx.background_executor()
6887 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6888 .await;
6889
6890 let (start_buffer, start, _, end, newest_selection) = this
6891 .update(cx, |this, cx| {
6892 let newest_selection = this.selections.newest_anchor().clone();
6893 if newest_selection.head().diff_base_anchor.is_some() {
6894 return None;
6895 }
6896 let display_snapshot = this.display_snapshot(cx);
6897 let newest_selection_adjusted =
6898 this.selections.newest_adjusted(&display_snapshot);
6899 let buffer = this.buffer.read(cx);
6900
6901 let (start_buffer, start) =
6902 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6903 let (end_buffer, end) =
6904 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6905
6906 Some((start_buffer, start, end_buffer, end, newest_selection))
6907 })?
6908 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6909 .context(
6910 "Expected selection to lie in a single buffer when refreshing code actions",
6911 )?;
6912 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6913 let providers = this.code_action_providers.clone();
6914 let tasks = this
6915 .code_action_providers
6916 .iter()
6917 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6918 .collect::<Vec<_>>();
6919 (providers, tasks)
6920 })?;
6921
6922 let mut actions = Vec::new();
6923 for (provider, provider_actions) in
6924 providers.into_iter().zip(future::join_all(tasks).await)
6925 {
6926 if let Some(provider_actions) = provider_actions.log_err() {
6927 actions.extend(provider_actions.into_iter().map(|action| {
6928 AvailableCodeAction {
6929 excerpt_id: newest_selection.start.excerpt_id,
6930 action,
6931 provider: provider.clone(),
6932 }
6933 }));
6934 }
6935 }
6936
6937 this.update(cx, |this, cx| {
6938 this.available_code_actions = if actions.is_empty() {
6939 None
6940 } else {
6941 Some((
6942 Location {
6943 buffer: start_buffer,
6944 range: start..end,
6945 },
6946 actions.into(),
6947 ))
6948 };
6949 cx.notify();
6950 })
6951 }));
6952 }
6953
6954 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6955 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6956 self.show_git_blame_inline = false;
6957
6958 self.show_git_blame_inline_delay_task =
6959 Some(cx.spawn_in(window, async move |this, cx| {
6960 cx.background_executor().timer(delay).await;
6961
6962 this.update(cx, |this, cx| {
6963 this.show_git_blame_inline = true;
6964 cx.notify();
6965 })
6966 .log_err();
6967 }));
6968 }
6969 }
6970
6971 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6972 let snapshot = self.snapshot(window, cx);
6973 let cursor = self
6974 .selections
6975 .newest::<Point>(&snapshot.display_snapshot)
6976 .head();
6977 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6978 else {
6979 return;
6980 };
6981
6982 if self.blame.is_none() {
6983 self.start_git_blame(true, window, cx);
6984 }
6985 let Some(blame) = self.blame.as_ref() else {
6986 return;
6987 };
6988
6989 let row_info = RowInfo {
6990 buffer_id: Some(buffer.remote_id()),
6991 buffer_row: Some(point.row),
6992 ..Default::default()
6993 };
6994 let Some((buffer, blame_entry)) = blame
6995 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6996 .flatten()
6997 else {
6998 return;
6999 };
7000
7001 let anchor = self.selections.newest_anchor().head();
7002 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
7003 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
7004 self.show_blame_popover(
7005 buffer,
7006 &blame_entry,
7007 position + last_bounds.origin,
7008 true,
7009 cx,
7010 );
7011 };
7012 }
7013
7014 fn show_blame_popover(
7015 &mut self,
7016 buffer: BufferId,
7017 blame_entry: &BlameEntry,
7018 position: gpui::Point<Pixels>,
7019 ignore_timeout: bool,
7020 cx: &mut Context<Self>,
7021 ) {
7022 if let Some(state) = &mut self.inline_blame_popover {
7023 state.hide_task.take();
7024 } else {
7025 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
7026 let blame_entry = blame_entry.clone();
7027 let show_task = cx.spawn(async move |editor, cx| {
7028 if !ignore_timeout {
7029 cx.background_executor()
7030 .timer(std::time::Duration::from_millis(blame_popover_delay))
7031 .await;
7032 }
7033 editor
7034 .update(cx, |editor, cx| {
7035 editor.inline_blame_popover_show_task.take();
7036 let Some(blame) = editor.blame.as_ref() else {
7037 return;
7038 };
7039 let blame = blame.read(cx);
7040 let details = blame.details_for_entry(buffer, &blame_entry);
7041 let markdown = cx.new(|cx| {
7042 Markdown::new(
7043 details
7044 .as_ref()
7045 .map(|message| message.message.clone())
7046 .unwrap_or_default(),
7047 None,
7048 None,
7049 cx,
7050 )
7051 });
7052 editor.inline_blame_popover = Some(InlineBlamePopover {
7053 position,
7054 hide_task: None,
7055 popover_bounds: None,
7056 popover_state: InlineBlamePopoverState {
7057 scroll_handle: ScrollHandle::new(),
7058 commit_message: details,
7059 markdown,
7060 },
7061 keyboard_grace: ignore_timeout,
7062 });
7063 cx.notify();
7064 })
7065 .ok();
7066 });
7067 self.inline_blame_popover_show_task = Some(show_task);
7068 }
7069 }
7070
7071 pub fn has_mouse_context_menu(&self) -> bool {
7072 self.mouse_context_menu.is_some()
7073 }
7074
7075 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7076 self.inline_blame_popover_show_task.take();
7077 if let Some(state) = &mut self.inline_blame_popover {
7078 let hide_task = cx.spawn(async move |editor, cx| {
7079 if !ignore_timeout {
7080 cx.background_executor()
7081 .timer(std::time::Duration::from_millis(100))
7082 .await;
7083 }
7084 editor
7085 .update(cx, |editor, cx| {
7086 editor.inline_blame_popover.take();
7087 cx.notify();
7088 })
7089 .ok();
7090 });
7091 state.hide_task = Some(hide_task);
7092 true
7093 } else {
7094 false
7095 }
7096 }
7097
7098 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7099 if self.pending_rename.is_some() {
7100 return None;
7101 }
7102
7103 let provider = self.semantics_provider.clone()?;
7104 let buffer = self.buffer.read(cx);
7105 let newest_selection = self.selections.newest_anchor().clone();
7106 let cursor_position = newest_selection.head();
7107 let (cursor_buffer, cursor_buffer_position) =
7108 buffer.text_anchor_for_position(cursor_position, cx)?;
7109 let (tail_buffer, tail_buffer_position) =
7110 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7111 if cursor_buffer != tail_buffer {
7112 return None;
7113 }
7114
7115 let snapshot = cursor_buffer.read(cx).snapshot();
7116 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7117 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7118 if start_word_range != end_word_range {
7119 self.document_highlights_task.take();
7120 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7121 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7122 return None;
7123 }
7124
7125 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7126 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7127 cx.background_executor()
7128 .timer(Duration::from_millis(debounce))
7129 .await;
7130
7131 let highlights = if let Some(highlights) = cx.update(|cx| {
7132 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7133 }) {
7134 highlights.await.log_err()
7135 } else {
7136 None
7137 };
7138
7139 if let Some(highlights) = highlights {
7140 this.update(cx, |this, cx| {
7141 if this.pending_rename.is_some() {
7142 return;
7143 }
7144
7145 let buffer = this.buffer.read(cx);
7146 if buffer
7147 .text_anchor_for_position(cursor_position, cx)
7148 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7149 {
7150 return;
7151 }
7152
7153 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7154 let mut write_ranges = Vec::new();
7155 let mut read_ranges = Vec::new();
7156 for highlight in highlights {
7157 let buffer_id = cursor_buffer.read(cx).remote_id();
7158 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7159 {
7160 let start = highlight
7161 .range
7162 .start
7163 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7164 let end = highlight
7165 .range
7166 .end
7167 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7168 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7169 continue;
7170 }
7171
7172 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7173 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7174 write_ranges.push(range);
7175 } else {
7176 read_ranges.push(range);
7177 }
7178 }
7179 }
7180
7181 this.highlight_background::<DocumentHighlightRead>(
7182 &read_ranges,
7183 |_, theme| theme.colors().editor_document_highlight_read_background,
7184 cx,
7185 );
7186 this.highlight_background::<DocumentHighlightWrite>(
7187 &write_ranges,
7188 |_, theme| theme.colors().editor_document_highlight_write_background,
7189 cx,
7190 );
7191 cx.notify();
7192 })
7193 .log_err();
7194 }
7195 }));
7196 None
7197 }
7198
7199 fn prepare_highlight_query_from_selection(
7200 &mut self,
7201 window: &Window,
7202 cx: &mut Context<Editor>,
7203 ) -> Option<(String, Range<Anchor>)> {
7204 if matches!(self.mode, EditorMode::SingleLine) {
7205 return None;
7206 }
7207 if !EditorSettings::get_global(cx).selection_highlight {
7208 return None;
7209 }
7210 if self.selections.count() != 1 || self.selections.line_mode() {
7211 return None;
7212 }
7213 let snapshot = self.snapshot(window, cx);
7214 let selection = self.selections.newest::<Point>(&snapshot);
7215 // If the selection spans multiple rows OR it is empty
7216 if selection.start.row != selection.end.row
7217 || selection.start.column == selection.end.column
7218 {
7219 return None;
7220 }
7221 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7222 let query = snapshot
7223 .buffer_snapshot()
7224 .text_for_range(selection_anchor_range.clone())
7225 .collect::<String>();
7226 if query.trim().is_empty() {
7227 return None;
7228 }
7229 Some((query, selection_anchor_range))
7230 }
7231
7232 #[ztracing::instrument(skip_all)]
7233 fn update_selection_occurrence_highlights(
7234 &mut self,
7235 query_text: String,
7236 query_range: Range<Anchor>,
7237 multi_buffer_range_to_query: Range<Point>,
7238 use_debounce: bool,
7239 window: &mut Window,
7240 cx: &mut Context<Editor>,
7241 ) -> Task<()> {
7242 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7243 cx.spawn_in(window, async move |editor, cx| {
7244 if use_debounce {
7245 cx.background_executor()
7246 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7247 .await;
7248 }
7249 let match_task = cx.background_spawn(async move {
7250 let buffer_ranges = multi_buffer_snapshot
7251 .range_to_buffer_ranges(multi_buffer_range_to_query)
7252 .into_iter()
7253 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7254 let mut match_ranges = Vec::new();
7255 let Ok(regex) = project::search::SearchQuery::text(
7256 query_text.clone(),
7257 false,
7258 false,
7259 false,
7260 Default::default(),
7261 Default::default(),
7262 false,
7263 None,
7264 ) else {
7265 return Vec::default();
7266 };
7267 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7268 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7269 match_ranges.extend(
7270 regex
7271 .search(
7272 buffer_snapshot,
7273 Some(search_range.start.0..search_range.end.0),
7274 )
7275 .await
7276 .into_iter()
7277 .filter_map(|match_range| {
7278 let match_start = buffer_snapshot
7279 .anchor_after(search_range.start + match_range.start);
7280 let match_end = buffer_snapshot
7281 .anchor_before(search_range.start + match_range.end);
7282 let match_anchor_range =
7283 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7284 (match_anchor_range != query_range).then_some(match_anchor_range)
7285 }),
7286 );
7287 }
7288 match_ranges
7289 });
7290 let match_ranges = match_task.await;
7291 editor
7292 .update_in(cx, |editor, _, cx| {
7293 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7294 if !match_ranges.is_empty() {
7295 editor.highlight_background::<SelectedTextHighlight>(
7296 &match_ranges,
7297 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7298 cx,
7299 )
7300 }
7301 })
7302 .log_err();
7303 })
7304 }
7305
7306 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7307 struct NewlineFold;
7308 let type_id = std::any::TypeId::of::<NewlineFold>();
7309 if !self.mode.is_single_line() {
7310 return;
7311 }
7312 let snapshot = self.snapshot(window, cx);
7313 if snapshot.buffer_snapshot().max_point().row == 0 {
7314 return;
7315 }
7316 let task = cx.background_spawn(async move {
7317 let new_newlines = snapshot
7318 .buffer_chars_at(MultiBufferOffset(0))
7319 .filter_map(|(c, i)| {
7320 if c == '\n' {
7321 Some(
7322 snapshot.buffer_snapshot().anchor_after(i)
7323 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7324 )
7325 } else {
7326 None
7327 }
7328 })
7329 .collect::<Vec<_>>();
7330 let existing_newlines = snapshot
7331 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7332 .filter_map(|fold| {
7333 if fold.placeholder.type_tag == Some(type_id) {
7334 Some(fold.range.start..fold.range.end)
7335 } else {
7336 None
7337 }
7338 })
7339 .collect::<Vec<_>>();
7340
7341 (new_newlines, existing_newlines)
7342 });
7343 self.folding_newlines = cx.spawn(async move |this, cx| {
7344 let (new_newlines, existing_newlines) = task.await;
7345 if new_newlines == existing_newlines {
7346 return;
7347 }
7348 let placeholder = FoldPlaceholder {
7349 render: Arc::new(move |_, _, cx| {
7350 div()
7351 .bg(cx.theme().status().hint_background)
7352 .border_b_1()
7353 .size_full()
7354 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7355 .border_color(cx.theme().status().hint)
7356 .child("\\n")
7357 .into_any()
7358 }),
7359 constrain_width: false,
7360 merge_adjacent: false,
7361 type_tag: Some(type_id),
7362 };
7363 let creases = new_newlines
7364 .into_iter()
7365 .map(|range| Crease::simple(range, placeholder.clone()))
7366 .collect();
7367 this.update(cx, |this, cx| {
7368 this.display_map.update(cx, |display_map, cx| {
7369 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7370 display_map.fold(creases, cx);
7371 });
7372 })
7373 .ok();
7374 });
7375 }
7376
7377 #[ztracing::instrument(skip_all)]
7378 fn refresh_selected_text_highlights(
7379 &mut self,
7380 on_buffer_edit: bool,
7381 window: &mut Window,
7382 cx: &mut Context<Editor>,
7383 ) {
7384 let Some((query_text, query_range)) =
7385 self.prepare_highlight_query_from_selection(window, cx)
7386 else {
7387 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7388 self.quick_selection_highlight_task.take();
7389 self.debounced_selection_highlight_task.take();
7390 return;
7391 };
7392 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7393 if on_buffer_edit
7394 || self
7395 .quick_selection_highlight_task
7396 .as_ref()
7397 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7398 {
7399 let multi_buffer_visible_start = self
7400 .scroll_manager
7401 .anchor()
7402 .anchor
7403 .to_point(&multi_buffer_snapshot);
7404 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7405 multi_buffer_visible_start
7406 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7407 Bias::Left,
7408 );
7409 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7410 self.quick_selection_highlight_task = Some((
7411 query_range.clone(),
7412 self.update_selection_occurrence_highlights(
7413 query_text.clone(),
7414 query_range.clone(),
7415 multi_buffer_visible_range,
7416 false,
7417 window,
7418 cx,
7419 ),
7420 ));
7421 }
7422 if on_buffer_edit
7423 || self
7424 .debounced_selection_highlight_task
7425 .as_ref()
7426 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7427 {
7428 let multi_buffer_start = multi_buffer_snapshot
7429 .anchor_before(MultiBufferOffset(0))
7430 .to_point(&multi_buffer_snapshot);
7431 let multi_buffer_end = multi_buffer_snapshot
7432 .anchor_after(multi_buffer_snapshot.len())
7433 .to_point(&multi_buffer_snapshot);
7434 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7435 self.debounced_selection_highlight_task = Some((
7436 query_range.clone(),
7437 self.update_selection_occurrence_highlights(
7438 query_text,
7439 query_range,
7440 multi_buffer_full_range,
7441 true,
7442 window,
7443 cx,
7444 ),
7445 ));
7446 }
7447 }
7448
7449 pub fn refresh_edit_prediction(
7450 &mut self,
7451 debounce: bool,
7452 user_requested: bool,
7453 window: &mut Window,
7454 cx: &mut Context<Self>,
7455 ) -> Option<()> {
7456 if DisableAiSettings::get_global(cx).disable_ai {
7457 return None;
7458 }
7459
7460 let provider = self.edit_prediction_provider()?;
7461 let cursor = self.selections.newest_anchor().head();
7462 let (buffer, cursor_buffer_position) =
7463 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7464
7465 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7466 self.discard_edit_prediction(false, cx);
7467 return None;
7468 }
7469
7470 self.update_visible_edit_prediction(window, cx);
7471
7472 if !user_requested
7473 && (!self.should_show_edit_predictions()
7474 || !self.is_focused(window)
7475 || buffer.read(cx).is_empty())
7476 {
7477 self.discard_edit_prediction(false, cx);
7478 return None;
7479 }
7480
7481 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7482 Some(())
7483 }
7484
7485 fn show_edit_predictions_in_menu(&self) -> bool {
7486 match self.edit_prediction_settings {
7487 EditPredictionSettings::Disabled => false,
7488 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7489 }
7490 }
7491
7492 pub fn edit_predictions_enabled(&self) -> bool {
7493 match self.edit_prediction_settings {
7494 EditPredictionSettings::Disabled => false,
7495 EditPredictionSettings::Enabled { .. } => true,
7496 }
7497 }
7498
7499 fn edit_prediction_requires_modifier(&self) -> bool {
7500 match self.edit_prediction_settings {
7501 EditPredictionSettings::Disabled => false,
7502 EditPredictionSettings::Enabled {
7503 preview_requires_modifier,
7504 ..
7505 } => preview_requires_modifier,
7506 }
7507 }
7508
7509 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7510 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7511 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7512 self.discard_edit_prediction(false, cx);
7513 } else {
7514 let selection = self.selections.newest_anchor();
7515 let cursor = selection.head();
7516
7517 if let Some((buffer, cursor_buffer_position)) =
7518 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7519 {
7520 self.edit_prediction_settings =
7521 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7522 }
7523 }
7524 }
7525
7526 fn edit_prediction_settings_at_position(
7527 &self,
7528 buffer: &Entity<Buffer>,
7529 buffer_position: language::Anchor,
7530 cx: &App,
7531 ) -> EditPredictionSettings {
7532 if !self.mode.is_full()
7533 || !self.show_edit_predictions_override.unwrap_or(true)
7534 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7535 {
7536 return EditPredictionSettings::Disabled;
7537 }
7538
7539 let buffer = buffer.read(cx);
7540
7541 let file = buffer.file();
7542
7543 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7544 return EditPredictionSettings::Disabled;
7545 };
7546
7547 let by_provider = matches!(
7548 self.menu_edit_predictions_policy,
7549 MenuEditPredictionsPolicy::ByProvider
7550 );
7551
7552 let show_in_menu = by_provider
7553 && self
7554 .edit_prediction_provider
7555 .as_ref()
7556 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7557
7558 let preview_requires_modifier =
7559 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7560
7561 EditPredictionSettings::Enabled {
7562 show_in_menu,
7563 preview_requires_modifier,
7564 }
7565 }
7566
7567 fn should_show_edit_predictions(&self) -> bool {
7568 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7569 }
7570
7571 pub fn edit_prediction_preview_is_active(&self) -> bool {
7572 matches!(
7573 self.edit_prediction_preview,
7574 EditPredictionPreview::Active { .. }
7575 )
7576 }
7577
7578 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7579 let cursor = self.selections.newest_anchor().head();
7580 if let Some((buffer, cursor_position)) =
7581 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7582 {
7583 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7584 } else {
7585 false
7586 }
7587 }
7588
7589 pub fn supports_minimap(&self, cx: &App) -> bool {
7590 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7591 }
7592
7593 fn edit_predictions_enabled_in_buffer(
7594 &self,
7595 buffer: &Entity<Buffer>,
7596 buffer_position: language::Anchor,
7597 cx: &App,
7598 ) -> bool {
7599 maybe!({
7600 if self.read_only(cx) {
7601 return Some(false);
7602 }
7603 let provider = self.edit_prediction_provider()?;
7604 if !provider.is_enabled(buffer, buffer_position, cx) {
7605 return Some(false);
7606 }
7607 let buffer = buffer.read(cx);
7608 let Some(file) = buffer.file() else {
7609 return Some(true);
7610 };
7611 let settings = all_language_settings(Some(file), cx);
7612 Some(settings.edit_predictions_enabled_for_file(file, cx))
7613 })
7614 .unwrap_or(false)
7615 }
7616
7617 pub fn show_edit_prediction(
7618 &mut self,
7619 _: &ShowEditPrediction,
7620 window: &mut Window,
7621 cx: &mut Context<Self>,
7622 ) {
7623 if !self.has_active_edit_prediction() {
7624 self.refresh_edit_prediction(false, true, window, cx);
7625 return;
7626 }
7627
7628 self.update_visible_edit_prediction(window, cx);
7629 }
7630
7631 pub fn display_cursor_names(
7632 &mut self,
7633 _: &DisplayCursorNames,
7634 window: &mut Window,
7635 cx: &mut Context<Self>,
7636 ) {
7637 self.show_cursor_names(window, cx);
7638 }
7639
7640 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7641 self.show_cursor_names = true;
7642 cx.notify();
7643 cx.spawn_in(window, async move |this, cx| {
7644 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7645 this.update(cx, |this, cx| {
7646 this.show_cursor_names = false;
7647 cx.notify()
7648 })
7649 .ok()
7650 })
7651 .detach();
7652 }
7653
7654 pub fn accept_partial_edit_prediction(
7655 &mut self,
7656 granularity: EditPredictionGranularity,
7657 window: &mut Window,
7658 cx: &mut Context<Self>,
7659 ) {
7660 if self.show_edit_predictions_in_menu() {
7661 self.hide_context_menu(window, cx);
7662 }
7663
7664 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7665 return;
7666 };
7667
7668 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7669 return;
7670 }
7671
7672 match &active_edit_prediction.completion {
7673 EditPrediction::MoveWithin { target, .. } => {
7674 let target = *target;
7675
7676 if matches!(granularity, EditPredictionGranularity::Full) {
7677 if let Some(position_map) = &self.last_position_map {
7678 let target_row = target.to_display_point(&position_map.snapshot).row();
7679 let is_visible = position_map.visible_row_range.contains(&target_row);
7680
7681 if is_visible || !self.edit_prediction_requires_modifier() {
7682 self.unfold_ranges(&[target..target], true, false, cx);
7683 self.change_selections(
7684 SelectionEffects::scroll(Autoscroll::newest()),
7685 window,
7686 cx,
7687 |selections| {
7688 selections.select_anchor_ranges([target..target]);
7689 },
7690 );
7691 self.clear_row_highlights::<EditPredictionPreview>();
7692 self.edit_prediction_preview
7693 .set_previous_scroll_position(None);
7694 } else {
7695 // Highlight and request scroll
7696 self.edit_prediction_preview
7697 .set_previous_scroll_position(Some(
7698 position_map.snapshot.scroll_anchor,
7699 ));
7700 self.highlight_rows::<EditPredictionPreview>(
7701 target..target,
7702 cx.theme().colors().editor_highlighted_line_background,
7703 RowHighlightOptions {
7704 autoscroll: true,
7705 ..Default::default()
7706 },
7707 cx,
7708 );
7709 self.request_autoscroll(Autoscroll::fit(), cx);
7710 }
7711 }
7712 } else {
7713 self.change_selections(
7714 SelectionEffects::scroll(Autoscroll::newest()),
7715 window,
7716 cx,
7717 |selections| {
7718 selections.select_anchor_ranges([target..target]);
7719 },
7720 );
7721 }
7722 }
7723 EditPrediction::MoveOutside { snapshot, target } => {
7724 if let Some(workspace) = self.workspace() {
7725 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7726 .detach_and_log_err(cx);
7727 }
7728 }
7729 EditPrediction::Edit { edits, .. } => {
7730 self.report_edit_prediction_event(
7731 active_edit_prediction.completion_id.clone(),
7732 true,
7733 cx,
7734 );
7735
7736 match granularity {
7737 EditPredictionGranularity::Full => {
7738 if let Some(provider) = self.edit_prediction_provider() {
7739 provider.accept(cx);
7740 }
7741
7742 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7743 let snapshot = self.buffer.read(cx).snapshot(cx);
7744 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7745
7746 self.buffer.update(cx, |buffer, cx| {
7747 buffer.edit(edits.iter().cloned(), None, cx)
7748 });
7749
7750 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7751 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7752 });
7753
7754 let selections = self.selections.disjoint_anchors_arc();
7755 if let Some(transaction_id_now) =
7756 self.buffer.read(cx).last_transaction_id(cx)
7757 {
7758 if transaction_id_prev != Some(transaction_id_now) {
7759 self.selection_history
7760 .insert_transaction(transaction_id_now, selections);
7761 }
7762 }
7763
7764 self.update_visible_edit_prediction(window, cx);
7765 if self.active_edit_prediction.is_none() {
7766 self.refresh_edit_prediction(true, true, window, cx);
7767 }
7768 cx.notify();
7769 }
7770 _ => {
7771 let snapshot = self.buffer.read(cx).snapshot(cx);
7772 let cursor_offset = self
7773 .selections
7774 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7775 .head();
7776
7777 let insertion = edits.iter().find_map(|(range, text)| {
7778 let range = range.to_offset(&snapshot);
7779 if range.is_empty() && range.start == cursor_offset {
7780 Some(text)
7781 } else {
7782 None
7783 }
7784 });
7785
7786 if let Some(text) = insertion {
7787 let text_to_insert = match granularity {
7788 EditPredictionGranularity::Word => {
7789 let mut partial = text
7790 .chars()
7791 .by_ref()
7792 .take_while(|c| c.is_alphabetic())
7793 .collect::<String>();
7794 if partial.is_empty() {
7795 partial = text
7796 .chars()
7797 .by_ref()
7798 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7799 .collect::<String>();
7800 }
7801 partial
7802 }
7803 EditPredictionGranularity::Line => {
7804 if let Some(line) = text.split_inclusive('\n').next() {
7805 line.to_string()
7806 } else {
7807 text.to_string()
7808 }
7809 }
7810 EditPredictionGranularity::Full => unreachable!(),
7811 };
7812
7813 cx.emit(EditorEvent::InputHandled {
7814 utf16_range_to_replace: None,
7815 text: text_to_insert.clone().into(),
7816 });
7817
7818 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7819 self.refresh_edit_prediction(true, true, window, cx);
7820 cx.notify();
7821 } else {
7822 self.accept_partial_edit_prediction(
7823 EditPredictionGranularity::Full,
7824 window,
7825 cx,
7826 );
7827 }
7828 }
7829 }
7830 }
7831 }
7832
7833 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7834 }
7835
7836 pub fn accept_next_word_edit_prediction(
7837 &mut self,
7838 _: &AcceptNextWordEditPrediction,
7839 window: &mut Window,
7840 cx: &mut Context<Self>,
7841 ) {
7842 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7843 }
7844
7845 pub fn accept_next_line_edit_prediction(
7846 &mut self,
7847 _: &AcceptNextLineEditPrediction,
7848 window: &mut Window,
7849 cx: &mut Context<Self>,
7850 ) {
7851 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7852 }
7853
7854 pub fn accept_edit_prediction(
7855 &mut self,
7856 _: &AcceptEditPrediction,
7857 window: &mut Window,
7858 cx: &mut Context<Self>,
7859 ) {
7860 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7861 }
7862
7863 fn discard_edit_prediction(
7864 &mut self,
7865 should_report_edit_prediction_event: bool,
7866 cx: &mut Context<Self>,
7867 ) -> bool {
7868 if should_report_edit_prediction_event {
7869 let completion_id = self
7870 .active_edit_prediction
7871 .as_ref()
7872 .and_then(|active_completion| active_completion.completion_id.clone());
7873
7874 self.report_edit_prediction_event(completion_id, false, cx);
7875 }
7876
7877 if let Some(provider) = self.edit_prediction_provider() {
7878 provider.discard(cx);
7879 }
7880
7881 self.take_active_edit_prediction(cx)
7882 }
7883
7884 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7885 let Some(provider) = self.edit_prediction_provider() else {
7886 return;
7887 };
7888
7889 let Some((_, buffer, _)) = self
7890 .buffer
7891 .read(cx)
7892 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7893 else {
7894 return;
7895 };
7896
7897 let extension = buffer
7898 .read(cx)
7899 .file()
7900 .and_then(|file| Some(file.path().extension()?.to_string()));
7901
7902 let event_type = match accepted {
7903 true => "Edit Prediction Accepted",
7904 false => "Edit Prediction Discarded",
7905 };
7906 telemetry::event!(
7907 event_type,
7908 provider = provider.name(),
7909 prediction_id = id,
7910 suggestion_accepted = accepted,
7911 file_extension = extension,
7912 );
7913 }
7914
7915 fn open_editor_at_anchor(
7916 snapshot: &language::BufferSnapshot,
7917 target: language::Anchor,
7918 workspace: &Entity<Workspace>,
7919 window: &mut Window,
7920 cx: &mut App,
7921 ) -> Task<Result<()>> {
7922 workspace.update(cx, |workspace, cx| {
7923 let path = snapshot.file().map(|file| file.full_path(cx));
7924 let Some(path) =
7925 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7926 else {
7927 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7928 };
7929 let target = text::ToPoint::to_point(&target, snapshot);
7930 let item = workspace.open_path(path, None, true, window, cx);
7931 window.spawn(cx, async move |cx| {
7932 let Some(editor) = item.await?.downcast::<Editor>() else {
7933 return Ok(());
7934 };
7935 editor
7936 .update_in(cx, |editor, window, cx| {
7937 editor.go_to_singleton_buffer_point(target, window, cx);
7938 })
7939 .ok();
7940 anyhow::Ok(())
7941 })
7942 })
7943 }
7944
7945 pub fn has_active_edit_prediction(&self) -> bool {
7946 self.active_edit_prediction.is_some()
7947 }
7948
7949 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7950 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7951 return false;
7952 };
7953
7954 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7955 self.clear_highlights::<EditPredictionHighlight>(cx);
7956 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7957 true
7958 }
7959
7960 /// Returns true when we're displaying the edit prediction popover below the cursor
7961 /// like we are not previewing and the LSP autocomplete menu is visible
7962 /// or we are in `when_holding_modifier` mode.
7963 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7964 if self.edit_prediction_preview_is_active()
7965 || !self.show_edit_predictions_in_menu()
7966 || !self.edit_predictions_enabled()
7967 {
7968 return false;
7969 }
7970
7971 if self.has_visible_completions_menu() {
7972 return true;
7973 }
7974
7975 has_completion && self.edit_prediction_requires_modifier()
7976 }
7977
7978 fn handle_modifiers_changed(
7979 &mut self,
7980 modifiers: Modifiers,
7981 position_map: &PositionMap,
7982 window: &mut Window,
7983 cx: &mut Context<Self>,
7984 ) {
7985 // Ensure that the edit prediction preview is updated, even when not
7986 // enabled, if there's an active edit prediction preview.
7987 if self.show_edit_predictions_in_menu()
7988 || matches!(
7989 self.edit_prediction_preview,
7990 EditPredictionPreview::Active { .. }
7991 )
7992 {
7993 self.update_edit_prediction_preview(&modifiers, window, cx);
7994 }
7995
7996 self.update_selection_mode(&modifiers, position_map, window, cx);
7997
7998 let mouse_position = window.mouse_position();
7999 if !position_map.text_hitbox.is_hovered(window) {
8000 return;
8001 }
8002
8003 self.update_hovered_link(
8004 position_map.point_for_position(mouse_position),
8005 &position_map.snapshot,
8006 modifiers,
8007 window,
8008 cx,
8009 )
8010 }
8011
8012 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8013 match EditorSettings::get_global(cx).multi_cursor_modifier {
8014 MultiCursorModifier::Alt => modifiers.secondary(),
8015 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8016 }
8017 }
8018
8019 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8020 match EditorSettings::get_global(cx).multi_cursor_modifier {
8021 MultiCursorModifier::Alt => modifiers.alt,
8022 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8023 }
8024 }
8025
8026 fn columnar_selection_mode(
8027 modifiers: &Modifiers,
8028 cx: &mut Context<Self>,
8029 ) -> Option<ColumnarMode> {
8030 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8031 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8032 Some(ColumnarMode::FromMouse)
8033 } else if Self::is_alt_pressed(modifiers, cx) {
8034 Some(ColumnarMode::FromSelection)
8035 } else {
8036 None
8037 }
8038 } else {
8039 None
8040 }
8041 }
8042
8043 fn update_selection_mode(
8044 &mut self,
8045 modifiers: &Modifiers,
8046 position_map: &PositionMap,
8047 window: &mut Window,
8048 cx: &mut Context<Self>,
8049 ) {
8050 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8051 return;
8052 };
8053 if self.selections.pending_anchor().is_none() {
8054 return;
8055 }
8056
8057 let mouse_position = window.mouse_position();
8058 let point_for_position = position_map.point_for_position(mouse_position);
8059 let position = point_for_position.previous_valid;
8060
8061 self.select(
8062 SelectPhase::BeginColumnar {
8063 position,
8064 reset: false,
8065 mode,
8066 goal_column: point_for_position.exact_unclipped.column(),
8067 },
8068 window,
8069 cx,
8070 );
8071 }
8072
8073 fn update_edit_prediction_preview(
8074 &mut self,
8075 modifiers: &Modifiers,
8076 window: &mut Window,
8077 cx: &mut Context<Self>,
8078 ) {
8079 let mut modifiers_held = false;
8080
8081 // Check bindings for all granularities.
8082 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8083 let granularities = [
8084 EditPredictionGranularity::Full,
8085 EditPredictionGranularity::Line,
8086 EditPredictionGranularity::Word,
8087 ];
8088
8089 for granularity in granularities {
8090 if let Some(keystroke) = self
8091 .accept_edit_prediction_keybind(granularity, window, cx)
8092 .keystroke()
8093 {
8094 modifiers_held = modifiers_held
8095 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8096 }
8097 }
8098
8099 if modifiers_held {
8100 if matches!(
8101 self.edit_prediction_preview,
8102 EditPredictionPreview::Inactive { .. }
8103 ) {
8104 self.edit_prediction_preview = EditPredictionPreview::Active {
8105 previous_scroll_position: None,
8106 since: Instant::now(),
8107 };
8108
8109 self.update_visible_edit_prediction(window, cx);
8110 cx.notify();
8111 }
8112 } else if let EditPredictionPreview::Active {
8113 previous_scroll_position,
8114 since,
8115 } = self.edit_prediction_preview
8116 {
8117 if let (Some(previous_scroll_position), Some(position_map)) =
8118 (previous_scroll_position, self.last_position_map.as_ref())
8119 {
8120 self.set_scroll_position(
8121 previous_scroll_position
8122 .scroll_position(&position_map.snapshot.display_snapshot),
8123 window,
8124 cx,
8125 );
8126 }
8127
8128 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8129 released_too_fast: since.elapsed() < Duration::from_millis(200),
8130 };
8131 self.clear_row_highlights::<EditPredictionPreview>();
8132 self.update_visible_edit_prediction(window, cx);
8133 cx.notify();
8134 }
8135 }
8136
8137 fn update_visible_edit_prediction(
8138 &mut self,
8139 _window: &mut Window,
8140 cx: &mut Context<Self>,
8141 ) -> Option<()> {
8142 if DisableAiSettings::get_global(cx).disable_ai {
8143 return None;
8144 }
8145
8146 if self.ime_transaction.is_some() {
8147 self.discard_edit_prediction(false, cx);
8148 return None;
8149 }
8150
8151 let selection = self.selections.newest_anchor();
8152 let cursor = selection.head();
8153 let multibuffer = self.buffer.read(cx).snapshot(cx);
8154 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8155 let excerpt_id = cursor.excerpt_id;
8156
8157 let show_in_menu = self.show_edit_predictions_in_menu();
8158 let completions_menu_has_precedence = !show_in_menu
8159 && (self.context_menu.borrow().is_some()
8160 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8161
8162 if completions_menu_has_precedence
8163 || !offset_selection.is_empty()
8164 || self
8165 .active_edit_prediction
8166 .as_ref()
8167 .is_some_and(|completion| {
8168 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8169 return false;
8170 };
8171 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8172 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8173 !invalidation_range.contains(&offset_selection.head())
8174 })
8175 {
8176 self.discard_edit_prediction(false, cx);
8177 return None;
8178 }
8179
8180 self.take_active_edit_prediction(cx);
8181 let Some(provider) = self.edit_prediction_provider() else {
8182 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8183 return None;
8184 };
8185
8186 let (buffer, cursor_buffer_position) =
8187 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8188
8189 self.edit_prediction_settings =
8190 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8191
8192 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8193
8194 if self.edit_prediction_indent_conflict {
8195 let cursor_point = cursor.to_point(&multibuffer);
8196 let mut suggested_indent = None;
8197 multibuffer.suggested_indents_callback(
8198 cursor_point.row..cursor_point.row + 1,
8199 |_, indent| {
8200 suggested_indent = Some(indent);
8201 ControlFlow::Break(())
8202 },
8203 cx,
8204 );
8205
8206 if let Some(indent) = suggested_indent
8207 && indent.len == cursor_point.column
8208 {
8209 self.edit_prediction_indent_conflict = false;
8210 }
8211 }
8212
8213 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8214
8215 let (completion_id, edits, edit_preview) = match edit_prediction {
8216 edit_prediction_types::EditPrediction::Local {
8217 id,
8218 edits,
8219 edit_preview,
8220 } => (id, edits, edit_preview),
8221 edit_prediction_types::EditPrediction::Jump {
8222 id,
8223 snapshot,
8224 target,
8225 } => {
8226 if let Some(provider) = &self.edit_prediction_provider {
8227 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8228 }
8229 self.stale_edit_prediction_in_menu = None;
8230 self.active_edit_prediction = Some(EditPredictionState {
8231 inlay_ids: vec![],
8232 completion: EditPrediction::MoveOutside { snapshot, target },
8233 completion_id: id,
8234 invalidation_range: None,
8235 });
8236 cx.notify();
8237 return Some(());
8238 }
8239 };
8240
8241 let edits = edits
8242 .into_iter()
8243 .flat_map(|(range, new_text)| {
8244 Some((
8245 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8246 new_text,
8247 ))
8248 })
8249 .collect::<Vec<_>>();
8250 if edits.is_empty() {
8251 return None;
8252 }
8253
8254 let first_edit_start = edits.first().unwrap().0.start;
8255 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8256 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8257
8258 let last_edit_end = edits.last().unwrap().0.end;
8259 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8260 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8261
8262 let cursor_row = cursor.to_point(&multibuffer).row;
8263
8264 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8265
8266 let mut inlay_ids = Vec::new();
8267 let invalidation_row_range;
8268 let move_invalidation_row_range = if cursor_row < edit_start_row {
8269 Some(cursor_row..edit_end_row)
8270 } else if cursor_row > edit_end_row {
8271 Some(edit_start_row..cursor_row)
8272 } else {
8273 None
8274 };
8275 let supports_jump = self
8276 .edit_prediction_provider
8277 .as_ref()
8278 .map(|provider| provider.provider.supports_jump_to_edit())
8279 .unwrap_or(true);
8280
8281 let is_move = supports_jump
8282 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8283 let completion = if is_move {
8284 if let Some(provider) = &self.edit_prediction_provider {
8285 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8286 }
8287 invalidation_row_range =
8288 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8289 let target = first_edit_start;
8290 EditPrediction::MoveWithin { target, snapshot }
8291 } else {
8292 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8293 && !self.edit_predictions_hidden_for_vim_mode;
8294
8295 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8296 if provider.show_tab_accept_marker() {
8297 EditDisplayMode::TabAccept
8298 } else {
8299 EditDisplayMode::Inline
8300 }
8301 } else {
8302 EditDisplayMode::DiffPopover
8303 };
8304
8305 if show_completions_in_buffer {
8306 if let Some(provider) = &self.edit_prediction_provider {
8307 let suggestion_display_type = match display_mode {
8308 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8309 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8310 SuggestionDisplayType::GhostText
8311 }
8312 };
8313 provider.provider.did_show(suggestion_display_type, cx);
8314 }
8315 if edits
8316 .iter()
8317 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8318 {
8319 let mut inlays = Vec::new();
8320 for (range, new_text) in &edits {
8321 let inlay = Inlay::edit_prediction(
8322 post_inc(&mut self.next_inlay_id),
8323 range.start,
8324 new_text.as_ref(),
8325 );
8326 inlay_ids.push(inlay.id);
8327 inlays.push(inlay);
8328 }
8329
8330 self.splice_inlays(&[], inlays, cx);
8331 } else {
8332 let background_color = cx.theme().status().deleted_background;
8333 self.highlight_text::<EditPredictionHighlight>(
8334 edits.iter().map(|(range, _)| range.clone()).collect(),
8335 HighlightStyle {
8336 background_color: Some(background_color),
8337 ..Default::default()
8338 },
8339 cx,
8340 );
8341 }
8342 }
8343
8344 invalidation_row_range = edit_start_row..edit_end_row;
8345
8346 EditPrediction::Edit {
8347 edits,
8348 edit_preview,
8349 display_mode,
8350 snapshot,
8351 }
8352 };
8353
8354 let invalidation_range = multibuffer
8355 .anchor_before(Point::new(invalidation_row_range.start, 0))
8356 ..multibuffer.anchor_after(Point::new(
8357 invalidation_row_range.end,
8358 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8359 ));
8360
8361 self.stale_edit_prediction_in_menu = None;
8362 self.active_edit_prediction = Some(EditPredictionState {
8363 inlay_ids,
8364 completion,
8365 completion_id,
8366 invalidation_range: Some(invalidation_range),
8367 });
8368
8369 cx.notify();
8370
8371 Some(())
8372 }
8373
8374 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8375 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8376 }
8377
8378 fn clear_tasks(&mut self) {
8379 self.tasks.clear()
8380 }
8381
8382 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8383 if self.tasks.insert(key, value).is_some() {
8384 // This case should hopefully be rare, but just in case...
8385 log::error!(
8386 "multiple different run targets found on a single line, only the last target will be rendered"
8387 )
8388 }
8389 }
8390
8391 /// Get all display points of breakpoints that will be rendered within editor
8392 ///
8393 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8394 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8395 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8396 fn active_breakpoints(
8397 &self,
8398 range: Range<DisplayRow>,
8399 window: &mut Window,
8400 cx: &mut Context<Self>,
8401 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8402 let mut breakpoint_display_points = HashMap::default();
8403
8404 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8405 return breakpoint_display_points;
8406 };
8407
8408 let snapshot = self.snapshot(window, cx);
8409
8410 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8411 let Some(project) = self.project() else {
8412 return breakpoint_display_points;
8413 };
8414
8415 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8416 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8417
8418 for (buffer_snapshot, range, excerpt_id) in
8419 multi_buffer_snapshot.range_to_buffer_ranges(range)
8420 {
8421 let Some(buffer) = project
8422 .read(cx)
8423 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8424 else {
8425 continue;
8426 };
8427 let breakpoints = breakpoint_store.read(cx).breakpoints(
8428 &buffer,
8429 Some(
8430 buffer_snapshot.anchor_before(range.start)
8431 ..buffer_snapshot.anchor_after(range.end),
8432 ),
8433 buffer_snapshot,
8434 cx,
8435 );
8436 for (breakpoint, state) in breakpoints {
8437 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8438 let position = multi_buffer_anchor
8439 .to_point(&multi_buffer_snapshot)
8440 .to_display_point(&snapshot);
8441
8442 breakpoint_display_points.insert(
8443 position.row(),
8444 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8445 );
8446 }
8447 }
8448
8449 breakpoint_display_points
8450 }
8451
8452 fn breakpoint_context_menu(
8453 &self,
8454 anchor: Anchor,
8455 window: &mut Window,
8456 cx: &mut Context<Self>,
8457 ) -> Entity<ui::ContextMenu> {
8458 let weak_editor = cx.weak_entity();
8459 let focus_handle = self.focus_handle(cx);
8460
8461 let row = self
8462 .buffer
8463 .read(cx)
8464 .snapshot(cx)
8465 .summary_for_anchor::<Point>(&anchor)
8466 .row;
8467
8468 let breakpoint = self
8469 .breakpoint_at_row(row, window, cx)
8470 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8471
8472 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8473 "Edit Log Breakpoint"
8474 } else {
8475 "Set Log Breakpoint"
8476 };
8477
8478 let condition_breakpoint_msg = if breakpoint
8479 .as_ref()
8480 .is_some_and(|bp| bp.1.condition.is_some())
8481 {
8482 "Edit Condition Breakpoint"
8483 } else {
8484 "Set Condition Breakpoint"
8485 };
8486
8487 let hit_condition_breakpoint_msg = if breakpoint
8488 .as_ref()
8489 .is_some_and(|bp| bp.1.hit_condition.is_some())
8490 {
8491 "Edit Hit Condition Breakpoint"
8492 } else {
8493 "Set Hit Condition Breakpoint"
8494 };
8495
8496 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8497 "Unset Breakpoint"
8498 } else {
8499 "Set Breakpoint"
8500 };
8501
8502 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8503
8504 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8505 BreakpointState::Enabled => Some("Disable"),
8506 BreakpointState::Disabled => Some("Enable"),
8507 });
8508
8509 let (anchor, breakpoint) =
8510 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8511
8512 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8513 menu.on_blur_subscription(Subscription::new(|| {}))
8514 .context(focus_handle)
8515 .when(run_to_cursor, |this| {
8516 let weak_editor = weak_editor.clone();
8517 this.entry("Run to cursor", None, move |window, cx| {
8518 weak_editor
8519 .update(cx, |editor, cx| {
8520 editor.change_selections(
8521 SelectionEffects::no_scroll(),
8522 window,
8523 cx,
8524 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8525 );
8526 })
8527 .ok();
8528
8529 window.dispatch_action(Box::new(RunToCursor), cx);
8530 })
8531 .separator()
8532 })
8533 .when_some(toggle_state_msg, |this, msg| {
8534 this.entry(msg, None, {
8535 let weak_editor = weak_editor.clone();
8536 let breakpoint = breakpoint.clone();
8537 move |_window, cx| {
8538 weak_editor
8539 .update(cx, |this, cx| {
8540 this.edit_breakpoint_at_anchor(
8541 anchor,
8542 breakpoint.as_ref().clone(),
8543 BreakpointEditAction::InvertState,
8544 cx,
8545 );
8546 })
8547 .log_err();
8548 }
8549 })
8550 })
8551 .entry(set_breakpoint_msg, None, {
8552 let weak_editor = weak_editor.clone();
8553 let breakpoint = breakpoint.clone();
8554 move |_window, cx| {
8555 weak_editor
8556 .update(cx, |this, cx| {
8557 this.edit_breakpoint_at_anchor(
8558 anchor,
8559 breakpoint.as_ref().clone(),
8560 BreakpointEditAction::Toggle,
8561 cx,
8562 );
8563 })
8564 .log_err();
8565 }
8566 })
8567 .entry(log_breakpoint_msg, None, {
8568 let breakpoint = breakpoint.clone();
8569 let weak_editor = weak_editor.clone();
8570 move |window, cx| {
8571 weak_editor
8572 .update(cx, |this, cx| {
8573 this.add_edit_breakpoint_block(
8574 anchor,
8575 breakpoint.as_ref(),
8576 BreakpointPromptEditAction::Log,
8577 window,
8578 cx,
8579 );
8580 })
8581 .log_err();
8582 }
8583 })
8584 .entry(condition_breakpoint_msg, None, {
8585 let breakpoint = breakpoint.clone();
8586 let weak_editor = weak_editor.clone();
8587 move |window, cx| {
8588 weak_editor
8589 .update(cx, |this, cx| {
8590 this.add_edit_breakpoint_block(
8591 anchor,
8592 breakpoint.as_ref(),
8593 BreakpointPromptEditAction::Condition,
8594 window,
8595 cx,
8596 );
8597 })
8598 .log_err();
8599 }
8600 })
8601 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8602 weak_editor
8603 .update(cx, |this, cx| {
8604 this.add_edit_breakpoint_block(
8605 anchor,
8606 breakpoint.as_ref(),
8607 BreakpointPromptEditAction::HitCondition,
8608 window,
8609 cx,
8610 );
8611 })
8612 .log_err();
8613 })
8614 })
8615 }
8616
8617 fn render_breakpoint(
8618 &self,
8619 position: Anchor,
8620 row: DisplayRow,
8621 breakpoint: &Breakpoint,
8622 state: Option<BreakpointSessionState>,
8623 cx: &mut Context<Self>,
8624 ) -> IconButton {
8625 let is_rejected = state.is_some_and(|s| !s.verified);
8626 // Is it a breakpoint that shows up when hovering over gutter?
8627 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8628 (false, false),
8629 |PhantomBreakpointIndicator {
8630 is_active,
8631 display_row,
8632 collides_with_existing_breakpoint,
8633 }| {
8634 (
8635 is_active && display_row == row,
8636 collides_with_existing_breakpoint,
8637 )
8638 },
8639 );
8640
8641 let (color, icon) = {
8642 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8643 (false, false) => ui::IconName::DebugBreakpoint,
8644 (true, false) => ui::IconName::DebugLogBreakpoint,
8645 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8646 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8647 };
8648
8649 let theme_colors = cx.theme().colors();
8650
8651 let color = if is_phantom {
8652 if collides_with_existing {
8653 Color::Custom(
8654 theme_colors
8655 .debugger_accent
8656 .blend(theme_colors.text.opacity(0.6)),
8657 )
8658 } else {
8659 Color::Hint
8660 }
8661 } else if is_rejected {
8662 Color::Disabled
8663 } else {
8664 Color::Debugger
8665 };
8666
8667 (color, icon)
8668 };
8669
8670 let breakpoint = Arc::from(breakpoint.clone());
8671
8672 let alt_as_text = gpui::Keystroke {
8673 modifiers: Modifiers::secondary_key(),
8674 ..Default::default()
8675 };
8676 let primary_action_text = if breakpoint.is_disabled() {
8677 "Enable breakpoint"
8678 } else if is_phantom && !collides_with_existing {
8679 "Set breakpoint"
8680 } else {
8681 "Unset breakpoint"
8682 };
8683 let focus_handle = self.focus_handle.clone();
8684
8685 let meta = if is_rejected {
8686 SharedString::from("No executable code is associated with this line.")
8687 } else if collides_with_existing && !breakpoint.is_disabled() {
8688 SharedString::from(format!(
8689 "{alt_as_text}-click to disable,\nright-click for more options."
8690 ))
8691 } else {
8692 SharedString::from("Right-click for more options.")
8693 };
8694 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8695 .icon_size(IconSize::XSmall)
8696 .size(ui::ButtonSize::None)
8697 .when(is_rejected, |this| {
8698 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8699 })
8700 .icon_color(color)
8701 .style(ButtonStyle::Transparent)
8702 .on_click(cx.listener({
8703 move |editor, event: &ClickEvent, window, cx| {
8704 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8705 BreakpointEditAction::InvertState
8706 } else {
8707 BreakpointEditAction::Toggle
8708 };
8709
8710 window.focus(&editor.focus_handle(cx), cx);
8711 editor.edit_breakpoint_at_anchor(
8712 position,
8713 breakpoint.as_ref().clone(),
8714 edit_action,
8715 cx,
8716 );
8717 }
8718 }))
8719 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8720 editor.set_breakpoint_context_menu(
8721 row,
8722 Some(position),
8723 event.position(),
8724 window,
8725 cx,
8726 );
8727 }))
8728 .tooltip(move |_window, cx| {
8729 Tooltip::with_meta_in(
8730 primary_action_text,
8731 Some(&ToggleBreakpoint),
8732 meta.clone(),
8733 &focus_handle,
8734 cx,
8735 )
8736 })
8737 }
8738
8739 fn build_tasks_context(
8740 project: &Entity<Project>,
8741 buffer: &Entity<Buffer>,
8742 buffer_row: u32,
8743 tasks: &Arc<RunnableTasks>,
8744 cx: &mut Context<Self>,
8745 ) -> Task<Option<task::TaskContext>> {
8746 let position = Point::new(buffer_row, tasks.column);
8747 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8748 let location = Location {
8749 buffer: buffer.clone(),
8750 range: range_start..range_start,
8751 };
8752 // Fill in the environmental variables from the tree-sitter captures
8753 let mut captured_task_variables = TaskVariables::default();
8754 for (capture_name, value) in tasks.extra_variables.clone() {
8755 captured_task_variables.insert(
8756 task::VariableName::Custom(capture_name.into()),
8757 value.clone(),
8758 );
8759 }
8760 project.update(cx, |project, cx| {
8761 project.task_store().update(cx, |task_store, cx| {
8762 task_store.task_context_for_location(captured_task_variables, location, cx)
8763 })
8764 })
8765 }
8766
8767 pub fn spawn_nearest_task(
8768 &mut self,
8769 action: &SpawnNearestTask,
8770 window: &mut Window,
8771 cx: &mut Context<Self>,
8772 ) {
8773 let Some((workspace, _)) = self.workspace.clone() else {
8774 return;
8775 };
8776 let Some(project) = self.project.clone() else {
8777 return;
8778 };
8779
8780 // Try to find a closest, enclosing node using tree-sitter that has a task
8781 let Some((buffer, buffer_row, tasks)) = self
8782 .find_enclosing_node_task(cx)
8783 // Or find the task that's closest in row-distance.
8784 .or_else(|| self.find_closest_task(cx))
8785 else {
8786 return;
8787 };
8788
8789 let reveal_strategy = action.reveal;
8790 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8791 cx.spawn_in(window, async move |_, cx| {
8792 let context = task_context.await?;
8793 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8794
8795 let resolved = &mut resolved_task.resolved;
8796 resolved.reveal = reveal_strategy;
8797
8798 workspace
8799 .update_in(cx, |workspace, window, cx| {
8800 workspace.schedule_resolved_task(
8801 task_source_kind,
8802 resolved_task,
8803 false,
8804 window,
8805 cx,
8806 );
8807 })
8808 .ok()
8809 })
8810 .detach();
8811 }
8812
8813 fn find_closest_task(
8814 &mut self,
8815 cx: &mut Context<Self>,
8816 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8817 let cursor_row = self
8818 .selections
8819 .newest_adjusted(&self.display_snapshot(cx))
8820 .head()
8821 .row;
8822
8823 let ((buffer_id, row), tasks) = self
8824 .tasks
8825 .iter()
8826 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8827
8828 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8829 let tasks = Arc::new(tasks.to_owned());
8830 Some((buffer, *row, tasks))
8831 }
8832
8833 fn find_enclosing_node_task(
8834 &mut self,
8835 cx: &mut Context<Self>,
8836 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8837 let snapshot = self.buffer.read(cx).snapshot(cx);
8838 let offset = self
8839 .selections
8840 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8841 .head();
8842 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8843 let offset = excerpt.map_offset_to_buffer(offset);
8844 let buffer_id = excerpt.buffer().remote_id();
8845
8846 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8847 let mut cursor = layer.node().walk();
8848
8849 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8850 if cursor.node().end_byte() == offset.0 {
8851 cursor.goto_next_sibling();
8852 }
8853 }
8854
8855 // Ascend to the smallest ancestor that contains the range and has a task.
8856 loop {
8857 let node = cursor.node();
8858 let node_range = node.byte_range();
8859 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8860
8861 // Check if this node contains our offset
8862 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8863 // If it contains offset, check for task
8864 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8865 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8866 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8867 }
8868 }
8869
8870 if !cursor.goto_parent() {
8871 break;
8872 }
8873 }
8874 None
8875 }
8876
8877 fn render_run_indicator(
8878 &self,
8879 _style: &EditorStyle,
8880 is_active: bool,
8881 row: DisplayRow,
8882 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8883 cx: &mut Context<Self>,
8884 ) -> IconButton {
8885 let color = Color::Muted;
8886 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8887
8888 IconButton::new(
8889 ("run_indicator", row.0 as usize),
8890 ui::IconName::PlayOutlined,
8891 )
8892 .shape(ui::IconButtonShape::Square)
8893 .icon_size(IconSize::XSmall)
8894 .icon_color(color)
8895 .toggle_state(is_active)
8896 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8897 let quick_launch = match e {
8898 ClickEvent::Keyboard(_) => true,
8899 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8900 };
8901
8902 window.focus(&editor.focus_handle(cx), cx);
8903 editor.toggle_code_actions(
8904 &ToggleCodeActions {
8905 deployed_from: Some(CodeActionSource::RunMenu(row)),
8906 quick_launch,
8907 },
8908 window,
8909 cx,
8910 );
8911 }))
8912 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8913 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8914 }))
8915 }
8916
8917 pub fn context_menu_visible(&self) -> bool {
8918 !self.edit_prediction_preview_is_active()
8919 && self
8920 .context_menu
8921 .borrow()
8922 .as_ref()
8923 .is_some_and(|menu| menu.visible())
8924 }
8925
8926 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8927 self.context_menu
8928 .borrow()
8929 .as_ref()
8930 .map(|menu| menu.origin())
8931 }
8932
8933 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8934 self.context_menu_options = Some(options);
8935 }
8936
8937 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8938 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8939
8940 fn render_edit_prediction_popover(
8941 &mut self,
8942 text_bounds: &Bounds<Pixels>,
8943 content_origin: gpui::Point<Pixels>,
8944 right_margin: Pixels,
8945 editor_snapshot: &EditorSnapshot,
8946 visible_row_range: Range<DisplayRow>,
8947 scroll_top: ScrollOffset,
8948 scroll_bottom: ScrollOffset,
8949 line_layouts: &[LineWithInvisibles],
8950 line_height: Pixels,
8951 scroll_position: gpui::Point<ScrollOffset>,
8952 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8953 newest_selection_head: Option<DisplayPoint>,
8954 editor_width: Pixels,
8955 style: &EditorStyle,
8956 window: &mut Window,
8957 cx: &mut App,
8958 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8959 if self.mode().is_minimap() {
8960 return None;
8961 }
8962 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8963
8964 if self.edit_prediction_visible_in_cursor_popover(true) {
8965 return None;
8966 }
8967
8968 match &active_edit_prediction.completion {
8969 EditPrediction::MoveWithin { target, .. } => {
8970 let target_display_point = target.to_display_point(editor_snapshot);
8971
8972 if self.edit_prediction_requires_modifier() {
8973 if !self.edit_prediction_preview_is_active() {
8974 return None;
8975 }
8976
8977 self.render_edit_prediction_modifier_jump_popover(
8978 text_bounds,
8979 content_origin,
8980 visible_row_range,
8981 line_layouts,
8982 line_height,
8983 scroll_pixel_position,
8984 newest_selection_head,
8985 target_display_point,
8986 window,
8987 cx,
8988 )
8989 } else {
8990 self.render_edit_prediction_eager_jump_popover(
8991 text_bounds,
8992 content_origin,
8993 editor_snapshot,
8994 visible_row_range,
8995 scroll_top,
8996 scroll_bottom,
8997 line_height,
8998 scroll_pixel_position,
8999 target_display_point,
9000 editor_width,
9001 window,
9002 cx,
9003 )
9004 }
9005 }
9006 EditPrediction::Edit {
9007 display_mode: EditDisplayMode::Inline,
9008 ..
9009 } => None,
9010 EditPrediction::Edit {
9011 display_mode: EditDisplayMode::TabAccept,
9012 edits,
9013 ..
9014 } => {
9015 let range = &edits.first()?.0;
9016 let target_display_point = range.end.to_display_point(editor_snapshot);
9017
9018 self.render_edit_prediction_end_of_line_popover(
9019 "Accept",
9020 editor_snapshot,
9021 visible_row_range,
9022 target_display_point,
9023 line_height,
9024 scroll_pixel_position,
9025 content_origin,
9026 editor_width,
9027 window,
9028 cx,
9029 )
9030 }
9031 EditPrediction::Edit {
9032 edits,
9033 edit_preview,
9034 display_mode: EditDisplayMode::DiffPopover,
9035 snapshot,
9036 } => self.render_edit_prediction_diff_popover(
9037 text_bounds,
9038 content_origin,
9039 right_margin,
9040 editor_snapshot,
9041 visible_row_range,
9042 line_layouts,
9043 line_height,
9044 scroll_position,
9045 scroll_pixel_position,
9046 newest_selection_head,
9047 editor_width,
9048 style,
9049 edits,
9050 edit_preview,
9051 snapshot,
9052 window,
9053 cx,
9054 ),
9055 EditPrediction::MoveOutside { snapshot, .. } => {
9056 let mut element = self
9057 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9058 .into_any();
9059
9060 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9061 let origin_x = text_bounds.size.width - size.width - px(30.);
9062 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9063 element.prepaint_at(origin, window, cx);
9064
9065 Some((element, origin))
9066 }
9067 }
9068 }
9069
9070 fn render_edit_prediction_modifier_jump_popover(
9071 &mut self,
9072 text_bounds: &Bounds<Pixels>,
9073 content_origin: gpui::Point<Pixels>,
9074 visible_row_range: Range<DisplayRow>,
9075 line_layouts: &[LineWithInvisibles],
9076 line_height: Pixels,
9077 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9078 newest_selection_head: Option<DisplayPoint>,
9079 target_display_point: DisplayPoint,
9080 window: &mut Window,
9081 cx: &mut App,
9082 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9083 let scrolled_content_origin =
9084 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9085
9086 const SCROLL_PADDING_Y: Pixels = px(12.);
9087
9088 if target_display_point.row() < visible_row_range.start {
9089 return self.render_edit_prediction_scroll_popover(
9090 |_| SCROLL_PADDING_Y,
9091 IconName::ArrowUp,
9092 visible_row_range,
9093 line_layouts,
9094 newest_selection_head,
9095 scrolled_content_origin,
9096 window,
9097 cx,
9098 );
9099 } else if target_display_point.row() >= visible_row_range.end {
9100 return self.render_edit_prediction_scroll_popover(
9101 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9102 IconName::ArrowDown,
9103 visible_row_range,
9104 line_layouts,
9105 newest_selection_head,
9106 scrolled_content_origin,
9107 window,
9108 cx,
9109 );
9110 }
9111
9112 const POLE_WIDTH: Pixels = px(2.);
9113
9114 let line_layout =
9115 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9116 let target_column = target_display_point.column() as usize;
9117
9118 let target_x = line_layout.x_for_index(target_column);
9119 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9120 - scroll_pixel_position.y;
9121
9122 let flag_on_right = target_x < text_bounds.size.width / 2.;
9123
9124 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9125 border_color.l += 0.001;
9126
9127 let mut element = v_flex()
9128 .items_end()
9129 .when(flag_on_right, |el| el.items_start())
9130 .child(if flag_on_right {
9131 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9132 .rounded_bl(px(0.))
9133 .rounded_tl(px(0.))
9134 .border_l_2()
9135 .border_color(border_color)
9136 } else {
9137 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9138 .rounded_br(px(0.))
9139 .rounded_tr(px(0.))
9140 .border_r_2()
9141 .border_color(border_color)
9142 })
9143 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9144 .into_any();
9145
9146 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9147
9148 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9149 - point(
9150 if flag_on_right {
9151 POLE_WIDTH
9152 } else {
9153 size.width - POLE_WIDTH
9154 },
9155 size.height - line_height,
9156 );
9157
9158 origin.x = origin.x.max(content_origin.x);
9159
9160 element.prepaint_at(origin, window, cx);
9161
9162 Some((element, origin))
9163 }
9164
9165 fn render_edit_prediction_scroll_popover(
9166 &mut self,
9167 to_y: impl Fn(Size<Pixels>) -> Pixels,
9168 scroll_icon: IconName,
9169 visible_row_range: Range<DisplayRow>,
9170 line_layouts: &[LineWithInvisibles],
9171 newest_selection_head: Option<DisplayPoint>,
9172 scrolled_content_origin: gpui::Point<Pixels>,
9173 window: &mut Window,
9174 cx: &mut App,
9175 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9176 let mut element = self
9177 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9178 .into_any();
9179
9180 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9181
9182 let cursor = newest_selection_head?;
9183 let cursor_row_layout =
9184 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9185 let cursor_column = cursor.column() as usize;
9186
9187 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9188
9189 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9190
9191 element.prepaint_at(origin, window, cx);
9192 Some((element, origin))
9193 }
9194
9195 fn render_edit_prediction_eager_jump_popover(
9196 &mut self,
9197 text_bounds: &Bounds<Pixels>,
9198 content_origin: gpui::Point<Pixels>,
9199 editor_snapshot: &EditorSnapshot,
9200 visible_row_range: Range<DisplayRow>,
9201 scroll_top: ScrollOffset,
9202 scroll_bottom: ScrollOffset,
9203 line_height: Pixels,
9204 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9205 target_display_point: DisplayPoint,
9206 editor_width: Pixels,
9207 window: &mut Window,
9208 cx: &mut App,
9209 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9210 if target_display_point.row().as_f64() < scroll_top {
9211 let mut element = self
9212 .render_edit_prediction_line_popover(
9213 "Jump to Edit",
9214 Some(IconName::ArrowUp),
9215 window,
9216 cx,
9217 )
9218 .into_any();
9219
9220 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9221 let offset = point(
9222 (text_bounds.size.width - size.width) / 2.,
9223 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9224 );
9225
9226 let origin = text_bounds.origin + offset;
9227 element.prepaint_at(origin, window, cx);
9228 Some((element, origin))
9229 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9230 let mut element = self
9231 .render_edit_prediction_line_popover(
9232 "Jump to Edit",
9233 Some(IconName::ArrowDown),
9234 window,
9235 cx,
9236 )
9237 .into_any();
9238
9239 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9240 let offset = point(
9241 (text_bounds.size.width - size.width) / 2.,
9242 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9243 );
9244
9245 let origin = text_bounds.origin + offset;
9246 element.prepaint_at(origin, window, cx);
9247 Some((element, origin))
9248 } else {
9249 self.render_edit_prediction_end_of_line_popover(
9250 "Jump to Edit",
9251 editor_snapshot,
9252 visible_row_range,
9253 target_display_point,
9254 line_height,
9255 scroll_pixel_position,
9256 content_origin,
9257 editor_width,
9258 window,
9259 cx,
9260 )
9261 }
9262 }
9263
9264 fn render_edit_prediction_end_of_line_popover(
9265 self: &mut Editor,
9266 label: &'static str,
9267 editor_snapshot: &EditorSnapshot,
9268 visible_row_range: Range<DisplayRow>,
9269 target_display_point: DisplayPoint,
9270 line_height: Pixels,
9271 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9272 content_origin: gpui::Point<Pixels>,
9273 editor_width: Pixels,
9274 window: &mut Window,
9275 cx: &mut App,
9276 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9277 let target_line_end = DisplayPoint::new(
9278 target_display_point.row(),
9279 editor_snapshot.line_len(target_display_point.row()),
9280 );
9281
9282 let mut element = self
9283 .render_edit_prediction_line_popover(label, None, window, cx)
9284 .into_any();
9285
9286 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9287
9288 let line_origin =
9289 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9290
9291 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9292 let mut origin = start_point
9293 + line_origin
9294 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9295 origin.x = origin.x.max(content_origin.x);
9296
9297 let max_x = content_origin.x + editor_width - size.width;
9298
9299 if origin.x > max_x {
9300 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9301
9302 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9303 origin.y += offset;
9304 IconName::ArrowUp
9305 } else {
9306 origin.y -= offset;
9307 IconName::ArrowDown
9308 };
9309
9310 element = self
9311 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9312 .into_any();
9313
9314 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9315
9316 origin.x = content_origin.x + editor_width - size.width - px(2.);
9317 }
9318
9319 element.prepaint_at(origin, window, cx);
9320 Some((element, origin))
9321 }
9322
9323 fn render_edit_prediction_diff_popover(
9324 self: &Editor,
9325 text_bounds: &Bounds<Pixels>,
9326 content_origin: gpui::Point<Pixels>,
9327 right_margin: Pixels,
9328 editor_snapshot: &EditorSnapshot,
9329 visible_row_range: Range<DisplayRow>,
9330 line_layouts: &[LineWithInvisibles],
9331 line_height: Pixels,
9332 scroll_position: gpui::Point<ScrollOffset>,
9333 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9334 newest_selection_head: Option<DisplayPoint>,
9335 editor_width: Pixels,
9336 style: &EditorStyle,
9337 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9338 edit_preview: &Option<language::EditPreview>,
9339 snapshot: &language::BufferSnapshot,
9340 window: &mut Window,
9341 cx: &mut App,
9342 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9343 let edit_start = edits
9344 .first()
9345 .unwrap()
9346 .0
9347 .start
9348 .to_display_point(editor_snapshot);
9349 let edit_end = edits
9350 .last()
9351 .unwrap()
9352 .0
9353 .end
9354 .to_display_point(editor_snapshot);
9355
9356 let is_visible = visible_row_range.contains(&edit_start.row())
9357 || visible_row_range.contains(&edit_end.row());
9358 if !is_visible {
9359 return None;
9360 }
9361
9362 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9363 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9364 } else {
9365 // Fallback for providers without edit_preview
9366 crate::edit_prediction_fallback_text(edits, cx)
9367 };
9368
9369 let styled_text = highlighted_edits.to_styled_text(&style.text);
9370 let line_count = highlighted_edits.text.lines().count();
9371
9372 const BORDER_WIDTH: Pixels = px(1.);
9373
9374 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9375 let has_keybind = keybind.is_some();
9376
9377 let mut element = h_flex()
9378 .items_start()
9379 .child(
9380 h_flex()
9381 .bg(cx.theme().colors().editor_background)
9382 .border(BORDER_WIDTH)
9383 .shadow_xs()
9384 .border_color(cx.theme().colors().border)
9385 .rounded_l_lg()
9386 .when(line_count > 1, |el| el.rounded_br_lg())
9387 .pr_1()
9388 .child(styled_text),
9389 )
9390 .child(
9391 h_flex()
9392 .h(line_height + BORDER_WIDTH * 2.)
9393 .px_1p5()
9394 .gap_1()
9395 // Workaround: For some reason, there's a gap if we don't do this
9396 .ml(-BORDER_WIDTH)
9397 .shadow(vec![gpui::BoxShadow {
9398 color: gpui::black().opacity(0.05),
9399 offset: point(px(1.), px(1.)),
9400 blur_radius: px(2.),
9401 spread_radius: px(0.),
9402 }])
9403 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9404 .border(BORDER_WIDTH)
9405 .border_color(cx.theme().colors().border)
9406 .rounded_r_lg()
9407 .id("edit_prediction_diff_popover_keybind")
9408 .when(!has_keybind, |el| {
9409 let status_colors = cx.theme().status();
9410
9411 el.bg(status_colors.error_background)
9412 .border_color(status_colors.error.opacity(0.6))
9413 .child(Icon::new(IconName::Info).color(Color::Error))
9414 .cursor_default()
9415 .hoverable_tooltip(move |_window, cx| {
9416 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9417 })
9418 })
9419 .children(keybind),
9420 )
9421 .into_any();
9422
9423 let longest_row =
9424 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9425 let longest_line_width = if visible_row_range.contains(&longest_row) {
9426 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9427 } else {
9428 layout_line(
9429 longest_row,
9430 editor_snapshot,
9431 style,
9432 editor_width,
9433 |_| false,
9434 window,
9435 cx,
9436 )
9437 .width
9438 };
9439
9440 let viewport_bounds =
9441 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9442 right: -right_margin,
9443 ..Default::default()
9444 });
9445
9446 let x_after_longest = Pixels::from(
9447 ScrollPixelOffset::from(
9448 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9449 ) - scroll_pixel_position.x,
9450 );
9451
9452 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9453
9454 // Fully visible if it can be displayed within the window (allow overlapping other
9455 // panes). However, this is only allowed if the popover starts within text_bounds.
9456 let can_position_to_the_right = x_after_longest < text_bounds.right()
9457 && x_after_longest + element_bounds.width < viewport_bounds.right();
9458
9459 let mut origin = if can_position_to_the_right {
9460 point(
9461 x_after_longest,
9462 text_bounds.origin.y
9463 + Pixels::from(
9464 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9465 - scroll_pixel_position.y,
9466 ),
9467 )
9468 } else {
9469 let cursor_row = newest_selection_head.map(|head| head.row());
9470 let above_edit = edit_start
9471 .row()
9472 .0
9473 .checked_sub(line_count as u32)
9474 .map(DisplayRow);
9475 let below_edit = Some(edit_end.row() + 1);
9476 let above_cursor =
9477 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9478 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9479
9480 // Place the edit popover adjacent to the edit if there is a location
9481 // available that is onscreen and does not obscure the cursor. Otherwise,
9482 // place it adjacent to the cursor.
9483 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9484 .into_iter()
9485 .flatten()
9486 .find(|&start_row| {
9487 let end_row = start_row + line_count as u32;
9488 visible_row_range.contains(&start_row)
9489 && visible_row_range.contains(&end_row)
9490 && cursor_row
9491 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9492 })?;
9493
9494 content_origin
9495 + point(
9496 Pixels::from(-scroll_pixel_position.x),
9497 Pixels::from(
9498 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9499 ),
9500 )
9501 };
9502
9503 origin.x -= BORDER_WIDTH;
9504
9505 window.defer_draw(element, origin, 1);
9506
9507 // Do not return an element, since it will already be drawn due to defer_draw.
9508 None
9509 }
9510
9511 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9512 px(30.)
9513 }
9514
9515 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9516 if self.read_only(cx) {
9517 cx.theme().players().read_only()
9518 } else {
9519 self.style.as_ref().unwrap().local_player
9520 }
9521 }
9522
9523 fn render_edit_prediction_accept_keybind(
9524 &self,
9525 window: &mut Window,
9526 cx: &mut App,
9527 ) -> Option<AnyElement> {
9528 let accept_binding =
9529 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9530 let accept_keystroke = accept_binding.keystroke()?;
9531
9532 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9533
9534 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9535 Color::Accent
9536 } else {
9537 Color::Muted
9538 };
9539
9540 h_flex()
9541 .px_0p5()
9542 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9543 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9544 .text_size(TextSize::XSmall.rems(cx))
9545 .child(h_flex().children(ui::render_modifiers(
9546 accept_keystroke.modifiers(),
9547 PlatformStyle::platform(),
9548 Some(modifiers_color),
9549 Some(IconSize::XSmall.rems().into()),
9550 true,
9551 )))
9552 .when(is_platform_style_mac, |parent| {
9553 parent.child(accept_keystroke.key().to_string())
9554 })
9555 .when(!is_platform_style_mac, |parent| {
9556 parent.child(
9557 Key::new(
9558 util::capitalize(accept_keystroke.key()),
9559 Some(Color::Default),
9560 )
9561 .size(Some(IconSize::XSmall.rems().into())),
9562 )
9563 })
9564 .into_any()
9565 .into()
9566 }
9567
9568 fn render_edit_prediction_line_popover(
9569 &self,
9570 label: impl Into<SharedString>,
9571 icon: Option<IconName>,
9572 window: &mut Window,
9573 cx: &mut App,
9574 ) -> Stateful<Div> {
9575 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9576
9577 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9578 let has_keybind = keybind.is_some();
9579
9580 h_flex()
9581 .id("ep-line-popover")
9582 .py_0p5()
9583 .pl_1()
9584 .pr(padding_right)
9585 .gap_1()
9586 .rounded_md()
9587 .border_1()
9588 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9589 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9590 .shadow_xs()
9591 .when(!has_keybind, |el| {
9592 let status_colors = cx.theme().status();
9593
9594 el.bg(status_colors.error_background)
9595 .border_color(status_colors.error.opacity(0.6))
9596 .pl_2()
9597 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9598 .cursor_default()
9599 .hoverable_tooltip(move |_window, cx| {
9600 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9601 })
9602 })
9603 .children(keybind)
9604 .child(
9605 Label::new(label)
9606 .size(LabelSize::Small)
9607 .when(!has_keybind, |el| {
9608 el.color(cx.theme().status().error.into()).strikethrough()
9609 }),
9610 )
9611 .when(!has_keybind, |el| {
9612 el.child(
9613 h_flex().ml_1().child(
9614 Icon::new(IconName::Info)
9615 .size(IconSize::Small)
9616 .color(cx.theme().status().error.into()),
9617 ),
9618 )
9619 })
9620 .when_some(icon, |element, icon| {
9621 element.child(
9622 div()
9623 .mt(px(1.5))
9624 .child(Icon::new(icon).size(IconSize::Small)),
9625 )
9626 })
9627 }
9628
9629 fn render_edit_prediction_jump_outside_popover(
9630 &self,
9631 snapshot: &BufferSnapshot,
9632 window: &mut Window,
9633 cx: &mut App,
9634 ) -> Stateful<Div> {
9635 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9636 let has_keybind = keybind.is_some();
9637
9638 let file_name = snapshot
9639 .file()
9640 .map(|file| SharedString::new(file.file_name(cx)))
9641 .unwrap_or(SharedString::new_static("untitled"));
9642
9643 h_flex()
9644 .id("ep-jump-outside-popover")
9645 .py_1()
9646 .px_2()
9647 .gap_1()
9648 .rounded_md()
9649 .border_1()
9650 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9651 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9652 .shadow_xs()
9653 .when(!has_keybind, |el| {
9654 let status_colors = cx.theme().status();
9655
9656 el.bg(status_colors.error_background)
9657 .border_color(status_colors.error.opacity(0.6))
9658 .pl_2()
9659 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9660 .cursor_default()
9661 .hoverable_tooltip(move |_window, cx| {
9662 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9663 })
9664 })
9665 .children(keybind)
9666 .child(
9667 Label::new(file_name)
9668 .size(LabelSize::Small)
9669 .buffer_font(cx)
9670 .when(!has_keybind, |el| {
9671 el.color(cx.theme().status().error.into()).strikethrough()
9672 }),
9673 )
9674 .when(!has_keybind, |el| {
9675 el.child(
9676 h_flex().ml_1().child(
9677 Icon::new(IconName::Info)
9678 .size(IconSize::Small)
9679 .color(cx.theme().status().error.into()),
9680 ),
9681 )
9682 })
9683 .child(
9684 div()
9685 .mt(px(1.5))
9686 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9687 )
9688 }
9689
9690 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9691 let accent_color = cx.theme().colors().text_accent;
9692 let editor_bg_color = cx.theme().colors().editor_background;
9693 editor_bg_color.blend(accent_color.opacity(0.1))
9694 }
9695
9696 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9697 let accent_color = cx.theme().colors().text_accent;
9698 let editor_bg_color = cx.theme().colors().editor_background;
9699 editor_bg_color.blend(accent_color.opacity(0.6))
9700 }
9701 fn get_prediction_provider_icon_name(
9702 provider: &Option<RegisteredEditPredictionDelegate>,
9703 ) -> IconName {
9704 match provider {
9705 Some(provider) => match provider.provider.name() {
9706 "copilot" => IconName::Copilot,
9707 "supermaven" => IconName::Supermaven,
9708 _ => IconName::ZedPredict,
9709 },
9710 None => IconName::ZedPredict,
9711 }
9712 }
9713
9714 fn render_edit_prediction_cursor_popover(
9715 &self,
9716 min_width: Pixels,
9717 max_width: Pixels,
9718 cursor_point: Point,
9719 style: &EditorStyle,
9720 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9721 _window: &Window,
9722 cx: &mut Context<Editor>,
9723 ) -> Option<AnyElement> {
9724 let provider = self.edit_prediction_provider.as_ref()?;
9725 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9726
9727 let is_refreshing = provider.provider.is_refreshing(cx);
9728
9729 fn pending_completion_container(icon: IconName) -> Div {
9730 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9731 }
9732
9733 let completion = match &self.active_edit_prediction {
9734 Some(prediction) => {
9735 if !self.has_visible_completions_menu() {
9736 const RADIUS: Pixels = px(6.);
9737 const BORDER_WIDTH: Pixels = px(1.);
9738
9739 return Some(
9740 h_flex()
9741 .elevation_2(cx)
9742 .border(BORDER_WIDTH)
9743 .border_color(cx.theme().colors().border)
9744 .when(accept_keystroke.is_none(), |el| {
9745 el.border_color(cx.theme().status().error)
9746 })
9747 .rounded(RADIUS)
9748 .rounded_tl(px(0.))
9749 .overflow_hidden()
9750 .child(div().px_1p5().child(match &prediction.completion {
9751 EditPrediction::MoveWithin { target, snapshot } => {
9752 use text::ToPoint as _;
9753 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9754 {
9755 Icon::new(IconName::ZedPredictDown)
9756 } else {
9757 Icon::new(IconName::ZedPredictUp)
9758 }
9759 }
9760 EditPrediction::MoveOutside { .. } => {
9761 // TODO [zeta2] custom icon for external jump?
9762 Icon::new(provider_icon)
9763 }
9764 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9765 }))
9766 .child(
9767 h_flex()
9768 .gap_1()
9769 .py_1()
9770 .px_2()
9771 .rounded_r(RADIUS - BORDER_WIDTH)
9772 .border_l_1()
9773 .border_color(cx.theme().colors().border)
9774 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9775 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9776 el.child(
9777 Label::new("Hold")
9778 .size(LabelSize::Small)
9779 .when(accept_keystroke.is_none(), |el| {
9780 el.strikethrough()
9781 })
9782 .line_height_style(LineHeightStyle::UiLabel),
9783 )
9784 })
9785 .id("edit_prediction_cursor_popover_keybind")
9786 .when(accept_keystroke.is_none(), |el| {
9787 let status_colors = cx.theme().status();
9788
9789 el.bg(status_colors.error_background)
9790 .border_color(status_colors.error.opacity(0.6))
9791 .child(Icon::new(IconName::Info).color(Color::Error))
9792 .cursor_default()
9793 .hoverable_tooltip(move |_window, cx| {
9794 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9795 .into()
9796 })
9797 })
9798 .when_some(
9799 accept_keystroke.as_ref(),
9800 |el, accept_keystroke| {
9801 el.child(h_flex().children(ui::render_modifiers(
9802 accept_keystroke.modifiers(),
9803 PlatformStyle::platform(),
9804 Some(Color::Default),
9805 Some(IconSize::XSmall.rems().into()),
9806 false,
9807 )))
9808 },
9809 ),
9810 )
9811 .into_any(),
9812 );
9813 }
9814
9815 self.render_edit_prediction_cursor_popover_preview(
9816 prediction,
9817 cursor_point,
9818 style,
9819 cx,
9820 )?
9821 }
9822
9823 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9824 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9825 stale_completion,
9826 cursor_point,
9827 style,
9828 cx,
9829 )?,
9830
9831 None => pending_completion_container(provider_icon)
9832 .child(Label::new("...").size(LabelSize::Small)),
9833 },
9834
9835 None => pending_completion_container(provider_icon)
9836 .child(Label::new("...").size(LabelSize::Small)),
9837 };
9838
9839 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9840 completion
9841 .with_animation(
9842 "loading-completion",
9843 Animation::new(Duration::from_secs(2))
9844 .repeat()
9845 .with_easing(pulsating_between(0.4, 0.8)),
9846 |label, delta| label.opacity(delta),
9847 )
9848 .into_any_element()
9849 } else {
9850 completion.into_any_element()
9851 };
9852
9853 let has_completion = self.active_edit_prediction.is_some();
9854
9855 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9856 Some(
9857 h_flex()
9858 .min_w(min_width)
9859 .max_w(max_width)
9860 .flex_1()
9861 .elevation_2(cx)
9862 .border_color(cx.theme().colors().border)
9863 .child(
9864 div()
9865 .flex_1()
9866 .py_1()
9867 .px_2()
9868 .overflow_hidden()
9869 .child(completion),
9870 )
9871 .when_some(accept_keystroke, |el, accept_keystroke| {
9872 if !accept_keystroke.modifiers().modified() {
9873 return el;
9874 }
9875
9876 el.child(
9877 h_flex()
9878 .h_full()
9879 .border_l_1()
9880 .rounded_r_lg()
9881 .border_color(cx.theme().colors().border)
9882 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9883 .gap_1()
9884 .py_1()
9885 .px_2()
9886 .child(
9887 h_flex()
9888 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9889 .when(is_platform_style_mac, |parent| parent.gap_1())
9890 .child(h_flex().children(ui::render_modifiers(
9891 accept_keystroke.modifiers(),
9892 PlatformStyle::platform(),
9893 Some(if !has_completion {
9894 Color::Muted
9895 } else {
9896 Color::Default
9897 }),
9898 None,
9899 false,
9900 ))),
9901 )
9902 .child(Label::new("Preview").into_any_element())
9903 .opacity(if has_completion { 1.0 } else { 0.4 }),
9904 )
9905 })
9906 .into_any(),
9907 )
9908 }
9909
9910 fn render_edit_prediction_cursor_popover_preview(
9911 &self,
9912 completion: &EditPredictionState,
9913 cursor_point: Point,
9914 style: &EditorStyle,
9915 cx: &mut Context<Editor>,
9916 ) -> Option<Div> {
9917 use text::ToPoint as _;
9918
9919 fn render_relative_row_jump(
9920 prefix: impl Into<String>,
9921 current_row: u32,
9922 target_row: u32,
9923 ) -> Div {
9924 let (row_diff, arrow) = if target_row < current_row {
9925 (current_row - target_row, IconName::ArrowUp)
9926 } else {
9927 (target_row - current_row, IconName::ArrowDown)
9928 };
9929
9930 h_flex()
9931 .child(
9932 Label::new(format!("{}{}", prefix.into(), row_diff))
9933 .color(Color::Muted)
9934 .size(LabelSize::Small),
9935 )
9936 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9937 }
9938
9939 let supports_jump = self
9940 .edit_prediction_provider
9941 .as_ref()
9942 .map(|provider| provider.provider.supports_jump_to_edit())
9943 .unwrap_or(true);
9944
9945 match &completion.completion {
9946 EditPrediction::MoveWithin {
9947 target, snapshot, ..
9948 } => {
9949 if !supports_jump {
9950 return None;
9951 }
9952
9953 Some(
9954 h_flex()
9955 .px_2()
9956 .gap_2()
9957 .flex_1()
9958 .child(
9959 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9960 Icon::new(IconName::ZedPredictDown)
9961 } else {
9962 Icon::new(IconName::ZedPredictUp)
9963 },
9964 )
9965 .child(Label::new("Jump to Edit")),
9966 )
9967 }
9968 EditPrediction::MoveOutside { snapshot, .. } => {
9969 let file_name = snapshot
9970 .file()
9971 .map(|file| file.file_name(cx))
9972 .unwrap_or("untitled");
9973 Some(
9974 h_flex()
9975 .px_2()
9976 .gap_2()
9977 .flex_1()
9978 .child(Icon::new(IconName::ZedPredict))
9979 .child(Label::new(format!("Jump to {file_name}"))),
9980 )
9981 }
9982 EditPrediction::Edit {
9983 edits,
9984 edit_preview,
9985 snapshot,
9986 display_mode: _,
9987 } => {
9988 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9989
9990 let (highlighted_edits, has_more_lines) =
9991 if let Some(edit_preview) = edit_preview.as_ref() {
9992 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9993 .first_line_preview()
9994 } else {
9995 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9996 };
9997
9998 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9999 .with_default_highlights(&style.text, highlighted_edits.highlights);
10000
10001 let preview = h_flex()
10002 .gap_1()
10003 .min_w_16()
10004 .child(styled_text)
10005 .when(has_more_lines, |parent| parent.child("…"));
10006
10007 let left = if supports_jump && first_edit_row != cursor_point.row {
10008 render_relative_row_jump("", cursor_point.row, first_edit_row)
10009 .into_any_element()
10010 } else {
10011 let icon_name =
10012 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
10013 Icon::new(icon_name).into_any_element()
10014 };
10015
10016 Some(
10017 h_flex()
10018 .h_full()
10019 .flex_1()
10020 .gap_2()
10021 .pr_1()
10022 .overflow_x_hidden()
10023 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10024 .child(left)
10025 .child(preview),
10026 )
10027 }
10028 }
10029 }
10030
10031 pub fn render_context_menu(
10032 &mut self,
10033 max_height_in_lines: u32,
10034 window: &mut Window,
10035 cx: &mut Context<Editor>,
10036 ) -> Option<AnyElement> {
10037 let menu = self.context_menu.borrow();
10038 let menu = menu.as_ref()?;
10039 if !menu.visible() {
10040 return None;
10041 };
10042 self.style
10043 .as_ref()
10044 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10045 }
10046
10047 fn render_context_menu_aside(
10048 &mut self,
10049 max_size: Size<Pixels>,
10050 window: &mut Window,
10051 cx: &mut Context<Editor>,
10052 ) -> Option<AnyElement> {
10053 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10054 if menu.visible() {
10055 menu.render_aside(max_size, window, cx)
10056 } else {
10057 None
10058 }
10059 })
10060 }
10061
10062 fn hide_context_menu(
10063 &mut self,
10064 window: &mut Window,
10065 cx: &mut Context<Self>,
10066 ) -> Option<CodeContextMenu> {
10067 cx.notify();
10068 self.completion_tasks.clear();
10069 let context_menu = self.context_menu.borrow_mut().take();
10070 self.stale_edit_prediction_in_menu.take();
10071 self.update_visible_edit_prediction(window, cx);
10072 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10073 && let Some(completion_provider) = &self.completion_provider
10074 {
10075 completion_provider.selection_changed(None, window, cx);
10076 }
10077 context_menu
10078 }
10079
10080 fn show_snippet_choices(
10081 &mut self,
10082 choices: &Vec<String>,
10083 selection: Range<Anchor>,
10084 cx: &mut Context<Self>,
10085 ) {
10086 let Some((_, buffer, _)) = self
10087 .buffer()
10088 .read(cx)
10089 .excerpt_containing(selection.start, cx)
10090 else {
10091 return;
10092 };
10093 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10094 else {
10095 return;
10096 };
10097 if buffer != end_buffer {
10098 log::error!("expected anchor range to have matching buffer IDs");
10099 return;
10100 }
10101
10102 let id = post_inc(&mut self.next_completion_id);
10103 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10104 let mut context_menu = self.context_menu.borrow_mut();
10105 let old_menu = context_menu.take();
10106 *context_menu = Some(CodeContextMenu::Completions(
10107 CompletionsMenu::new_snippet_choices(
10108 id,
10109 true,
10110 choices,
10111 selection,
10112 buffer,
10113 old_menu.map(|menu| menu.primary_scroll_handle()),
10114 snippet_sort_order,
10115 ),
10116 ));
10117 }
10118
10119 pub fn insert_snippet(
10120 &mut self,
10121 insertion_ranges: &[Range<MultiBufferOffset>],
10122 snippet: Snippet,
10123 window: &mut Window,
10124 cx: &mut Context<Self>,
10125 ) -> Result<()> {
10126 struct Tabstop<T> {
10127 is_end_tabstop: bool,
10128 ranges: Vec<Range<T>>,
10129 choices: Option<Vec<String>>,
10130 }
10131
10132 let tabstops = self.buffer.update(cx, |buffer, cx| {
10133 let snippet_text: Arc<str> = snippet.text.clone().into();
10134 let edits = insertion_ranges
10135 .iter()
10136 .cloned()
10137 .map(|range| (range, snippet_text.clone()));
10138 let autoindent_mode = AutoindentMode::Block {
10139 original_indent_columns: Vec::new(),
10140 };
10141 buffer.edit(edits, Some(autoindent_mode), cx);
10142
10143 let snapshot = &*buffer.read(cx);
10144 let snippet = &snippet;
10145 snippet
10146 .tabstops
10147 .iter()
10148 .map(|tabstop| {
10149 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10150 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10151 });
10152 let mut tabstop_ranges = tabstop
10153 .ranges
10154 .iter()
10155 .flat_map(|tabstop_range| {
10156 let mut delta = 0_isize;
10157 insertion_ranges.iter().map(move |insertion_range| {
10158 let insertion_start = insertion_range.start + delta;
10159 delta += snippet.text.len() as isize
10160 - (insertion_range.end - insertion_range.start) as isize;
10161
10162 let start =
10163 (insertion_start + tabstop_range.start).min(snapshot.len());
10164 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10165 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10166 })
10167 })
10168 .collect::<Vec<_>>();
10169 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10170
10171 Tabstop {
10172 is_end_tabstop,
10173 ranges: tabstop_ranges,
10174 choices: tabstop.choices.clone(),
10175 }
10176 })
10177 .collect::<Vec<_>>()
10178 });
10179 if let Some(tabstop) = tabstops.first() {
10180 self.change_selections(Default::default(), window, cx, |s| {
10181 // Reverse order so that the first range is the newest created selection.
10182 // Completions will use it and autoscroll will prioritize it.
10183 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10184 });
10185
10186 if let Some(choices) = &tabstop.choices
10187 && let Some(selection) = tabstop.ranges.first()
10188 {
10189 self.show_snippet_choices(choices, selection.clone(), cx)
10190 }
10191
10192 // If we're already at the last tabstop and it's at the end of the snippet,
10193 // we're done, we don't need to keep the state around.
10194 if !tabstop.is_end_tabstop {
10195 let choices = tabstops
10196 .iter()
10197 .map(|tabstop| tabstop.choices.clone())
10198 .collect();
10199
10200 let ranges = tabstops
10201 .into_iter()
10202 .map(|tabstop| tabstop.ranges)
10203 .collect::<Vec<_>>();
10204
10205 self.snippet_stack.push(SnippetState {
10206 active_index: 0,
10207 ranges,
10208 choices,
10209 });
10210 }
10211
10212 // Check whether the just-entered snippet ends with an auto-closable bracket.
10213 if self.autoclose_regions.is_empty() {
10214 let snapshot = self.buffer.read(cx).snapshot(cx);
10215 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10216 let selection_head = selection.head();
10217 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10218 continue;
10219 };
10220
10221 let mut bracket_pair = None;
10222 let max_lookup_length = scope
10223 .brackets()
10224 .map(|(pair, _)| {
10225 pair.start
10226 .as_str()
10227 .chars()
10228 .count()
10229 .max(pair.end.as_str().chars().count())
10230 })
10231 .max();
10232 if let Some(max_lookup_length) = max_lookup_length {
10233 let next_text = snapshot
10234 .chars_at(selection_head)
10235 .take(max_lookup_length)
10236 .collect::<String>();
10237 let prev_text = snapshot
10238 .reversed_chars_at(selection_head)
10239 .take(max_lookup_length)
10240 .collect::<String>();
10241
10242 for (pair, enabled) in scope.brackets() {
10243 if enabled
10244 && pair.close
10245 && prev_text.starts_with(pair.start.as_str())
10246 && next_text.starts_with(pair.end.as_str())
10247 {
10248 bracket_pair = Some(pair.clone());
10249 break;
10250 }
10251 }
10252 }
10253
10254 if let Some(pair) = bracket_pair {
10255 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10256 let autoclose_enabled =
10257 self.use_autoclose && snapshot_settings.use_autoclose;
10258 if autoclose_enabled {
10259 let start = snapshot.anchor_after(selection_head);
10260 let end = snapshot.anchor_after(selection_head);
10261 self.autoclose_regions.push(AutocloseRegion {
10262 selection_id: selection.id,
10263 range: start..end,
10264 pair,
10265 });
10266 }
10267 }
10268 }
10269 }
10270 }
10271 Ok(())
10272 }
10273
10274 pub fn move_to_next_snippet_tabstop(
10275 &mut self,
10276 window: &mut Window,
10277 cx: &mut Context<Self>,
10278 ) -> bool {
10279 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10280 }
10281
10282 pub fn move_to_prev_snippet_tabstop(
10283 &mut self,
10284 window: &mut Window,
10285 cx: &mut Context<Self>,
10286 ) -> bool {
10287 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10288 }
10289
10290 pub fn move_to_snippet_tabstop(
10291 &mut self,
10292 bias: Bias,
10293 window: &mut Window,
10294 cx: &mut Context<Self>,
10295 ) -> bool {
10296 if let Some(mut snippet) = self.snippet_stack.pop() {
10297 match bias {
10298 Bias::Left => {
10299 if snippet.active_index > 0 {
10300 snippet.active_index -= 1;
10301 } else {
10302 self.snippet_stack.push(snippet);
10303 return false;
10304 }
10305 }
10306 Bias::Right => {
10307 if snippet.active_index + 1 < snippet.ranges.len() {
10308 snippet.active_index += 1;
10309 } else {
10310 self.snippet_stack.push(snippet);
10311 return false;
10312 }
10313 }
10314 }
10315 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10316 self.change_selections(Default::default(), window, cx, |s| {
10317 // Reverse order so that the first range is the newest created selection.
10318 // Completions will use it and autoscroll will prioritize it.
10319 s.select_ranges(current_ranges.iter().rev().cloned())
10320 });
10321
10322 if let Some(choices) = &snippet.choices[snippet.active_index]
10323 && let Some(selection) = current_ranges.first()
10324 {
10325 self.show_snippet_choices(choices, selection.clone(), cx);
10326 }
10327
10328 // If snippet state is not at the last tabstop, push it back on the stack
10329 if snippet.active_index + 1 < snippet.ranges.len() {
10330 self.snippet_stack.push(snippet);
10331 }
10332 return true;
10333 }
10334 }
10335
10336 false
10337 }
10338
10339 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10340 self.transact(window, cx, |this, window, cx| {
10341 this.select_all(&SelectAll, window, cx);
10342 this.insert("", window, cx);
10343 });
10344 }
10345
10346 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10347 if self.read_only(cx) {
10348 return;
10349 }
10350 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10351 self.transact(window, cx, |this, window, cx| {
10352 this.select_autoclose_pair(window, cx);
10353
10354 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10355
10356 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10357 if !this.linked_edit_ranges.is_empty() {
10358 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10359 let snapshot = this.buffer.read(cx).snapshot(cx);
10360
10361 for selection in selections.iter() {
10362 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10363 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10364 if selection_start.buffer_id != selection_end.buffer_id {
10365 continue;
10366 }
10367 if let Some(ranges) =
10368 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10369 {
10370 for (buffer, entries) in ranges {
10371 linked_ranges.entry(buffer).or_default().extend(entries);
10372 }
10373 }
10374 }
10375 }
10376
10377 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10378 for selection in &mut selections {
10379 if selection.is_empty() {
10380 let old_head = selection.head();
10381 let mut new_head =
10382 movement::left(&display_map, old_head.to_display_point(&display_map))
10383 .to_point(&display_map);
10384 if let Some((buffer, line_buffer_range)) = display_map
10385 .buffer_snapshot()
10386 .buffer_line_for_row(MultiBufferRow(old_head.row))
10387 {
10388 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10389 let indent_len = match indent_size.kind {
10390 IndentKind::Space => {
10391 buffer.settings_at(line_buffer_range.start, cx).tab_size
10392 }
10393 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10394 };
10395 if old_head.column <= indent_size.len && old_head.column > 0 {
10396 let indent_len = indent_len.get();
10397 new_head = cmp::min(
10398 new_head,
10399 MultiBufferPoint::new(
10400 old_head.row,
10401 ((old_head.column - 1) / indent_len) * indent_len,
10402 ),
10403 );
10404 }
10405 }
10406
10407 selection.set_head(new_head, SelectionGoal::None);
10408 }
10409 }
10410
10411 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10412 this.insert("", window, cx);
10413 let empty_str: Arc<str> = Arc::from("");
10414 for (buffer, edits) in linked_ranges {
10415 let snapshot = buffer.read(cx).snapshot();
10416 use text::ToPoint as TP;
10417
10418 let edits = edits
10419 .into_iter()
10420 .map(|range| {
10421 let end_point = TP::to_point(&range.end, &snapshot);
10422 let mut start_point = TP::to_point(&range.start, &snapshot);
10423
10424 if end_point == start_point {
10425 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10426 .saturating_sub(1);
10427 start_point =
10428 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10429 };
10430
10431 (start_point..end_point, empty_str.clone())
10432 })
10433 .sorted_by_key(|(range, _)| range.start)
10434 .collect::<Vec<_>>();
10435 buffer.update(cx, |this, cx| {
10436 this.edit(edits, None, cx);
10437 })
10438 }
10439 this.refresh_edit_prediction(true, false, window, cx);
10440 refresh_linked_ranges(this, window, cx);
10441 });
10442 }
10443
10444 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10445 if self.read_only(cx) {
10446 return;
10447 }
10448 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10449 self.transact(window, cx, |this, window, cx| {
10450 this.change_selections(Default::default(), window, cx, |s| {
10451 s.move_with(|map, selection| {
10452 if selection.is_empty() {
10453 let cursor = movement::right(map, selection.head());
10454 selection.end = cursor;
10455 selection.reversed = true;
10456 selection.goal = SelectionGoal::None;
10457 }
10458 })
10459 });
10460 this.insert("", window, cx);
10461 this.refresh_edit_prediction(true, false, window, cx);
10462 });
10463 }
10464
10465 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10466 if self.mode.is_single_line() {
10467 cx.propagate();
10468 return;
10469 }
10470
10471 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10472 if self.move_to_prev_snippet_tabstop(window, cx) {
10473 return;
10474 }
10475 self.outdent(&Outdent, window, cx);
10476 }
10477
10478 pub fn next_snippet_tabstop(
10479 &mut self,
10480 _: &NextSnippetTabstop,
10481 window: &mut Window,
10482 cx: &mut Context<Self>,
10483 ) {
10484 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10485 cx.propagate();
10486 return;
10487 }
10488
10489 if self.move_to_next_snippet_tabstop(window, cx) {
10490 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10491 return;
10492 }
10493 cx.propagate();
10494 }
10495
10496 pub fn previous_snippet_tabstop(
10497 &mut self,
10498 _: &PreviousSnippetTabstop,
10499 window: &mut Window,
10500 cx: &mut Context<Self>,
10501 ) {
10502 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10503 cx.propagate();
10504 return;
10505 }
10506
10507 if self.move_to_prev_snippet_tabstop(window, cx) {
10508 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10509 return;
10510 }
10511 cx.propagate();
10512 }
10513
10514 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10515 if self.mode.is_single_line() {
10516 cx.propagate();
10517 return;
10518 }
10519
10520 if self.move_to_next_snippet_tabstop(window, cx) {
10521 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10522 return;
10523 }
10524 if self.read_only(cx) {
10525 return;
10526 }
10527 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10528 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10529 let buffer = self.buffer.read(cx);
10530 let snapshot = buffer.snapshot(cx);
10531 let rows_iter = selections.iter().map(|s| s.head().row);
10532 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10533
10534 let has_some_cursor_in_whitespace = selections
10535 .iter()
10536 .filter(|selection| selection.is_empty())
10537 .any(|selection| {
10538 let cursor = selection.head();
10539 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10540 cursor.column < current_indent.len
10541 });
10542
10543 let mut edits = Vec::new();
10544 let mut prev_edited_row = 0;
10545 let mut row_delta = 0;
10546 for selection in &mut selections {
10547 if selection.start.row != prev_edited_row {
10548 row_delta = 0;
10549 }
10550 prev_edited_row = selection.end.row;
10551
10552 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10553 if selection.is_empty() {
10554 let cursor = selection.head();
10555 let settings = buffer.language_settings_at(cursor, cx);
10556 if settings.indent_list_on_tab {
10557 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10558 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10559 row_delta = Self::indent_selection(
10560 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10561 );
10562 continue;
10563 }
10564 }
10565 }
10566 }
10567
10568 // If the selection is non-empty, then increase the indentation of the selected lines.
10569 if !selection.is_empty() {
10570 row_delta =
10571 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10572 continue;
10573 }
10574
10575 let cursor = selection.head();
10576 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10577 if let Some(suggested_indent) =
10578 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10579 {
10580 // Don't do anything if already at suggested indent
10581 // and there is any other cursor which is not
10582 if has_some_cursor_in_whitespace
10583 && cursor.column == current_indent.len
10584 && current_indent.len == suggested_indent.len
10585 {
10586 continue;
10587 }
10588
10589 // Adjust line and move cursor to suggested indent
10590 // if cursor is not at suggested indent
10591 if cursor.column < suggested_indent.len
10592 && cursor.column <= current_indent.len
10593 && current_indent.len <= suggested_indent.len
10594 {
10595 selection.start = Point::new(cursor.row, suggested_indent.len);
10596 selection.end = selection.start;
10597 if row_delta == 0 {
10598 edits.extend(Buffer::edit_for_indent_size_adjustment(
10599 cursor.row,
10600 current_indent,
10601 suggested_indent,
10602 ));
10603 row_delta = suggested_indent.len - current_indent.len;
10604 }
10605 continue;
10606 }
10607
10608 // If current indent is more than suggested indent
10609 // only move cursor to current indent and skip indent
10610 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10611 selection.start = Point::new(cursor.row, current_indent.len);
10612 selection.end = selection.start;
10613 continue;
10614 }
10615 }
10616
10617 // Otherwise, insert a hard or soft tab.
10618 let settings = buffer.language_settings_at(cursor, cx);
10619 let tab_size = if settings.hard_tabs {
10620 IndentSize::tab()
10621 } else {
10622 let tab_size = settings.tab_size.get();
10623 let indent_remainder = snapshot
10624 .text_for_range(Point::new(cursor.row, 0)..cursor)
10625 .flat_map(str::chars)
10626 .fold(row_delta % tab_size, |counter: u32, c| {
10627 if c == '\t' {
10628 0
10629 } else {
10630 (counter + 1) % tab_size
10631 }
10632 });
10633
10634 let chars_to_next_tab_stop = tab_size - indent_remainder;
10635 IndentSize::spaces(chars_to_next_tab_stop)
10636 };
10637 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10638 selection.end = selection.start;
10639 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10640 row_delta += tab_size.len;
10641 }
10642
10643 self.transact(window, cx, |this, window, cx| {
10644 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10645 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10646 this.refresh_edit_prediction(true, false, window, cx);
10647 });
10648 }
10649
10650 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10651 if self.read_only(cx) {
10652 return;
10653 }
10654 if self.mode.is_single_line() {
10655 cx.propagate();
10656 return;
10657 }
10658
10659 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10660 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10661 let mut prev_edited_row = 0;
10662 let mut row_delta = 0;
10663 let mut edits = Vec::new();
10664 let buffer = self.buffer.read(cx);
10665 let snapshot = buffer.snapshot(cx);
10666 for selection in &mut selections {
10667 if selection.start.row != prev_edited_row {
10668 row_delta = 0;
10669 }
10670 prev_edited_row = selection.end.row;
10671
10672 row_delta =
10673 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10674 }
10675
10676 self.transact(window, cx, |this, window, cx| {
10677 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10678 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10679 });
10680 }
10681
10682 fn indent_selection(
10683 buffer: &MultiBuffer,
10684 snapshot: &MultiBufferSnapshot,
10685 selection: &mut Selection<Point>,
10686 edits: &mut Vec<(Range<Point>, String)>,
10687 delta_for_start_row: u32,
10688 cx: &App,
10689 ) -> u32 {
10690 let settings = buffer.language_settings_at(selection.start, cx);
10691 let tab_size = settings.tab_size.get();
10692 let indent_kind = if settings.hard_tabs {
10693 IndentKind::Tab
10694 } else {
10695 IndentKind::Space
10696 };
10697 let mut start_row = selection.start.row;
10698 let mut end_row = selection.end.row + 1;
10699
10700 // If a selection ends at the beginning of a line, don't indent
10701 // that last line.
10702 if selection.end.column == 0 && selection.end.row > selection.start.row {
10703 end_row -= 1;
10704 }
10705
10706 // Avoid re-indenting a row that has already been indented by a
10707 // previous selection, but still update this selection's column
10708 // to reflect that indentation.
10709 if delta_for_start_row > 0 {
10710 start_row += 1;
10711 selection.start.column += delta_for_start_row;
10712 if selection.end.row == selection.start.row {
10713 selection.end.column += delta_for_start_row;
10714 }
10715 }
10716
10717 let mut delta_for_end_row = 0;
10718 let has_multiple_rows = start_row + 1 != end_row;
10719 for row in start_row..end_row {
10720 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10721 let indent_delta = match (current_indent.kind, indent_kind) {
10722 (IndentKind::Space, IndentKind::Space) => {
10723 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10724 IndentSize::spaces(columns_to_next_tab_stop)
10725 }
10726 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10727 (_, IndentKind::Tab) => IndentSize::tab(),
10728 };
10729
10730 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10731 0
10732 } else {
10733 selection.start.column
10734 };
10735 let row_start = Point::new(row, start);
10736 edits.push((
10737 row_start..row_start,
10738 indent_delta.chars().collect::<String>(),
10739 ));
10740
10741 // Update this selection's endpoints to reflect the indentation.
10742 if row == selection.start.row {
10743 selection.start.column += indent_delta.len;
10744 }
10745 if row == selection.end.row {
10746 selection.end.column += indent_delta.len;
10747 delta_for_end_row = indent_delta.len;
10748 }
10749 }
10750
10751 if selection.start.row == selection.end.row {
10752 delta_for_start_row + delta_for_end_row
10753 } else {
10754 delta_for_end_row
10755 }
10756 }
10757
10758 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10759 if self.read_only(cx) {
10760 return;
10761 }
10762 if self.mode.is_single_line() {
10763 cx.propagate();
10764 return;
10765 }
10766
10767 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10768 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10769 let selections = self.selections.all::<Point>(&display_map);
10770 let mut deletion_ranges = Vec::new();
10771 let mut last_outdent = None;
10772 {
10773 let buffer = self.buffer.read(cx);
10774 let snapshot = buffer.snapshot(cx);
10775 for selection in &selections {
10776 let settings = buffer.language_settings_at(selection.start, cx);
10777 let tab_size = settings.tab_size.get();
10778 let mut rows = selection.spanned_rows(false, &display_map);
10779
10780 // Avoid re-outdenting a row that has already been outdented by a
10781 // previous selection.
10782 if let Some(last_row) = last_outdent
10783 && last_row == rows.start
10784 {
10785 rows.start = rows.start.next_row();
10786 }
10787 let has_multiple_rows = rows.len() > 1;
10788 for row in rows.iter_rows() {
10789 let indent_size = snapshot.indent_size_for_line(row);
10790 if indent_size.len > 0 {
10791 let deletion_len = match indent_size.kind {
10792 IndentKind::Space => {
10793 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10794 if columns_to_prev_tab_stop == 0 {
10795 tab_size
10796 } else {
10797 columns_to_prev_tab_stop
10798 }
10799 }
10800 IndentKind::Tab => 1,
10801 };
10802 let start = if has_multiple_rows
10803 || deletion_len > selection.start.column
10804 || indent_size.len < selection.start.column
10805 {
10806 0
10807 } else {
10808 selection.start.column - deletion_len
10809 };
10810 deletion_ranges.push(
10811 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10812 );
10813 last_outdent = Some(row);
10814 }
10815 }
10816 }
10817 }
10818
10819 self.transact(window, cx, |this, window, cx| {
10820 this.buffer.update(cx, |buffer, cx| {
10821 let empty_str: Arc<str> = Arc::default();
10822 buffer.edit(
10823 deletion_ranges
10824 .into_iter()
10825 .map(|range| (range, empty_str.clone())),
10826 None,
10827 cx,
10828 );
10829 });
10830 let selections = this
10831 .selections
10832 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10833 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10834 });
10835 }
10836
10837 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10838 if self.read_only(cx) {
10839 return;
10840 }
10841 if self.mode.is_single_line() {
10842 cx.propagate();
10843 return;
10844 }
10845
10846 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10847 let selections = self
10848 .selections
10849 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10850 .into_iter()
10851 .map(|s| s.range());
10852
10853 self.transact(window, cx, |this, window, cx| {
10854 this.buffer.update(cx, |buffer, cx| {
10855 buffer.autoindent_ranges(selections, cx);
10856 });
10857 let selections = this
10858 .selections
10859 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10860 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10861 });
10862 }
10863
10864 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10865 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10866 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10867 let selections = self.selections.all::<Point>(&display_map);
10868
10869 let mut new_cursors = Vec::new();
10870 let mut edit_ranges = Vec::new();
10871 let mut selections = selections.iter().peekable();
10872 while let Some(selection) = selections.next() {
10873 let mut rows = selection.spanned_rows(false, &display_map);
10874
10875 // Accumulate contiguous regions of rows that we want to delete.
10876 while let Some(next_selection) = selections.peek() {
10877 let next_rows = next_selection.spanned_rows(false, &display_map);
10878 if next_rows.start <= rows.end {
10879 rows.end = next_rows.end;
10880 selections.next().unwrap();
10881 } else {
10882 break;
10883 }
10884 }
10885
10886 let buffer = display_map.buffer_snapshot();
10887 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10888 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10889 // If there's a line after the range, delete the \n from the end of the row range
10890 (
10891 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10892 rows.end,
10893 )
10894 } else {
10895 // If there isn't a line after the range, delete the \n from the line before the
10896 // start of the row range
10897 edit_start = edit_start.saturating_sub_usize(1);
10898 (buffer.len(), rows.start.previous_row())
10899 };
10900
10901 let text_layout_details = self.text_layout_details(window);
10902 let x = display_map.x_for_display_point(
10903 selection.head().to_display_point(&display_map),
10904 &text_layout_details,
10905 );
10906 let row = Point::new(target_row.0, 0)
10907 .to_display_point(&display_map)
10908 .row();
10909 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10910
10911 new_cursors.push((
10912 selection.id,
10913 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10914 SelectionGoal::None,
10915 ));
10916 edit_ranges.push(edit_start..edit_end);
10917 }
10918
10919 self.transact(window, cx, |this, window, cx| {
10920 let buffer = this.buffer.update(cx, |buffer, cx| {
10921 let empty_str: Arc<str> = Arc::default();
10922 buffer.edit(
10923 edit_ranges
10924 .into_iter()
10925 .map(|range| (range, empty_str.clone())),
10926 None,
10927 cx,
10928 );
10929 buffer.snapshot(cx)
10930 });
10931 let new_selections = new_cursors
10932 .into_iter()
10933 .map(|(id, cursor, goal)| {
10934 let cursor = cursor.to_point(&buffer);
10935 Selection {
10936 id,
10937 start: cursor,
10938 end: cursor,
10939 reversed: false,
10940 goal,
10941 }
10942 })
10943 .collect();
10944
10945 this.change_selections(Default::default(), window, cx, |s| {
10946 s.select(new_selections);
10947 });
10948 });
10949 }
10950
10951 pub fn join_lines_impl(
10952 &mut self,
10953 insert_whitespace: bool,
10954 window: &mut Window,
10955 cx: &mut Context<Self>,
10956 ) {
10957 if self.read_only(cx) {
10958 return;
10959 }
10960 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10961 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10962 let start = MultiBufferRow(selection.start.row);
10963 // Treat single line selections as if they include the next line. Otherwise this action
10964 // would do nothing for single line selections individual cursors.
10965 let end = if selection.start.row == selection.end.row {
10966 MultiBufferRow(selection.start.row + 1)
10967 } else {
10968 MultiBufferRow(selection.end.row)
10969 };
10970
10971 if let Some(last_row_range) = row_ranges.last_mut()
10972 && start <= last_row_range.end
10973 {
10974 last_row_range.end = end;
10975 continue;
10976 }
10977 row_ranges.push(start..end);
10978 }
10979
10980 let snapshot = self.buffer.read(cx).snapshot(cx);
10981 let mut cursor_positions = Vec::new();
10982 for row_range in &row_ranges {
10983 let anchor = snapshot.anchor_before(Point::new(
10984 row_range.end.previous_row().0,
10985 snapshot.line_len(row_range.end.previous_row()),
10986 ));
10987 cursor_positions.push(anchor..anchor);
10988 }
10989
10990 self.transact(window, cx, |this, window, cx| {
10991 for row_range in row_ranges.into_iter().rev() {
10992 for row in row_range.iter_rows().rev() {
10993 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10994 let next_line_row = row.next_row();
10995 let indent = snapshot.indent_size_for_line(next_line_row);
10996 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10997
10998 let replace =
10999 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
11000 " "
11001 } else {
11002 ""
11003 };
11004
11005 this.buffer.update(cx, |buffer, cx| {
11006 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11007 });
11008 }
11009 }
11010
11011 this.change_selections(Default::default(), window, cx, |s| {
11012 s.select_anchor_ranges(cursor_positions)
11013 });
11014 });
11015 }
11016
11017 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11019 self.join_lines_impl(true, window, cx);
11020 }
11021
11022 pub fn sort_lines_case_sensitive(
11023 &mut self,
11024 _: &SortLinesCaseSensitive,
11025 window: &mut Window,
11026 cx: &mut Context<Self>,
11027 ) {
11028 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11029 }
11030
11031 pub fn sort_lines_by_length(
11032 &mut self,
11033 _: &SortLinesByLength,
11034 window: &mut Window,
11035 cx: &mut Context<Self>,
11036 ) {
11037 self.manipulate_immutable_lines(window, cx, |lines| {
11038 lines.sort_by_key(|&line| line.chars().count())
11039 })
11040 }
11041
11042 pub fn sort_lines_case_insensitive(
11043 &mut self,
11044 _: &SortLinesCaseInsensitive,
11045 window: &mut Window,
11046 cx: &mut Context<Self>,
11047 ) {
11048 self.manipulate_immutable_lines(window, cx, |lines| {
11049 lines.sort_by_key(|line| line.to_lowercase())
11050 })
11051 }
11052
11053 pub fn unique_lines_case_insensitive(
11054 &mut self,
11055 _: &UniqueLinesCaseInsensitive,
11056 window: &mut Window,
11057 cx: &mut Context<Self>,
11058 ) {
11059 self.manipulate_immutable_lines(window, cx, |lines| {
11060 let mut seen = HashSet::default();
11061 lines.retain(|line| seen.insert(line.to_lowercase()));
11062 })
11063 }
11064
11065 pub fn unique_lines_case_sensitive(
11066 &mut self,
11067 _: &UniqueLinesCaseSensitive,
11068 window: &mut Window,
11069 cx: &mut Context<Self>,
11070 ) {
11071 self.manipulate_immutable_lines(window, cx, |lines| {
11072 let mut seen = HashSet::default();
11073 lines.retain(|line| seen.insert(*line));
11074 })
11075 }
11076
11077 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11078 let snapshot = self.buffer.read(cx).snapshot(cx);
11079 for selection in self.selections.disjoint_anchors_arc().iter() {
11080 if snapshot
11081 .language_at(selection.start)
11082 .and_then(|lang| lang.config().wrap_characters.as_ref())
11083 .is_some()
11084 {
11085 return true;
11086 }
11087 }
11088 false
11089 }
11090
11091 fn wrap_selections_in_tag(
11092 &mut self,
11093 _: &WrapSelectionsInTag,
11094 window: &mut Window,
11095 cx: &mut Context<Self>,
11096 ) {
11097 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11098
11099 let snapshot = self.buffer.read(cx).snapshot(cx);
11100
11101 let mut edits = Vec::new();
11102 let mut boundaries = Vec::new();
11103
11104 for selection in self
11105 .selections
11106 .all_adjusted(&self.display_snapshot(cx))
11107 .iter()
11108 {
11109 let Some(wrap_config) = snapshot
11110 .language_at(selection.start)
11111 .and_then(|lang| lang.config().wrap_characters.clone())
11112 else {
11113 continue;
11114 };
11115
11116 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11117 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11118
11119 let start_before = snapshot.anchor_before(selection.start);
11120 let end_after = snapshot.anchor_after(selection.end);
11121
11122 edits.push((start_before..start_before, open_tag));
11123 edits.push((end_after..end_after, close_tag));
11124
11125 boundaries.push((
11126 start_before,
11127 end_after,
11128 wrap_config.start_prefix.len(),
11129 wrap_config.end_suffix.len(),
11130 ));
11131 }
11132
11133 if edits.is_empty() {
11134 return;
11135 }
11136
11137 self.transact(window, cx, |this, window, cx| {
11138 let buffer = this.buffer.update(cx, |buffer, cx| {
11139 buffer.edit(edits, None, cx);
11140 buffer.snapshot(cx)
11141 });
11142
11143 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11144 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11145 boundaries.into_iter()
11146 {
11147 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11148 let close_offset = end_after
11149 .to_offset(&buffer)
11150 .saturating_sub_usize(end_suffix_len);
11151 new_selections.push(open_offset..open_offset);
11152 new_selections.push(close_offset..close_offset);
11153 }
11154
11155 this.change_selections(Default::default(), window, cx, |s| {
11156 s.select_ranges(new_selections);
11157 });
11158
11159 this.request_autoscroll(Autoscroll::fit(), cx);
11160 });
11161 }
11162
11163 pub fn toggle_read_only(
11164 &mut self,
11165 _: &workspace::ToggleReadOnlyFile,
11166 _: &mut Window,
11167 cx: &mut Context<Self>,
11168 ) {
11169 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11170 buffer.update(cx, |buffer, cx| {
11171 buffer.set_capability(
11172 match buffer.capability() {
11173 Capability::ReadWrite => Capability::Read,
11174 Capability::Read => Capability::ReadWrite,
11175 Capability::ReadOnly => Capability::ReadOnly,
11176 },
11177 cx,
11178 );
11179 })
11180 }
11181 }
11182
11183 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11184 let Some(project) = self.project.clone() else {
11185 return;
11186 };
11187 self.reload(project, window, cx)
11188 .detach_and_notify_err(window, cx);
11189 }
11190
11191 pub fn restore_file(
11192 &mut self,
11193 _: &::git::RestoreFile,
11194 window: &mut Window,
11195 cx: &mut Context<Self>,
11196 ) {
11197 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11198 let mut buffer_ids = HashSet::default();
11199 let snapshot = self.buffer().read(cx).snapshot(cx);
11200 for selection in self
11201 .selections
11202 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11203 {
11204 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11205 }
11206
11207 let buffer = self.buffer().read(cx);
11208 let ranges = buffer_ids
11209 .into_iter()
11210 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11211 .collect::<Vec<_>>();
11212
11213 self.restore_hunks_in_ranges(ranges, window, cx);
11214 }
11215
11216 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11217 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11218 let selections = self
11219 .selections
11220 .all(&self.display_snapshot(cx))
11221 .into_iter()
11222 .map(|s| s.range())
11223 .collect();
11224 self.restore_hunks_in_ranges(selections, window, cx);
11225 }
11226
11227 pub fn restore_hunks_in_ranges(
11228 &mut self,
11229 ranges: Vec<Range<Point>>,
11230 window: &mut Window,
11231 cx: &mut Context<Editor>,
11232 ) {
11233 let mut revert_changes = HashMap::default();
11234 let chunk_by = self
11235 .snapshot(window, cx)
11236 .hunks_for_ranges(ranges)
11237 .into_iter()
11238 .chunk_by(|hunk| hunk.buffer_id);
11239 for (buffer_id, hunks) in &chunk_by {
11240 let hunks = hunks.collect::<Vec<_>>();
11241 for hunk in &hunks {
11242 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11243 }
11244 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11245 }
11246 drop(chunk_by);
11247 if !revert_changes.is_empty() {
11248 self.transact(window, cx, |editor, window, cx| {
11249 editor.restore(revert_changes, window, cx);
11250 });
11251 }
11252 }
11253
11254 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11255 if let Some(status) = self
11256 .addons
11257 .iter()
11258 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11259 {
11260 return Some(status);
11261 }
11262 self.project
11263 .as_ref()?
11264 .read(cx)
11265 .status_for_buffer_id(buffer_id, cx)
11266 }
11267
11268 pub fn open_active_item_in_terminal(
11269 &mut self,
11270 _: &OpenInTerminal,
11271 window: &mut Window,
11272 cx: &mut Context<Self>,
11273 ) {
11274 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11275 let project_path = buffer.read(cx).project_path(cx)?;
11276 let project = self.project()?.read(cx);
11277 let entry = project.entry_for_path(&project_path, cx)?;
11278 let parent = match &entry.canonical_path {
11279 Some(canonical_path) => canonical_path.to_path_buf(),
11280 None => project.absolute_path(&project_path, cx)?,
11281 }
11282 .parent()?
11283 .to_path_buf();
11284 Some(parent)
11285 }) {
11286 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11287 }
11288 }
11289
11290 fn set_breakpoint_context_menu(
11291 &mut self,
11292 display_row: DisplayRow,
11293 position: Option<Anchor>,
11294 clicked_point: gpui::Point<Pixels>,
11295 window: &mut Window,
11296 cx: &mut Context<Self>,
11297 ) {
11298 let source = self
11299 .buffer
11300 .read(cx)
11301 .snapshot(cx)
11302 .anchor_before(Point::new(display_row.0, 0u32));
11303
11304 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11305
11306 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11307 self,
11308 source,
11309 clicked_point,
11310 context_menu,
11311 window,
11312 cx,
11313 );
11314 }
11315
11316 fn add_edit_breakpoint_block(
11317 &mut self,
11318 anchor: Anchor,
11319 breakpoint: &Breakpoint,
11320 edit_action: BreakpointPromptEditAction,
11321 window: &mut Window,
11322 cx: &mut Context<Self>,
11323 ) {
11324 let weak_editor = cx.weak_entity();
11325 let bp_prompt = cx.new(|cx| {
11326 BreakpointPromptEditor::new(
11327 weak_editor,
11328 anchor,
11329 breakpoint.clone(),
11330 edit_action,
11331 window,
11332 cx,
11333 )
11334 });
11335
11336 let height = bp_prompt.update(cx, |this, cx| {
11337 this.prompt
11338 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11339 });
11340 let cloned_prompt = bp_prompt.clone();
11341 let blocks = vec![BlockProperties {
11342 style: BlockStyle::Sticky,
11343 placement: BlockPlacement::Above(anchor),
11344 height: Some(height),
11345 render: Arc::new(move |cx| {
11346 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11347 cloned_prompt.clone().into_any_element()
11348 }),
11349 priority: 0,
11350 }];
11351
11352 let focus_handle = bp_prompt.focus_handle(cx);
11353 window.focus(&focus_handle, cx);
11354
11355 let block_ids = self.insert_blocks(blocks, None, cx);
11356 bp_prompt.update(cx, |prompt, _| {
11357 prompt.add_block_ids(block_ids);
11358 });
11359 }
11360
11361 pub(crate) fn breakpoint_at_row(
11362 &self,
11363 row: u32,
11364 window: &mut Window,
11365 cx: &mut Context<Self>,
11366 ) -> Option<(Anchor, Breakpoint)> {
11367 let snapshot = self.snapshot(window, cx);
11368 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11369
11370 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11371 }
11372
11373 pub(crate) fn breakpoint_at_anchor(
11374 &self,
11375 breakpoint_position: Anchor,
11376 snapshot: &EditorSnapshot,
11377 cx: &mut Context<Self>,
11378 ) -> Option<(Anchor, Breakpoint)> {
11379 let buffer = self
11380 .buffer
11381 .read(cx)
11382 .buffer_for_anchor(breakpoint_position, cx)?;
11383
11384 let enclosing_excerpt = breakpoint_position.excerpt_id;
11385 let buffer_snapshot = buffer.read(cx).snapshot();
11386
11387 let row = buffer_snapshot
11388 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11389 .row;
11390
11391 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11392 let anchor_end = snapshot
11393 .buffer_snapshot()
11394 .anchor_after(Point::new(row, line_len));
11395
11396 self.breakpoint_store
11397 .as_ref()?
11398 .read_with(cx, |breakpoint_store, cx| {
11399 breakpoint_store
11400 .breakpoints(
11401 &buffer,
11402 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11403 &buffer_snapshot,
11404 cx,
11405 )
11406 .next()
11407 .and_then(|(bp, _)| {
11408 let breakpoint_row = buffer_snapshot
11409 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11410 .row;
11411
11412 if breakpoint_row == row {
11413 snapshot
11414 .buffer_snapshot()
11415 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11416 .map(|position| (position, bp.bp.clone()))
11417 } else {
11418 None
11419 }
11420 })
11421 })
11422 }
11423
11424 pub fn edit_log_breakpoint(
11425 &mut self,
11426 _: &EditLogBreakpoint,
11427 window: &mut Window,
11428 cx: &mut Context<Self>,
11429 ) {
11430 if self.breakpoint_store.is_none() {
11431 return;
11432 }
11433
11434 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11435 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11436 message: None,
11437 state: BreakpointState::Enabled,
11438 condition: None,
11439 hit_condition: None,
11440 });
11441
11442 self.add_edit_breakpoint_block(
11443 anchor,
11444 &breakpoint,
11445 BreakpointPromptEditAction::Log,
11446 window,
11447 cx,
11448 );
11449 }
11450 }
11451
11452 fn breakpoints_at_cursors(
11453 &self,
11454 window: &mut Window,
11455 cx: &mut Context<Self>,
11456 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11457 let snapshot = self.snapshot(window, cx);
11458 let cursors = self
11459 .selections
11460 .disjoint_anchors_arc()
11461 .iter()
11462 .map(|selection| {
11463 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11464
11465 let breakpoint_position = self
11466 .breakpoint_at_row(cursor_position.row, window, cx)
11467 .map(|bp| bp.0)
11468 .unwrap_or_else(|| {
11469 snapshot
11470 .display_snapshot
11471 .buffer_snapshot()
11472 .anchor_after(Point::new(cursor_position.row, 0))
11473 });
11474
11475 let breakpoint = self
11476 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11477 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11478
11479 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11480 })
11481 // 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.
11482 .collect::<HashMap<Anchor, _>>();
11483
11484 cursors.into_iter().collect()
11485 }
11486
11487 pub fn enable_breakpoint(
11488 &mut self,
11489 _: &crate::actions::EnableBreakpoint,
11490 window: &mut Window,
11491 cx: &mut Context<Self>,
11492 ) {
11493 if self.breakpoint_store.is_none() {
11494 return;
11495 }
11496
11497 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11498 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11499 continue;
11500 };
11501 self.edit_breakpoint_at_anchor(
11502 anchor,
11503 breakpoint,
11504 BreakpointEditAction::InvertState,
11505 cx,
11506 );
11507 }
11508 }
11509
11510 pub fn disable_breakpoint(
11511 &mut self,
11512 _: &crate::actions::DisableBreakpoint,
11513 window: &mut Window,
11514 cx: &mut Context<Self>,
11515 ) {
11516 if self.breakpoint_store.is_none() {
11517 return;
11518 }
11519
11520 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11521 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11522 continue;
11523 };
11524 self.edit_breakpoint_at_anchor(
11525 anchor,
11526 breakpoint,
11527 BreakpointEditAction::InvertState,
11528 cx,
11529 );
11530 }
11531 }
11532
11533 pub fn toggle_breakpoint(
11534 &mut self,
11535 _: &crate::actions::ToggleBreakpoint,
11536 window: &mut Window,
11537 cx: &mut Context<Self>,
11538 ) {
11539 if self.breakpoint_store.is_none() {
11540 return;
11541 }
11542
11543 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11544 if let Some(breakpoint) = breakpoint {
11545 self.edit_breakpoint_at_anchor(
11546 anchor,
11547 breakpoint,
11548 BreakpointEditAction::Toggle,
11549 cx,
11550 );
11551 } else {
11552 self.edit_breakpoint_at_anchor(
11553 anchor,
11554 Breakpoint::new_standard(),
11555 BreakpointEditAction::Toggle,
11556 cx,
11557 );
11558 }
11559 }
11560 }
11561
11562 pub fn edit_breakpoint_at_anchor(
11563 &mut self,
11564 breakpoint_position: Anchor,
11565 breakpoint: Breakpoint,
11566 edit_action: BreakpointEditAction,
11567 cx: &mut Context<Self>,
11568 ) {
11569 let Some(breakpoint_store) = &self.breakpoint_store else {
11570 return;
11571 };
11572
11573 let Some(buffer) = self
11574 .buffer
11575 .read(cx)
11576 .buffer_for_anchor(breakpoint_position, cx)
11577 else {
11578 return;
11579 };
11580
11581 breakpoint_store.update(cx, |breakpoint_store, cx| {
11582 breakpoint_store.toggle_breakpoint(
11583 buffer,
11584 BreakpointWithPosition {
11585 position: breakpoint_position.text_anchor,
11586 bp: breakpoint,
11587 },
11588 edit_action,
11589 cx,
11590 );
11591 });
11592
11593 cx.notify();
11594 }
11595
11596 #[cfg(any(test, feature = "test-support"))]
11597 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11598 self.breakpoint_store.clone()
11599 }
11600
11601 pub fn prepare_restore_change(
11602 &self,
11603 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11604 hunk: &MultiBufferDiffHunk,
11605 cx: &mut App,
11606 ) -> Option<()> {
11607 if hunk.is_created_file() {
11608 return None;
11609 }
11610 let buffer = self.buffer.read(cx);
11611 let diff = buffer.diff_for(hunk.buffer_id)?;
11612 let buffer = buffer.buffer(hunk.buffer_id)?;
11613 let buffer = buffer.read(cx);
11614 let original_text = diff
11615 .read(cx)
11616 .base_text(cx)
11617 .as_rope()
11618 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11619 let buffer_snapshot = buffer.snapshot();
11620 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11621 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11622 probe
11623 .0
11624 .start
11625 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11626 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11627 }) {
11628 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11629 Some(())
11630 } else {
11631 None
11632 }
11633 }
11634
11635 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11636 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11637 }
11638
11639 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11640 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11641 }
11642
11643 pub fn rotate_selections_forward(
11644 &mut self,
11645 _: &RotateSelectionsForward,
11646 window: &mut Window,
11647 cx: &mut Context<Self>,
11648 ) {
11649 self.rotate_selections(window, cx, false)
11650 }
11651
11652 pub fn rotate_selections_backward(
11653 &mut self,
11654 _: &RotateSelectionsBackward,
11655 window: &mut Window,
11656 cx: &mut Context<Self>,
11657 ) {
11658 self.rotate_selections(window, cx, true)
11659 }
11660
11661 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11662 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11663 let display_snapshot = self.display_snapshot(cx);
11664 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11665
11666 if selections.len() < 2 {
11667 return;
11668 }
11669
11670 let (edits, new_selections) = {
11671 let buffer = self.buffer.read(cx).read(cx);
11672 let has_selections = selections.iter().any(|s| !s.is_empty());
11673 if has_selections {
11674 let mut selected_texts: Vec<String> = selections
11675 .iter()
11676 .map(|selection| {
11677 buffer
11678 .text_for_range(selection.start..selection.end)
11679 .collect()
11680 })
11681 .collect();
11682
11683 if reverse {
11684 selected_texts.rotate_left(1);
11685 } else {
11686 selected_texts.rotate_right(1);
11687 }
11688
11689 let mut offset_delta: i64 = 0;
11690 let mut new_selections = Vec::new();
11691 let edits: Vec<_> = selections
11692 .iter()
11693 .zip(selected_texts.iter())
11694 .map(|(selection, new_text)| {
11695 let old_len = (selection.end.0 - selection.start.0) as i64;
11696 let new_len = new_text.len() as i64;
11697 let adjusted_start =
11698 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11699 let adjusted_end =
11700 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11701
11702 new_selections.push(Selection {
11703 id: selection.id,
11704 start: adjusted_start,
11705 end: adjusted_end,
11706 reversed: selection.reversed,
11707 goal: selection.goal,
11708 });
11709
11710 offset_delta += new_len - old_len;
11711 (selection.start..selection.end, new_text.clone())
11712 })
11713 .collect();
11714 (edits, new_selections)
11715 } else {
11716 let mut all_rows: Vec<u32> = selections
11717 .iter()
11718 .map(|selection| buffer.offset_to_point(selection.start).row)
11719 .collect();
11720 all_rows.sort_unstable();
11721 all_rows.dedup();
11722
11723 if all_rows.len() < 2 {
11724 return;
11725 }
11726
11727 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11728 .iter()
11729 .map(|&row| {
11730 let start = Point::new(row, 0);
11731 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11732 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11733 })
11734 .collect();
11735
11736 let mut line_texts: Vec<String> = line_ranges
11737 .iter()
11738 .map(|range| buffer.text_for_range(range.clone()).collect())
11739 .collect();
11740
11741 if reverse {
11742 line_texts.rotate_left(1);
11743 } else {
11744 line_texts.rotate_right(1);
11745 }
11746
11747 let edits = line_ranges
11748 .iter()
11749 .zip(line_texts.iter())
11750 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11751 .collect();
11752
11753 let num_rows = all_rows.len();
11754 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11755 .iter()
11756 .enumerate()
11757 .map(|(i, &row)| (row, i))
11758 .collect();
11759
11760 // Compute new line start offsets after rotation (handles CRLF)
11761 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11762 let first_line_start = line_ranges[0].start.0;
11763 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11764 for text in line_texts.iter().take(num_rows - 1) {
11765 let prev_start = *new_line_starts.last().unwrap();
11766 new_line_starts.push(prev_start + text.len() + newline_len);
11767 }
11768
11769 let new_selections = selections
11770 .iter()
11771 .map(|selection| {
11772 let point = buffer.offset_to_point(selection.start);
11773 let old_index = row_to_index[&point.row];
11774 let new_index = if reverse {
11775 (old_index + num_rows - 1) % num_rows
11776 } else {
11777 (old_index + 1) % num_rows
11778 };
11779 let new_offset =
11780 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11781 Selection {
11782 id: selection.id,
11783 start: new_offset,
11784 end: new_offset,
11785 reversed: selection.reversed,
11786 goal: selection.goal,
11787 }
11788 })
11789 .collect();
11790
11791 (edits, new_selections)
11792 }
11793 };
11794
11795 self.transact(window, cx, |this, window, cx| {
11796 this.buffer.update(cx, |buffer, cx| {
11797 buffer.edit(edits, None, cx);
11798 });
11799 this.change_selections(Default::default(), window, cx, |s| {
11800 s.select(new_selections);
11801 });
11802 });
11803 }
11804
11805 fn manipulate_lines<M>(
11806 &mut self,
11807 window: &mut Window,
11808 cx: &mut Context<Self>,
11809 mut manipulate: M,
11810 ) where
11811 M: FnMut(&str) -> LineManipulationResult,
11812 {
11813 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11814
11815 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11816 let buffer = self.buffer.read(cx).snapshot(cx);
11817
11818 let mut edits = Vec::new();
11819
11820 let selections = self.selections.all::<Point>(&display_map);
11821 let mut selections = selections.iter().peekable();
11822 let mut contiguous_row_selections = Vec::new();
11823 let mut new_selections = Vec::new();
11824 let mut added_lines = 0;
11825 let mut removed_lines = 0;
11826
11827 while let Some(selection) = selections.next() {
11828 let (start_row, end_row) = consume_contiguous_rows(
11829 &mut contiguous_row_selections,
11830 selection,
11831 &display_map,
11832 &mut selections,
11833 );
11834
11835 let start_point = Point::new(start_row.0, 0);
11836 let end_point = Point::new(
11837 end_row.previous_row().0,
11838 buffer.line_len(end_row.previous_row()),
11839 );
11840 let text = buffer
11841 .text_for_range(start_point..end_point)
11842 .collect::<String>();
11843
11844 let LineManipulationResult {
11845 new_text,
11846 line_count_before,
11847 line_count_after,
11848 } = manipulate(&text);
11849
11850 edits.push((start_point..end_point, new_text));
11851
11852 // Selections must change based on added and removed line count
11853 let start_row =
11854 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11855 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11856 new_selections.push(Selection {
11857 id: selection.id,
11858 start: start_row,
11859 end: end_row,
11860 goal: SelectionGoal::None,
11861 reversed: selection.reversed,
11862 });
11863
11864 if line_count_after > line_count_before {
11865 added_lines += line_count_after - line_count_before;
11866 } else if line_count_before > line_count_after {
11867 removed_lines += line_count_before - line_count_after;
11868 }
11869 }
11870
11871 self.transact(window, cx, |this, window, cx| {
11872 let buffer = this.buffer.update(cx, |buffer, cx| {
11873 buffer.edit(edits, None, cx);
11874 buffer.snapshot(cx)
11875 });
11876
11877 // Recalculate offsets on newly edited buffer
11878 let new_selections = new_selections
11879 .iter()
11880 .map(|s| {
11881 let start_point = Point::new(s.start.0, 0);
11882 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11883 Selection {
11884 id: s.id,
11885 start: buffer.point_to_offset(start_point),
11886 end: buffer.point_to_offset(end_point),
11887 goal: s.goal,
11888 reversed: s.reversed,
11889 }
11890 })
11891 .collect();
11892
11893 this.change_selections(Default::default(), window, cx, |s| {
11894 s.select(new_selections);
11895 });
11896
11897 this.request_autoscroll(Autoscroll::fit(), cx);
11898 });
11899 }
11900
11901 fn manipulate_immutable_lines<Fn>(
11902 &mut self,
11903 window: &mut Window,
11904 cx: &mut Context<Self>,
11905 mut callback: Fn,
11906 ) where
11907 Fn: FnMut(&mut Vec<&str>),
11908 {
11909 self.manipulate_lines(window, cx, |text| {
11910 let mut lines: Vec<&str> = text.split('\n').collect();
11911 let line_count_before = lines.len();
11912
11913 callback(&mut lines);
11914
11915 LineManipulationResult {
11916 new_text: lines.join("\n"),
11917 line_count_before,
11918 line_count_after: lines.len(),
11919 }
11920 });
11921 }
11922
11923 fn manipulate_mutable_lines<Fn>(
11924 &mut self,
11925 window: &mut Window,
11926 cx: &mut Context<Self>,
11927 mut callback: Fn,
11928 ) where
11929 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11930 {
11931 self.manipulate_lines(window, cx, |text| {
11932 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11933 let line_count_before = lines.len();
11934
11935 callback(&mut lines);
11936
11937 LineManipulationResult {
11938 new_text: lines.join("\n"),
11939 line_count_before,
11940 line_count_after: lines.len(),
11941 }
11942 });
11943 }
11944
11945 pub fn convert_indentation_to_spaces(
11946 &mut self,
11947 _: &ConvertIndentationToSpaces,
11948 window: &mut Window,
11949 cx: &mut Context<Self>,
11950 ) {
11951 let settings = self.buffer.read(cx).language_settings(cx);
11952 let tab_size = settings.tab_size.get() as usize;
11953
11954 self.manipulate_mutable_lines(window, cx, |lines| {
11955 // Allocates a reasonably sized scratch buffer once for the whole loop
11956 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11957 // Avoids recomputing spaces that could be inserted many times
11958 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11959 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11960 .collect();
11961
11962 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11963 let mut chars = line.as_ref().chars();
11964 let mut col = 0;
11965 let mut changed = false;
11966
11967 for ch in chars.by_ref() {
11968 match ch {
11969 ' ' => {
11970 reindented_line.push(' ');
11971 col += 1;
11972 }
11973 '\t' => {
11974 // \t are converted to spaces depending on the current column
11975 let spaces_len = tab_size - (col % tab_size);
11976 reindented_line.extend(&space_cache[spaces_len - 1]);
11977 col += spaces_len;
11978 changed = true;
11979 }
11980 _ => {
11981 // If we dont append before break, the character is consumed
11982 reindented_line.push(ch);
11983 break;
11984 }
11985 }
11986 }
11987
11988 if !changed {
11989 reindented_line.clear();
11990 continue;
11991 }
11992 // Append the rest of the line and replace old reference with new one
11993 reindented_line.extend(chars);
11994 *line = Cow::Owned(reindented_line.clone());
11995 reindented_line.clear();
11996 }
11997 });
11998 }
11999
12000 pub fn convert_indentation_to_tabs(
12001 &mut self,
12002 _: &ConvertIndentationToTabs,
12003 window: &mut Window,
12004 cx: &mut Context<Self>,
12005 ) {
12006 let settings = self.buffer.read(cx).language_settings(cx);
12007 let tab_size = settings.tab_size.get() as usize;
12008
12009 self.manipulate_mutable_lines(window, cx, |lines| {
12010 // Allocates a reasonably sized buffer once for the whole loop
12011 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
12012 // Avoids recomputing spaces that could be inserted many times
12013 let space_cache: Vec<Vec<char>> = (1..=tab_size)
12014 .map(|n| IndentSize::spaces(n as u32).chars().collect())
12015 .collect();
12016
12017 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
12018 let mut chars = line.chars();
12019 let mut spaces_count = 0;
12020 let mut first_non_indent_char = None;
12021 let mut changed = false;
12022
12023 for ch in chars.by_ref() {
12024 match ch {
12025 ' ' => {
12026 // Keep track of spaces. Append \t when we reach tab_size
12027 spaces_count += 1;
12028 changed = true;
12029 if spaces_count == tab_size {
12030 reindented_line.push('\t');
12031 spaces_count = 0;
12032 }
12033 }
12034 '\t' => {
12035 reindented_line.push('\t');
12036 spaces_count = 0;
12037 }
12038 _ => {
12039 // Dont append it yet, we might have remaining spaces
12040 first_non_indent_char = Some(ch);
12041 break;
12042 }
12043 }
12044 }
12045
12046 if !changed {
12047 reindented_line.clear();
12048 continue;
12049 }
12050 // Remaining spaces that didn't make a full tab stop
12051 if spaces_count > 0 {
12052 reindented_line.extend(&space_cache[spaces_count - 1]);
12053 }
12054 // If we consume an extra character that was not indentation, add it back
12055 if let Some(extra_char) = first_non_indent_char {
12056 reindented_line.push(extra_char);
12057 }
12058 // Append the rest of the line and replace old reference with new one
12059 reindented_line.extend(chars);
12060 *line = Cow::Owned(reindented_line.clone());
12061 reindented_line.clear();
12062 }
12063 });
12064 }
12065
12066 pub fn convert_to_upper_case(
12067 &mut self,
12068 _: &ConvertToUpperCase,
12069 window: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 self.manipulate_text(window, cx, |text| text.to_uppercase())
12073 }
12074
12075 pub fn convert_to_lower_case(
12076 &mut self,
12077 _: &ConvertToLowerCase,
12078 window: &mut Window,
12079 cx: &mut Context<Self>,
12080 ) {
12081 self.manipulate_text(window, cx, |text| text.to_lowercase())
12082 }
12083
12084 pub fn convert_to_title_case(
12085 &mut self,
12086 _: &ConvertToTitleCase,
12087 window: &mut Window,
12088 cx: &mut Context<Self>,
12089 ) {
12090 self.manipulate_text(window, cx, |text| {
12091 text.split('\n')
12092 .map(|line| line.to_case(Case::Title))
12093 .join("\n")
12094 })
12095 }
12096
12097 pub fn convert_to_snake_case(
12098 &mut self,
12099 _: &ConvertToSnakeCase,
12100 window: &mut Window,
12101 cx: &mut Context<Self>,
12102 ) {
12103 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12104 }
12105
12106 pub fn convert_to_kebab_case(
12107 &mut self,
12108 _: &ConvertToKebabCase,
12109 window: &mut Window,
12110 cx: &mut Context<Self>,
12111 ) {
12112 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12113 }
12114
12115 pub fn convert_to_upper_camel_case(
12116 &mut self,
12117 _: &ConvertToUpperCamelCase,
12118 window: &mut Window,
12119 cx: &mut Context<Self>,
12120 ) {
12121 self.manipulate_text(window, cx, |text| {
12122 text.split('\n')
12123 .map(|line| line.to_case(Case::UpperCamel))
12124 .join("\n")
12125 })
12126 }
12127
12128 pub fn convert_to_lower_camel_case(
12129 &mut self,
12130 _: &ConvertToLowerCamelCase,
12131 window: &mut Window,
12132 cx: &mut Context<Self>,
12133 ) {
12134 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12135 }
12136
12137 pub fn convert_to_opposite_case(
12138 &mut self,
12139 _: &ConvertToOppositeCase,
12140 window: &mut Window,
12141 cx: &mut Context<Self>,
12142 ) {
12143 self.manipulate_text(window, cx, |text| {
12144 text.chars()
12145 .fold(String::with_capacity(text.len()), |mut t, c| {
12146 if c.is_uppercase() {
12147 t.extend(c.to_lowercase());
12148 } else {
12149 t.extend(c.to_uppercase());
12150 }
12151 t
12152 })
12153 })
12154 }
12155
12156 pub fn convert_to_sentence_case(
12157 &mut self,
12158 _: &ConvertToSentenceCase,
12159 window: &mut Window,
12160 cx: &mut Context<Self>,
12161 ) {
12162 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12163 }
12164
12165 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12166 self.manipulate_text(window, cx, |text| {
12167 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12168 if has_upper_case_characters {
12169 text.to_lowercase()
12170 } else {
12171 text.to_uppercase()
12172 }
12173 })
12174 }
12175
12176 pub fn convert_to_rot13(
12177 &mut self,
12178 _: &ConvertToRot13,
12179 window: &mut Window,
12180 cx: &mut Context<Self>,
12181 ) {
12182 self.manipulate_text(window, cx, |text| {
12183 text.chars()
12184 .map(|c| match c {
12185 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12186 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12187 _ => c,
12188 })
12189 .collect()
12190 })
12191 }
12192
12193 pub fn convert_to_rot47(
12194 &mut self,
12195 _: &ConvertToRot47,
12196 window: &mut Window,
12197 cx: &mut Context<Self>,
12198 ) {
12199 self.manipulate_text(window, cx, |text| {
12200 text.chars()
12201 .map(|c| {
12202 let code_point = c as u32;
12203 if code_point >= 33 && code_point <= 126 {
12204 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12205 }
12206 c
12207 })
12208 .collect()
12209 })
12210 }
12211
12212 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12213 where
12214 Fn: FnMut(&str) -> String,
12215 {
12216 let buffer = self.buffer.read(cx).snapshot(cx);
12217
12218 let mut new_selections = Vec::new();
12219 let mut edits = Vec::new();
12220 let mut selection_adjustment = 0isize;
12221
12222 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12223 let selection_is_empty = selection.is_empty();
12224
12225 let (start, end) = if selection_is_empty {
12226 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12227 (word_range.start, word_range.end)
12228 } else {
12229 (
12230 buffer.point_to_offset(selection.start),
12231 buffer.point_to_offset(selection.end),
12232 )
12233 };
12234
12235 let text = buffer.text_for_range(start..end).collect::<String>();
12236 let old_length = text.len() as isize;
12237 let text = callback(&text);
12238
12239 new_selections.push(Selection {
12240 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12241 end: MultiBufferOffset(
12242 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12243 ),
12244 goal: SelectionGoal::None,
12245 id: selection.id,
12246 reversed: selection.reversed,
12247 });
12248
12249 selection_adjustment += old_length - text.len() as isize;
12250
12251 edits.push((start..end, text));
12252 }
12253
12254 self.transact(window, cx, |this, window, cx| {
12255 this.buffer.update(cx, |buffer, cx| {
12256 buffer.edit(edits, None, cx);
12257 });
12258
12259 this.change_selections(Default::default(), window, cx, |s| {
12260 s.select(new_selections);
12261 });
12262
12263 this.request_autoscroll(Autoscroll::fit(), cx);
12264 });
12265 }
12266
12267 pub fn move_selection_on_drop(
12268 &mut self,
12269 selection: &Selection<Anchor>,
12270 target: DisplayPoint,
12271 is_cut: bool,
12272 window: &mut Window,
12273 cx: &mut Context<Self>,
12274 ) {
12275 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12276 let buffer = display_map.buffer_snapshot();
12277 let mut edits = Vec::new();
12278 let insert_point = display_map
12279 .clip_point(target, Bias::Left)
12280 .to_point(&display_map);
12281 let text = buffer
12282 .text_for_range(selection.start..selection.end)
12283 .collect::<String>();
12284 if is_cut {
12285 edits.push(((selection.start..selection.end), String::new()));
12286 }
12287 let insert_anchor = buffer.anchor_before(insert_point);
12288 edits.push(((insert_anchor..insert_anchor), text));
12289 let last_edit_start = insert_anchor.bias_left(buffer);
12290 let last_edit_end = insert_anchor.bias_right(buffer);
12291 self.transact(window, cx, |this, window, cx| {
12292 this.buffer.update(cx, |buffer, cx| {
12293 buffer.edit(edits, None, cx);
12294 });
12295 this.change_selections(Default::default(), window, cx, |s| {
12296 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12297 });
12298 });
12299 }
12300
12301 pub fn clear_selection_drag_state(&mut self) {
12302 self.selection_drag_state = SelectionDragState::None;
12303 }
12304
12305 pub fn duplicate(
12306 &mut self,
12307 upwards: bool,
12308 whole_lines: bool,
12309 window: &mut Window,
12310 cx: &mut Context<Self>,
12311 ) {
12312 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12313
12314 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12315 let buffer = display_map.buffer_snapshot();
12316 let selections = self.selections.all::<Point>(&display_map);
12317
12318 let mut edits = Vec::new();
12319 let mut selections_iter = selections.iter().peekable();
12320 while let Some(selection) = selections_iter.next() {
12321 let mut rows = selection.spanned_rows(false, &display_map);
12322 // duplicate line-wise
12323 if whole_lines || selection.start == selection.end {
12324 // Avoid duplicating the same lines twice.
12325 while let Some(next_selection) = selections_iter.peek() {
12326 let next_rows = next_selection.spanned_rows(false, &display_map);
12327 if next_rows.start < rows.end {
12328 rows.end = next_rows.end;
12329 selections_iter.next().unwrap();
12330 } else {
12331 break;
12332 }
12333 }
12334
12335 // Copy the text from the selected row region and splice it either at the start
12336 // or end of the region.
12337 let start = Point::new(rows.start.0, 0);
12338 let end = Point::new(
12339 rows.end.previous_row().0,
12340 buffer.line_len(rows.end.previous_row()),
12341 );
12342
12343 let mut text = buffer.text_for_range(start..end).collect::<String>();
12344
12345 let insert_location = if upwards {
12346 // When duplicating upward, we need to insert before the current line.
12347 // If we're on the last line and it doesn't end with a newline,
12348 // we need to add a newline before the duplicated content.
12349 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12350 && buffer.max_point().column > 0
12351 && !text.ends_with('\n');
12352
12353 if needs_leading_newline {
12354 text.insert(0, '\n');
12355 end
12356 } else {
12357 text.push('\n');
12358 Point::new(rows.start.0, 0)
12359 }
12360 } else {
12361 text.push('\n');
12362 start
12363 };
12364 edits.push((insert_location..insert_location, text));
12365 } else {
12366 // duplicate character-wise
12367 let start = selection.start;
12368 let end = selection.end;
12369 let text = buffer.text_for_range(start..end).collect::<String>();
12370 edits.push((selection.end..selection.end, text));
12371 }
12372 }
12373
12374 self.transact(window, cx, |this, window, cx| {
12375 this.buffer.update(cx, |buffer, cx| {
12376 buffer.edit(edits, None, cx);
12377 });
12378
12379 // When duplicating upward with whole lines, move the cursor to the duplicated line
12380 if upwards && whole_lines {
12381 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12382
12383 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12384 let mut new_ranges = Vec::new();
12385 let selections = s.all::<Point>(&display_map);
12386 let mut selections_iter = selections.iter().peekable();
12387
12388 while let Some(first_selection) = selections_iter.next() {
12389 // Group contiguous selections together to find the total row span
12390 let mut group_selections = vec![first_selection];
12391 let mut rows = first_selection.spanned_rows(false, &display_map);
12392
12393 while let Some(next_selection) = selections_iter.peek() {
12394 let next_rows = next_selection.spanned_rows(false, &display_map);
12395 if next_rows.start < rows.end {
12396 rows.end = next_rows.end;
12397 group_selections.push(selections_iter.next().unwrap());
12398 } else {
12399 break;
12400 }
12401 }
12402
12403 let row_count = rows.end.0 - rows.start.0;
12404
12405 // Move all selections in this group up by the total number of duplicated rows
12406 for selection in group_selections {
12407 let new_start = Point::new(
12408 selection.start.row.saturating_sub(row_count),
12409 selection.start.column,
12410 );
12411
12412 let new_end = Point::new(
12413 selection.end.row.saturating_sub(row_count),
12414 selection.end.column,
12415 );
12416
12417 new_ranges.push(new_start..new_end);
12418 }
12419 }
12420
12421 s.select_ranges(new_ranges);
12422 });
12423 }
12424
12425 this.request_autoscroll(Autoscroll::fit(), cx);
12426 });
12427 }
12428
12429 pub fn duplicate_line_up(
12430 &mut self,
12431 _: &DuplicateLineUp,
12432 window: &mut Window,
12433 cx: &mut Context<Self>,
12434 ) {
12435 self.duplicate(true, true, window, cx);
12436 }
12437
12438 pub fn duplicate_line_down(
12439 &mut self,
12440 _: &DuplicateLineDown,
12441 window: &mut Window,
12442 cx: &mut Context<Self>,
12443 ) {
12444 self.duplicate(false, true, window, cx);
12445 }
12446
12447 pub fn duplicate_selection(
12448 &mut self,
12449 _: &DuplicateSelection,
12450 window: &mut Window,
12451 cx: &mut Context<Self>,
12452 ) {
12453 self.duplicate(false, false, window, cx);
12454 }
12455
12456 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12458 if self.mode.is_single_line() {
12459 cx.propagate();
12460 return;
12461 }
12462
12463 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12464 let buffer = self.buffer.read(cx).snapshot(cx);
12465
12466 let mut edits = Vec::new();
12467 let mut unfold_ranges = Vec::new();
12468 let mut refold_creases = Vec::new();
12469
12470 let selections = self.selections.all::<Point>(&display_map);
12471 let mut selections = selections.iter().peekable();
12472 let mut contiguous_row_selections = Vec::new();
12473 let mut new_selections = Vec::new();
12474
12475 while let Some(selection) = selections.next() {
12476 // Find all the selections that span a contiguous row range
12477 let (start_row, end_row) = consume_contiguous_rows(
12478 &mut contiguous_row_selections,
12479 selection,
12480 &display_map,
12481 &mut selections,
12482 );
12483
12484 // Move the text spanned by the row range to be before the line preceding the row range
12485 if start_row.0 > 0 {
12486 let range_to_move = Point::new(
12487 start_row.previous_row().0,
12488 buffer.line_len(start_row.previous_row()),
12489 )
12490 ..Point::new(
12491 end_row.previous_row().0,
12492 buffer.line_len(end_row.previous_row()),
12493 );
12494 let insertion_point = display_map
12495 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12496 .0;
12497
12498 // Don't move lines across excerpts
12499 if buffer
12500 .excerpt_containing(insertion_point..range_to_move.end)
12501 .is_some()
12502 {
12503 let text = buffer
12504 .text_for_range(range_to_move.clone())
12505 .flat_map(|s| s.chars())
12506 .skip(1)
12507 .chain(['\n'])
12508 .collect::<String>();
12509
12510 edits.push((
12511 buffer.anchor_after(range_to_move.start)
12512 ..buffer.anchor_before(range_to_move.end),
12513 String::new(),
12514 ));
12515 let insertion_anchor = buffer.anchor_after(insertion_point);
12516 edits.push((insertion_anchor..insertion_anchor, text));
12517
12518 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12519
12520 // Move selections up
12521 new_selections.extend(contiguous_row_selections.drain(..).map(
12522 |mut selection| {
12523 selection.start.row -= row_delta;
12524 selection.end.row -= row_delta;
12525 selection
12526 },
12527 ));
12528
12529 // Move folds up
12530 unfold_ranges.push(range_to_move.clone());
12531 for fold in display_map.folds_in_range(
12532 buffer.anchor_before(range_to_move.start)
12533 ..buffer.anchor_after(range_to_move.end),
12534 ) {
12535 let mut start = fold.range.start.to_point(&buffer);
12536 let mut end = fold.range.end.to_point(&buffer);
12537 start.row -= row_delta;
12538 end.row -= row_delta;
12539 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12540 }
12541 }
12542 }
12543
12544 // If we didn't move line(s), preserve the existing selections
12545 new_selections.append(&mut contiguous_row_selections);
12546 }
12547
12548 self.transact(window, cx, |this, window, cx| {
12549 this.unfold_ranges(&unfold_ranges, true, true, cx);
12550 this.buffer.update(cx, |buffer, cx| {
12551 for (range, text) in edits {
12552 buffer.edit([(range, text)], None, cx);
12553 }
12554 });
12555 this.fold_creases(refold_creases, true, window, cx);
12556 this.change_selections(Default::default(), window, cx, |s| {
12557 s.select(new_selections);
12558 })
12559 });
12560 }
12561
12562 pub fn move_line_down(
12563 &mut self,
12564 _: &MoveLineDown,
12565 window: &mut Window,
12566 cx: &mut Context<Self>,
12567 ) {
12568 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12569 if self.mode.is_single_line() {
12570 cx.propagate();
12571 return;
12572 }
12573
12574 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12575 let buffer = self.buffer.read(cx).snapshot(cx);
12576
12577 let mut edits = Vec::new();
12578 let mut unfold_ranges = Vec::new();
12579 let mut refold_creases = Vec::new();
12580
12581 let selections = self.selections.all::<Point>(&display_map);
12582 let mut selections = selections.iter().peekable();
12583 let mut contiguous_row_selections = Vec::new();
12584 let mut new_selections = Vec::new();
12585
12586 while let Some(selection) = selections.next() {
12587 // Find all the selections that span a contiguous row range
12588 let (start_row, end_row) = consume_contiguous_rows(
12589 &mut contiguous_row_selections,
12590 selection,
12591 &display_map,
12592 &mut selections,
12593 );
12594
12595 // Move the text spanned by the row range to be after the last line of the row range
12596 if end_row.0 <= buffer.max_point().row {
12597 let range_to_move =
12598 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12599 let insertion_point = display_map
12600 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12601 .0;
12602
12603 // Don't move lines across excerpt boundaries
12604 if buffer
12605 .excerpt_containing(range_to_move.start..insertion_point)
12606 .is_some()
12607 {
12608 let mut text = String::from("\n");
12609 text.extend(buffer.text_for_range(range_to_move.clone()));
12610 text.pop(); // Drop trailing newline
12611 edits.push((
12612 buffer.anchor_after(range_to_move.start)
12613 ..buffer.anchor_before(range_to_move.end),
12614 String::new(),
12615 ));
12616 let insertion_anchor = buffer.anchor_after(insertion_point);
12617 edits.push((insertion_anchor..insertion_anchor, text));
12618
12619 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12620
12621 // Move selections down
12622 new_selections.extend(contiguous_row_selections.drain(..).map(
12623 |mut selection| {
12624 selection.start.row += row_delta;
12625 selection.end.row += row_delta;
12626 selection
12627 },
12628 ));
12629
12630 // Move folds down
12631 unfold_ranges.push(range_to_move.clone());
12632 for fold in display_map.folds_in_range(
12633 buffer.anchor_before(range_to_move.start)
12634 ..buffer.anchor_after(range_to_move.end),
12635 ) {
12636 let mut start = fold.range.start.to_point(&buffer);
12637 let mut end = fold.range.end.to_point(&buffer);
12638 start.row += row_delta;
12639 end.row += row_delta;
12640 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12641 }
12642 }
12643 }
12644
12645 // If we didn't move line(s), preserve the existing selections
12646 new_selections.append(&mut contiguous_row_selections);
12647 }
12648
12649 self.transact(window, cx, |this, window, cx| {
12650 this.unfold_ranges(&unfold_ranges, true, true, cx);
12651 this.buffer.update(cx, |buffer, cx| {
12652 for (range, text) in edits {
12653 buffer.edit([(range, text)], None, cx);
12654 }
12655 });
12656 this.fold_creases(refold_creases, true, window, cx);
12657 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12658 });
12659 }
12660
12661 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12662 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12663 let text_layout_details = &self.text_layout_details(window);
12664 self.transact(window, cx, |this, window, cx| {
12665 let edits = this.change_selections(Default::default(), window, cx, |s| {
12666 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12667 s.move_with(|display_map, selection| {
12668 if !selection.is_empty() {
12669 return;
12670 }
12671
12672 let mut head = selection.head();
12673 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12674 if head.column() == display_map.line_len(head.row()) {
12675 transpose_offset = display_map
12676 .buffer_snapshot()
12677 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12678 }
12679
12680 if transpose_offset == MultiBufferOffset(0) {
12681 return;
12682 }
12683
12684 *head.column_mut() += 1;
12685 head = display_map.clip_point(head, Bias::Right);
12686 let goal = SelectionGoal::HorizontalPosition(
12687 display_map
12688 .x_for_display_point(head, text_layout_details)
12689 .into(),
12690 );
12691 selection.collapse_to(head, goal);
12692
12693 let transpose_start = display_map
12694 .buffer_snapshot()
12695 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12696 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12697 let transpose_end = display_map
12698 .buffer_snapshot()
12699 .clip_offset(transpose_offset + 1usize, Bias::Right);
12700 if let Some(ch) = display_map
12701 .buffer_snapshot()
12702 .chars_at(transpose_start)
12703 .next()
12704 {
12705 edits.push((transpose_start..transpose_offset, String::new()));
12706 edits.push((transpose_end..transpose_end, ch.to_string()));
12707 }
12708 }
12709 });
12710 edits
12711 });
12712 this.buffer
12713 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12714 let selections = this
12715 .selections
12716 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12717 this.change_selections(Default::default(), window, cx, |s| {
12718 s.select(selections);
12719 });
12720 });
12721 }
12722
12723 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12724 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12725 if self.mode.is_single_line() {
12726 cx.propagate();
12727 return;
12728 }
12729
12730 self.rewrap_impl(RewrapOptions::default(), cx)
12731 }
12732
12733 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12734 let buffer = self.buffer.read(cx).snapshot(cx);
12735 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12736
12737 #[derive(Clone, Debug, PartialEq)]
12738 enum CommentFormat {
12739 /// single line comment, with prefix for line
12740 Line(String),
12741 /// single line within a block comment, with prefix for line
12742 BlockLine(String),
12743 /// a single line of a block comment that includes the initial delimiter
12744 BlockCommentWithStart(BlockCommentConfig),
12745 /// a single line of a block comment that includes the ending delimiter
12746 BlockCommentWithEnd(BlockCommentConfig),
12747 }
12748
12749 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12750 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12751 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12752 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12753 .peekable();
12754
12755 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12756 row
12757 } else {
12758 return Vec::new();
12759 };
12760
12761 let language_settings = buffer.language_settings_at(selection.head(), cx);
12762 let language_scope = buffer.language_scope_at(selection.head());
12763
12764 let indent_and_prefix_for_row =
12765 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12766 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12767 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12768 &language_scope
12769 {
12770 let indent_end = Point::new(row, indent.len);
12771 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12772 let line_text_after_indent = buffer
12773 .text_for_range(indent_end..line_end)
12774 .collect::<String>();
12775
12776 let is_within_comment_override = buffer
12777 .language_scope_at(indent_end)
12778 .is_some_and(|scope| scope.override_name() == Some("comment"));
12779 let comment_delimiters = if is_within_comment_override {
12780 // we are within a comment syntax node, but we don't
12781 // yet know what kind of comment: block, doc or line
12782 match (
12783 language_scope.documentation_comment(),
12784 language_scope.block_comment(),
12785 ) {
12786 (Some(config), _) | (_, Some(config))
12787 if buffer.contains_str_at(indent_end, &config.start) =>
12788 {
12789 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12790 }
12791 (Some(config), _) | (_, Some(config))
12792 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12793 {
12794 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12795 }
12796 (Some(config), _) | (_, Some(config))
12797 if buffer.contains_str_at(indent_end, &config.prefix) =>
12798 {
12799 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12800 }
12801 (_, _) => language_scope
12802 .line_comment_prefixes()
12803 .iter()
12804 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12805 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12806 }
12807 } else {
12808 // we not in an overridden comment node, but we may
12809 // be within a non-overridden line comment node
12810 language_scope
12811 .line_comment_prefixes()
12812 .iter()
12813 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12814 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12815 };
12816
12817 let rewrap_prefix = language_scope
12818 .rewrap_prefixes()
12819 .iter()
12820 .find_map(|prefix_regex| {
12821 prefix_regex.find(&line_text_after_indent).map(|mat| {
12822 if mat.start() == 0 {
12823 Some(mat.as_str().to_string())
12824 } else {
12825 None
12826 }
12827 })
12828 })
12829 .flatten();
12830 (comment_delimiters, rewrap_prefix)
12831 } else {
12832 (None, None)
12833 };
12834 (indent, comment_prefix, rewrap_prefix)
12835 };
12836
12837 let mut ranges = Vec::new();
12838 let from_empty_selection = selection.is_empty();
12839
12840 let mut current_range_start = first_row;
12841 let mut prev_row = first_row;
12842 let (
12843 mut current_range_indent,
12844 mut current_range_comment_delimiters,
12845 mut current_range_rewrap_prefix,
12846 ) = indent_and_prefix_for_row(first_row);
12847
12848 for row in non_blank_rows_iter.skip(1) {
12849 let has_paragraph_break = row > prev_row + 1;
12850
12851 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12852 indent_and_prefix_for_row(row);
12853
12854 let has_indent_change = row_indent != current_range_indent;
12855 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12856
12857 let has_boundary_change = has_comment_change
12858 || row_rewrap_prefix.is_some()
12859 || (has_indent_change && current_range_comment_delimiters.is_some());
12860
12861 if has_paragraph_break || has_boundary_change {
12862 ranges.push((
12863 language_settings.clone(),
12864 Point::new(current_range_start, 0)
12865 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12866 current_range_indent,
12867 current_range_comment_delimiters.clone(),
12868 current_range_rewrap_prefix.clone(),
12869 from_empty_selection,
12870 ));
12871 current_range_start = row;
12872 current_range_indent = row_indent;
12873 current_range_comment_delimiters = row_comment_delimiters;
12874 current_range_rewrap_prefix = row_rewrap_prefix;
12875 }
12876 prev_row = row;
12877 }
12878
12879 ranges.push((
12880 language_settings.clone(),
12881 Point::new(current_range_start, 0)
12882 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12883 current_range_indent,
12884 current_range_comment_delimiters,
12885 current_range_rewrap_prefix,
12886 from_empty_selection,
12887 ));
12888
12889 ranges
12890 });
12891
12892 let mut edits = Vec::new();
12893 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12894
12895 for (
12896 language_settings,
12897 wrap_range,
12898 mut indent_size,
12899 comment_prefix,
12900 rewrap_prefix,
12901 from_empty_selection,
12902 ) in wrap_ranges
12903 {
12904 let mut start_row = wrap_range.start.row;
12905 let mut end_row = wrap_range.end.row;
12906
12907 // Skip selections that overlap with a range that has already been rewrapped.
12908 let selection_range = start_row..end_row;
12909 if rewrapped_row_ranges
12910 .iter()
12911 .any(|range| range.overlaps(&selection_range))
12912 {
12913 continue;
12914 }
12915
12916 let tab_size = language_settings.tab_size;
12917
12918 let (line_prefix, inside_comment) = match &comment_prefix {
12919 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12920 (Some(prefix.as_str()), true)
12921 }
12922 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12923 (Some(prefix.as_ref()), true)
12924 }
12925 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12926 start: _,
12927 end: _,
12928 prefix,
12929 tab_size,
12930 })) => {
12931 indent_size.len += tab_size;
12932 (Some(prefix.as_ref()), true)
12933 }
12934 None => (None, false),
12935 };
12936 let indent_prefix = indent_size.chars().collect::<String>();
12937 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12938
12939 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12940 RewrapBehavior::InComments => inside_comment,
12941 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12942 RewrapBehavior::Anywhere => true,
12943 };
12944
12945 let should_rewrap = options.override_language_settings
12946 || allow_rewrap_based_on_language
12947 || self.hard_wrap.is_some();
12948 if !should_rewrap {
12949 continue;
12950 }
12951
12952 if from_empty_selection {
12953 'expand_upwards: while start_row > 0 {
12954 let prev_row = start_row - 1;
12955 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12956 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12957 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12958 {
12959 start_row = prev_row;
12960 } else {
12961 break 'expand_upwards;
12962 }
12963 }
12964
12965 'expand_downwards: while end_row < buffer.max_point().row {
12966 let next_row = end_row + 1;
12967 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12968 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12969 && !buffer.is_line_blank(MultiBufferRow(next_row))
12970 {
12971 end_row = next_row;
12972 } else {
12973 break 'expand_downwards;
12974 }
12975 }
12976 }
12977
12978 let start = Point::new(start_row, 0);
12979 let start_offset = ToOffset::to_offset(&start, &buffer);
12980 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12981 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12982 let mut first_line_delimiter = None;
12983 let mut last_line_delimiter = None;
12984 let Some(lines_without_prefixes) = selection_text
12985 .lines()
12986 .enumerate()
12987 .map(|(ix, line)| {
12988 let line_trimmed = line.trim_start();
12989 if rewrap_prefix.is_some() && ix > 0 {
12990 Ok(line_trimmed)
12991 } else if let Some(
12992 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12993 start,
12994 prefix,
12995 end,
12996 tab_size,
12997 })
12998 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12999 start,
13000 prefix,
13001 end,
13002 tab_size,
13003 }),
13004 ) = &comment_prefix
13005 {
13006 let line_trimmed = line_trimmed
13007 .strip_prefix(start.as_ref())
13008 .map(|s| {
13009 let mut indent_size = indent_size;
13010 indent_size.len -= tab_size;
13011 let indent_prefix: String = indent_size.chars().collect();
13012 first_line_delimiter = Some((indent_prefix, start));
13013 s.trim_start()
13014 })
13015 .unwrap_or(line_trimmed);
13016 let line_trimmed = line_trimmed
13017 .strip_suffix(end.as_ref())
13018 .map(|s| {
13019 last_line_delimiter = Some(end);
13020 s.trim_end()
13021 })
13022 .unwrap_or(line_trimmed);
13023 let line_trimmed = line_trimmed
13024 .strip_prefix(prefix.as_ref())
13025 .unwrap_or(line_trimmed);
13026 Ok(line_trimmed)
13027 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13028 line_trimmed.strip_prefix(prefix).with_context(|| {
13029 format!("line did not start with prefix {prefix:?}: {line:?}")
13030 })
13031 } else {
13032 line_trimmed
13033 .strip_prefix(&line_prefix.trim_start())
13034 .with_context(|| {
13035 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13036 })
13037 }
13038 })
13039 .collect::<Result<Vec<_>, _>>()
13040 .log_err()
13041 else {
13042 continue;
13043 };
13044
13045 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13046 buffer
13047 .language_settings_at(Point::new(start_row, 0), cx)
13048 .preferred_line_length as usize
13049 });
13050
13051 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13052 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13053 } else {
13054 line_prefix.clone()
13055 };
13056
13057 let wrapped_text = {
13058 let mut wrapped_text = wrap_with_prefix(
13059 line_prefix,
13060 subsequent_lines_prefix,
13061 lines_without_prefixes.join("\n"),
13062 wrap_column,
13063 tab_size,
13064 options.preserve_existing_whitespace,
13065 );
13066
13067 if let Some((indent, delimiter)) = first_line_delimiter {
13068 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13069 }
13070 if let Some(last_line) = last_line_delimiter {
13071 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13072 }
13073
13074 wrapped_text
13075 };
13076
13077 // TODO: should always use char-based diff while still supporting cursor behavior that
13078 // matches vim.
13079 let mut diff_options = DiffOptions::default();
13080 if options.override_language_settings {
13081 diff_options.max_word_diff_len = 0;
13082 diff_options.max_word_diff_line_count = 0;
13083 } else {
13084 diff_options.max_word_diff_len = usize::MAX;
13085 diff_options.max_word_diff_line_count = usize::MAX;
13086 }
13087
13088 for (old_range, new_text) in
13089 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13090 {
13091 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13092 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13093 edits.push((edit_start..edit_end, new_text));
13094 }
13095
13096 rewrapped_row_ranges.push(start_row..=end_row);
13097 }
13098
13099 self.buffer
13100 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13101 }
13102
13103 pub fn cut_common(
13104 &mut self,
13105 cut_no_selection_line: bool,
13106 window: &mut Window,
13107 cx: &mut Context<Self>,
13108 ) -> ClipboardItem {
13109 let mut text = String::new();
13110 let buffer = self.buffer.read(cx).snapshot(cx);
13111 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13112 let mut clipboard_selections = Vec::with_capacity(selections.len());
13113 {
13114 let max_point = buffer.max_point();
13115 let mut is_first = true;
13116 let mut prev_selection_was_entire_line = false;
13117 for selection in &mut selections {
13118 let is_entire_line =
13119 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13120 if is_entire_line {
13121 selection.start = Point::new(selection.start.row, 0);
13122 if !selection.is_empty() && selection.end.column == 0 {
13123 selection.end = cmp::min(max_point, selection.end);
13124 } else {
13125 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13126 }
13127 selection.goal = SelectionGoal::None;
13128 }
13129 if is_first {
13130 is_first = false;
13131 } else if !prev_selection_was_entire_line {
13132 text += "\n";
13133 }
13134 prev_selection_was_entire_line = is_entire_line;
13135 let mut len = 0;
13136 for chunk in buffer.text_for_range(selection.start..selection.end) {
13137 text.push_str(chunk);
13138 len += chunk.len();
13139 }
13140
13141 clipboard_selections.push(ClipboardSelection::for_buffer(
13142 len,
13143 is_entire_line,
13144 selection.range(),
13145 &buffer,
13146 self.project.as_ref(),
13147 cx,
13148 ));
13149 }
13150 }
13151
13152 self.transact(window, cx, |this, window, cx| {
13153 this.change_selections(Default::default(), window, cx, |s| {
13154 s.select(selections);
13155 });
13156 this.insert("", window, cx);
13157 });
13158 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13159 }
13160
13161 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13162 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13163 let item = self.cut_common(true, window, cx);
13164 cx.write_to_clipboard(item);
13165 }
13166
13167 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13168 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13169 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13170 s.move_with(|snapshot, sel| {
13171 if sel.is_empty() {
13172 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13173 }
13174 if sel.is_empty() {
13175 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13176 }
13177 });
13178 });
13179 let item = self.cut_common(false, window, cx);
13180 cx.set_global(KillRing(item))
13181 }
13182
13183 pub fn kill_ring_yank(
13184 &mut self,
13185 _: &KillRingYank,
13186 window: &mut Window,
13187 cx: &mut Context<Self>,
13188 ) {
13189 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13190 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13191 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13192 (kill_ring.text().to_string(), kill_ring.metadata_json())
13193 } else {
13194 return;
13195 }
13196 } else {
13197 return;
13198 };
13199 self.do_paste(&text, metadata, false, window, cx);
13200 }
13201
13202 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13203 self.do_copy(true, cx);
13204 }
13205
13206 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13207 self.do_copy(false, cx);
13208 }
13209
13210 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13211 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13212 let buffer = self.buffer.read(cx).read(cx);
13213 let mut text = String::new();
13214
13215 let mut clipboard_selections = Vec::with_capacity(selections.len());
13216 {
13217 let max_point = buffer.max_point();
13218 let mut is_first = true;
13219 let mut prev_selection_was_entire_line = false;
13220 for selection in &selections {
13221 let mut start = selection.start;
13222 let mut end = selection.end;
13223 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13224 let mut add_trailing_newline = false;
13225 if is_entire_line {
13226 start = Point::new(start.row, 0);
13227 let next_line_start = Point::new(end.row + 1, 0);
13228 if next_line_start <= max_point {
13229 end = next_line_start;
13230 } else {
13231 // We're on the last line without a trailing newline.
13232 // Copy to the end of the line and add a newline afterwards.
13233 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13234 add_trailing_newline = true;
13235 }
13236 }
13237
13238 let mut trimmed_selections = Vec::new();
13239 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13240 let row = MultiBufferRow(start.row);
13241 let first_indent = buffer.indent_size_for_line(row);
13242 if first_indent.len == 0 || start.column > first_indent.len {
13243 trimmed_selections.push(start..end);
13244 } else {
13245 trimmed_selections.push(
13246 Point::new(row.0, first_indent.len)
13247 ..Point::new(row.0, buffer.line_len(row)),
13248 );
13249 for row in start.row + 1..=end.row {
13250 let mut line_len = buffer.line_len(MultiBufferRow(row));
13251 if row == end.row {
13252 line_len = end.column;
13253 }
13254 if line_len == 0 {
13255 trimmed_selections
13256 .push(Point::new(row, 0)..Point::new(row, line_len));
13257 continue;
13258 }
13259 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13260 if row_indent_size.len >= first_indent.len {
13261 trimmed_selections.push(
13262 Point::new(row, first_indent.len)..Point::new(row, line_len),
13263 );
13264 } else {
13265 trimmed_selections.clear();
13266 trimmed_selections.push(start..end);
13267 break;
13268 }
13269 }
13270 }
13271 } else {
13272 trimmed_selections.push(start..end);
13273 }
13274
13275 for trimmed_range in trimmed_selections {
13276 if is_first {
13277 is_first = false;
13278 } else if !prev_selection_was_entire_line {
13279 text += "\n";
13280 }
13281 prev_selection_was_entire_line = is_entire_line;
13282 let mut len = 0;
13283 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13284 text.push_str(chunk);
13285 len += chunk.len();
13286 }
13287 if add_trailing_newline {
13288 text.push('\n');
13289 len += 1;
13290 }
13291 clipboard_selections.push(ClipboardSelection::for_buffer(
13292 len,
13293 is_entire_line,
13294 trimmed_range,
13295 &buffer,
13296 self.project.as_ref(),
13297 cx,
13298 ));
13299 }
13300 }
13301 }
13302
13303 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13304 text,
13305 clipboard_selections,
13306 ));
13307 }
13308
13309 pub fn do_paste(
13310 &mut self,
13311 text: &String,
13312 clipboard_selections: Option<Vec<ClipboardSelection>>,
13313 handle_entire_lines: bool,
13314 window: &mut Window,
13315 cx: &mut Context<Self>,
13316 ) {
13317 if self.read_only(cx) {
13318 return;
13319 }
13320
13321 let clipboard_text = Cow::Borrowed(text.as_str());
13322
13323 self.transact(window, cx, |this, window, cx| {
13324 let had_active_edit_prediction = this.has_active_edit_prediction();
13325 let display_map = this.display_snapshot(cx);
13326 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13327 let cursor_offset = this
13328 .selections
13329 .last::<MultiBufferOffset>(&display_map)
13330 .head();
13331
13332 if let Some(mut clipboard_selections) = clipboard_selections {
13333 let all_selections_were_entire_line =
13334 clipboard_selections.iter().all(|s| s.is_entire_line);
13335 let first_selection_indent_column =
13336 clipboard_selections.first().map(|s| s.first_line_indent);
13337 if clipboard_selections.len() != old_selections.len() {
13338 clipboard_selections.drain(..);
13339 }
13340 let mut auto_indent_on_paste = true;
13341
13342 this.buffer.update(cx, |buffer, cx| {
13343 let snapshot = buffer.read(cx);
13344 auto_indent_on_paste = snapshot
13345 .language_settings_at(cursor_offset, cx)
13346 .auto_indent_on_paste;
13347
13348 let mut start_offset = 0;
13349 let mut edits = Vec::new();
13350 let mut original_indent_columns = Vec::new();
13351 for (ix, selection) in old_selections.iter().enumerate() {
13352 let to_insert;
13353 let entire_line;
13354 let original_indent_column;
13355 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13356 let end_offset = start_offset + clipboard_selection.len;
13357 to_insert = &clipboard_text[start_offset..end_offset];
13358 entire_line = clipboard_selection.is_entire_line;
13359 start_offset = if entire_line {
13360 end_offset
13361 } else {
13362 end_offset + 1
13363 };
13364 original_indent_column = Some(clipboard_selection.first_line_indent);
13365 } else {
13366 to_insert = &*clipboard_text;
13367 entire_line = all_selections_were_entire_line;
13368 original_indent_column = first_selection_indent_column
13369 }
13370
13371 let (range, to_insert) =
13372 if selection.is_empty() && handle_entire_lines && entire_line {
13373 // If the corresponding selection was empty when this slice of the
13374 // clipboard text was written, then the entire line containing the
13375 // selection was copied. If this selection is also currently empty,
13376 // then paste the line before the current line of the buffer.
13377 let column = selection.start.to_point(&snapshot).column as usize;
13378 let line_start = selection.start - column;
13379 (line_start..line_start, Cow::Borrowed(to_insert))
13380 } else {
13381 let language = snapshot.language_at(selection.head());
13382 let range = selection.range();
13383 if let Some(language) = language
13384 && language.name() == "Markdown".into()
13385 {
13386 edit_for_markdown_paste(
13387 &snapshot,
13388 range,
13389 to_insert,
13390 url::Url::parse(to_insert).ok(),
13391 )
13392 } else {
13393 (range, Cow::Borrowed(to_insert))
13394 }
13395 };
13396
13397 edits.push((range, to_insert));
13398 original_indent_columns.push(original_indent_column);
13399 }
13400 drop(snapshot);
13401
13402 buffer.edit(
13403 edits,
13404 if auto_indent_on_paste {
13405 Some(AutoindentMode::Block {
13406 original_indent_columns,
13407 })
13408 } else {
13409 None
13410 },
13411 cx,
13412 );
13413 });
13414
13415 let selections = this
13416 .selections
13417 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13418 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13419 } else {
13420 let url = url::Url::parse(&clipboard_text).ok();
13421
13422 let auto_indent_mode = if !clipboard_text.is_empty() {
13423 Some(AutoindentMode::Block {
13424 original_indent_columns: Vec::new(),
13425 })
13426 } else {
13427 None
13428 };
13429
13430 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13431 let snapshot = buffer.snapshot(cx);
13432
13433 let anchors = old_selections
13434 .iter()
13435 .map(|s| {
13436 let anchor = snapshot.anchor_after(s.head());
13437 s.map(|_| anchor)
13438 })
13439 .collect::<Vec<_>>();
13440
13441 let mut edits = Vec::new();
13442
13443 for selection in old_selections.iter() {
13444 let language = snapshot.language_at(selection.head());
13445 let range = selection.range();
13446
13447 let (edit_range, edit_text) = if let Some(language) = language
13448 && language.name() == "Markdown".into()
13449 {
13450 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13451 } else {
13452 (range, clipboard_text.clone())
13453 };
13454
13455 edits.push((edit_range, edit_text));
13456 }
13457
13458 drop(snapshot);
13459 buffer.edit(edits, auto_indent_mode, cx);
13460
13461 anchors
13462 });
13463
13464 this.change_selections(Default::default(), window, cx, |s| {
13465 s.select_anchors(selection_anchors);
13466 });
13467 }
13468
13469 // 🤔 | .. | show_in_menu |
13470 // | .. | true true
13471 // | had_edit_prediction | false true
13472
13473 let trigger_in_words =
13474 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13475
13476 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13477 });
13478 }
13479
13480 pub fn diff_clipboard_with_selection(
13481 &mut self,
13482 _: &DiffClipboardWithSelection,
13483 window: &mut Window,
13484 cx: &mut Context<Self>,
13485 ) {
13486 let selections = self
13487 .selections
13488 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13489
13490 if selections.is_empty() {
13491 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13492 return;
13493 };
13494
13495 let clipboard_text = match cx.read_from_clipboard() {
13496 Some(item) => match item.entries().first() {
13497 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13498 _ => None,
13499 },
13500 None => None,
13501 };
13502
13503 let Some(clipboard_text) = clipboard_text else {
13504 log::warn!("Clipboard doesn't contain text.");
13505 return;
13506 };
13507
13508 window.dispatch_action(
13509 Box::new(DiffClipboardWithSelectionData {
13510 clipboard_text,
13511 editor: cx.entity(),
13512 }),
13513 cx,
13514 );
13515 }
13516
13517 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13518 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13519 if let Some(item) = cx.read_from_clipboard() {
13520 let entries = item.entries();
13521
13522 match entries.first() {
13523 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13524 // of all the pasted entries.
13525 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13526 .do_paste(
13527 clipboard_string.text(),
13528 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13529 true,
13530 window,
13531 cx,
13532 ),
13533 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13534 }
13535 }
13536 }
13537
13538 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13539 if self.read_only(cx) {
13540 return;
13541 }
13542
13543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13544
13545 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13546 if let Some((selections, _)) =
13547 self.selection_history.transaction(transaction_id).cloned()
13548 {
13549 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13550 s.select_anchors(selections.to_vec());
13551 });
13552 } else {
13553 log::error!(
13554 "No entry in selection_history found for undo. \
13555 This may correspond to a bug where undo does not update the selection. \
13556 If this is occurring, please add details to \
13557 https://github.com/zed-industries/zed/issues/22692"
13558 );
13559 }
13560 self.request_autoscroll(Autoscroll::fit(), cx);
13561 self.unmark_text(window, cx);
13562 self.refresh_edit_prediction(true, false, window, cx);
13563 cx.emit(EditorEvent::Edited { transaction_id });
13564 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13565 }
13566 }
13567
13568 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13569 if self.read_only(cx) {
13570 return;
13571 }
13572
13573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13574
13575 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13576 if let Some((_, Some(selections))) =
13577 self.selection_history.transaction(transaction_id).cloned()
13578 {
13579 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13580 s.select_anchors(selections.to_vec());
13581 });
13582 } else {
13583 log::error!(
13584 "No entry in selection_history found for redo. \
13585 This may correspond to a bug where undo does not update the selection. \
13586 If this is occurring, please add details to \
13587 https://github.com/zed-industries/zed/issues/22692"
13588 );
13589 }
13590 self.request_autoscroll(Autoscroll::fit(), cx);
13591 self.unmark_text(window, cx);
13592 self.refresh_edit_prediction(true, false, window, cx);
13593 cx.emit(EditorEvent::Edited { transaction_id });
13594 }
13595 }
13596
13597 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13598 self.buffer
13599 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13600 }
13601
13602 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13603 self.buffer
13604 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13605 }
13606
13607 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13608 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13609 self.change_selections(Default::default(), window, cx, |s| {
13610 s.move_with(|map, selection| {
13611 let cursor = if selection.is_empty() {
13612 movement::left(map, selection.start)
13613 } else {
13614 selection.start
13615 };
13616 selection.collapse_to(cursor, SelectionGoal::None);
13617 });
13618 })
13619 }
13620
13621 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13622 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13623 self.change_selections(Default::default(), window, cx, |s| {
13624 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13625 })
13626 }
13627
13628 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13629 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13630 self.change_selections(Default::default(), window, cx, |s| {
13631 s.move_with(|map, selection| {
13632 let cursor = if selection.is_empty() {
13633 movement::right(map, selection.end)
13634 } else {
13635 selection.end
13636 };
13637 selection.collapse_to(cursor, SelectionGoal::None)
13638 });
13639 })
13640 }
13641
13642 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13644 self.change_selections(Default::default(), window, cx, |s| {
13645 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13646 });
13647 }
13648
13649 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13650 if self.take_rename(true, window, cx).is_some() {
13651 return;
13652 }
13653
13654 if self.mode.is_single_line() {
13655 cx.propagate();
13656 return;
13657 }
13658
13659 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13660
13661 let text_layout_details = &self.text_layout_details(window);
13662 let selection_count = self.selections.count();
13663 let first_selection = self.selections.first_anchor();
13664
13665 self.change_selections(Default::default(), window, cx, |s| {
13666 s.move_with(|map, selection| {
13667 if !selection.is_empty() {
13668 selection.goal = SelectionGoal::None;
13669 }
13670 let (cursor, goal) = movement::up(
13671 map,
13672 selection.start,
13673 selection.goal,
13674 false,
13675 text_layout_details,
13676 );
13677 selection.collapse_to(cursor, goal);
13678 });
13679 });
13680
13681 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13682 {
13683 cx.propagate();
13684 }
13685 }
13686
13687 pub fn move_up_by_lines(
13688 &mut self,
13689 action: &MoveUpByLines,
13690 window: &mut Window,
13691 cx: &mut Context<Self>,
13692 ) {
13693 if self.take_rename(true, window, cx).is_some() {
13694 return;
13695 }
13696
13697 if self.mode.is_single_line() {
13698 cx.propagate();
13699 return;
13700 }
13701
13702 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13703
13704 let text_layout_details = &self.text_layout_details(window);
13705
13706 self.change_selections(Default::default(), window, cx, |s| {
13707 s.move_with(|map, selection| {
13708 if !selection.is_empty() {
13709 selection.goal = SelectionGoal::None;
13710 }
13711 let (cursor, goal) = movement::up_by_rows(
13712 map,
13713 selection.start,
13714 action.lines,
13715 selection.goal,
13716 false,
13717 text_layout_details,
13718 );
13719 selection.collapse_to(cursor, goal);
13720 });
13721 })
13722 }
13723
13724 pub fn move_down_by_lines(
13725 &mut self,
13726 action: &MoveDownByLines,
13727 window: &mut Window,
13728 cx: &mut Context<Self>,
13729 ) {
13730 if self.take_rename(true, window, cx).is_some() {
13731 return;
13732 }
13733
13734 if self.mode.is_single_line() {
13735 cx.propagate();
13736 return;
13737 }
13738
13739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13740
13741 let text_layout_details = &self.text_layout_details(window);
13742
13743 self.change_selections(Default::default(), window, cx, |s| {
13744 s.move_with(|map, selection| {
13745 if !selection.is_empty() {
13746 selection.goal = SelectionGoal::None;
13747 }
13748 let (cursor, goal) = movement::down_by_rows(
13749 map,
13750 selection.start,
13751 action.lines,
13752 selection.goal,
13753 false,
13754 text_layout_details,
13755 );
13756 selection.collapse_to(cursor, goal);
13757 });
13758 })
13759 }
13760
13761 pub fn select_down_by_lines(
13762 &mut self,
13763 action: &SelectDownByLines,
13764 window: &mut Window,
13765 cx: &mut Context<Self>,
13766 ) {
13767 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13768 let text_layout_details = &self.text_layout_details(window);
13769 self.change_selections(Default::default(), window, cx, |s| {
13770 s.move_heads_with(|map, head, goal| {
13771 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13772 })
13773 })
13774 }
13775
13776 pub fn select_up_by_lines(
13777 &mut self,
13778 action: &SelectUpByLines,
13779 window: &mut Window,
13780 cx: &mut Context<Self>,
13781 ) {
13782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13783 let text_layout_details = &self.text_layout_details(window);
13784 self.change_selections(Default::default(), window, cx, |s| {
13785 s.move_heads_with(|map, head, goal| {
13786 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13787 })
13788 })
13789 }
13790
13791 pub fn select_page_up(
13792 &mut self,
13793 _: &SelectPageUp,
13794 window: &mut Window,
13795 cx: &mut Context<Self>,
13796 ) {
13797 let Some(row_count) = self.visible_row_count() else {
13798 return;
13799 };
13800
13801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13802
13803 let text_layout_details = &self.text_layout_details(window);
13804
13805 self.change_selections(Default::default(), window, cx, |s| {
13806 s.move_heads_with(|map, head, goal| {
13807 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13808 })
13809 })
13810 }
13811
13812 pub fn move_page_up(
13813 &mut self,
13814 action: &MovePageUp,
13815 window: &mut Window,
13816 cx: &mut Context<Self>,
13817 ) {
13818 if self.take_rename(true, window, cx).is_some() {
13819 return;
13820 }
13821
13822 if self
13823 .context_menu
13824 .borrow_mut()
13825 .as_mut()
13826 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13827 .unwrap_or(false)
13828 {
13829 return;
13830 }
13831
13832 if matches!(self.mode, EditorMode::SingleLine) {
13833 cx.propagate();
13834 return;
13835 }
13836
13837 let Some(row_count) = self.visible_row_count() else {
13838 return;
13839 };
13840
13841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13842
13843 let effects = if action.center_cursor {
13844 SelectionEffects::scroll(Autoscroll::center())
13845 } else {
13846 SelectionEffects::default()
13847 };
13848
13849 let text_layout_details = &self.text_layout_details(window);
13850
13851 self.change_selections(effects, window, cx, |s| {
13852 s.move_with(|map, selection| {
13853 if !selection.is_empty() {
13854 selection.goal = SelectionGoal::None;
13855 }
13856 let (cursor, goal) = movement::up_by_rows(
13857 map,
13858 selection.end,
13859 row_count,
13860 selection.goal,
13861 false,
13862 text_layout_details,
13863 );
13864 selection.collapse_to(cursor, goal);
13865 });
13866 });
13867 }
13868
13869 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13870 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13871 let text_layout_details = &self.text_layout_details(window);
13872 self.change_selections(Default::default(), window, cx, |s| {
13873 s.move_heads_with(|map, head, goal| {
13874 movement::up(map, head, goal, false, text_layout_details)
13875 })
13876 })
13877 }
13878
13879 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13880 self.take_rename(true, window, cx);
13881
13882 if self.mode.is_single_line() {
13883 cx.propagate();
13884 return;
13885 }
13886
13887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13888
13889 let text_layout_details = &self.text_layout_details(window);
13890 let selection_count = self.selections.count();
13891 let first_selection = self.selections.first_anchor();
13892
13893 self.change_selections(Default::default(), window, cx, |s| {
13894 s.move_with(|map, selection| {
13895 if !selection.is_empty() {
13896 selection.goal = SelectionGoal::None;
13897 }
13898 let (cursor, goal) = movement::down(
13899 map,
13900 selection.end,
13901 selection.goal,
13902 false,
13903 text_layout_details,
13904 );
13905 selection.collapse_to(cursor, goal);
13906 });
13907 });
13908
13909 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13910 {
13911 cx.propagate();
13912 }
13913 }
13914
13915 pub fn select_page_down(
13916 &mut self,
13917 _: &SelectPageDown,
13918 window: &mut Window,
13919 cx: &mut Context<Self>,
13920 ) {
13921 let Some(row_count) = self.visible_row_count() else {
13922 return;
13923 };
13924
13925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13926
13927 let text_layout_details = &self.text_layout_details(window);
13928
13929 self.change_selections(Default::default(), window, cx, |s| {
13930 s.move_heads_with(|map, head, goal| {
13931 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13932 })
13933 })
13934 }
13935
13936 pub fn move_page_down(
13937 &mut self,
13938 action: &MovePageDown,
13939 window: &mut Window,
13940 cx: &mut Context<Self>,
13941 ) {
13942 if self.take_rename(true, window, cx).is_some() {
13943 return;
13944 }
13945
13946 if self
13947 .context_menu
13948 .borrow_mut()
13949 .as_mut()
13950 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13951 .unwrap_or(false)
13952 {
13953 return;
13954 }
13955
13956 if matches!(self.mode, EditorMode::SingleLine) {
13957 cx.propagate();
13958 return;
13959 }
13960
13961 let Some(row_count) = self.visible_row_count() else {
13962 return;
13963 };
13964
13965 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13966
13967 let effects = if action.center_cursor {
13968 SelectionEffects::scroll(Autoscroll::center())
13969 } else {
13970 SelectionEffects::default()
13971 };
13972
13973 let text_layout_details = &self.text_layout_details(window);
13974 self.change_selections(effects, window, cx, |s| {
13975 s.move_with(|map, selection| {
13976 if !selection.is_empty() {
13977 selection.goal = SelectionGoal::None;
13978 }
13979 let (cursor, goal) = movement::down_by_rows(
13980 map,
13981 selection.end,
13982 row_count,
13983 selection.goal,
13984 false,
13985 text_layout_details,
13986 );
13987 selection.collapse_to(cursor, goal);
13988 });
13989 });
13990 }
13991
13992 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13993 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13994 let text_layout_details = &self.text_layout_details(window);
13995 self.change_selections(Default::default(), window, cx, |s| {
13996 s.move_heads_with(|map, head, goal| {
13997 movement::down(map, head, goal, false, text_layout_details)
13998 })
13999 });
14000 }
14001
14002 pub fn context_menu_first(
14003 &mut self,
14004 _: &ContextMenuFirst,
14005 window: &mut Window,
14006 cx: &mut Context<Self>,
14007 ) {
14008 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14009 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
14010 }
14011 }
14012
14013 pub fn context_menu_prev(
14014 &mut self,
14015 _: &ContextMenuPrevious,
14016 window: &mut Window,
14017 cx: &mut Context<Self>,
14018 ) {
14019 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14020 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14021 }
14022 }
14023
14024 pub fn context_menu_next(
14025 &mut self,
14026 _: &ContextMenuNext,
14027 window: &mut Window,
14028 cx: &mut Context<Self>,
14029 ) {
14030 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14031 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14032 }
14033 }
14034
14035 pub fn context_menu_last(
14036 &mut self,
14037 _: &ContextMenuLast,
14038 window: &mut Window,
14039 cx: &mut Context<Self>,
14040 ) {
14041 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14042 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14043 }
14044 }
14045
14046 pub fn signature_help_prev(
14047 &mut self,
14048 _: &SignatureHelpPrevious,
14049 _: &mut Window,
14050 cx: &mut Context<Self>,
14051 ) {
14052 if let Some(popover) = self.signature_help_state.popover_mut() {
14053 if popover.current_signature == 0 {
14054 popover.current_signature = popover.signatures.len() - 1;
14055 } else {
14056 popover.current_signature -= 1;
14057 }
14058 cx.notify();
14059 }
14060 }
14061
14062 pub fn signature_help_next(
14063 &mut self,
14064 _: &SignatureHelpNext,
14065 _: &mut Window,
14066 cx: &mut Context<Self>,
14067 ) {
14068 if let Some(popover) = self.signature_help_state.popover_mut() {
14069 if popover.current_signature + 1 == popover.signatures.len() {
14070 popover.current_signature = 0;
14071 } else {
14072 popover.current_signature += 1;
14073 }
14074 cx.notify();
14075 }
14076 }
14077
14078 pub fn move_to_previous_word_start(
14079 &mut self,
14080 _: &MoveToPreviousWordStart,
14081 window: &mut Window,
14082 cx: &mut Context<Self>,
14083 ) {
14084 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14085 self.change_selections(Default::default(), window, cx, |s| {
14086 s.move_cursors_with(|map, head, _| {
14087 (
14088 movement::previous_word_start(map, head),
14089 SelectionGoal::None,
14090 )
14091 });
14092 })
14093 }
14094
14095 pub fn move_to_previous_subword_start(
14096 &mut self,
14097 _: &MoveToPreviousSubwordStart,
14098 window: &mut Window,
14099 cx: &mut Context<Self>,
14100 ) {
14101 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14102 self.change_selections(Default::default(), window, cx, |s| {
14103 s.move_cursors_with(|map, head, _| {
14104 (
14105 movement::previous_subword_start(map, head),
14106 SelectionGoal::None,
14107 )
14108 });
14109 })
14110 }
14111
14112 pub fn select_to_previous_word_start(
14113 &mut self,
14114 _: &SelectToPreviousWordStart,
14115 window: &mut Window,
14116 cx: &mut Context<Self>,
14117 ) {
14118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14119 self.change_selections(Default::default(), window, cx, |s| {
14120 s.move_heads_with(|map, head, _| {
14121 (
14122 movement::previous_word_start(map, head),
14123 SelectionGoal::None,
14124 )
14125 });
14126 })
14127 }
14128
14129 pub fn select_to_previous_subword_start(
14130 &mut self,
14131 _: &SelectToPreviousSubwordStart,
14132 window: &mut Window,
14133 cx: &mut Context<Self>,
14134 ) {
14135 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14136 self.change_selections(Default::default(), window, cx, |s| {
14137 s.move_heads_with(|map, head, _| {
14138 (
14139 movement::previous_subword_start(map, head),
14140 SelectionGoal::None,
14141 )
14142 });
14143 })
14144 }
14145
14146 pub fn delete_to_previous_word_start(
14147 &mut self,
14148 action: &DeleteToPreviousWordStart,
14149 window: &mut Window,
14150 cx: &mut Context<Self>,
14151 ) {
14152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14153 self.transact(window, cx, |this, window, cx| {
14154 this.select_autoclose_pair(window, cx);
14155 this.change_selections(Default::default(), window, cx, |s| {
14156 s.move_with(|map, selection| {
14157 if selection.is_empty() {
14158 let mut cursor = if action.ignore_newlines {
14159 movement::previous_word_start(map, selection.head())
14160 } else {
14161 movement::previous_word_start_or_newline(map, selection.head())
14162 };
14163 cursor = movement::adjust_greedy_deletion(
14164 map,
14165 selection.head(),
14166 cursor,
14167 action.ignore_brackets,
14168 );
14169 selection.set_head(cursor, SelectionGoal::None);
14170 }
14171 });
14172 });
14173 this.insert("", window, cx);
14174 });
14175 }
14176
14177 pub fn delete_to_previous_subword_start(
14178 &mut self,
14179 action: &DeleteToPreviousSubwordStart,
14180 window: &mut Window,
14181 cx: &mut Context<Self>,
14182 ) {
14183 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14184 self.transact(window, cx, |this, window, cx| {
14185 this.select_autoclose_pair(window, cx);
14186 this.change_selections(Default::default(), window, cx, |s| {
14187 s.move_with(|map, selection| {
14188 if selection.is_empty() {
14189 let mut cursor = if action.ignore_newlines {
14190 movement::previous_subword_start(map, selection.head())
14191 } else {
14192 movement::previous_subword_start_or_newline(map, selection.head())
14193 };
14194 cursor = movement::adjust_greedy_deletion(
14195 map,
14196 selection.head(),
14197 cursor,
14198 action.ignore_brackets,
14199 );
14200 selection.set_head(cursor, SelectionGoal::None);
14201 }
14202 });
14203 });
14204 this.insert("", window, cx);
14205 });
14206 }
14207
14208 pub fn move_to_next_word_end(
14209 &mut self,
14210 _: &MoveToNextWordEnd,
14211 window: &mut Window,
14212 cx: &mut Context<Self>,
14213 ) {
14214 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14215 self.change_selections(Default::default(), window, cx, |s| {
14216 s.move_cursors_with(|map, head, _| {
14217 (movement::next_word_end(map, head), SelectionGoal::None)
14218 });
14219 })
14220 }
14221
14222 pub fn move_to_next_subword_end(
14223 &mut self,
14224 _: &MoveToNextSubwordEnd,
14225 window: &mut Window,
14226 cx: &mut Context<Self>,
14227 ) {
14228 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14229 self.change_selections(Default::default(), window, cx, |s| {
14230 s.move_cursors_with(|map, head, _| {
14231 (movement::next_subword_end(map, head), SelectionGoal::None)
14232 });
14233 })
14234 }
14235
14236 pub fn select_to_next_word_end(
14237 &mut self,
14238 _: &SelectToNextWordEnd,
14239 window: &mut Window,
14240 cx: &mut Context<Self>,
14241 ) {
14242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14243 self.change_selections(Default::default(), window, cx, |s| {
14244 s.move_heads_with(|map, head, _| {
14245 (movement::next_word_end(map, head), SelectionGoal::None)
14246 });
14247 })
14248 }
14249
14250 pub fn select_to_next_subword_end(
14251 &mut self,
14252 _: &SelectToNextSubwordEnd,
14253 window: &mut Window,
14254 cx: &mut Context<Self>,
14255 ) {
14256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14257 self.change_selections(Default::default(), window, cx, |s| {
14258 s.move_heads_with(|map, head, _| {
14259 (movement::next_subword_end(map, head), SelectionGoal::None)
14260 });
14261 })
14262 }
14263
14264 pub fn delete_to_next_word_end(
14265 &mut self,
14266 action: &DeleteToNextWordEnd,
14267 window: &mut Window,
14268 cx: &mut Context<Self>,
14269 ) {
14270 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14271 self.transact(window, cx, |this, window, cx| {
14272 this.change_selections(Default::default(), window, cx, |s| {
14273 s.move_with(|map, selection| {
14274 if selection.is_empty() {
14275 let mut cursor = if action.ignore_newlines {
14276 movement::next_word_end(map, selection.head())
14277 } else {
14278 movement::next_word_end_or_newline(map, selection.head())
14279 };
14280 cursor = movement::adjust_greedy_deletion(
14281 map,
14282 selection.head(),
14283 cursor,
14284 action.ignore_brackets,
14285 );
14286 selection.set_head(cursor, SelectionGoal::None);
14287 }
14288 });
14289 });
14290 this.insert("", window, cx);
14291 });
14292 }
14293
14294 pub fn delete_to_next_subword_end(
14295 &mut self,
14296 action: &DeleteToNextSubwordEnd,
14297 window: &mut Window,
14298 cx: &mut Context<Self>,
14299 ) {
14300 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14301 self.transact(window, cx, |this, window, cx| {
14302 this.change_selections(Default::default(), window, cx, |s| {
14303 s.move_with(|map, selection| {
14304 if selection.is_empty() {
14305 let mut cursor = if action.ignore_newlines {
14306 movement::next_subword_end(map, selection.head())
14307 } else {
14308 movement::next_subword_end_or_newline(map, selection.head())
14309 };
14310 cursor = movement::adjust_greedy_deletion(
14311 map,
14312 selection.head(),
14313 cursor,
14314 action.ignore_brackets,
14315 );
14316 selection.set_head(cursor, SelectionGoal::None);
14317 }
14318 });
14319 });
14320 this.insert("", window, cx);
14321 });
14322 }
14323
14324 pub fn move_to_beginning_of_line(
14325 &mut self,
14326 action: &MoveToBeginningOfLine,
14327 window: &mut Window,
14328 cx: &mut Context<Self>,
14329 ) {
14330 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14331 self.change_selections(Default::default(), window, cx, |s| {
14332 s.move_cursors_with(|map, head, _| {
14333 (
14334 movement::indented_line_beginning(
14335 map,
14336 head,
14337 action.stop_at_soft_wraps,
14338 action.stop_at_indent,
14339 ),
14340 SelectionGoal::None,
14341 )
14342 });
14343 })
14344 }
14345
14346 pub fn select_to_beginning_of_line(
14347 &mut self,
14348 action: &SelectToBeginningOfLine,
14349 window: &mut Window,
14350 cx: &mut Context<Self>,
14351 ) {
14352 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14353 self.change_selections(Default::default(), window, cx, |s| {
14354 s.move_heads_with(|map, head, _| {
14355 (
14356 movement::indented_line_beginning(
14357 map,
14358 head,
14359 action.stop_at_soft_wraps,
14360 action.stop_at_indent,
14361 ),
14362 SelectionGoal::None,
14363 )
14364 });
14365 });
14366 }
14367
14368 pub fn delete_to_beginning_of_line(
14369 &mut self,
14370 action: &DeleteToBeginningOfLine,
14371 window: &mut Window,
14372 cx: &mut Context<Self>,
14373 ) {
14374 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14375 self.transact(window, cx, |this, window, cx| {
14376 this.change_selections(Default::default(), window, cx, |s| {
14377 s.move_with(|_, selection| {
14378 selection.reversed = true;
14379 });
14380 });
14381
14382 this.select_to_beginning_of_line(
14383 &SelectToBeginningOfLine {
14384 stop_at_soft_wraps: false,
14385 stop_at_indent: action.stop_at_indent,
14386 },
14387 window,
14388 cx,
14389 );
14390 this.backspace(&Backspace, window, cx);
14391 });
14392 }
14393
14394 pub fn move_to_end_of_line(
14395 &mut self,
14396 action: &MoveToEndOfLine,
14397 window: &mut Window,
14398 cx: &mut Context<Self>,
14399 ) {
14400 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14401 self.change_selections(Default::default(), window, cx, |s| {
14402 s.move_cursors_with(|map, head, _| {
14403 (
14404 movement::line_end(map, head, action.stop_at_soft_wraps),
14405 SelectionGoal::None,
14406 )
14407 });
14408 })
14409 }
14410
14411 pub fn select_to_end_of_line(
14412 &mut self,
14413 action: &SelectToEndOfLine,
14414 window: &mut Window,
14415 cx: &mut Context<Self>,
14416 ) {
14417 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14418 self.change_selections(Default::default(), window, cx, |s| {
14419 s.move_heads_with(|map, head, _| {
14420 (
14421 movement::line_end(map, head, action.stop_at_soft_wraps),
14422 SelectionGoal::None,
14423 )
14424 });
14425 })
14426 }
14427
14428 pub fn delete_to_end_of_line(
14429 &mut self,
14430 _: &DeleteToEndOfLine,
14431 window: &mut Window,
14432 cx: &mut Context<Self>,
14433 ) {
14434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14435 self.transact(window, cx, |this, window, cx| {
14436 this.select_to_end_of_line(
14437 &SelectToEndOfLine {
14438 stop_at_soft_wraps: false,
14439 },
14440 window,
14441 cx,
14442 );
14443 this.delete(&Delete, window, cx);
14444 });
14445 }
14446
14447 pub fn cut_to_end_of_line(
14448 &mut self,
14449 action: &CutToEndOfLine,
14450 window: &mut Window,
14451 cx: &mut Context<Self>,
14452 ) {
14453 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14454 self.transact(window, cx, |this, window, cx| {
14455 this.select_to_end_of_line(
14456 &SelectToEndOfLine {
14457 stop_at_soft_wraps: false,
14458 },
14459 window,
14460 cx,
14461 );
14462 if !action.stop_at_newlines {
14463 this.change_selections(Default::default(), window, cx, |s| {
14464 s.move_with(|_, sel| {
14465 if sel.is_empty() {
14466 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14467 }
14468 });
14469 });
14470 }
14471 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14472 let item = this.cut_common(false, window, cx);
14473 cx.write_to_clipboard(item);
14474 });
14475 }
14476
14477 pub fn move_to_start_of_paragraph(
14478 &mut self,
14479 _: &MoveToStartOfParagraph,
14480 window: &mut Window,
14481 cx: &mut Context<Self>,
14482 ) {
14483 if matches!(self.mode, EditorMode::SingleLine) {
14484 cx.propagate();
14485 return;
14486 }
14487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14488 self.change_selections(Default::default(), window, cx, |s| {
14489 s.move_with(|map, selection| {
14490 selection.collapse_to(
14491 movement::start_of_paragraph(map, selection.head(), 1),
14492 SelectionGoal::None,
14493 )
14494 });
14495 })
14496 }
14497
14498 pub fn move_to_end_of_paragraph(
14499 &mut self,
14500 _: &MoveToEndOfParagraph,
14501 window: &mut Window,
14502 cx: &mut Context<Self>,
14503 ) {
14504 if matches!(self.mode, EditorMode::SingleLine) {
14505 cx.propagate();
14506 return;
14507 }
14508 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14509 self.change_selections(Default::default(), window, cx, |s| {
14510 s.move_with(|map, selection| {
14511 selection.collapse_to(
14512 movement::end_of_paragraph(map, selection.head(), 1),
14513 SelectionGoal::None,
14514 )
14515 });
14516 })
14517 }
14518
14519 pub fn select_to_start_of_paragraph(
14520 &mut self,
14521 _: &SelectToStartOfParagraph,
14522 window: &mut Window,
14523 cx: &mut Context<Self>,
14524 ) {
14525 if matches!(self.mode, EditorMode::SingleLine) {
14526 cx.propagate();
14527 return;
14528 }
14529 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14530 self.change_selections(Default::default(), window, cx, |s| {
14531 s.move_heads_with(|map, head, _| {
14532 (
14533 movement::start_of_paragraph(map, head, 1),
14534 SelectionGoal::None,
14535 )
14536 });
14537 })
14538 }
14539
14540 pub fn select_to_end_of_paragraph(
14541 &mut self,
14542 _: &SelectToEndOfParagraph,
14543 window: &mut Window,
14544 cx: &mut Context<Self>,
14545 ) {
14546 if matches!(self.mode, EditorMode::SingleLine) {
14547 cx.propagate();
14548 return;
14549 }
14550 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14551 self.change_selections(Default::default(), window, cx, |s| {
14552 s.move_heads_with(|map, head, _| {
14553 (
14554 movement::end_of_paragraph(map, head, 1),
14555 SelectionGoal::None,
14556 )
14557 });
14558 })
14559 }
14560
14561 pub fn move_to_start_of_excerpt(
14562 &mut self,
14563 _: &MoveToStartOfExcerpt,
14564 window: &mut Window,
14565 cx: &mut Context<Self>,
14566 ) {
14567 if matches!(self.mode, EditorMode::SingleLine) {
14568 cx.propagate();
14569 return;
14570 }
14571 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14572 self.change_selections(Default::default(), window, cx, |s| {
14573 s.move_with(|map, selection| {
14574 selection.collapse_to(
14575 movement::start_of_excerpt(
14576 map,
14577 selection.head(),
14578 workspace::searchable::Direction::Prev,
14579 ),
14580 SelectionGoal::None,
14581 )
14582 });
14583 })
14584 }
14585
14586 pub fn move_to_start_of_next_excerpt(
14587 &mut self,
14588 _: &MoveToStartOfNextExcerpt,
14589 window: &mut Window,
14590 cx: &mut Context<Self>,
14591 ) {
14592 if matches!(self.mode, EditorMode::SingleLine) {
14593 cx.propagate();
14594 return;
14595 }
14596
14597 self.change_selections(Default::default(), window, cx, |s| {
14598 s.move_with(|map, selection| {
14599 selection.collapse_to(
14600 movement::start_of_excerpt(
14601 map,
14602 selection.head(),
14603 workspace::searchable::Direction::Next,
14604 ),
14605 SelectionGoal::None,
14606 )
14607 });
14608 })
14609 }
14610
14611 pub fn move_to_end_of_excerpt(
14612 &mut self,
14613 _: &MoveToEndOfExcerpt,
14614 window: &mut Window,
14615 cx: &mut Context<Self>,
14616 ) {
14617 if matches!(self.mode, EditorMode::SingleLine) {
14618 cx.propagate();
14619 return;
14620 }
14621 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14622 self.change_selections(Default::default(), window, cx, |s| {
14623 s.move_with(|map, selection| {
14624 selection.collapse_to(
14625 movement::end_of_excerpt(
14626 map,
14627 selection.head(),
14628 workspace::searchable::Direction::Next,
14629 ),
14630 SelectionGoal::None,
14631 )
14632 });
14633 })
14634 }
14635
14636 pub fn move_to_end_of_previous_excerpt(
14637 &mut self,
14638 _: &MoveToEndOfPreviousExcerpt,
14639 window: &mut Window,
14640 cx: &mut Context<Self>,
14641 ) {
14642 if matches!(self.mode, EditorMode::SingleLine) {
14643 cx.propagate();
14644 return;
14645 }
14646 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14647 self.change_selections(Default::default(), window, cx, |s| {
14648 s.move_with(|map, selection| {
14649 selection.collapse_to(
14650 movement::end_of_excerpt(
14651 map,
14652 selection.head(),
14653 workspace::searchable::Direction::Prev,
14654 ),
14655 SelectionGoal::None,
14656 )
14657 });
14658 })
14659 }
14660
14661 pub fn select_to_start_of_excerpt(
14662 &mut self,
14663 _: &SelectToStartOfExcerpt,
14664 window: &mut Window,
14665 cx: &mut Context<Self>,
14666 ) {
14667 if matches!(self.mode, EditorMode::SingleLine) {
14668 cx.propagate();
14669 return;
14670 }
14671 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14672 self.change_selections(Default::default(), window, cx, |s| {
14673 s.move_heads_with(|map, head, _| {
14674 (
14675 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14676 SelectionGoal::None,
14677 )
14678 });
14679 })
14680 }
14681
14682 pub fn select_to_start_of_next_excerpt(
14683 &mut self,
14684 _: &SelectToStartOfNextExcerpt,
14685 window: &mut Window,
14686 cx: &mut Context<Self>,
14687 ) {
14688 if matches!(self.mode, EditorMode::SingleLine) {
14689 cx.propagate();
14690 return;
14691 }
14692 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14693 self.change_selections(Default::default(), window, cx, |s| {
14694 s.move_heads_with(|map, head, _| {
14695 (
14696 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14697 SelectionGoal::None,
14698 )
14699 });
14700 })
14701 }
14702
14703 pub fn select_to_end_of_excerpt(
14704 &mut self,
14705 _: &SelectToEndOfExcerpt,
14706 window: &mut Window,
14707 cx: &mut Context<Self>,
14708 ) {
14709 if matches!(self.mode, EditorMode::SingleLine) {
14710 cx.propagate();
14711 return;
14712 }
14713 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14714 self.change_selections(Default::default(), window, cx, |s| {
14715 s.move_heads_with(|map, head, _| {
14716 (
14717 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14718 SelectionGoal::None,
14719 )
14720 });
14721 })
14722 }
14723
14724 pub fn select_to_end_of_previous_excerpt(
14725 &mut self,
14726 _: &SelectToEndOfPreviousExcerpt,
14727 window: &mut Window,
14728 cx: &mut Context<Self>,
14729 ) {
14730 if matches!(self.mode, EditorMode::SingleLine) {
14731 cx.propagate();
14732 return;
14733 }
14734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14735 self.change_selections(Default::default(), window, cx, |s| {
14736 s.move_heads_with(|map, head, _| {
14737 (
14738 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14739 SelectionGoal::None,
14740 )
14741 });
14742 })
14743 }
14744
14745 pub fn move_to_beginning(
14746 &mut self,
14747 _: &MoveToBeginning,
14748 window: &mut Window,
14749 cx: &mut Context<Self>,
14750 ) {
14751 if matches!(self.mode, EditorMode::SingleLine) {
14752 cx.propagate();
14753 return;
14754 }
14755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14756 self.change_selections(Default::default(), window, cx, |s| {
14757 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14758 });
14759 }
14760
14761 pub fn select_to_beginning(
14762 &mut self,
14763 _: &SelectToBeginning,
14764 window: &mut Window,
14765 cx: &mut Context<Self>,
14766 ) {
14767 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14768 selection.set_head(Point::zero(), SelectionGoal::None);
14769 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14770 self.change_selections(Default::default(), window, cx, |s| {
14771 s.select(vec![selection]);
14772 });
14773 }
14774
14775 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14776 if matches!(self.mode, EditorMode::SingleLine) {
14777 cx.propagate();
14778 return;
14779 }
14780 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14781 let cursor = self.buffer.read(cx).read(cx).len();
14782 self.change_selections(Default::default(), window, cx, |s| {
14783 s.select_ranges(vec![cursor..cursor])
14784 });
14785 }
14786
14787 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14788 self.nav_history = nav_history;
14789 }
14790
14791 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14792 self.nav_history.as_ref()
14793 }
14794
14795 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14796 self.push_to_nav_history(
14797 self.selections.newest_anchor().head(),
14798 None,
14799 false,
14800 true,
14801 cx,
14802 );
14803 }
14804
14805 fn push_to_nav_history(
14806 &mut self,
14807 cursor_anchor: Anchor,
14808 new_position: Option<Point>,
14809 is_deactivate: bool,
14810 always: bool,
14811 cx: &mut Context<Self>,
14812 ) {
14813 if let Some(nav_history) = self.nav_history.as_mut() {
14814 let buffer = self.buffer.read(cx).read(cx);
14815 let cursor_position = cursor_anchor.to_point(&buffer);
14816 let scroll_state = self.scroll_manager.anchor();
14817 let scroll_top_row = scroll_state.top_row(&buffer);
14818 drop(buffer);
14819
14820 if let Some(new_position) = new_position {
14821 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14822 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14823 return;
14824 }
14825 }
14826
14827 nav_history.push(
14828 Some(NavigationData {
14829 cursor_anchor,
14830 cursor_position,
14831 scroll_anchor: scroll_state,
14832 scroll_top_row,
14833 }),
14834 cx,
14835 );
14836 cx.emit(EditorEvent::PushedToNavHistory {
14837 anchor: cursor_anchor,
14838 is_deactivate,
14839 })
14840 }
14841 }
14842
14843 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14844 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14845 let buffer = self.buffer.read(cx).snapshot(cx);
14846 let mut selection = self
14847 .selections
14848 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14849 selection.set_head(buffer.len(), SelectionGoal::None);
14850 self.change_selections(Default::default(), window, cx, |s| {
14851 s.select(vec![selection]);
14852 });
14853 }
14854
14855 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14856 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14857 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14858 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14859 });
14860 }
14861
14862 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14863 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14864 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14865 let mut selections = self.selections.all::<Point>(&display_map);
14866 let max_point = display_map.buffer_snapshot().max_point();
14867 for selection in &mut selections {
14868 let rows = selection.spanned_rows(true, &display_map);
14869 selection.start = Point::new(rows.start.0, 0);
14870 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14871 selection.reversed = false;
14872 }
14873 self.change_selections(Default::default(), window, cx, |s| {
14874 s.select(selections);
14875 });
14876 }
14877
14878 pub fn split_selection_into_lines(
14879 &mut self,
14880 action: &SplitSelectionIntoLines,
14881 window: &mut Window,
14882 cx: &mut Context<Self>,
14883 ) {
14884 let selections = self
14885 .selections
14886 .all::<Point>(&self.display_snapshot(cx))
14887 .into_iter()
14888 .map(|selection| selection.start..selection.end)
14889 .collect::<Vec<_>>();
14890 self.unfold_ranges(&selections, true, true, cx);
14891
14892 let mut new_selection_ranges = Vec::new();
14893 {
14894 let buffer = self.buffer.read(cx).read(cx);
14895 for selection in selections {
14896 for row in selection.start.row..selection.end.row {
14897 let line_start = Point::new(row, 0);
14898 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14899
14900 if action.keep_selections {
14901 // Keep the selection range for each line
14902 let selection_start = if row == selection.start.row {
14903 selection.start
14904 } else {
14905 line_start
14906 };
14907 new_selection_ranges.push(selection_start..line_end);
14908 } else {
14909 // Collapse to cursor at end of line
14910 new_selection_ranges.push(line_end..line_end);
14911 }
14912 }
14913
14914 let is_multiline_selection = selection.start.row != selection.end.row;
14915 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14916 // so this action feels more ergonomic when paired with other selection operations
14917 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14918 if !should_skip_last {
14919 if action.keep_selections {
14920 if is_multiline_selection {
14921 let line_start = Point::new(selection.end.row, 0);
14922 new_selection_ranges.push(line_start..selection.end);
14923 } else {
14924 new_selection_ranges.push(selection.start..selection.end);
14925 }
14926 } else {
14927 new_selection_ranges.push(selection.end..selection.end);
14928 }
14929 }
14930 }
14931 }
14932 self.change_selections(Default::default(), window, cx, |s| {
14933 s.select_ranges(new_selection_ranges);
14934 });
14935 }
14936
14937 pub fn add_selection_above(
14938 &mut self,
14939 action: &AddSelectionAbove,
14940 window: &mut Window,
14941 cx: &mut Context<Self>,
14942 ) {
14943 self.add_selection(true, action.skip_soft_wrap, window, cx);
14944 }
14945
14946 pub fn add_selection_below(
14947 &mut self,
14948 action: &AddSelectionBelow,
14949 window: &mut Window,
14950 cx: &mut Context<Self>,
14951 ) {
14952 self.add_selection(false, action.skip_soft_wrap, window, cx);
14953 }
14954
14955 fn add_selection(
14956 &mut self,
14957 above: bool,
14958 skip_soft_wrap: bool,
14959 window: &mut Window,
14960 cx: &mut Context<Self>,
14961 ) {
14962 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14963
14964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14965 let all_selections = self.selections.all::<Point>(&display_map);
14966 let text_layout_details = self.text_layout_details(window);
14967
14968 let (mut columnar_selections, new_selections_to_columnarize) = {
14969 if let Some(state) = self.add_selections_state.as_ref() {
14970 let columnar_selection_ids: HashSet<_> = state
14971 .groups
14972 .iter()
14973 .flat_map(|group| group.stack.iter())
14974 .copied()
14975 .collect();
14976
14977 all_selections
14978 .into_iter()
14979 .partition(|s| columnar_selection_ids.contains(&s.id))
14980 } else {
14981 (Vec::new(), all_selections)
14982 }
14983 };
14984
14985 let mut state = self
14986 .add_selections_state
14987 .take()
14988 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14989
14990 for selection in new_selections_to_columnarize {
14991 let range = selection.display_range(&display_map).sorted();
14992 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14993 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14994 let positions = start_x.min(end_x)..start_x.max(end_x);
14995 let mut stack = Vec::new();
14996 for row in range.start.row().0..=range.end.row().0 {
14997 if let Some(selection) = self.selections.build_columnar_selection(
14998 &display_map,
14999 DisplayRow(row),
15000 &positions,
15001 selection.reversed,
15002 &text_layout_details,
15003 ) {
15004 stack.push(selection.id);
15005 columnar_selections.push(selection);
15006 }
15007 }
15008 if !stack.is_empty() {
15009 if above {
15010 stack.reverse();
15011 }
15012 state.groups.push(AddSelectionsGroup { above, stack });
15013 }
15014 }
15015
15016 let mut final_selections = Vec::new();
15017 let end_row = if above {
15018 DisplayRow(0)
15019 } else {
15020 display_map.max_point().row()
15021 };
15022
15023 let mut last_added_item_per_group = HashMap::default();
15024 for group in state.groups.iter_mut() {
15025 if let Some(last_id) = group.stack.last() {
15026 last_added_item_per_group.insert(*last_id, group);
15027 }
15028 }
15029
15030 for selection in columnar_selections {
15031 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
15032 if above == group.above {
15033 let range = selection.display_range(&display_map).sorted();
15034 debug_assert_eq!(range.start.row(), range.end.row());
15035 let mut row = range.start.row();
15036 let positions =
15037 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15038 Pixels::from(start)..Pixels::from(end)
15039 } else {
15040 let start_x =
15041 display_map.x_for_display_point(range.start, &text_layout_details);
15042 let end_x =
15043 display_map.x_for_display_point(range.end, &text_layout_details);
15044 start_x.min(end_x)..start_x.max(end_x)
15045 };
15046
15047 let mut maybe_new_selection = None;
15048 let direction = if above { -1 } else { 1 };
15049
15050 while row != end_row {
15051 let new_buffer_row = if skip_soft_wrap {
15052 let new_row = display_map
15053 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction);
15054 row = new_row.row();
15055 Some(new_row.to_point(&display_map).row)
15056 } else {
15057 if above {
15058 row.0 -= 1;
15059 } else {
15060 row.0 += 1;
15061 }
15062 None
15063 };
15064
15065 let new_selection = if let Some(buffer_row) = new_buffer_row {
15066 let start_col = selection.start.column;
15067 let end_col = selection.end.column;
15068 let buffer_columns = start_col.min(end_col)..start_col.max(end_col);
15069
15070 self.selections
15071 .build_columnar_selection_from_buffer_columns(
15072 &display_map,
15073 buffer_row,
15074 &buffer_columns,
15075 selection.reversed,
15076 &text_layout_details,
15077 )
15078 } else {
15079 self.selections.build_columnar_selection(
15080 &display_map,
15081 row,
15082 &positions,
15083 selection.reversed,
15084 &text_layout_details,
15085 )
15086 };
15087
15088 if let Some(new_selection) = new_selection {
15089 maybe_new_selection = Some(new_selection);
15090 break;
15091 }
15092 }
15093
15094 if let Some(new_selection) = maybe_new_selection {
15095 group.stack.push(new_selection.id);
15096 if above {
15097 final_selections.push(new_selection);
15098 final_selections.push(selection);
15099 } else {
15100 final_selections.push(selection);
15101 final_selections.push(new_selection);
15102 }
15103 } else {
15104 final_selections.push(selection);
15105 }
15106 } else {
15107 group.stack.pop();
15108 }
15109 } else {
15110 final_selections.push(selection);
15111 }
15112 }
15113
15114 self.change_selections(Default::default(), window, cx, |s| {
15115 s.select(final_selections);
15116 });
15117
15118 let final_selection_ids: HashSet<_> = self
15119 .selections
15120 .all::<Point>(&display_map)
15121 .iter()
15122 .map(|s| s.id)
15123 .collect();
15124 state.groups.retain_mut(|group| {
15125 // selections might get merged above so we remove invalid items from stacks
15126 group.stack.retain(|id| final_selection_ids.contains(id));
15127
15128 // single selection in stack can be treated as initial state
15129 group.stack.len() > 1
15130 });
15131
15132 if !state.groups.is_empty() {
15133 self.add_selections_state = Some(state);
15134 }
15135 }
15136
15137 pub fn insert_snippet_at_selections(
15138 &mut self,
15139 action: &InsertSnippet,
15140 window: &mut Window,
15141 cx: &mut Context<Self>,
15142 ) {
15143 self.try_insert_snippet_at_selections(action, window, cx)
15144 .log_err();
15145 }
15146
15147 fn try_insert_snippet_at_selections(
15148 &mut self,
15149 action: &InsertSnippet,
15150 window: &mut Window,
15151 cx: &mut Context<Self>,
15152 ) -> Result<()> {
15153 let insertion_ranges = self
15154 .selections
15155 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15156 .into_iter()
15157 .map(|selection| selection.range())
15158 .collect_vec();
15159
15160 let snippet = if let Some(snippet_body) = &action.snippet {
15161 if action.language.is_none() && action.name.is_none() {
15162 Snippet::parse(snippet_body)?
15163 } else {
15164 bail!("`snippet` is mutually exclusive with `language` and `name`")
15165 }
15166 } else if let Some(name) = &action.name {
15167 let project = self.project().context("no project")?;
15168 let snippet_store = project.read(cx).snippets().read(cx);
15169 let snippet = snippet_store
15170 .snippets_for(action.language.clone(), cx)
15171 .into_iter()
15172 .find(|snippet| snippet.name == *name)
15173 .context("snippet not found")?;
15174 Snippet::parse(&snippet.body)?
15175 } else {
15176 // todo(andrew): open modal to select snippet
15177 bail!("`name` or `snippet` is required")
15178 };
15179
15180 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15181 }
15182
15183 fn select_match_ranges(
15184 &mut self,
15185 range: Range<MultiBufferOffset>,
15186 reversed: bool,
15187 replace_newest: bool,
15188 auto_scroll: Option<Autoscroll>,
15189 window: &mut Window,
15190 cx: &mut Context<Editor>,
15191 ) {
15192 self.unfold_ranges(
15193 std::slice::from_ref(&range),
15194 false,
15195 auto_scroll.is_some(),
15196 cx,
15197 );
15198 let effects = if let Some(scroll) = auto_scroll {
15199 SelectionEffects::scroll(scroll)
15200 } else {
15201 SelectionEffects::no_scroll()
15202 };
15203 self.change_selections(effects, window, cx, |s| {
15204 if replace_newest {
15205 s.delete(s.newest_anchor().id);
15206 }
15207 if reversed {
15208 s.insert_range(range.end..range.start);
15209 } else {
15210 s.insert_range(range);
15211 }
15212 });
15213 }
15214
15215 pub fn select_next_match_internal(
15216 &mut self,
15217 display_map: &DisplaySnapshot,
15218 replace_newest: bool,
15219 autoscroll: Option<Autoscroll>,
15220 window: &mut Window,
15221 cx: &mut Context<Self>,
15222 ) -> Result<()> {
15223 let buffer = display_map.buffer_snapshot();
15224 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15225 if let Some(mut select_next_state) = self.select_next_state.take() {
15226 let query = &select_next_state.query;
15227 if !select_next_state.done {
15228 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15229 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15230 let mut next_selected_range = None;
15231
15232 let bytes_after_last_selection =
15233 buffer.bytes_in_range(last_selection.end..buffer.len());
15234 let bytes_before_first_selection =
15235 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15236 let query_matches = query
15237 .stream_find_iter(bytes_after_last_selection)
15238 .map(|result| (last_selection.end, result))
15239 .chain(
15240 query
15241 .stream_find_iter(bytes_before_first_selection)
15242 .map(|result| (MultiBufferOffset(0), result)),
15243 );
15244
15245 for (start_offset, query_match) in query_matches {
15246 let query_match = query_match.unwrap(); // can only fail due to I/O
15247 let offset_range =
15248 start_offset + query_match.start()..start_offset + query_match.end();
15249
15250 if !select_next_state.wordwise
15251 || (!buffer.is_inside_word(offset_range.start, None)
15252 && !buffer.is_inside_word(offset_range.end, None))
15253 {
15254 let idx = selections
15255 .partition_point(|selection| selection.end <= offset_range.start);
15256 let overlaps = selections
15257 .get(idx)
15258 .map_or(false, |selection| selection.start < offset_range.end);
15259
15260 if !overlaps {
15261 next_selected_range = Some(offset_range);
15262 break;
15263 }
15264 }
15265 }
15266
15267 if let Some(next_selected_range) = next_selected_range {
15268 self.select_match_ranges(
15269 next_selected_range,
15270 last_selection.reversed,
15271 replace_newest,
15272 autoscroll,
15273 window,
15274 cx,
15275 );
15276 } else {
15277 select_next_state.done = true;
15278 }
15279 }
15280
15281 self.select_next_state = Some(select_next_state);
15282 } else {
15283 let mut only_carets = true;
15284 let mut same_text_selected = true;
15285 let mut selected_text = None;
15286
15287 let mut selections_iter = selections.iter().peekable();
15288 while let Some(selection) = selections_iter.next() {
15289 if selection.start != selection.end {
15290 only_carets = false;
15291 }
15292
15293 if same_text_selected {
15294 if selected_text.is_none() {
15295 selected_text =
15296 Some(buffer.text_for_range(selection.range()).collect::<String>());
15297 }
15298
15299 if let Some(next_selection) = selections_iter.peek() {
15300 if next_selection.len() == selection.len() {
15301 let next_selected_text = buffer
15302 .text_for_range(next_selection.range())
15303 .collect::<String>();
15304 if Some(next_selected_text) != selected_text {
15305 same_text_selected = false;
15306 selected_text = None;
15307 }
15308 } else {
15309 same_text_selected = false;
15310 selected_text = None;
15311 }
15312 }
15313 }
15314 }
15315
15316 if only_carets {
15317 for selection in &mut selections {
15318 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15319 selection.start = word_range.start;
15320 selection.end = word_range.end;
15321 selection.goal = SelectionGoal::None;
15322 selection.reversed = false;
15323 self.select_match_ranges(
15324 selection.start..selection.end,
15325 selection.reversed,
15326 replace_newest,
15327 autoscroll,
15328 window,
15329 cx,
15330 );
15331 }
15332
15333 if selections.len() == 1 {
15334 let selection = selections
15335 .last()
15336 .expect("ensured that there's only one selection");
15337 let query = buffer
15338 .text_for_range(selection.start..selection.end)
15339 .collect::<String>();
15340 let is_empty = query.is_empty();
15341 let select_state = SelectNextState {
15342 query: self.build_query(&[query], cx)?,
15343 wordwise: true,
15344 done: is_empty,
15345 };
15346 self.select_next_state = Some(select_state);
15347 } else {
15348 self.select_next_state = None;
15349 }
15350 } else if let Some(selected_text) = selected_text {
15351 self.select_next_state = Some(SelectNextState {
15352 query: self.build_query(&[selected_text], cx)?,
15353 wordwise: false,
15354 done: false,
15355 });
15356 self.select_next_match_internal(
15357 display_map,
15358 replace_newest,
15359 autoscroll,
15360 window,
15361 cx,
15362 )?;
15363 }
15364 }
15365 Ok(())
15366 }
15367
15368 pub fn select_all_matches(
15369 &mut self,
15370 _action: &SelectAllMatches,
15371 window: &mut Window,
15372 cx: &mut Context<Self>,
15373 ) -> Result<()> {
15374 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15375
15376 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15377
15378 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15379 let Some(select_next_state) = self.select_next_state.as_mut().filter(|state| !state.done)
15380 else {
15381 return Ok(());
15382 };
15383
15384 let mut new_selections = Vec::new();
15385
15386 let reversed = self
15387 .selections
15388 .oldest::<MultiBufferOffset>(&display_map)
15389 .reversed;
15390 let buffer = display_map.buffer_snapshot();
15391 let query_matches = select_next_state
15392 .query
15393 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15394
15395 for query_match in query_matches.into_iter() {
15396 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15397 let offset_range = if reversed {
15398 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15399 } else {
15400 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15401 };
15402
15403 if !select_next_state.wordwise
15404 || (!buffer.is_inside_word(offset_range.start, None)
15405 && !buffer.is_inside_word(offset_range.end, None))
15406 {
15407 new_selections.push(offset_range.start..offset_range.end);
15408 }
15409 }
15410
15411 select_next_state.done = true;
15412
15413 if new_selections.is_empty() {
15414 log::error!("bug: new_selections is empty in select_all_matches");
15415 return Ok(());
15416 }
15417
15418 self.unfold_ranges(&new_selections, false, false, cx);
15419 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15420 selections.select_ranges(new_selections)
15421 });
15422
15423 Ok(())
15424 }
15425
15426 pub fn select_next(
15427 &mut self,
15428 action: &SelectNext,
15429 window: &mut Window,
15430 cx: &mut Context<Self>,
15431 ) -> Result<()> {
15432 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15433 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15434 self.select_next_match_internal(
15435 &display_map,
15436 action.replace_newest,
15437 Some(Autoscroll::newest()),
15438 window,
15439 cx,
15440 )
15441 }
15442
15443 pub fn select_previous(
15444 &mut self,
15445 action: &SelectPrevious,
15446 window: &mut Window,
15447 cx: &mut Context<Self>,
15448 ) -> Result<()> {
15449 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15450 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15451 let buffer = display_map.buffer_snapshot();
15452 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15453 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15454 let query = &select_prev_state.query;
15455 if !select_prev_state.done {
15456 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15457 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15458 let mut next_selected_range = None;
15459 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15460 let bytes_before_last_selection =
15461 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15462 let bytes_after_first_selection =
15463 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15464 let query_matches = query
15465 .stream_find_iter(bytes_before_last_selection)
15466 .map(|result| (last_selection.start, result))
15467 .chain(
15468 query
15469 .stream_find_iter(bytes_after_first_selection)
15470 .map(|result| (buffer.len(), result)),
15471 );
15472 for (end_offset, query_match) in query_matches {
15473 let query_match = query_match.unwrap(); // can only fail due to I/O
15474 let offset_range =
15475 end_offset - query_match.end()..end_offset - query_match.start();
15476
15477 if !select_prev_state.wordwise
15478 || (!buffer.is_inside_word(offset_range.start, None)
15479 && !buffer.is_inside_word(offset_range.end, None))
15480 {
15481 next_selected_range = Some(offset_range);
15482 break;
15483 }
15484 }
15485
15486 if let Some(next_selected_range) = next_selected_range {
15487 self.select_match_ranges(
15488 next_selected_range,
15489 last_selection.reversed,
15490 action.replace_newest,
15491 Some(Autoscroll::newest()),
15492 window,
15493 cx,
15494 );
15495 } else {
15496 select_prev_state.done = true;
15497 }
15498 }
15499
15500 self.select_prev_state = Some(select_prev_state);
15501 } else {
15502 let mut only_carets = true;
15503 let mut same_text_selected = true;
15504 let mut selected_text = None;
15505
15506 let mut selections_iter = selections.iter().peekable();
15507 while let Some(selection) = selections_iter.next() {
15508 if selection.start != selection.end {
15509 only_carets = false;
15510 }
15511
15512 if same_text_selected {
15513 if selected_text.is_none() {
15514 selected_text =
15515 Some(buffer.text_for_range(selection.range()).collect::<String>());
15516 }
15517
15518 if let Some(next_selection) = selections_iter.peek() {
15519 if next_selection.len() == selection.len() {
15520 let next_selected_text = buffer
15521 .text_for_range(next_selection.range())
15522 .collect::<String>();
15523 if Some(next_selected_text) != selected_text {
15524 same_text_selected = false;
15525 selected_text = None;
15526 }
15527 } else {
15528 same_text_selected = false;
15529 selected_text = None;
15530 }
15531 }
15532 }
15533 }
15534
15535 if only_carets {
15536 for selection in &mut selections {
15537 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15538 selection.start = word_range.start;
15539 selection.end = word_range.end;
15540 selection.goal = SelectionGoal::None;
15541 selection.reversed = false;
15542 self.select_match_ranges(
15543 selection.start..selection.end,
15544 selection.reversed,
15545 action.replace_newest,
15546 Some(Autoscroll::newest()),
15547 window,
15548 cx,
15549 );
15550 }
15551 if selections.len() == 1 {
15552 let selection = selections
15553 .last()
15554 .expect("ensured that there's only one selection");
15555 let query = buffer
15556 .text_for_range(selection.start..selection.end)
15557 .collect::<String>();
15558 let is_empty = query.is_empty();
15559 let select_state = SelectNextState {
15560 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15561 wordwise: true,
15562 done: is_empty,
15563 };
15564 self.select_prev_state = Some(select_state);
15565 } else {
15566 self.select_prev_state = None;
15567 }
15568 } else if let Some(selected_text) = selected_text {
15569 self.select_prev_state = Some(SelectNextState {
15570 query: self
15571 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15572 wordwise: false,
15573 done: false,
15574 });
15575 self.select_previous(action, window, cx)?;
15576 }
15577 }
15578 Ok(())
15579 }
15580
15581 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15582 /// setting the case sensitivity based on the global
15583 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15584 /// editor's settings.
15585 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15586 where
15587 I: IntoIterator<Item = P>,
15588 P: AsRef<[u8]>,
15589 {
15590 let case_sensitive = self
15591 .select_next_is_case_sensitive
15592 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15593
15594 let mut builder = AhoCorasickBuilder::new();
15595 builder.ascii_case_insensitive(!case_sensitive);
15596 builder.build(patterns)
15597 }
15598
15599 pub fn find_next_match(
15600 &mut self,
15601 _: &FindNextMatch,
15602 window: &mut Window,
15603 cx: &mut Context<Self>,
15604 ) -> Result<()> {
15605 let selections = self.selections.disjoint_anchors_arc();
15606 match selections.first() {
15607 Some(first) if selections.len() >= 2 => {
15608 self.change_selections(Default::default(), window, cx, |s| {
15609 s.select_ranges([first.range()]);
15610 });
15611 }
15612 _ => self.select_next(
15613 &SelectNext {
15614 replace_newest: true,
15615 },
15616 window,
15617 cx,
15618 )?,
15619 }
15620 Ok(())
15621 }
15622
15623 pub fn find_previous_match(
15624 &mut self,
15625 _: &FindPreviousMatch,
15626 window: &mut Window,
15627 cx: &mut Context<Self>,
15628 ) -> Result<()> {
15629 let selections = self.selections.disjoint_anchors_arc();
15630 match selections.last() {
15631 Some(last) if selections.len() >= 2 => {
15632 self.change_selections(Default::default(), window, cx, |s| {
15633 s.select_ranges([last.range()]);
15634 });
15635 }
15636 _ => self.select_previous(
15637 &SelectPrevious {
15638 replace_newest: true,
15639 },
15640 window,
15641 cx,
15642 )?,
15643 }
15644 Ok(())
15645 }
15646
15647 pub fn toggle_comments(
15648 &mut self,
15649 action: &ToggleComments,
15650 window: &mut Window,
15651 cx: &mut Context<Self>,
15652 ) {
15653 if self.read_only(cx) {
15654 return;
15655 }
15656 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15657 let text_layout_details = &self.text_layout_details(window);
15658 self.transact(window, cx, |this, window, cx| {
15659 let mut selections = this
15660 .selections
15661 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15662 let mut edits = Vec::new();
15663 let mut selection_edit_ranges = Vec::new();
15664 let mut last_toggled_row = None;
15665 let snapshot = this.buffer.read(cx).read(cx);
15666 let empty_str: Arc<str> = Arc::default();
15667 let mut suffixes_inserted = Vec::new();
15668 let ignore_indent = action.ignore_indent;
15669
15670 fn comment_prefix_range(
15671 snapshot: &MultiBufferSnapshot,
15672 row: MultiBufferRow,
15673 comment_prefix: &str,
15674 comment_prefix_whitespace: &str,
15675 ignore_indent: bool,
15676 ) -> Range<Point> {
15677 let indent_size = if ignore_indent {
15678 0
15679 } else {
15680 snapshot.indent_size_for_line(row).len
15681 };
15682
15683 let start = Point::new(row.0, indent_size);
15684
15685 let mut line_bytes = snapshot
15686 .bytes_in_range(start..snapshot.max_point())
15687 .flatten()
15688 .copied();
15689
15690 // If this line currently begins with the line comment prefix, then record
15691 // the range containing the prefix.
15692 if line_bytes
15693 .by_ref()
15694 .take(comment_prefix.len())
15695 .eq(comment_prefix.bytes())
15696 {
15697 // Include any whitespace that matches the comment prefix.
15698 let matching_whitespace_len = line_bytes
15699 .zip(comment_prefix_whitespace.bytes())
15700 .take_while(|(a, b)| a == b)
15701 .count() as u32;
15702 let end = Point::new(
15703 start.row,
15704 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15705 );
15706 start..end
15707 } else {
15708 start..start
15709 }
15710 }
15711
15712 fn comment_suffix_range(
15713 snapshot: &MultiBufferSnapshot,
15714 row: MultiBufferRow,
15715 comment_suffix: &str,
15716 comment_suffix_has_leading_space: bool,
15717 ) -> Range<Point> {
15718 let end = Point::new(row.0, snapshot.line_len(row));
15719 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15720
15721 let mut line_end_bytes = snapshot
15722 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15723 .flatten()
15724 .copied();
15725
15726 let leading_space_len = if suffix_start_column > 0
15727 && line_end_bytes.next() == Some(b' ')
15728 && comment_suffix_has_leading_space
15729 {
15730 1
15731 } else {
15732 0
15733 };
15734
15735 // If this line currently begins with the line comment prefix, then record
15736 // the range containing the prefix.
15737 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15738 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15739 start..end
15740 } else {
15741 end..end
15742 }
15743 }
15744
15745 // TODO: Handle selections that cross excerpts
15746 for selection in &mut selections {
15747 let start_column = snapshot
15748 .indent_size_for_line(MultiBufferRow(selection.start.row))
15749 .len;
15750 let language = if let Some(language) =
15751 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15752 {
15753 language
15754 } else {
15755 continue;
15756 };
15757
15758 selection_edit_ranges.clear();
15759
15760 // If multiple selections contain a given row, avoid processing that
15761 // row more than once.
15762 let mut start_row = MultiBufferRow(selection.start.row);
15763 if last_toggled_row == Some(start_row) {
15764 start_row = start_row.next_row();
15765 }
15766 let end_row =
15767 if selection.end.row > selection.start.row && selection.end.column == 0 {
15768 MultiBufferRow(selection.end.row - 1)
15769 } else {
15770 MultiBufferRow(selection.end.row)
15771 };
15772 last_toggled_row = Some(end_row);
15773
15774 if start_row > end_row {
15775 continue;
15776 }
15777
15778 // If the language has line comments, toggle those.
15779 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15780
15781 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15782 if ignore_indent {
15783 full_comment_prefixes = full_comment_prefixes
15784 .into_iter()
15785 .map(|s| Arc::from(s.trim_end()))
15786 .collect();
15787 }
15788
15789 if !full_comment_prefixes.is_empty() {
15790 let first_prefix = full_comment_prefixes
15791 .first()
15792 .expect("prefixes is non-empty");
15793 let prefix_trimmed_lengths = full_comment_prefixes
15794 .iter()
15795 .map(|p| p.trim_end_matches(' ').len())
15796 .collect::<SmallVec<[usize; 4]>>();
15797
15798 let mut all_selection_lines_are_comments = true;
15799
15800 for row in start_row.0..=end_row.0 {
15801 let row = MultiBufferRow(row);
15802 if start_row < end_row && snapshot.is_line_blank(row) {
15803 continue;
15804 }
15805
15806 let prefix_range = full_comment_prefixes
15807 .iter()
15808 .zip(prefix_trimmed_lengths.iter().copied())
15809 .map(|(prefix, trimmed_prefix_len)| {
15810 comment_prefix_range(
15811 snapshot.deref(),
15812 row,
15813 &prefix[..trimmed_prefix_len],
15814 &prefix[trimmed_prefix_len..],
15815 ignore_indent,
15816 )
15817 })
15818 .max_by_key(|range| range.end.column - range.start.column)
15819 .expect("prefixes is non-empty");
15820
15821 if prefix_range.is_empty() {
15822 all_selection_lines_are_comments = false;
15823 }
15824
15825 selection_edit_ranges.push(prefix_range);
15826 }
15827
15828 if all_selection_lines_are_comments {
15829 edits.extend(
15830 selection_edit_ranges
15831 .iter()
15832 .cloned()
15833 .map(|range| (range, empty_str.clone())),
15834 );
15835 } else {
15836 let min_column = selection_edit_ranges
15837 .iter()
15838 .map(|range| range.start.column)
15839 .min()
15840 .unwrap_or(0);
15841 edits.extend(selection_edit_ranges.iter().map(|range| {
15842 let position = Point::new(range.start.row, min_column);
15843 (position..position, first_prefix.clone())
15844 }));
15845 }
15846 } else if let Some(BlockCommentConfig {
15847 start: full_comment_prefix,
15848 end: comment_suffix,
15849 ..
15850 }) = language.block_comment()
15851 {
15852 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15853 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15854 let prefix_range = comment_prefix_range(
15855 snapshot.deref(),
15856 start_row,
15857 comment_prefix,
15858 comment_prefix_whitespace,
15859 ignore_indent,
15860 );
15861 let suffix_range = comment_suffix_range(
15862 snapshot.deref(),
15863 end_row,
15864 comment_suffix.trim_start_matches(' '),
15865 comment_suffix.starts_with(' '),
15866 );
15867
15868 if prefix_range.is_empty() || suffix_range.is_empty() {
15869 edits.push((
15870 prefix_range.start..prefix_range.start,
15871 full_comment_prefix.clone(),
15872 ));
15873 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15874 suffixes_inserted.push((end_row, comment_suffix.len()));
15875 } else {
15876 edits.push((prefix_range, empty_str.clone()));
15877 edits.push((suffix_range, empty_str.clone()));
15878 }
15879 } else {
15880 continue;
15881 }
15882 }
15883
15884 drop(snapshot);
15885 this.buffer.update(cx, |buffer, cx| {
15886 buffer.edit(edits, None, cx);
15887 });
15888
15889 // Adjust selections so that they end before any comment suffixes that
15890 // were inserted.
15891 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15892 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15893 let snapshot = this.buffer.read(cx).read(cx);
15894 for selection in &mut selections {
15895 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15896 match row.cmp(&MultiBufferRow(selection.end.row)) {
15897 Ordering::Less => {
15898 suffixes_inserted.next();
15899 continue;
15900 }
15901 Ordering::Greater => break,
15902 Ordering::Equal => {
15903 if selection.end.column == snapshot.line_len(row) {
15904 if selection.is_empty() {
15905 selection.start.column -= suffix_len as u32;
15906 }
15907 selection.end.column -= suffix_len as u32;
15908 }
15909 break;
15910 }
15911 }
15912 }
15913 }
15914
15915 drop(snapshot);
15916 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15917
15918 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15919 let selections_on_single_row = selections.windows(2).all(|selections| {
15920 selections[0].start.row == selections[1].start.row
15921 && selections[0].end.row == selections[1].end.row
15922 && selections[0].start.row == selections[0].end.row
15923 });
15924 let selections_selecting = selections
15925 .iter()
15926 .any(|selection| selection.start != selection.end);
15927 let advance_downwards = action.advance_downwards
15928 && selections_on_single_row
15929 && !selections_selecting
15930 && !matches!(this.mode, EditorMode::SingleLine);
15931
15932 if advance_downwards {
15933 let snapshot = this.buffer.read(cx).snapshot(cx);
15934
15935 this.change_selections(Default::default(), window, cx, |s| {
15936 s.move_cursors_with(|display_snapshot, display_point, _| {
15937 let mut point = display_point.to_point(display_snapshot);
15938 point.row += 1;
15939 point = snapshot.clip_point(point, Bias::Left);
15940 let display_point = point.to_display_point(display_snapshot);
15941 let goal = SelectionGoal::HorizontalPosition(
15942 display_snapshot
15943 .x_for_display_point(display_point, text_layout_details)
15944 .into(),
15945 );
15946 (display_point, goal)
15947 })
15948 });
15949 }
15950 });
15951 }
15952
15953 pub fn select_enclosing_symbol(
15954 &mut self,
15955 _: &SelectEnclosingSymbol,
15956 window: &mut Window,
15957 cx: &mut Context<Self>,
15958 ) {
15959 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15960
15961 let buffer = self.buffer.read(cx).snapshot(cx);
15962 let old_selections = self
15963 .selections
15964 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15965 .into_boxed_slice();
15966
15967 fn update_selection(
15968 selection: &Selection<MultiBufferOffset>,
15969 buffer_snap: &MultiBufferSnapshot,
15970 ) -> Option<Selection<MultiBufferOffset>> {
15971 let cursor = selection.head();
15972 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15973 for symbol in symbols.iter().rev() {
15974 let start = symbol.range.start.to_offset(buffer_snap);
15975 let end = symbol.range.end.to_offset(buffer_snap);
15976 let new_range = start..end;
15977 if start < selection.start || end > selection.end {
15978 return Some(Selection {
15979 id: selection.id,
15980 start: new_range.start,
15981 end: new_range.end,
15982 goal: SelectionGoal::None,
15983 reversed: selection.reversed,
15984 });
15985 }
15986 }
15987 None
15988 }
15989
15990 let mut selected_larger_symbol = false;
15991 let new_selections = old_selections
15992 .iter()
15993 .map(|selection| match update_selection(selection, &buffer) {
15994 Some(new_selection) => {
15995 if new_selection.range() != selection.range() {
15996 selected_larger_symbol = true;
15997 }
15998 new_selection
15999 }
16000 None => selection.clone(),
16001 })
16002 .collect::<Vec<_>>();
16003
16004 if selected_larger_symbol {
16005 self.change_selections(Default::default(), window, cx, |s| {
16006 s.select(new_selections);
16007 });
16008 }
16009 }
16010
16011 pub fn select_larger_syntax_node(
16012 &mut self,
16013 _: &SelectLargerSyntaxNode,
16014 window: &mut Window,
16015 cx: &mut Context<Self>,
16016 ) {
16017 let Some(visible_row_count) = self.visible_row_count() else {
16018 return;
16019 };
16020 let old_selections: Box<[_]> = self
16021 .selections
16022 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16023 .into();
16024 if old_selections.is_empty() {
16025 return;
16026 }
16027
16028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16029
16030 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
16031 let buffer = self.buffer.read(cx).snapshot(cx);
16032
16033 let mut selected_larger_node = false;
16034 let mut new_selections = old_selections
16035 .iter()
16036 .map(|selection| {
16037 let old_range = selection.start..selection.end;
16038
16039 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16040 // manually select word at selection
16041 if ["string_content", "inline"].contains(&node.kind()) {
16042 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16043 // ignore if word is already selected
16044 if !word_range.is_empty() && old_range != word_range {
16045 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16046 // only select word if start and end point belongs to same word
16047 if word_range == last_word_range {
16048 selected_larger_node = true;
16049 return Selection {
16050 id: selection.id,
16051 start: word_range.start,
16052 end: word_range.end,
16053 goal: SelectionGoal::None,
16054 reversed: selection.reversed,
16055 };
16056 }
16057 }
16058 }
16059 }
16060
16061 let mut new_range = old_range.clone();
16062 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16063 new_range = range;
16064 if !node.is_named() {
16065 continue;
16066 }
16067 if !display_map.intersects_fold(new_range.start)
16068 && !display_map.intersects_fold(new_range.end)
16069 {
16070 break;
16071 }
16072 }
16073
16074 selected_larger_node |= new_range != old_range;
16075 Selection {
16076 id: selection.id,
16077 start: new_range.start,
16078 end: new_range.end,
16079 goal: SelectionGoal::None,
16080 reversed: selection.reversed,
16081 }
16082 })
16083 .collect::<Vec<_>>();
16084
16085 if !selected_larger_node {
16086 return; // don't put this call in the history
16087 }
16088
16089 // scroll based on transformation done to the last selection created by the user
16090 let (last_old, last_new) = old_selections
16091 .last()
16092 .zip(new_selections.last().cloned())
16093 .expect("old_selections isn't empty");
16094
16095 // revert selection
16096 let is_selection_reversed = {
16097 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16098 new_selections.last_mut().expect("checked above").reversed =
16099 should_newest_selection_be_reversed;
16100 should_newest_selection_be_reversed
16101 };
16102
16103 if selected_larger_node {
16104 self.select_syntax_node_history.disable_clearing = true;
16105 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16106 s.select(new_selections.clone());
16107 });
16108 self.select_syntax_node_history.disable_clearing = false;
16109 }
16110
16111 let start_row = last_new.start.to_display_point(&display_map).row().0;
16112 let end_row = last_new.end.to_display_point(&display_map).row().0;
16113 let selection_height = end_row - start_row + 1;
16114 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16115
16116 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16117 let scroll_behavior = if fits_on_the_screen {
16118 self.request_autoscroll(Autoscroll::fit(), cx);
16119 SelectSyntaxNodeScrollBehavior::FitSelection
16120 } else if is_selection_reversed {
16121 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16122 SelectSyntaxNodeScrollBehavior::CursorTop
16123 } else {
16124 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16125 SelectSyntaxNodeScrollBehavior::CursorBottom
16126 };
16127
16128 self.select_syntax_node_history.push((
16129 old_selections,
16130 scroll_behavior,
16131 is_selection_reversed,
16132 ));
16133 }
16134
16135 pub fn select_smaller_syntax_node(
16136 &mut self,
16137 _: &SelectSmallerSyntaxNode,
16138 window: &mut Window,
16139 cx: &mut Context<Self>,
16140 ) {
16141 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16142
16143 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16144 self.select_syntax_node_history.pop()
16145 {
16146 if let Some(selection) = selections.last_mut() {
16147 selection.reversed = is_selection_reversed;
16148 }
16149
16150 self.select_syntax_node_history.disable_clearing = true;
16151 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16152 s.select(selections.to_vec());
16153 });
16154 self.select_syntax_node_history.disable_clearing = false;
16155
16156 match scroll_behavior {
16157 SelectSyntaxNodeScrollBehavior::CursorTop => {
16158 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16159 }
16160 SelectSyntaxNodeScrollBehavior::FitSelection => {
16161 self.request_autoscroll(Autoscroll::fit(), cx);
16162 }
16163 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16164 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16165 }
16166 }
16167 }
16168 }
16169
16170 pub fn unwrap_syntax_node(
16171 &mut self,
16172 _: &UnwrapSyntaxNode,
16173 window: &mut Window,
16174 cx: &mut Context<Self>,
16175 ) {
16176 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16177
16178 let buffer = self.buffer.read(cx).snapshot(cx);
16179 let selections = self
16180 .selections
16181 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16182 .into_iter()
16183 // subtracting the offset requires sorting
16184 .sorted_by_key(|i| i.start);
16185
16186 let full_edits = selections
16187 .into_iter()
16188 .filter_map(|selection| {
16189 let child = if selection.is_empty()
16190 && let Some((_, ancestor_range)) =
16191 buffer.syntax_ancestor(selection.start..selection.end)
16192 {
16193 ancestor_range
16194 } else {
16195 selection.range()
16196 };
16197
16198 let mut parent = child.clone();
16199 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16200 parent = ancestor_range;
16201 if parent.start < child.start || parent.end > child.end {
16202 break;
16203 }
16204 }
16205
16206 if parent == child {
16207 return None;
16208 }
16209 let text = buffer.text_for_range(child).collect::<String>();
16210 Some((selection.id, parent, text))
16211 })
16212 .collect::<Vec<_>>();
16213 if full_edits.is_empty() {
16214 return;
16215 }
16216
16217 self.transact(window, cx, |this, window, cx| {
16218 this.buffer.update(cx, |buffer, cx| {
16219 buffer.edit(
16220 full_edits
16221 .iter()
16222 .map(|(_, p, t)| (p.clone(), t.clone()))
16223 .collect::<Vec<_>>(),
16224 None,
16225 cx,
16226 );
16227 });
16228 this.change_selections(Default::default(), window, cx, |s| {
16229 let mut offset = 0;
16230 let mut selections = vec![];
16231 for (id, parent, text) in full_edits {
16232 let start = parent.start - offset;
16233 offset += (parent.end - parent.start) - text.len();
16234 selections.push(Selection {
16235 id,
16236 start,
16237 end: start + text.len(),
16238 reversed: false,
16239 goal: Default::default(),
16240 });
16241 }
16242 s.select(selections);
16243 });
16244 });
16245 }
16246
16247 pub fn select_next_syntax_node(
16248 &mut self,
16249 _: &SelectNextSyntaxNode,
16250 window: &mut Window,
16251 cx: &mut Context<Self>,
16252 ) {
16253 let old_selections: Box<[_]> = self
16254 .selections
16255 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16256 .into();
16257 if old_selections.is_empty() {
16258 return;
16259 }
16260
16261 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16262
16263 let buffer = self.buffer.read(cx).snapshot(cx);
16264 let mut selected_sibling = false;
16265
16266 let new_selections = old_selections
16267 .iter()
16268 .map(|selection| {
16269 let old_range = selection.start..selection.end;
16270
16271 let old_range =
16272 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16273 let excerpt = buffer.excerpt_containing(old_range.clone());
16274
16275 if let Some(mut excerpt) = excerpt
16276 && let Some(node) = excerpt
16277 .buffer()
16278 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16279 {
16280 let new_range = excerpt.map_range_from_buffer(
16281 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16282 );
16283 selected_sibling = true;
16284 Selection {
16285 id: selection.id,
16286 start: new_range.start,
16287 end: new_range.end,
16288 goal: SelectionGoal::None,
16289 reversed: selection.reversed,
16290 }
16291 } else {
16292 selection.clone()
16293 }
16294 })
16295 .collect::<Vec<_>>();
16296
16297 if selected_sibling {
16298 self.change_selections(
16299 SelectionEffects::scroll(Autoscroll::fit()),
16300 window,
16301 cx,
16302 |s| {
16303 s.select(new_selections);
16304 },
16305 );
16306 }
16307 }
16308
16309 pub fn select_prev_syntax_node(
16310 &mut self,
16311 _: &SelectPreviousSyntaxNode,
16312 window: &mut Window,
16313 cx: &mut Context<Self>,
16314 ) {
16315 let old_selections: Box<[_]> = self
16316 .selections
16317 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16318 .into();
16319 if old_selections.is_empty() {
16320 return;
16321 }
16322
16323 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16324
16325 let buffer = self.buffer.read(cx).snapshot(cx);
16326 let mut selected_sibling = false;
16327
16328 let new_selections = old_selections
16329 .iter()
16330 .map(|selection| {
16331 let old_range = selection.start..selection.end;
16332 let old_range =
16333 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16334 let excerpt = buffer.excerpt_containing(old_range.clone());
16335
16336 if let Some(mut excerpt) = excerpt
16337 && let Some(node) = excerpt
16338 .buffer()
16339 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16340 {
16341 let new_range = excerpt.map_range_from_buffer(
16342 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16343 );
16344 selected_sibling = true;
16345 Selection {
16346 id: selection.id,
16347 start: new_range.start,
16348 end: new_range.end,
16349 goal: SelectionGoal::None,
16350 reversed: selection.reversed,
16351 }
16352 } else {
16353 selection.clone()
16354 }
16355 })
16356 .collect::<Vec<_>>();
16357
16358 if selected_sibling {
16359 self.change_selections(
16360 SelectionEffects::scroll(Autoscroll::fit()),
16361 window,
16362 cx,
16363 |s| {
16364 s.select(new_selections);
16365 },
16366 );
16367 }
16368 }
16369
16370 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16371 if !EditorSettings::get_global(cx).gutter.runnables {
16372 self.clear_tasks();
16373 return Task::ready(());
16374 }
16375 let project = self.project().map(Entity::downgrade);
16376 let task_sources = self.lsp_task_sources(cx);
16377 let multi_buffer = self.buffer.downgrade();
16378 cx.spawn_in(window, async move |editor, cx| {
16379 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16380 let Some(project) = project.and_then(|p| p.upgrade()) else {
16381 return;
16382 };
16383 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16384 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16385 }) else {
16386 return;
16387 };
16388
16389 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
16390 if hide_runnables {
16391 return;
16392 }
16393 let new_rows =
16394 cx.background_spawn({
16395 let snapshot = display_snapshot.clone();
16396 async move {
16397 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16398 }
16399 })
16400 .await;
16401 let Ok(lsp_tasks) =
16402 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16403 else {
16404 return;
16405 };
16406 let lsp_tasks = lsp_tasks.await;
16407
16408 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16409 lsp_tasks
16410 .into_iter()
16411 .flat_map(|(kind, tasks)| {
16412 tasks.into_iter().filter_map(move |(location, task)| {
16413 Some((kind.clone(), location?, task))
16414 })
16415 })
16416 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16417 let buffer = location.target.buffer;
16418 let buffer_snapshot = buffer.read(cx).snapshot();
16419 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16420 |(excerpt_id, snapshot, _)| {
16421 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16422 display_snapshot
16423 .buffer_snapshot()
16424 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16425 } else {
16426 None
16427 }
16428 },
16429 );
16430 if let Some(offset) = offset {
16431 let task_buffer_range =
16432 location.target.range.to_point(&buffer_snapshot);
16433 let context_buffer_range =
16434 task_buffer_range.to_offset(&buffer_snapshot);
16435 let context_range = BufferOffset(context_buffer_range.start)
16436 ..BufferOffset(context_buffer_range.end);
16437
16438 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16439 .or_insert_with(|| RunnableTasks {
16440 templates: Vec::new(),
16441 offset,
16442 column: task_buffer_range.start.column,
16443 extra_variables: HashMap::default(),
16444 context_range,
16445 })
16446 .templates
16447 .push((kind, task.original_task().clone()));
16448 }
16449
16450 acc
16451 })
16452 }) else {
16453 return;
16454 };
16455
16456 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16457 buffer.language_settings(cx).tasks.prefer_lsp
16458 }) else {
16459 return;
16460 };
16461
16462 let rows = Self::runnable_rows(
16463 project,
16464 display_snapshot,
16465 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16466 new_rows,
16467 cx.clone(),
16468 )
16469 .await;
16470 editor
16471 .update(cx, |editor, _| {
16472 editor.clear_tasks();
16473 for (key, mut value) in rows {
16474 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16475 value.templates.extend(lsp_tasks.templates);
16476 }
16477
16478 editor.insert_tasks(key, value);
16479 }
16480 for (key, value) in lsp_tasks_by_rows {
16481 editor.insert_tasks(key, value);
16482 }
16483 })
16484 .ok();
16485 })
16486 }
16487 fn fetch_runnable_ranges(
16488 snapshot: &DisplaySnapshot,
16489 range: Range<Anchor>,
16490 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16491 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16492 }
16493
16494 fn runnable_rows(
16495 project: Entity<Project>,
16496 snapshot: DisplaySnapshot,
16497 prefer_lsp: bool,
16498 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16499 cx: AsyncWindowContext,
16500 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16501 cx.spawn(async move |cx| {
16502 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16503 for (run_range, mut runnable) in runnable_ranges {
16504 let Some(tasks) = cx
16505 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16506 .ok()
16507 else {
16508 continue;
16509 };
16510 let mut tasks = tasks.await;
16511
16512 if prefer_lsp {
16513 tasks.retain(|(task_kind, _)| {
16514 !matches!(task_kind, TaskSourceKind::Language { .. })
16515 });
16516 }
16517 if tasks.is_empty() {
16518 continue;
16519 }
16520
16521 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16522 let Some(row) = snapshot
16523 .buffer_snapshot()
16524 .buffer_line_for_row(MultiBufferRow(point.row))
16525 .map(|(_, range)| range.start.row)
16526 else {
16527 continue;
16528 };
16529
16530 let context_range =
16531 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16532 runnable_rows.push((
16533 (runnable.buffer_id, row),
16534 RunnableTasks {
16535 templates: tasks,
16536 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16537 context_range,
16538 column: point.column,
16539 extra_variables: runnable.extra_captures,
16540 },
16541 ));
16542 }
16543 runnable_rows
16544 })
16545 }
16546
16547 fn templates_with_tags(
16548 project: &Entity<Project>,
16549 runnable: &mut Runnable,
16550 cx: &mut App,
16551 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16552 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16553 let (worktree_id, file) = project
16554 .buffer_for_id(runnable.buffer, cx)
16555 .and_then(|buffer| buffer.read(cx).file())
16556 .map(|file| (file.worktree_id(cx), file.clone()))
16557 .unzip();
16558
16559 (
16560 project.task_store().read(cx).task_inventory().cloned(),
16561 worktree_id,
16562 file,
16563 )
16564 });
16565
16566 let tags = mem::take(&mut runnable.tags);
16567 let language = runnable.language.clone();
16568 cx.spawn(async move |cx| {
16569 let mut templates_with_tags = Vec::new();
16570 if let Some(inventory) = inventory {
16571 for RunnableTag(tag) in tags {
16572 let new_tasks = inventory.update(cx, |inventory, cx| {
16573 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16574 });
16575 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16576 move |(_, template)| {
16577 template.tags.iter().any(|source_tag| source_tag == &tag)
16578 },
16579 ));
16580 }
16581 }
16582 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16583
16584 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16585 // Strongest source wins; if we have worktree tag binding, prefer that to
16586 // global and language bindings;
16587 // if we have a global binding, prefer that to language binding.
16588 let first_mismatch = templates_with_tags
16589 .iter()
16590 .position(|(tag_source, _)| tag_source != leading_tag_source);
16591 if let Some(index) = first_mismatch {
16592 templates_with_tags.truncate(index);
16593 }
16594 }
16595
16596 templates_with_tags
16597 })
16598 }
16599
16600 pub fn move_to_enclosing_bracket(
16601 &mut self,
16602 _: &MoveToEnclosingBracket,
16603 window: &mut Window,
16604 cx: &mut Context<Self>,
16605 ) {
16606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16607 self.change_selections(Default::default(), window, cx, |s| {
16608 s.move_offsets_with(|snapshot, selection| {
16609 let Some(enclosing_bracket_ranges) =
16610 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16611 else {
16612 return;
16613 };
16614
16615 let mut best_length = usize::MAX;
16616 let mut best_inside = false;
16617 let mut best_in_bracket_range = false;
16618 let mut best_destination = None;
16619 for (open, close) in enclosing_bracket_ranges {
16620 let close = close.to_inclusive();
16621 let length = *close.end() - open.start;
16622 let inside = selection.start >= open.end && selection.end <= *close.start();
16623 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16624 || close.contains(&selection.head());
16625
16626 // If best is next to a bracket and current isn't, skip
16627 if !in_bracket_range && best_in_bracket_range {
16628 continue;
16629 }
16630
16631 // Prefer smaller lengths unless best is inside and current isn't
16632 if length > best_length && (best_inside || !inside) {
16633 continue;
16634 }
16635
16636 best_length = length;
16637 best_inside = inside;
16638 best_in_bracket_range = in_bracket_range;
16639 best_destination = Some(
16640 if close.contains(&selection.start) && close.contains(&selection.end) {
16641 if inside { open.end } else { open.start }
16642 } else if inside {
16643 *close.start()
16644 } else {
16645 *close.end()
16646 },
16647 );
16648 }
16649
16650 if let Some(destination) = best_destination {
16651 selection.collapse_to(destination, SelectionGoal::None);
16652 }
16653 })
16654 });
16655 }
16656
16657 pub fn undo_selection(
16658 &mut self,
16659 _: &UndoSelection,
16660 window: &mut Window,
16661 cx: &mut Context<Self>,
16662 ) {
16663 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16664 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16665 self.selection_history.mode = SelectionHistoryMode::Undoing;
16666 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16667 this.end_selection(window, cx);
16668 this.change_selections(
16669 SelectionEffects::scroll(Autoscroll::newest()),
16670 window,
16671 cx,
16672 |s| s.select_anchors(entry.selections.to_vec()),
16673 );
16674 });
16675 self.selection_history.mode = SelectionHistoryMode::Normal;
16676
16677 self.select_next_state = entry.select_next_state;
16678 self.select_prev_state = entry.select_prev_state;
16679 self.add_selections_state = entry.add_selections_state;
16680 }
16681 }
16682
16683 pub fn redo_selection(
16684 &mut self,
16685 _: &RedoSelection,
16686 window: &mut Window,
16687 cx: &mut Context<Self>,
16688 ) {
16689 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16690 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16691 self.selection_history.mode = SelectionHistoryMode::Redoing;
16692 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16693 this.end_selection(window, cx);
16694 this.change_selections(
16695 SelectionEffects::scroll(Autoscroll::newest()),
16696 window,
16697 cx,
16698 |s| s.select_anchors(entry.selections.to_vec()),
16699 );
16700 });
16701 self.selection_history.mode = SelectionHistoryMode::Normal;
16702
16703 self.select_next_state = entry.select_next_state;
16704 self.select_prev_state = entry.select_prev_state;
16705 self.add_selections_state = entry.add_selections_state;
16706 }
16707 }
16708
16709 pub fn expand_excerpts(
16710 &mut self,
16711 action: &ExpandExcerpts,
16712 _: &mut Window,
16713 cx: &mut Context<Self>,
16714 ) {
16715 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16716 }
16717
16718 pub fn expand_excerpts_down(
16719 &mut self,
16720 action: &ExpandExcerptsDown,
16721 _: &mut Window,
16722 cx: &mut Context<Self>,
16723 ) {
16724 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16725 }
16726
16727 pub fn expand_excerpts_up(
16728 &mut self,
16729 action: &ExpandExcerptsUp,
16730 _: &mut Window,
16731 cx: &mut Context<Self>,
16732 ) {
16733 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16734 }
16735
16736 pub fn expand_excerpts_for_direction(
16737 &mut self,
16738 lines: u32,
16739 direction: ExpandExcerptDirection,
16740 cx: &mut Context<Self>,
16741 ) {
16742 let selections = self.selections.disjoint_anchors_arc();
16743
16744 let lines = if lines == 0 {
16745 EditorSettings::get_global(cx).expand_excerpt_lines
16746 } else {
16747 lines
16748 };
16749
16750 let snapshot = self.buffer.read(cx).snapshot(cx);
16751 let mut excerpt_ids = selections
16752 .iter()
16753 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16754 .collect::<Vec<_>>();
16755 excerpt_ids.sort();
16756 excerpt_ids.dedup();
16757
16758 if self.delegate_expand_excerpts {
16759 cx.emit(EditorEvent::ExpandExcerptsRequested {
16760 excerpt_ids,
16761 lines,
16762 direction,
16763 });
16764 return;
16765 }
16766
16767 self.buffer.update(cx, |buffer, cx| {
16768 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16769 })
16770 }
16771
16772 pub fn expand_excerpt(
16773 &mut self,
16774 excerpt: ExcerptId,
16775 direction: ExpandExcerptDirection,
16776 window: &mut Window,
16777 cx: &mut Context<Self>,
16778 ) {
16779 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16780
16781 if self.delegate_expand_excerpts {
16782 cx.emit(EditorEvent::ExpandExcerptsRequested {
16783 excerpt_ids: vec![excerpt],
16784 lines: lines_to_expand,
16785 direction,
16786 });
16787 return;
16788 }
16789
16790 let current_scroll_position = self.scroll_position(cx);
16791 let mut scroll = None;
16792
16793 if direction == ExpandExcerptDirection::Down {
16794 let multi_buffer = self.buffer.read(cx);
16795 let snapshot = multi_buffer.snapshot(cx);
16796 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16797 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16798 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16799 {
16800 let buffer_snapshot = buffer.read(cx).snapshot();
16801 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16802 let last_row = buffer_snapshot.max_point().row;
16803 let lines_below = last_row.saturating_sub(excerpt_end_row);
16804 if lines_below >= lines_to_expand {
16805 scroll = Some(
16806 current_scroll_position
16807 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16808 );
16809 }
16810 }
16811 }
16812 if direction == ExpandExcerptDirection::Up
16813 && self
16814 .buffer
16815 .read(cx)
16816 .snapshot(cx)
16817 .excerpt_before(excerpt)
16818 .is_none()
16819 {
16820 scroll = Some(current_scroll_position);
16821 }
16822
16823 self.buffer.update(cx, |buffer, cx| {
16824 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16825 });
16826
16827 if let Some(new_scroll_position) = scroll {
16828 self.set_scroll_position(new_scroll_position, window, cx);
16829 }
16830 }
16831
16832 pub fn go_to_singleton_buffer_point(
16833 &mut self,
16834 point: Point,
16835 window: &mut Window,
16836 cx: &mut Context<Self>,
16837 ) {
16838 self.go_to_singleton_buffer_range(point..point, window, cx);
16839 }
16840
16841 pub fn go_to_singleton_buffer_range(
16842 &mut self,
16843 range: Range<Point>,
16844 window: &mut Window,
16845 cx: &mut Context<Self>,
16846 ) {
16847 let multibuffer = self.buffer().read(cx);
16848 let Some(buffer) = multibuffer.as_singleton() else {
16849 return;
16850 };
16851 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16852 return;
16853 };
16854 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16855 return;
16856 };
16857 self.change_selections(
16858 SelectionEffects::default().nav_history(true),
16859 window,
16860 cx,
16861 |s| s.select_anchor_ranges([start..end]),
16862 );
16863 }
16864
16865 pub fn go_to_diagnostic(
16866 &mut self,
16867 action: &GoToDiagnostic,
16868 window: &mut Window,
16869 cx: &mut Context<Self>,
16870 ) {
16871 if !self.diagnostics_enabled() {
16872 return;
16873 }
16874 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16875 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16876 }
16877
16878 pub fn go_to_prev_diagnostic(
16879 &mut self,
16880 action: &GoToPreviousDiagnostic,
16881 window: &mut Window,
16882 cx: &mut Context<Self>,
16883 ) {
16884 if !self.diagnostics_enabled() {
16885 return;
16886 }
16887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16888 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16889 }
16890
16891 pub fn go_to_diagnostic_impl(
16892 &mut self,
16893 direction: Direction,
16894 severity: GoToDiagnosticSeverityFilter,
16895 window: &mut Window,
16896 cx: &mut Context<Self>,
16897 ) {
16898 let buffer = self.buffer.read(cx).snapshot(cx);
16899 let selection = self
16900 .selections
16901 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16902
16903 let mut active_group_id = None;
16904 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16905 && active_group.active_range.start.to_offset(&buffer) == selection.start
16906 {
16907 active_group_id = Some(active_group.group_id);
16908 }
16909
16910 fn filtered<'a>(
16911 severity: GoToDiagnosticSeverityFilter,
16912 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16913 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16914 diagnostics
16915 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16916 .filter(|entry| entry.range.start != entry.range.end)
16917 .filter(|entry| !entry.diagnostic.is_unnecessary)
16918 }
16919
16920 let before = filtered(
16921 severity,
16922 buffer
16923 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16924 .filter(|entry| entry.range.start <= selection.start),
16925 );
16926 let after = filtered(
16927 severity,
16928 buffer
16929 .diagnostics_in_range(selection.start..buffer.len())
16930 .filter(|entry| entry.range.start >= selection.start),
16931 );
16932
16933 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16934 if direction == Direction::Prev {
16935 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16936 {
16937 for diagnostic in prev_diagnostics.into_iter().rev() {
16938 if diagnostic.range.start != selection.start
16939 || active_group_id
16940 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16941 {
16942 found = Some(diagnostic);
16943 break 'outer;
16944 }
16945 }
16946 }
16947 } else {
16948 for diagnostic in after.chain(before) {
16949 if diagnostic.range.start != selection.start
16950 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16951 {
16952 found = Some(diagnostic);
16953 break;
16954 }
16955 }
16956 }
16957 let Some(next_diagnostic) = found else {
16958 return;
16959 };
16960
16961 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16962 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16963 return;
16964 };
16965 let snapshot = self.snapshot(window, cx);
16966 if snapshot.intersects_fold(next_diagnostic.range.start) {
16967 self.unfold_ranges(
16968 std::slice::from_ref(&next_diagnostic.range),
16969 true,
16970 false,
16971 cx,
16972 );
16973 }
16974 self.change_selections(Default::default(), window, cx, |s| {
16975 s.select_ranges(vec![
16976 next_diagnostic.range.start..next_diagnostic.range.start,
16977 ])
16978 });
16979 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16980 self.refresh_edit_prediction(false, true, window, cx);
16981 }
16982
16983 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16985 let snapshot = self.snapshot(window, cx);
16986 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16987 self.go_to_hunk_before_or_after_position(
16988 &snapshot,
16989 selection.head(),
16990 Direction::Next,
16991 window,
16992 cx,
16993 );
16994 }
16995
16996 pub fn go_to_hunk_before_or_after_position(
16997 &mut self,
16998 snapshot: &EditorSnapshot,
16999 position: Point,
17000 direction: Direction,
17001 window: &mut Window,
17002 cx: &mut Context<Editor>,
17003 ) {
17004 let row = if direction == Direction::Next {
17005 self.hunk_after_position(snapshot, position)
17006 .map(|hunk| hunk.row_range.start)
17007 } else {
17008 self.hunk_before_position(snapshot, position)
17009 };
17010
17011 if let Some(row) = row {
17012 let destination = Point::new(row.0, 0);
17013 let autoscroll = Autoscroll::center();
17014
17015 self.unfold_ranges(&[destination..destination], false, false, cx);
17016 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17017 s.select_ranges([destination..destination]);
17018 });
17019 }
17020 }
17021
17022 fn hunk_after_position(
17023 &mut self,
17024 snapshot: &EditorSnapshot,
17025 position: Point,
17026 ) -> Option<MultiBufferDiffHunk> {
17027 snapshot
17028 .buffer_snapshot()
17029 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
17030 .find(|hunk| hunk.row_range.start.0 > position.row)
17031 .or_else(|| {
17032 snapshot
17033 .buffer_snapshot()
17034 .diff_hunks_in_range(Point::zero()..position)
17035 .find(|hunk| hunk.row_range.end.0 < position.row)
17036 })
17037 }
17038
17039 fn go_to_prev_hunk(
17040 &mut self,
17041 _: &GoToPreviousHunk,
17042 window: &mut Window,
17043 cx: &mut Context<Self>,
17044 ) {
17045 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17046 let snapshot = self.snapshot(window, cx);
17047 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17048 self.go_to_hunk_before_or_after_position(
17049 &snapshot,
17050 selection.head(),
17051 Direction::Prev,
17052 window,
17053 cx,
17054 );
17055 }
17056
17057 fn hunk_before_position(
17058 &mut self,
17059 snapshot: &EditorSnapshot,
17060 position: Point,
17061 ) -> Option<MultiBufferRow> {
17062 snapshot
17063 .buffer_snapshot()
17064 .diff_hunk_before(position)
17065 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17066 }
17067
17068 fn go_to_next_change(
17069 &mut self,
17070 _: &GoToNextChange,
17071 window: &mut Window,
17072 cx: &mut Context<Self>,
17073 ) {
17074 if let Some(selections) = self
17075 .change_list
17076 .next_change(1, Direction::Next)
17077 .map(|s| s.to_vec())
17078 {
17079 self.change_selections(Default::default(), window, cx, |s| {
17080 let map = s.display_snapshot();
17081 s.select_display_ranges(selections.iter().map(|a| {
17082 let point = a.to_display_point(&map);
17083 point..point
17084 }))
17085 })
17086 }
17087 }
17088
17089 fn go_to_previous_change(
17090 &mut self,
17091 _: &GoToPreviousChange,
17092 window: &mut Window,
17093 cx: &mut Context<Self>,
17094 ) {
17095 if let Some(selections) = self
17096 .change_list
17097 .next_change(1, Direction::Prev)
17098 .map(|s| s.to_vec())
17099 {
17100 self.change_selections(Default::default(), window, cx, |s| {
17101 let map = s.display_snapshot();
17102 s.select_display_ranges(selections.iter().map(|a| {
17103 let point = a.to_display_point(&map);
17104 point..point
17105 }))
17106 })
17107 }
17108 }
17109
17110 pub fn go_to_next_document_highlight(
17111 &mut self,
17112 _: &GoToNextDocumentHighlight,
17113 window: &mut Window,
17114 cx: &mut Context<Self>,
17115 ) {
17116 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17117 }
17118
17119 pub fn go_to_prev_document_highlight(
17120 &mut self,
17121 _: &GoToPreviousDocumentHighlight,
17122 window: &mut Window,
17123 cx: &mut Context<Self>,
17124 ) {
17125 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17126 }
17127
17128 pub fn go_to_document_highlight_before_or_after_position(
17129 &mut self,
17130 direction: Direction,
17131 window: &mut Window,
17132 cx: &mut Context<Editor>,
17133 ) {
17134 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17135 let snapshot = self.snapshot(window, cx);
17136 let buffer = &snapshot.buffer_snapshot();
17137 let position = self
17138 .selections
17139 .newest::<Point>(&snapshot.display_snapshot)
17140 .head();
17141 let anchor_position = buffer.anchor_after(position);
17142
17143 // Get all document highlights (both read and write)
17144 let mut all_highlights = Vec::new();
17145
17146 if let Some((_, read_highlights)) = self
17147 .background_highlights
17148 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17149 {
17150 all_highlights.extend(read_highlights.iter());
17151 }
17152
17153 if let Some((_, write_highlights)) = self
17154 .background_highlights
17155 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17156 {
17157 all_highlights.extend(write_highlights.iter());
17158 }
17159
17160 if all_highlights.is_empty() {
17161 return;
17162 }
17163
17164 // Sort highlights by position
17165 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17166
17167 let target_highlight = match direction {
17168 Direction::Next => {
17169 // Find the first highlight after the current position
17170 all_highlights
17171 .iter()
17172 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17173 }
17174 Direction::Prev => {
17175 // Find the last highlight before the current position
17176 all_highlights
17177 .iter()
17178 .rev()
17179 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17180 }
17181 };
17182
17183 if let Some(highlight) = target_highlight {
17184 let destination = highlight.start.to_point(buffer);
17185 let autoscroll = Autoscroll::center();
17186
17187 self.unfold_ranges(&[destination..destination], false, false, cx);
17188 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17189 s.select_ranges([destination..destination]);
17190 });
17191 }
17192 }
17193
17194 fn go_to_line<T: 'static>(
17195 &mut self,
17196 position: Anchor,
17197 highlight_color: Option<Hsla>,
17198 window: &mut Window,
17199 cx: &mut Context<Self>,
17200 ) {
17201 let snapshot = self.snapshot(window, cx).display_snapshot;
17202 let position = position.to_point(&snapshot.buffer_snapshot());
17203 let start = snapshot
17204 .buffer_snapshot()
17205 .clip_point(Point::new(position.row, 0), Bias::Left);
17206 let end = start + Point::new(1, 0);
17207 let start = snapshot.buffer_snapshot().anchor_before(start);
17208 let end = snapshot.buffer_snapshot().anchor_before(end);
17209
17210 self.highlight_rows::<T>(
17211 start..end,
17212 highlight_color
17213 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17214 Default::default(),
17215 cx,
17216 );
17217
17218 if self.buffer.read(cx).is_singleton() {
17219 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17220 }
17221 }
17222
17223 pub fn go_to_definition(
17224 &mut self,
17225 _: &GoToDefinition,
17226 window: &mut Window,
17227 cx: &mut Context<Self>,
17228 ) -> Task<Result<Navigated>> {
17229 let definition =
17230 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17231 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17232 cx.spawn_in(window, async move |editor, cx| {
17233 if definition.await? == Navigated::Yes {
17234 return Ok(Navigated::Yes);
17235 }
17236 match fallback_strategy {
17237 GoToDefinitionFallback::None => Ok(Navigated::No),
17238 GoToDefinitionFallback::FindAllReferences => {
17239 match editor.update_in(cx, |editor, window, cx| {
17240 editor.find_all_references(&FindAllReferences::default(), window, cx)
17241 })? {
17242 Some(references) => references.await,
17243 None => Ok(Navigated::No),
17244 }
17245 }
17246 }
17247 })
17248 }
17249
17250 pub fn go_to_declaration(
17251 &mut self,
17252 _: &GoToDeclaration,
17253 window: &mut Window,
17254 cx: &mut Context<Self>,
17255 ) -> Task<Result<Navigated>> {
17256 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17257 }
17258
17259 pub fn go_to_declaration_split(
17260 &mut self,
17261 _: &GoToDeclaration,
17262 window: &mut Window,
17263 cx: &mut Context<Self>,
17264 ) -> Task<Result<Navigated>> {
17265 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17266 }
17267
17268 pub fn go_to_implementation(
17269 &mut self,
17270 _: &GoToImplementation,
17271 window: &mut Window,
17272 cx: &mut Context<Self>,
17273 ) -> Task<Result<Navigated>> {
17274 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17275 }
17276
17277 pub fn go_to_implementation_split(
17278 &mut self,
17279 _: &GoToImplementationSplit,
17280 window: &mut Window,
17281 cx: &mut Context<Self>,
17282 ) -> Task<Result<Navigated>> {
17283 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17284 }
17285
17286 pub fn go_to_type_definition(
17287 &mut self,
17288 _: &GoToTypeDefinition,
17289 window: &mut Window,
17290 cx: &mut Context<Self>,
17291 ) -> Task<Result<Navigated>> {
17292 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17293 }
17294
17295 pub fn go_to_definition_split(
17296 &mut self,
17297 _: &GoToDefinitionSplit,
17298 window: &mut Window,
17299 cx: &mut Context<Self>,
17300 ) -> Task<Result<Navigated>> {
17301 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17302 }
17303
17304 pub fn go_to_type_definition_split(
17305 &mut self,
17306 _: &GoToTypeDefinitionSplit,
17307 window: &mut Window,
17308 cx: &mut Context<Self>,
17309 ) -> Task<Result<Navigated>> {
17310 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17311 }
17312
17313 fn go_to_definition_of_kind(
17314 &mut self,
17315 kind: GotoDefinitionKind,
17316 split: bool,
17317 window: &mut Window,
17318 cx: &mut Context<Self>,
17319 ) -> Task<Result<Navigated>> {
17320 let Some(provider) = self.semantics_provider.clone() else {
17321 return Task::ready(Ok(Navigated::No));
17322 };
17323 let head = self
17324 .selections
17325 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17326 .head();
17327 let buffer = self.buffer.read(cx);
17328 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17329 return Task::ready(Ok(Navigated::No));
17330 };
17331 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17332 return Task::ready(Ok(Navigated::No));
17333 };
17334
17335 cx.spawn_in(window, async move |editor, cx| {
17336 let Some(definitions) = definitions.await? else {
17337 return Ok(Navigated::No);
17338 };
17339 let navigated = editor
17340 .update_in(cx, |editor, window, cx| {
17341 editor.navigate_to_hover_links(
17342 Some(kind),
17343 definitions
17344 .into_iter()
17345 .filter(|location| {
17346 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17347 })
17348 .map(HoverLink::Text)
17349 .collect::<Vec<_>>(),
17350 split,
17351 window,
17352 cx,
17353 )
17354 })?
17355 .await?;
17356 anyhow::Ok(navigated)
17357 })
17358 }
17359
17360 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17361 let selection = self.selections.newest_anchor();
17362 let head = selection.head();
17363 let tail = selection.tail();
17364
17365 let Some((buffer, start_position)) =
17366 self.buffer.read(cx).text_anchor_for_position(head, cx)
17367 else {
17368 return;
17369 };
17370
17371 let end_position = if head != tail {
17372 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17373 return;
17374 };
17375 Some(pos)
17376 } else {
17377 None
17378 };
17379
17380 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17381 let url = if let Some(end_pos) = end_position {
17382 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17383 } else {
17384 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17385 };
17386
17387 if let Some(url) = url {
17388 cx.update(|window, cx| {
17389 if parse_zed_link(&url, cx).is_some() {
17390 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17391 } else {
17392 cx.open_url(&url);
17393 }
17394 })?;
17395 }
17396
17397 anyhow::Ok(())
17398 });
17399
17400 url_finder.detach();
17401 }
17402
17403 pub fn open_selected_filename(
17404 &mut self,
17405 _: &OpenSelectedFilename,
17406 window: &mut Window,
17407 cx: &mut Context<Self>,
17408 ) {
17409 let Some(workspace) = self.workspace() else {
17410 return;
17411 };
17412
17413 let position = self.selections.newest_anchor().head();
17414
17415 let Some((buffer, buffer_position)) =
17416 self.buffer.read(cx).text_anchor_for_position(position, cx)
17417 else {
17418 return;
17419 };
17420
17421 let project = self.project.clone();
17422
17423 cx.spawn_in(window, async move |_, cx| {
17424 let result = find_file(&buffer, project, buffer_position, cx).await;
17425
17426 if let Some((_, path)) = result {
17427 workspace
17428 .update_in(cx, |workspace, window, cx| {
17429 workspace.open_resolved_path(path, window, cx)
17430 })?
17431 .await?;
17432 }
17433 anyhow::Ok(())
17434 })
17435 .detach();
17436 }
17437
17438 pub(crate) fn navigate_to_hover_links(
17439 &mut self,
17440 kind: Option<GotoDefinitionKind>,
17441 definitions: Vec<HoverLink>,
17442 split: bool,
17443 window: &mut Window,
17444 cx: &mut Context<Editor>,
17445 ) -> Task<Result<Navigated>> {
17446 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17447 let mut first_url_or_file = None;
17448 let definitions: Vec<_> = definitions
17449 .into_iter()
17450 .filter_map(|def| match def {
17451 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17452 HoverLink::InlayHint(lsp_location, server_id) => {
17453 let computation =
17454 self.compute_target_location(lsp_location, server_id, window, cx);
17455 Some(cx.background_spawn(computation))
17456 }
17457 HoverLink::Url(url) => {
17458 first_url_or_file = Some(Either::Left(url));
17459 None
17460 }
17461 HoverLink::File(path) => {
17462 first_url_or_file = Some(Either::Right(path));
17463 None
17464 }
17465 })
17466 .collect();
17467
17468 let workspace = self.workspace();
17469
17470 cx.spawn_in(window, async move |editor, cx| {
17471 let locations: Vec<Location> = future::join_all(definitions)
17472 .await
17473 .into_iter()
17474 .filter_map(|location| location.transpose())
17475 .collect::<Result<_>>()
17476 .context("location tasks")?;
17477 let mut locations = cx.update(|_, cx| {
17478 locations
17479 .into_iter()
17480 .map(|location| {
17481 let buffer = location.buffer.read(cx);
17482 (location.buffer, location.range.to_point(buffer))
17483 })
17484 .into_group_map()
17485 })?;
17486 let mut num_locations = 0;
17487 for ranges in locations.values_mut() {
17488 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17489 ranges.dedup();
17490 num_locations += ranges.len();
17491 }
17492
17493 if num_locations > 1 {
17494 let tab_kind = match kind {
17495 Some(GotoDefinitionKind::Implementation) => "Implementations",
17496 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17497 Some(GotoDefinitionKind::Declaration) => "Declarations",
17498 Some(GotoDefinitionKind::Type) => "Types",
17499 };
17500 let title = editor
17501 .update_in(cx, |_, _, cx| {
17502 let target = locations
17503 .iter()
17504 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17505 .map(|(buffer, location)| {
17506 buffer
17507 .read(cx)
17508 .text_for_range(location.clone())
17509 .collect::<String>()
17510 })
17511 .filter(|text| !text.contains('\n'))
17512 .unique()
17513 .take(3)
17514 .join(", ");
17515 if target.is_empty() {
17516 tab_kind.to_owned()
17517 } else {
17518 format!("{tab_kind} for {target}")
17519 }
17520 })
17521 .context("buffer title")?;
17522
17523 let Some(workspace) = workspace else {
17524 return Ok(Navigated::No);
17525 };
17526
17527 let opened = workspace
17528 .update_in(cx, |workspace, window, cx| {
17529 let allow_preview = PreviewTabsSettings::get_global(cx)
17530 .enable_preview_multibuffer_from_code_navigation;
17531 Self::open_locations_in_multibuffer(
17532 workspace,
17533 locations,
17534 title,
17535 split,
17536 allow_preview,
17537 MultibufferSelectionMode::First,
17538 window,
17539 cx,
17540 )
17541 })
17542 .is_ok();
17543
17544 anyhow::Ok(Navigated::from_bool(opened))
17545 } else if num_locations == 0 {
17546 // If there is one url or file, open it directly
17547 match first_url_or_file {
17548 Some(Either::Left(url)) => {
17549 cx.update(|window, cx| {
17550 if parse_zed_link(&url, cx).is_some() {
17551 window
17552 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17553 } else {
17554 cx.open_url(&url);
17555 }
17556 })?;
17557 Ok(Navigated::Yes)
17558 }
17559 Some(Either::Right(path)) => {
17560 // TODO(andrew): respect preview tab settings
17561 // `enable_keep_preview_on_code_navigation` and
17562 // `enable_preview_file_from_code_navigation`
17563 let Some(workspace) = workspace else {
17564 return Ok(Navigated::No);
17565 };
17566 workspace
17567 .update_in(cx, |workspace, window, cx| {
17568 workspace.open_resolved_path(path, window, cx)
17569 })?
17570 .await?;
17571 Ok(Navigated::Yes)
17572 }
17573 None => Ok(Navigated::No),
17574 }
17575 } else {
17576 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17577 let target_range = target_ranges.first().unwrap().clone();
17578
17579 editor.update_in(cx, |editor, window, cx| {
17580 let range = target_range.to_point(target_buffer.read(cx));
17581 let range = editor.range_for_match(&range);
17582 let range = collapse_multiline_range(range);
17583
17584 if !split
17585 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17586 {
17587 editor.go_to_singleton_buffer_range(range, window, cx);
17588 } else {
17589 let Some(workspace) = workspace else {
17590 return Navigated::No;
17591 };
17592 let pane = workspace.read(cx).active_pane().clone();
17593 window.defer(cx, move |window, cx| {
17594 let target_editor: Entity<Self> =
17595 workspace.update(cx, |workspace, cx| {
17596 let pane = if split {
17597 workspace.adjacent_pane(window, cx)
17598 } else {
17599 workspace.active_pane().clone()
17600 };
17601
17602 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17603 let keep_old_preview = preview_tabs_settings
17604 .enable_keep_preview_on_code_navigation;
17605 let allow_new_preview = preview_tabs_settings
17606 .enable_preview_file_from_code_navigation;
17607
17608 workspace.open_project_item(
17609 pane,
17610 target_buffer.clone(),
17611 true,
17612 true,
17613 keep_old_preview,
17614 allow_new_preview,
17615 window,
17616 cx,
17617 )
17618 });
17619 target_editor.update(cx, |target_editor, cx| {
17620 // When selecting a definition in a different buffer, disable the nav history
17621 // to avoid creating a history entry at the previous cursor location.
17622 pane.update(cx, |pane, _| pane.disable_history());
17623 target_editor.go_to_singleton_buffer_range(range, window, cx);
17624 pane.update(cx, |pane, _| pane.enable_history());
17625 });
17626 });
17627 }
17628 Navigated::Yes
17629 })
17630 }
17631 })
17632 }
17633
17634 fn compute_target_location(
17635 &self,
17636 lsp_location: lsp::Location,
17637 server_id: LanguageServerId,
17638 window: &mut Window,
17639 cx: &mut Context<Self>,
17640 ) -> Task<anyhow::Result<Option<Location>>> {
17641 let Some(project) = self.project.clone() else {
17642 return Task::ready(Ok(None));
17643 };
17644
17645 cx.spawn_in(window, async move |editor, cx| {
17646 let location_task = editor.update(cx, |_, cx| {
17647 project.update(cx, |project, cx| {
17648 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17649 })
17650 })?;
17651 let location = Some({
17652 let target_buffer_handle = location_task.await.context("open local buffer")?;
17653 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17654 let target_start = target_buffer
17655 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17656 let target_end = target_buffer
17657 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17658 target_buffer.anchor_after(target_start)
17659 ..target_buffer.anchor_before(target_end)
17660 });
17661 Location {
17662 buffer: target_buffer_handle,
17663 range,
17664 }
17665 });
17666 Ok(location)
17667 })
17668 }
17669
17670 fn go_to_next_reference(
17671 &mut self,
17672 _: &GoToNextReference,
17673 window: &mut Window,
17674 cx: &mut Context<Self>,
17675 ) {
17676 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17677 if let Some(task) = task {
17678 task.detach();
17679 };
17680 }
17681
17682 fn go_to_prev_reference(
17683 &mut self,
17684 _: &GoToPreviousReference,
17685 window: &mut Window,
17686 cx: &mut Context<Self>,
17687 ) {
17688 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17689 if let Some(task) = task {
17690 task.detach();
17691 };
17692 }
17693
17694 pub fn go_to_reference_before_or_after_position(
17695 &mut self,
17696 direction: Direction,
17697 count: usize,
17698 window: &mut Window,
17699 cx: &mut Context<Self>,
17700 ) -> Option<Task<Result<()>>> {
17701 let selection = self.selections.newest_anchor();
17702 let head = selection.head();
17703
17704 let multi_buffer = self.buffer.read(cx);
17705
17706 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17707 let workspace = self.workspace()?;
17708 let project = workspace.read(cx).project().clone();
17709 let references =
17710 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17711 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17712 let Some(locations) = references.await? else {
17713 return Ok(());
17714 };
17715
17716 if locations.is_empty() {
17717 // totally normal - the cursor may be on something which is not
17718 // a symbol (e.g. a keyword)
17719 log::info!("no references found under cursor");
17720 return Ok(());
17721 }
17722
17723 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17724
17725 let (locations, current_location_index) =
17726 multi_buffer.update(cx, |multi_buffer, cx| {
17727 let mut locations = locations
17728 .into_iter()
17729 .filter_map(|loc| {
17730 let start = multi_buffer.buffer_anchor_to_anchor(
17731 &loc.buffer,
17732 loc.range.start,
17733 cx,
17734 )?;
17735 let end = multi_buffer.buffer_anchor_to_anchor(
17736 &loc.buffer,
17737 loc.range.end,
17738 cx,
17739 )?;
17740 Some(start..end)
17741 })
17742 .collect::<Vec<_>>();
17743
17744 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17745 // There is an O(n) implementation, but given this list will be
17746 // small (usually <100 items), the extra O(log(n)) factor isn't
17747 // worth the (surprisingly large amount of) extra complexity.
17748 locations
17749 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17750
17751 let head_offset = head.to_offset(&multi_buffer_snapshot);
17752
17753 let current_location_index = locations.iter().position(|loc| {
17754 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17755 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17756 });
17757
17758 (locations, current_location_index)
17759 });
17760
17761 let Some(current_location_index) = current_location_index else {
17762 // This indicates something has gone wrong, because we already
17763 // handle the "no references" case above
17764 log::error!(
17765 "failed to find current reference under cursor. Total references: {}",
17766 locations.len()
17767 );
17768 return Ok(());
17769 };
17770
17771 let destination_location_index = match direction {
17772 Direction::Next => (current_location_index + count) % locations.len(),
17773 Direction::Prev => {
17774 (current_location_index + locations.len() - count % locations.len())
17775 % locations.len()
17776 }
17777 };
17778
17779 // TODO(cameron): is this needed?
17780 // the thinking is to avoid "jumping to the current location" (avoid
17781 // polluting "jumplist" in vim terms)
17782 if current_location_index == destination_location_index {
17783 return Ok(());
17784 }
17785
17786 let Range { start, end } = locations[destination_location_index];
17787
17788 editor.update_in(cx, |editor, window, cx| {
17789 let effects = SelectionEffects::default();
17790
17791 editor.unfold_ranges(&[start..end], false, false, cx);
17792 editor.change_selections(effects, window, cx, |s| {
17793 s.select_ranges([start..start]);
17794 });
17795 })?;
17796
17797 Ok(())
17798 }))
17799 }
17800
17801 pub fn find_all_references(
17802 &mut self,
17803 action: &FindAllReferences,
17804 window: &mut Window,
17805 cx: &mut Context<Self>,
17806 ) -> Option<Task<Result<Navigated>>> {
17807 let always_open_multibuffer = action.always_open_multibuffer;
17808 let selection = self.selections.newest_anchor();
17809 let multi_buffer = self.buffer.read(cx);
17810 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17811 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17812 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17813 let head = selection_offset.head();
17814
17815 let head_anchor = multi_buffer_snapshot.anchor_at(
17816 head,
17817 if head < selection_offset.tail() {
17818 Bias::Right
17819 } else {
17820 Bias::Left
17821 },
17822 );
17823
17824 match self
17825 .find_all_references_task_sources
17826 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17827 {
17828 Ok(_) => {
17829 log::info!(
17830 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17831 );
17832 return None;
17833 }
17834 Err(i) => {
17835 self.find_all_references_task_sources.insert(i, head_anchor);
17836 }
17837 }
17838
17839 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17840 let workspace = self.workspace()?;
17841 let project = workspace.read(cx).project().clone();
17842 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17843 Some(cx.spawn_in(window, async move |editor, cx| {
17844 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17845 if let Ok(i) = editor
17846 .find_all_references_task_sources
17847 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17848 {
17849 editor.find_all_references_task_sources.remove(i);
17850 }
17851 });
17852
17853 let Some(locations) = references.await? else {
17854 return anyhow::Ok(Navigated::No);
17855 };
17856 let mut locations = cx.update(|_, cx| {
17857 locations
17858 .into_iter()
17859 .map(|location| {
17860 let buffer = location.buffer.read(cx);
17861 (location.buffer, location.range.to_point(buffer))
17862 })
17863 // if special-casing the single-match case, remove ranges
17864 // that intersect current selection
17865 .filter(|(location_buffer, location)| {
17866 if always_open_multibuffer || &buffer != location_buffer {
17867 return true;
17868 }
17869
17870 !location.contains_inclusive(&selection_point.range())
17871 })
17872 .into_group_map()
17873 })?;
17874 if locations.is_empty() {
17875 return anyhow::Ok(Navigated::No);
17876 }
17877 for ranges in locations.values_mut() {
17878 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17879 ranges.dedup();
17880 }
17881 let mut num_locations = 0;
17882 for ranges in locations.values_mut() {
17883 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17884 ranges.dedup();
17885 num_locations += ranges.len();
17886 }
17887
17888 if num_locations == 1 && !always_open_multibuffer {
17889 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17890 let target_range = target_ranges.first().unwrap().clone();
17891
17892 return editor.update_in(cx, |editor, window, cx| {
17893 let range = target_range.to_point(target_buffer.read(cx));
17894 let range = editor.range_for_match(&range);
17895 let range = range.start..range.start;
17896
17897 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17898 editor.go_to_singleton_buffer_range(range, window, cx);
17899 } else {
17900 let pane = workspace.read(cx).active_pane().clone();
17901 window.defer(cx, move |window, cx| {
17902 let target_editor: Entity<Self> =
17903 workspace.update(cx, |workspace, cx| {
17904 let pane = workspace.active_pane().clone();
17905
17906 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17907 let keep_old_preview = preview_tabs_settings
17908 .enable_keep_preview_on_code_navigation;
17909 let allow_new_preview = preview_tabs_settings
17910 .enable_preview_file_from_code_navigation;
17911
17912 workspace.open_project_item(
17913 pane,
17914 target_buffer.clone(),
17915 true,
17916 true,
17917 keep_old_preview,
17918 allow_new_preview,
17919 window,
17920 cx,
17921 )
17922 });
17923 target_editor.update(cx, |target_editor, cx| {
17924 // When selecting a definition in a different buffer, disable the nav history
17925 // to avoid creating a history entry at the previous cursor location.
17926 pane.update(cx, |pane, _| pane.disable_history());
17927 target_editor.go_to_singleton_buffer_range(range, window, cx);
17928 pane.update(cx, |pane, _| pane.enable_history());
17929 });
17930 });
17931 }
17932 Navigated::No
17933 });
17934 }
17935
17936 workspace.update_in(cx, |workspace, window, cx| {
17937 let target = locations
17938 .iter()
17939 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17940 .map(|(buffer, location)| {
17941 buffer
17942 .read(cx)
17943 .text_for_range(location.clone())
17944 .collect::<String>()
17945 })
17946 .filter(|text| !text.contains('\n'))
17947 .unique()
17948 .take(3)
17949 .join(", ");
17950 let title = if target.is_empty() {
17951 "References".to_owned()
17952 } else {
17953 format!("References to {target}")
17954 };
17955 let allow_preview = PreviewTabsSettings::get_global(cx)
17956 .enable_preview_multibuffer_from_code_navigation;
17957 Self::open_locations_in_multibuffer(
17958 workspace,
17959 locations,
17960 title,
17961 false,
17962 allow_preview,
17963 MultibufferSelectionMode::First,
17964 window,
17965 cx,
17966 );
17967 Navigated::Yes
17968 })
17969 }))
17970 }
17971
17972 /// Opens a multibuffer with the given project locations in it.
17973 pub fn open_locations_in_multibuffer(
17974 workspace: &mut Workspace,
17975 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17976 title: String,
17977 split: bool,
17978 allow_preview: bool,
17979 multibuffer_selection_mode: MultibufferSelectionMode,
17980 window: &mut Window,
17981 cx: &mut Context<Workspace>,
17982 ) {
17983 if locations.is_empty() {
17984 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17985 return;
17986 }
17987
17988 let capability = workspace.project().read(cx).capability();
17989 let mut ranges = <Vec<Range<Anchor>>>::new();
17990
17991 // a key to find existing multibuffer editors with the same set of locations
17992 // to prevent us from opening more and more multibuffer tabs for searches and the like
17993 let mut key = (title.clone(), vec![]);
17994 let excerpt_buffer = cx.new(|cx| {
17995 let key = &mut key.1;
17996 let mut multibuffer = MultiBuffer::new(capability);
17997 for (buffer, mut ranges_for_buffer) in locations {
17998 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17999 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
18000 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
18001 PathKey::for_buffer(&buffer, cx),
18002 buffer.clone(),
18003 ranges_for_buffer,
18004 multibuffer_context_lines(cx),
18005 cx,
18006 );
18007 ranges.extend(new_ranges)
18008 }
18009
18010 multibuffer.with_title(title)
18011 });
18012 let existing = workspace.active_pane().update(cx, |pane, cx| {
18013 pane.items()
18014 .filter_map(|item| item.downcast::<Editor>())
18015 .find(|editor| {
18016 editor
18017 .read(cx)
18018 .lookup_key
18019 .as_ref()
18020 .and_then(|it| {
18021 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
18022 })
18023 .is_some_and(|it| *it == key)
18024 })
18025 });
18026 let was_existing = existing.is_some();
18027 let editor = existing.unwrap_or_else(|| {
18028 cx.new(|cx| {
18029 let mut editor = Editor::for_multibuffer(
18030 excerpt_buffer,
18031 Some(workspace.project().clone()),
18032 window,
18033 cx,
18034 );
18035 editor.lookup_key = Some(Box::new(key));
18036 editor
18037 })
18038 });
18039 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18040 MultibufferSelectionMode::First => {
18041 if let Some(first_range) = ranges.first() {
18042 editor.change_selections(
18043 SelectionEffects::no_scroll(),
18044 window,
18045 cx,
18046 |selections| {
18047 selections.clear_disjoint();
18048 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18049 },
18050 );
18051 }
18052 editor.highlight_background::<Self>(
18053 &ranges,
18054 |_, theme| theme.colors().editor_highlighted_line_background,
18055 cx,
18056 );
18057 }
18058 MultibufferSelectionMode::All => {
18059 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18060 selections.clear_disjoint();
18061 selections.select_anchor_ranges(ranges);
18062 });
18063 }
18064 });
18065
18066 let item = Box::new(editor);
18067
18068 let pane = if split {
18069 workspace.adjacent_pane(window, cx)
18070 } else {
18071 workspace.active_pane().clone()
18072 };
18073 let activate_pane = split;
18074
18075 let mut destination_index = None;
18076 pane.update(cx, |pane, cx| {
18077 if allow_preview && !was_existing {
18078 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18079 }
18080 if was_existing && !allow_preview {
18081 pane.unpreview_item_if_preview(item.item_id());
18082 }
18083 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18084 });
18085 }
18086
18087 pub fn rename(
18088 &mut self,
18089 _: &Rename,
18090 window: &mut Window,
18091 cx: &mut Context<Self>,
18092 ) -> Option<Task<Result<()>>> {
18093 use language::ToOffset as _;
18094
18095 let provider = self.semantics_provider.clone()?;
18096 let selection = self.selections.newest_anchor().clone();
18097 let (cursor_buffer, cursor_buffer_position) = self
18098 .buffer
18099 .read(cx)
18100 .text_anchor_for_position(selection.head(), cx)?;
18101 let (tail_buffer, cursor_buffer_position_end) = self
18102 .buffer
18103 .read(cx)
18104 .text_anchor_for_position(selection.tail(), cx)?;
18105 if tail_buffer != cursor_buffer {
18106 return None;
18107 }
18108
18109 let snapshot = cursor_buffer.read(cx).snapshot();
18110 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18111 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18112 let prepare_rename = provider
18113 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18114 .unwrap_or_else(|| Task::ready(Ok(None)));
18115 drop(snapshot);
18116
18117 Some(cx.spawn_in(window, async move |this, cx| {
18118 let rename_range = if let Some(range) = prepare_rename.await? {
18119 Some(range)
18120 } else {
18121 this.update(cx, |this, cx| {
18122 let buffer = this.buffer.read(cx).snapshot(cx);
18123 let mut buffer_highlights = this
18124 .document_highlights_for_position(selection.head(), &buffer)
18125 .filter(|highlight| {
18126 highlight.start.excerpt_id == selection.head().excerpt_id
18127 && highlight.end.excerpt_id == selection.head().excerpt_id
18128 });
18129 buffer_highlights
18130 .next()
18131 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18132 })?
18133 };
18134 if let Some(rename_range) = rename_range {
18135 this.update_in(cx, |this, window, cx| {
18136 let snapshot = cursor_buffer.read(cx).snapshot();
18137 let rename_buffer_range = rename_range.to_offset(&snapshot);
18138 let cursor_offset_in_rename_range =
18139 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18140 let cursor_offset_in_rename_range_end =
18141 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18142
18143 this.take_rename(false, window, cx);
18144 let buffer = this.buffer.read(cx).read(cx);
18145 let cursor_offset = selection.head().to_offset(&buffer);
18146 let rename_start =
18147 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18148 let rename_end = rename_start + rename_buffer_range.len();
18149 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18150 let mut old_highlight_id = None;
18151 let old_name: Arc<str> = buffer
18152 .chunks(rename_start..rename_end, true)
18153 .map(|chunk| {
18154 if old_highlight_id.is_none() {
18155 old_highlight_id = chunk.syntax_highlight_id;
18156 }
18157 chunk.text
18158 })
18159 .collect::<String>()
18160 .into();
18161
18162 drop(buffer);
18163
18164 // Position the selection in the rename editor so that it matches the current selection.
18165 this.show_local_selections = false;
18166 let rename_editor = cx.new(|cx| {
18167 let mut editor = Editor::single_line(window, cx);
18168 editor.buffer.update(cx, |buffer, cx| {
18169 buffer.edit(
18170 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18171 None,
18172 cx,
18173 )
18174 });
18175 let cursor_offset_in_rename_range =
18176 MultiBufferOffset(cursor_offset_in_rename_range);
18177 let cursor_offset_in_rename_range_end =
18178 MultiBufferOffset(cursor_offset_in_rename_range_end);
18179 let rename_selection_range = match cursor_offset_in_rename_range
18180 .cmp(&cursor_offset_in_rename_range_end)
18181 {
18182 Ordering::Equal => {
18183 editor.select_all(&SelectAll, window, cx);
18184 return editor;
18185 }
18186 Ordering::Less => {
18187 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18188 }
18189 Ordering::Greater => {
18190 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18191 }
18192 };
18193 if rename_selection_range.end.0 > old_name.len() {
18194 editor.select_all(&SelectAll, window, cx);
18195 } else {
18196 editor.change_selections(Default::default(), window, cx, |s| {
18197 s.select_ranges([rename_selection_range]);
18198 });
18199 }
18200 editor
18201 });
18202 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18203 if e == &EditorEvent::Focused {
18204 cx.emit(EditorEvent::FocusedIn)
18205 }
18206 })
18207 .detach();
18208
18209 let write_highlights =
18210 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18211 let read_highlights =
18212 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18213 let ranges = write_highlights
18214 .iter()
18215 .flat_map(|(_, ranges)| ranges.iter())
18216 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18217 .cloned()
18218 .collect();
18219
18220 this.highlight_text::<Rename>(
18221 ranges,
18222 HighlightStyle {
18223 fade_out: Some(0.6),
18224 ..Default::default()
18225 },
18226 cx,
18227 );
18228 let rename_focus_handle = rename_editor.focus_handle(cx);
18229 window.focus(&rename_focus_handle, cx);
18230 let block_id = this.insert_blocks(
18231 [BlockProperties {
18232 style: BlockStyle::Flex,
18233 placement: BlockPlacement::Below(range.start),
18234 height: Some(1),
18235 render: Arc::new({
18236 let rename_editor = rename_editor.clone();
18237 move |cx: &mut BlockContext| {
18238 let mut text_style = cx.editor_style.text.clone();
18239 if let Some(highlight_style) = old_highlight_id
18240 .and_then(|h| h.style(&cx.editor_style.syntax))
18241 {
18242 text_style = text_style.highlight(highlight_style);
18243 }
18244 div()
18245 .block_mouse_except_scroll()
18246 .pl(cx.anchor_x)
18247 .child(EditorElement::new(
18248 &rename_editor,
18249 EditorStyle {
18250 background: cx.theme().system().transparent,
18251 local_player: cx.editor_style.local_player,
18252 text: text_style,
18253 scrollbar_width: cx.editor_style.scrollbar_width,
18254 syntax: cx.editor_style.syntax.clone(),
18255 status: cx.editor_style.status.clone(),
18256 inlay_hints_style: HighlightStyle {
18257 font_weight: Some(FontWeight::BOLD),
18258 ..make_inlay_hints_style(cx.app)
18259 },
18260 edit_prediction_styles: make_suggestion_styles(
18261 cx.app,
18262 ),
18263 ..EditorStyle::default()
18264 },
18265 ))
18266 .into_any_element()
18267 }
18268 }),
18269 priority: 0,
18270 }],
18271 Some(Autoscroll::fit()),
18272 cx,
18273 )[0];
18274 this.pending_rename = Some(RenameState {
18275 range,
18276 old_name,
18277 editor: rename_editor,
18278 block_id,
18279 });
18280 })?;
18281 }
18282
18283 Ok(())
18284 }))
18285 }
18286
18287 pub fn confirm_rename(
18288 &mut self,
18289 _: &ConfirmRename,
18290 window: &mut Window,
18291 cx: &mut Context<Self>,
18292 ) -> Option<Task<Result<()>>> {
18293 let rename = self.take_rename(false, window, cx)?;
18294 let workspace = self.workspace()?.downgrade();
18295 let (buffer, start) = self
18296 .buffer
18297 .read(cx)
18298 .text_anchor_for_position(rename.range.start, cx)?;
18299 let (end_buffer, _) = self
18300 .buffer
18301 .read(cx)
18302 .text_anchor_for_position(rename.range.end, cx)?;
18303 if buffer != end_buffer {
18304 return None;
18305 }
18306
18307 let old_name = rename.old_name;
18308 let new_name = rename.editor.read(cx).text(cx);
18309
18310 let rename = self.semantics_provider.as_ref()?.perform_rename(
18311 &buffer,
18312 start,
18313 new_name.clone(),
18314 cx,
18315 )?;
18316
18317 Some(cx.spawn_in(window, async move |editor, cx| {
18318 let project_transaction = rename.await?;
18319 Self::open_project_transaction(
18320 &editor,
18321 workspace,
18322 project_transaction,
18323 format!("Rename: {} → {}", old_name, new_name),
18324 cx,
18325 )
18326 .await?;
18327
18328 editor.update(cx, |editor, cx| {
18329 editor.refresh_document_highlights(cx);
18330 })?;
18331 Ok(())
18332 }))
18333 }
18334
18335 fn take_rename(
18336 &mut self,
18337 moving_cursor: bool,
18338 window: &mut Window,
18339 cx: &mut Context<Self>,
18340 ) -> Option<RenameState> {
18341 let rename = self.pending_rename.take()?;
18342 if rename.editor.focus_handle(cx).is_focused(window) {
18343 window.focus(&self.focus_handle, cx);
18344 }
18345
18346 self.remove_blocks(
18347 [rename.block_id].into_iter().collect(),
18348 Some(Autoscroll::fit()),
18349 cx,
18350 );
18351 self.clear_highlights::<Rename>(cx);
18352 self.show_local_selections = true;
18353
18354 if moving_cursor {
18355 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18356 editor
18357 .selections
18358 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18359 .head()
18360 });
18361
18362 // Update the selection to match the position of the selection inside
18363 // the rename editor.
18364 let snapshot = self.buffer.read(cx).read(cx);
18365 let rename_range = rename.range.to_offset(&snapshot);
18366 let cursor_in_editor = snapshot
18367 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18368 .min(rename_range.end);
18369 drop(snapshot);
18370
18371 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18372 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18373 });
18374 } else {
18375 self.refresh_document_highlights(cx);
18376 }
18377
18378 Some(rename)
18379 }
18380
18381 pub fn pending_rename(&self) -> Option<&RenameState> {
18382 self.pending_rename.as_ref()
18383 }
18384
18385 fn format(
18386 &mut self,
18387 _: &Format,
18388 window: &mut Window,
18389 cx: &mut Context<Self>,
18390 ) -> Option<Task<Result<()>>> {
18391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18392
18393 let project = match &self.project {
18394 Some(project) => project.clone(),
18395 None => return None,
18396 };
18397
18398 Some(self.perform_format(
18399 project,
18400 FormatTrigger::Manual,
18401 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18402 window,
18403 cx,
18404 ))
18405 }
18406
18407 fn format_selections(
18408 &mut self,
18409 _: &FormatSelections,
18410 window: &mut Window,
18411 cx: &mut Context<Self>,
18412 ) -> Option<Task<Result<()>>> {
18413 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18414
18415 let project = match &self.project {
18416 Some(project) => project.clone(),
18417 None => return None,
18418 };
18419
18420 let ranges = self
18421 .selections
18422 .all_adjusted(&self.display_snapshot(cx))
18423 .into_iter()
18424 .map(|selection| selection.range())
18425 .collect_vec();
18426
18427 Some(self.perform_format(
18428 project,
18429 FormatTrigger::Manual,
18430 FormatTarget::Ranges(ranges),
18431 window,
18432 cx,
18433 ))
18434 }
18435
18436 fn perform_format(
18437 &mut self,
18438 project: Entity<Project>,
18439 trigger: FormatTrigger,
18440 target: FormatTarget,
18441 window: &mut Window,
18442 cx: &mut Context<Self>,
18443 ) -> Task<Result<()>> {
18444 let buffer = self.buffer.clone();
18445 let (buffers, target) = match target {
18446 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18447 FormatTarget::Ranges(selection_ranges) => {
18448 let multi_buffer = buffer.read(cx);
18449 let snapshot = multi_buffer.read(cx);
18450 let mut buffers = HashSet::default();
18451 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18452 BTreeMap::new();
18453 for selection_range in selection_ranges {
18454 for (buffer, buffer_range, _) in
18455 snapshot.range_to_buffer_ranges(selection_range)
18456 {
18457 let buffer_id = buffer.remote_id();
18458 let start = buffer.anchor_before(buffer_range.start);
18459 let end = buffer.anchor_after(buffer_range.end);
18460 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18461 buffer_id_to_ranges
18462 .entry(buffer_id)
18463 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18464 .or_insert_with(|| vec![start..end]);
18465 }
18466 }
18467 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18468 }
18469 };
18470
18471 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18472 let selections_prev = transaction_id_prev
18473 .and_then(|transaction_id_prev| {
18474 // default to selections as they were after the last edit, if we have them,
18475 // instead of how they are now.
18476 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18477 // will take you back to where you made the last edit, instead of staying where you scrolled
18478 self.selection_history
18479 .transaction(transaction_id_prev)
18480 .map(|t| t.0.clone())
18481 })
18482 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18483
18484 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18485 let format = project.update(cx, |project, cx| {
18486 project.format(buffers, target, true, trigger, cx)
18487 });
18488
18489 cx.spawn_in(window, async move |editor, cx| {
18490 let transaction = futures::select_biased! {
18491 transaction = format.log_err().fuse() => transaction,
18492 () = timeout => {
18493 log::warn!("timed out waiting for formatting");
18494 None
18495 }
18496 };
18497
18498 buffer.update(cx, |buffer, cx| {
18499 if let Some(transaction) = transaction
18500 && !buffer.is_singleton()
18501 {
18502 buffer.push_transaction(&transaction.0, cx);
18503 }
18504 cx.notify();
18505 });
18506
18507 if let Some(transaction_id_now) =
18508 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
18509 {
18510 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18511 if has_new_transaction {
18512 editor
18513 .update(cx, |editor, _| {
18514 editor
18515 .selection_history
18516 .insert_transaction(transaction_id_now, selections_prev);
18517 })
18518 .ok();
18519 }
18520 }
18521
18522 Ok(())
18523 })
18524 }
18525
18526 fn organize_imports(
18527 &mut self,
18528 _: &OrganizeImports,
18529 window: &mut Window,
18530 cx: &mut Context<Self>,
18531 ) -> Option<Task<Result<()>>> {
18532 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18533 let project = match &self.project {
18534 Some(project) => project.clone(),
18535 None => return None,
18536 };
18537 Some(self.perform_code_action_kind(
18538 project,
18539 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18540 window,
18541 cx,
18542 ))
18543 }
18544
18545 fn perform_code_action_kind(
18546 &mut self,
18547 project: Entity<Project>,
18548 kind: CodeActionKind,
18549 window: &mut Window,
18550 cx: &mut Context<Self>,
18551 ) -> Task<Result<()>> {
18552 let buffer = self.buffer.clone();
18553 let buffers = buffer.read(cx).all_buffers();
18554 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18555 let apply_action = project.update(cx, |project, cx| {
18556 project.apply_code_action_kind(buffers, kind, true, cx)
18557 });
18558 cx.spawn_in(window, async move |_, cx| {
18559 let transaction = futures::select_biased! {
18560 () = timeout => {
18561 log::warn!("timed out waiting for executing code action");
18562 None
18563 }
18564 transaction = apply_action.log_err().fuse() => transaction,
18565 };
18566 buffer.update(cx, |buffer, cx| {
18567 // check if we need this
18568 if let Some(transaction) = transaction
18569 && !buffer.is_singleton()
18570 {
18571 buffer.push_transaction(&transaction.0, cx);
18572 }
18573 cx.notify();
18574 });
18575 Ok(())
18576 })
18577 }
18578
18579 pub fn restart_language_server(
18580 &mut self,
18581 _: &RestartLanguageServer,
18582 _: &mut Window,
18583 cx: &mut Context<Self>,
18584 ) {
18585 if let Some(project) = self.project.clone() {
18586 self.buffer.update(cx, |multi_buffer, cx| {
18587 project.update(cx, |project, cx| {
18588 project.restart_language_servers_for_buffers(
18589 multi_buffer.all_buffers().into_iter().collect(),
18590 HashSet::default(),
18591 cx,
18592 );
18593 });
18594 })
18595 }
18596 }
18597
18598 pub fn stop_language_server(
18599 &mut self,
18600 _: &StopLanguageServer,
18601 _: &mut Window,
18602 cx: &mut Context<Self>,
18603 ) {
18604 if let Some(project) = self.project.clone() {
18605 self.buffer.update(cx, |multi_buffer, cx| {
18606 project.update(cx, |project, cx| {
18607 project.stop_language_servers_for_buffers(
18608 multi_buffer.all_buffers().into_iter().collect(),
18609 HashSet::default(),
18610 cx,
18611 );
18612 });
18613 });
18614 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18615 }
18616 }
18617
18618 fn cancel_language_server_work(
18619 workspace: &mut Workspace,
18620 _: &actions::CancelLanguageServerWork,
18621 _: &mut Window,
18622 cx: &mut Context<Workspace>,
18623 ) {
18624 let project = workspace.project();
18625 let buffers = workspace
18626 .active_item(cx)
18627 .and_then(|item| item.act_as::<Editor>(cx))
18628 .map_or(HashSet::default(), |editor| {
18629 editor.read(cx).buffer.read(cx).all_buffers()
18630 });
18631 project.update(cx, |project, cx| {
18632 project.cancel_language_server_work_for_buffers(buffers, cx);
18633 });
18634 }
18635
18636 fn show_character_palette(
18637 &mut self,
18638 _: &ShowCharacterPalette,
18639 window: &mut Window,
18640 _: &mut Context<Self>,
18641 ) {
18642 window.show_character_palette();
18643 }
18644
18645 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18646 if !self.diagnostics_enabled() {
18647 return;
18648 }
18649
18650 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18651 let buffer = self.buffer.read(cx).snapshot(cx);
18652 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18653 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18654 let is_valid = buffer
18655 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18656 .any(|entry| {
18657 entry.diagnostic.is_primary
18658 && !entry.range.is_empty()
18659 && entry.range.start == primary_range_start
18660 && entry.diagnostic.message == active_diagnostics.active_message
18661 });
18662
18663 if !is_valid {
18664 self.dismiss_diagnostics(cx);
18665 }
18666 }
18667 }
18668
18669 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18670 match &self.active_diagnostics {
18671 ActiveDiagnostic::Group(group) => Some(group),
18672 _ => None,
18673 }
18674 }
18675
18676 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18677 if !self.diagnostics_enabled() {
18678 return;
18679 }
18680 self.dismiss_diagnostics(cx);
18681 self.active_diagnostics = ActiveDiagnostic::All;
18682 }
18683
18684 fn activate_diagnostics(
18685 &mut self,
18686 buffer_id: BufferId,
18687 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18688 window: &mut Window,
18689 cx: &mut Context<Self>,
18690 ) {
18691 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18692 return;
18693 }
18694 self.dismiss_diagnostics(cx);
18695 let snapshot = self.snapshot(window, cx);
18696 let buffer = self.buffer.read(cx).snapshot(cx);
18697 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18698 return;
18699 };
18700
18701 let diagnostic_group = buffer
18702 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18703 .collect::<Vec<_>>();
18704
18705 let language_registry = self
18706 .project()
18707 .map(|project| project.read(cx).languages().clone());
18708
18709 let blocks = renderer.render_group(
18710 diagnostic_group,
18711 buffer_id,
18712 snapshot,
18713 cx.weak_entity(),
18714 language_registry,
18715 cx,
18716 );
18717
18718 let blocks = self.display_map.update(cx, |display_map, cx| {
18719 display_map.insert_blocks(blocks, cx).into_iter().collect()
18720 });
18721 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18722 active_range: buffer.anchor_before(diagnostic.range.start)
18723 ..buffer.anchor_after(diagnostic.range.end),
18724 active_message: diagnostic.diagnostic.message.clone(),
18725 group_id: diagnostic.diagnostic.group_id,
18726 blocks,
18727 });
18728 cx.notify();
18729 }
18730
18731 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18732 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18733 return;
18734 };
18735
18736 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18737 if let ActiveDiagnostic::Group(group) = prev {
18738 self.display_map.update(cx, |display_map, cx| {
18739 display_map.remove_blocks(group.blocks, cx);
18740 });
18741 cx.notify();
18742 }
18743 }
18744
18745 /// Disable inline diagnostics rendering for this editor.
18746 pub fn disable_inline_diagnostics(&mut self) {
18747 self.inline_diagnostics_enabled = false;
18748 self.inline_diagnostics_update = Task::ready(());
18749 self.inline_diagnostics.clear();
18750 }
18751
18752 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18753 self.diagnostics_enabled = false;
18754 self.dismiss_diagnostics(cx);
18755 self.inline_diagnostics_update = Task::ready(());
18756 self.inline_diagnostics.clear();
18757 }
18758
18759 pub fn disable_word_completions(&mut self) {
18760 self.word_completions_enabled = false;
18761 }
18762
18763 pub fn diagnostics_enabled(&self) -> bool {
18764 self.diagnostics_enabled && self.mode.is_full()
18765 }
18766
18767 pub fn inline_diagnostics_enabled(&self) -> bool {
18768 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18769 }
18770
18771 pub fn show_inline_diagnostics(&self) -> bool {
18772 self.show_inline_diagnostics
18773 }
18774
18775 pub fn toggle_inline_diagnostics(
18776 &mut self,
18777 _: &ToggleInlineDiagnostics,
18778 window: &mut Window,
18779 cx: &mut Context<Editor>,
18780 ) {
18781 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18782 self.refresh_inline_diagnostics(false, window, cx);
18783 }
18784
18785 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18786 self.diagnostics_max_severity = severity;
18787 self.display_map.update(cx, |display_map, _| {
18788 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18789 });
18790 }
18791
18792 pub fn toggle_diagnostics(
18793 &mut self,
18794 _: &ToggleDiagnostics,
18795 window: &mut Window,
18796 cx: &mut Context<Editor>,
18797 ) {
18798 if !self.diagnostics_enabled() {
18799 return;
18800 }
18801
18802 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18803 EditorSettings::get_global(cx)
18804 .diagnostics_max_severity
18805 .filter(|severity| severity != &DiagnosticSeverity::Off)
18806 .unwrap_or(DiagnosticSeverity::Hint)
18807 } else {
18808 DiagnosticSeverity::Off
18809 };
18810 self.set_max_diagnostics_severity(new_severity, cx);
18811 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18812 self.active_diagnostics = ActiveDiagnostic::None;
18813 self.inline_diagnostics_update = Task::ready(());
18814 self.inline_diagnostics.clear();
18815 } else {
18816 self.refresh_inline_diagnostics(false, window, cx);
18817 }
18818
18819 cx.notify();
18820 }
18821
18822 pub fn toggle_minimap(
18823 &mut self,
18824 _: &ToggleMinimap,
18825 window: &mut Window,
18826 cx: &mut Context<Editor>,
18827 ) {
18828 if self.supports_minimap(cx) {
18829 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18830 }
18831 }
18832
18833 fn refresh_inline_diagnostics(
18834 &mut self,
18835 debounce: bool,
18836 window: &mut Window,
18837 cx: &mut Context<Self>,
18838 ) {
18839 let max_severity = ProjectSettings::get_global(cx)
18840 .diagnostics
18841 .inline
18842 .max_severity
18843 .unwrap_or(self.diagnostics_max_severity);
18844
18845 if !self.inline_diagnostics_enabled()
18846 || !self.diagnostics_enabled()
18847 || !self.show_inline_diagnostics
18848 || max_severity == DiagnosticSeverity::Off
18849 {
18850 self.inline_diagnostics_update = Task::ready(());
18851 self.inline_diagnostics.clear();
18852 return;
18853 }
18854
18855 let debounce_ms = ProjectSettings::get_global(cx)
18856 .diagnostics
18857 .inline
18858 .update_debounce_ms;
18859 let debounce = if debounce && debounce_ms > 0 {
18860 Some(Duration::from_millis(debounce_ms))
18861 } else {
18862 None
18863 };
18864 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18865 if let Some(debounce) = debounce {
18866 cx.background_executor().timer(debounce).await;
18867 }
18868 let Some(snapshot) = editor.upgrade().map(|editor| {
18869 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18870 }) else {
18871 return;
18872 };
18873
18874 let new_inline_diagnostics = cx
18875 .background_spawn(async move {
18876 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18877 for diagnostic_entry in
18878 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18879 {
18880 let message = diagnostic_entry
18881 .diagnostic
18882 .message
18883 .split_once('\n')
18884 .map(|(line, _)| line)
18885 .map(SharedString::new)
18886 .unwrap_or_else(|| {
18887 SharedString::new(&*diagnostic_entry.diagnostic.message)
18888 });
18889 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18890 let (Ok(i) | Err(i)) = inline_diagnostics
18891 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18892 inline_diagnostics.insert(
18893 i,
18894 (
18895 start_anchor,
18896 InlineDiagnostic {
18897 message,
18898 group_id: diagnostic_entry.diagnostic.group_id,
18899 start: diagnostic_entry.range.start.to_point(&snapshot),
18900 is_primary: diagnostic_entry.diagnostic.is_primary,
18901 severity: diagnostic_entry.diagnostic.severity,
18902 },
18903 ),
18904 );
18905 }
18906 inline_diagnostics
18907 })
18908 .await;
18909
18910 editor
18911 .update(cx, |editor, cx| {
18912 editor.inline_diagnostics = new_inline_diagnostics;
18913 cx.notify();
18914 })
18915 .ok();
18916 });
18917 }
18918
18919 fn pull_diagnostics(
18920 &mut self,
18921 buffer_id: Option<BufferId>,
18922 window: &Window,
18923 cx: &mut Context<Self>,
18924 ) -> Option<()> {
18925 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18926 return None;
18927 }
18928 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18929 .diagnostics
18930 .lsp_pull_diagnostics;
18931 if !pull_diagnostics_settings.enabled {
18932 return None;
18933 }
18934 let project = self.project()?.downgrade();
18935
18936 let mut edited_buffer_ids = HashSet::default();
18937 let mut edited_worktree_ids = HashSet::default();
18938 let edited_buffers = match buffer_id {
18939 Some(buffer_id) => {
18940 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18941 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18942 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18943 edited_worktree_ids.insert(worktree_id);
18944 vec![buffer]
18945 }
18946 None => self
18947 .buffer()
18948 .read(cx)
18949 .all_buffers()
18950 .into_iter()
18951 .filter(|buffer| {
18952 let buffer = buffer.read(cx);
18953 match buffer.file().map(|f| f.worktree_id(cx)) {
18954 Some(worktree_id) => {
18955 edited_buffer_ids.insert(buffer.remote_id());
18956 edited_worktree_ids.insert(worktree_id);
18957 true
18958 }
18959 None => false,
18960 }
18961 })
18962 .collect::<Vec<_>>(),
18963 };
18964
18965 if edited_buffers.is_empty() {
18966 self.pull_diagnostics_task = Task::ready(());
18967 self.pull_diagnostics_background_task = Task::ready(());
18968 return None;
18969 }
18970
18971 let mut already_used_buffers = HashSet::default();
18972 let related_open_buffers = self
18973 .workspace
18974 .as_ref()
18975 .and_then(|(workspace, _)| workspace.upgrade())
18976 .into_iter()
18977 .flat_map(|workspace| workspace.read(cx).panes())
18978 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18979 .filter(|editor| editor != &cx.entity())
18980 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18981 .filter(|buffer| {
18982 let buffer = buffer.read(cx);
18983 let buffer_id = buffer.remote_id();
18984 if already_used_buffers.insert(buffer_id) {
18985 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18986 return !edited_buffer_ids.contains(&buffer_id)
18987 && edited_worktree_ids.contains(&worktree_id);
18988 }
18989 }
18990 false
18991 })
18992 .collect::<Vec<_>>();
18993
18994 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18995 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18996 if buffers.is_empty() {
18997 return Task::ready(());
18998 }
18999 let project_weak = project.clone();
19000 cx.spawn_in(window, async move |_, cx| {
19001 cx.background_executor().timer(delay).await;
19002
19003 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
19004 buffers
19005 .into_iter()
19006 .filter_map(|buffer| {
19007 project_weak
19008 .update(cx, |project, cx| {
19009 project.lsp_store().update(cx, |lsp_store, cx| {
19010 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
19011 })
19012 })
19013 .ok()
19014 })
19015 .collect::<FuturesUnordered<_>>()
19016 }) else {
19017 return;
19018 };
19019
19020 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
19021 if let Err(e) = pull_task {
19022 log::error!("Failed to update project diagnostics: {e:#}");
19023 }
19024 }
19025 })
19026 };
19027
19028 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
19029 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
19030
19031 Some(())
19032 }
19033
19034 pub fn set_selections_from_remote(
19035 &mut self,
19036 selections: Vec<Selection<Anchor>>,
19037 pending_selection: Option<Selection<Anchor>>,
19038 window: &mut Window,
19039 cx: &mut Context<Self>,
19040 ) {
19041 let old_cursor_position = self.selections.newest_anchor().head();
19042 self.selections
19043 .change_with(&self.display_snapshot(cx), |s| {
19044 s.select_anchors(selections);
19045 if let Some(pending_selection) = pending_selection {
19046 s.set_pending(pending_selection, SelectMode::Character);
19047 } else {
19048 s.clear_pending();
19049 }
19050 });
19051 self.selections_did_change(
19052 false,
19053 &old_cursor_position,
19054 SelectionEffects::default(),
19055 window,
19056 cx,
19057 );
19058 }
19059
19060 pub fn transact(
19061 &mut self,
19062 window: &mut Window,
19063 cx: &mut Context<Self>,
19064 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19065 ) -> Option<TransactionId> {
19066 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19067 this.start_transaction_at(Instant::now(), window, cx);
19068 update(this, window, cx);
19069 this.end_transaction_at(Instant::now(), cx)
19070 })
19071 }
19072
19073 pub fn start_transaction_at(
19074 &mut self,
19075 now: Instant,
19076 window: &mut Window,
19077 cx: &mut Context<Self>,
19078 ) -> Option<TransactionId> {
19079 self.end_selection(window, cx);
19080 if let Some(tx_id) = self
19081 .buffer
19082 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19083 {
19084 self.selection_history
19085 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19086 cx.emit(EditorEvent::TransactionBegun {
19087 transaction_id: tx_id,
19088 });
19089 Some(tx_id)
19090 } else {
19091 None
19092 }
19093 }
19094
19095 pub fn end_transaction_at(
19096 &mut self,
19097 now: Instant,
19098 cx: &mut Context<Self>,
19099 ) -> Option<TransactionId> {
19100 if let Some(transaction_id) = self
19101 .buffer
19102 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19103 {
19104 if let Some((_, end_selections)) =
19105 self.selection_history.transaction_mut(transaction_id)
19106 {
19107 *end_selections = Some(self.selections.disjoint_anchors_arc());
19108 } else {
19109 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19110 }
19111
19112 cx.emit(EditorEvent::Edited { transaction_id });
19113 Some(transaction_id)
19114 } else {
19115 None
19116 }
19117 }
19118
19119 pub fn modify_transaction_selection_history(
19120 &mut self,
19121 transaction_id: TransactionId,
19122 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19123 ) -> bool {
19124 self.selection_history
19125 .transaction_mut(transaction_id)
19126 .map(modify)
19127 .is_some()
19128 }
19129
19130 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19131 if self.selection_mark_mode {
19132 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19133 s.move_with(|_, sel| {
19134 sel.collapse_to(sel.head(), SelectionGoal::None);
19135 });
19136 })
19137 }
19138 self.selection_mark_mode = true;
19139 cx.notify();
19140 }
19141
19142 pub fn swap_selection_ends(
19143 &mut self,
19144 _: &actions::SwapSelectionEnds,
19145 window: &mut Window,
19146 cx: &mut Context<Self>,
19147 ) {
19148 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19149 s.move_with(|_, sel| {
19150 if sel.start != sel.end {
19151 sel.reversed = !sel.reversed
19152 }
19153 });
19154 });
19155 self.request_autoscroll(Autoscroll::newest(), cx);
19156 cx.notify();
19157 }
19158
19159 pub fn toggle_focus(
19160 workspace: &mut Workspace,
19161 _: &actions::ToggleFocus,
19162 window: &mut Window,
19163 cx: &mut Context<Workspace>,
19164 ) {
19165 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19166 return;
19167 };
19168 workspace.activate_item(&item, true, true, window, cx);
19169 }
19170
19171 pub fn toggle_fold(
19172 &mut self,
19173 _: &actions::ToggleFold,
19174 window: &mut Window,
19175 cx: &mut Context<Self>,
19176 ) {
19177 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19178 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19179 let selection = self.selections.newest::<Point>(&display_map);
19180
19181 let range = if selection.is_empty() {
19182 let point = selection.head().to_display_point(&display_map);
19183 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19184 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19185 .to_point(&display_map);
19186 start..end
19187 } else {
19188 selection.range()
19189 };
19190 if display_map.folds_in_range(range).next().is_some() {
19191 self.unfold_lines(&Default::default(), window, cx)
19192 } else {
19193 self.fold(&Default::default(), window, cx)
19194 }
19195 } else {
19196 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19197 let buffer_ids: HashSet<_> = self
19198 .selections
19199 .disjoint_anchor_ranges()
19200 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19201 .collect();
19202
19203 let should_unfold = buffer_ids
19204 .iter()
19205 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19206
19207 for buffer_id in buffer_ids {
19208 if should_unfold {
19209 self.unfold_buffer(buffer_id, cx);
19210 } else {
19211 self.fold_buffer(buffer_id, cx);
19212 }
19213 }
19214 }
19215 }
19216
19217 pub fn toggle_fold_recursive(
19218 &mut self,
19219 _: &actions::ToggleFoldRecursive,
19220 window: &mut Window,
19221 cx: &mut Context<Self>,
19222 ) {
19223 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19224
19225 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19226 let range = if selection.is_empty() {
19227 let point = selection.head().to_display_point(&display_map);
19228 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19229 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19230 .to_point(&display_map);
19231 start..end
19232 } else {
19233 selection.range()
19234 };
19235 if display_map.folds_in_range(range).next().is_some() {
19236 self.unfold_recursive(&Default::default(), window, cx)
19237 } else {
19238 self.fold_recursive(&Default::default(), window, cx)
19239 }
19240 }
19241
19242 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19243 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19244 let mut to_fold = Vec::new();
19245 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19246 let selections = self.selections.all_adjusted(&display_map);
19247
19248 for selection in selections {
19249 let range = selection.range().sorted();
19250 let buffer_start_row = range.start.row;
19251
19252 if range.start.row != range.end.row {
19253 let mut found = false;
19254 let mut row = range.start.row;
19255 while row <= range.end.row {
19256 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19257 {
19258 found = true;
19259 row = crease.range().end.row + 1;
19260 to_fold.push(crease);
19261 } else {
19262 row += 1
19263 }
19264 }
19265 if found {
19266 continue;
19267 }
19268 }
19269
19270 for row in (0..=range.start.row).rev() {
19271 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19272 && crease.range().end.row >= buffer_start_row
19273 {
19274 to_fold.push(crease);
19275 if row <= range.start.row {
19276 break;
19277 }
19278 }
19279 }
19280 }
19281
19282 self.fold_creases(to_fold, true, window, cx);
19283 } else {
19284 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19285 let buffer_ids = self
19286 .selections
19287 .disjoint_anchor_ranges()
19288 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19289 .collect::<HashSet<_>>();
19290 for buffer_id in buffer_ids {
19291 self.fold_buffer(buffer_id, cx);
19292 }
19293 }
19294 }
19295
19296 pub fn toggle_fold_all(
19297 &mut self,
19298 _: &actions::ToggleFoldAll,
19299 window: &mut Window,
19300 cx: &mut Context<Self>,
19301 ) {
19302 let has_folds = if self.buffer.read(cx).is_singleton() {
19303 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19304 let has_folds = display_map
19305 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19306 .next()
19307 .is_some();
19308 has_folds
19309 } else {
19310 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19311 let has_folds = buffer_ids
19312 .iter()
19313 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19314 has_folds
19315 };
19316
19317 if has_folds {
19318 self.unfold_all(&actions::UnfoldAll, window, cx);
19319 } else {
19320 self.fold_all(&actions::FoldAll, window, cx);
19321 }
19322 }
19323
19324 fn fold_at_level(
19325 &mut self,
19326 fold_at: &FoldAtLevel,
19327 window: &mut Window,
19328 cx: &mut Context<Self>,
19329 ) {
19330 if !self.buffer.read(cx).is_singleton() {
19331 return;
19332 }
19333
19334 let fold_at_level = fold_at.0;
19335 let snapshot = self.buffer.read(cx).snapshot(cx);
19336 let mut to_fold = Vec::new();
19337 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19338
19339 let row_ranges_to_keep: Vec<Range<u32>> = self
19340 .selections
19341 .all::<Point>(&self.display_snapshot(cx))
19342 .into_iter()
19343 .map(|sel| sel.start.row..sel.end.row)
19344 .collect();
19345
19346 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19347 while start_row < end_row {
19348 match self
19349 .snapshot(window, cx)
19350 .crease_for_buffer_row(MultiBufferRow(start_row))
19351 {
19352 Some(crease) => {
19353 let nested_start_row = crease.range().start.row + 1;
19354 let nested_end_row = crease.range().end.row;
19355
19356 if current_level < fold_at_level {
19357 stack.push((nested_start_row, nested_end_row, current_level + 1));
19358 } else if current_level == fold_at_level {
19359 // Fold iff there is no selection completely contained within the fold region
19360 if !row_ranges_to_keep.iter().any(|selection| {
19361 selection.end >= nested_start_row
19362 && selection.start <= nested_end_row
19363 }) {
19364 to_fold.push(crease);
19365 }
19366 }
19367
19368 start_row = nested_end_row + 1;
19369 }
19370 None => start_row += 1,
19371 }
19372 }
19373 }
19374
19375 self.fold_creases(to_fold, true, window, cx);
19376 }
19377
19378 pub fn fold_at_level_1(
19379 &mut self,
19380 _: &actions::FoldAtLevel1,
19381 window: &mut Window,
19382 cx: &mut Context<Self>,
19383 ) {
19384 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19385 }
19386
19387 pub fn fold_at_level_2(
19388 &mut self,
19389 _: &actions::FoldAtLevel2,
19390 window: &mut Window,
19391 cx: &mut Context<Self>,
19392 ) {
19393 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19394 }
19395
19396 pub fn fold_at_level_3(
19397 &mut self,
19398 _: &actions::FoldAtLevel3,
19399 window: &mut Window,
19400 cx: &mut Context<Self>,
19401 ) {
19402 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19403 }
19404
19405 pub fn fold_at_level_4(
19406 &mut self,
19407 _: &actions::FoldAtLevel4,
19408 window: &mut Window,
19409 cx: &mut Context<Self>,
19410 ) {
19411 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19412 }
19413
19414 pub fn fold_at_level_5(
19415 &mut self,
19416 _: &actions::FoldAtLevel5,
19417 window: &mut Window,
19418 cx: &mut Context<Self>,
19419 ) {
19420 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19421 }
19422
19423 pub fn fold_at_level_6(
19424 &mut self,
19425 _: &actions::FoldAtLevel6,
19426 window: &mut Window,
19427 cx: &mut Context<Self>,
19428 ) {
19429 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19430 }
19431
19432 pub fn fold_at_level_7(
19433 &mut self,
19434 _: &actions::FoldAtLevel7,
19435 window: &mut Window,
19436 cx: &mut Context<Self>,
19437 ) {
19438 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19439 }
19440
19441 pub fn fold_at_level_8(
19442 &mut self,
19443 _: &actions::FoldAtLevel8,
19444 window: &mut Window,
19445 cx: &mut Context<Self>,
19446 ) {
19447 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19448 }
19449
19450 pub fn fold_at_level_9(
19451 &mut self,
19452 _: &actions::FoldAtLevel9,
19453 window: &mut Window,
19454 cx: &mut Context<Self>,
19455 ) {
19456 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19457 }
19458
19459 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19460 if self.buffer.read(cx).is_singleton() {
19461 let mut fold_ranges = Vec::new();
19462 let snapshot = self.buffer.read(cx).snapshot(cx);
19463
19464 for row in 0..snapshot.max_row().0 {
19465 if let Some(foldable_range) = self
19466 .snapshot(window, cx)
19467 .crease_for_buffer_row(MultiBufferRow(row))
19468 {
19469 fold_ranges.push(foldable_range);
19470 }
19471 }
19472
19473 self.fold_creases(fold_ranges, true, window, cx);
19474 } else {
19475 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19476 editor
19477 .update_in(cx, |editor, _, cx| {
19478 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19479 editor.fold_buffer(buffer_id, cx);
19480 }
19481 })
19482 .ok();
19483 });
19484 }
19485 cx.emit(SearchEvent::ResultsCollapsedChanged(
19486 CollapseDirection::Collapsed,
19487 ));
19488 }
19489
19490 pub fn fold_function_bodies(
19491 &mut self,
19492 _: &actions::FoldFunctionBodies,
19493 window: &mut Window,
19494 cx: &mut Context<Self>,
19495 ) {
19496 let snapshot = self.buffer.read(cx).snapshot(cx);
19497
19498 let ranges = snapshot
19499 .text_object_ranges(
19500 MultiBufferOffset(0)..snapshot.len(),
19501 TreeSitterOptions::default(),
19502 )
19503 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19504 .collect::<Vec<_>>();
19505
19506 let creases = ranges
19507 .into_iter()
19508 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19509 .collect();
19510
19511 self.fold_creases(creases, true, window, cx);
19512 }
19513
19514 pub fn fold_recursive(
19515 &mut self,
19516 _: &actions::FoldRecursive,
19517 window: &mut Window,
19518 cx: &mut Context<Self>,
19519 ) {
19520 let mut to_fold = Vec::new();
19521 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19522 let selections = self.selections.all_adjusted(&display_map);
19523
19524 for selection in selections {
19525 let range = selection.range().sorted();
19526 let buffer_start_row = range.start.row;
19527
19528 if range.start.row != range.end.row {
19529 let mut found = false;
19530 for row in range.start.row..=range.end.row {
19531 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19532 found = true;
19533 to_fold.push(crease);
19534 }
19535 }
19536 if found {
19537 continue;
19538 }
19539 }
19540
19541 for row in (0..=range.start.row).rev() {
19542 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19543 if crease.range().end.row >= buffer_start_row {
19544 to_fold.push(crease);
19545 } else {
19546 break;
19547 }
19548 }
19549 }
19550 }
19551
19552 self.fold_creases(to_fold, true, window, cx);
19553 }
19554
19555 pub fn fold_at(
19556 &mut self,
19557 buffer_row: MultiBufferRow,
19558 window: &mut Window,
19559 cx: &mut Context<Self>,
19560 ) {
19561 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19562
19563 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19564 let autoscroll = self
19565 .selections
19566 .all::<Point>(&display_map)
19567 .iter()
19568 .any(|selection| crease.range().overlaps(&selection.range()));
19569
19570 self.fold_creases(vec![crease], autoscroll, window, cx);
19571 }
19572 }
19573
19574 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19575 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19576 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19577 let buffer = display_map.buffer_snapshot();
19578 let selections = self.selections.all::<Point>(&display_map);
19579 let ranges = selections
19580 .iter()
19581 .map(|s| {
19582 let range = s.display_range(&display_map).sorted();
19583 let mut start = range.start.to_point(&display_map);
19584 let mut end = range.end.to_point(&display_map);
19585 start.column = 0;
19586 end.column = buffer.line_len(MultiBufferRow(end.row));
19587 start..end
19588 })
19589 .collect::<Vec<_>>();
19590
19591 self.unfold_ranges(&ranges, true, true, cx);
19592 } else {
19593 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19594 let buffer_ids = self
19595 .selections
19596 .disjoint_anchor_ranges()
19597 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19598 .collect::<HashSet<_>>();
19599 for buffer_id in buffer_ids {
19600 self.unfold_buffer(buffer_id, cx);
19601 }
19602 }
19603 }
19604
19605 pub fn unfold_recursive(
19606 &mut self,
19607 _: &UnfoldRecursive,
19608 _window: &mut Window,
19609 cx: &mut Context<Self>,
19610 ) {
19611 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19612 let selections = self.selections.all::<Point>(&display_map);
19613 let ranges = selections
19614 .iter()
19615 .map(|s| {
19616 let mut range = s.display_range(&display_map).sorted();
19617 *range.start.column_mut() = 0;
19618 *range.end.column_mut() = display_map.line_len(range.end.row());
19619 let start = range.start.to_point(&display_map);
19620 let end = range.end.to_point(&display_map);
19621 start..end
19622 })
19623 .collect::<Vec<_>>();
19624
19625 self.unfold_ranges(&ranges, true, true, cx);
19626 }
19627
19628 pub fn unfold_at(
19629 &mut self,
19630 buffer_row: MultiBufferRow,
19631 _window: &mut Window,
19632 cx: &mut Context<Self>,
19633 ) {
19634 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19635
19636 let intersection_range = Point::new(buffer_row.0, 0)
19637 ..Point::new(
19638 buffer_row.0,
19639 display_map.buffer_snapshot().line_len(buffer_row),
19640 );
19641
19642 let autoscroll = self
19643 .selections
19644 .all::<Point>(&display_map)
19645 .iter()
19646 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19647
19648 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19649 }
19650
19651 pub fn unfold_all(
19652 &mut self,
19653 _: &actions::UnfoldAll,
19654 _window: &mut Window,
19655 cx: &mut Context<Self>,
19656 ) {
19657 if self.buffer.read(cx).is_singleton() {
19658 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19659 self.unfold_ranges(
19660 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19661 true,
19662 true,
19663 cx,
19664 );
19665 } else {
19666 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19667 editor
19668 .update(cx, |editor, cx| {
19669 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19670 editor.unfold_buffer(buffer_id, cx);
19671 }
19672 })
19673 .ok();
19674 });
19675 }
19676 cx.emit(SearchEvent::ResultsCollapsedChanged(
19677 CollapseDirection::Expanded,
19678 ));
19679 }
19680
19681 pub fn fold_selected_ranges(
19682 &mut self,
19683 _: &FoldSelectedRanges,
19684 window: &mut Window,
19685 cx: &mut Context<Self>,
19686 ) {
19687 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19688 let selections = self.selections.all_adjusted(&display_map);
19689 let ranges = selections
19690 .into_iter()
19691 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19692 .collect::<Vec<_>>();
19693 self.fold_creases(ranges, true, window, cx);
19694 }
19695
19696 pub fn fold_ranges<T: ToOffset + Clone>(
19697 &mut self,
19698 ranges: Vec<Range<T>>,
19699 auto_scroll: bool,
19700 window: &mut Window,
19701 cx: &mut Context<Self>,
19702 ) {
19703 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19704 let ranges = ranges
19705 .into_iter()
19706 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19707 .collect::<Vec<_>>();
19708 self.fold_creases(ranges, auto_scroll, window, cx);
19709 }
19710
19711 pub fn fold_creases<T: ToOffset + Clone>(
19712 &mut self,
19713 creases: Vec<Crease<T>>,
19714 auto_scroll: bool,
19715 _window: &mut Window,
19716 cx: &mut Context<Self>,
19717 ) {
19718 if creases.is_empty() {
19719 return;
19720 }
19721
19722 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19723
19724 if auto_scroll {
19725 self.request_autoscroll(Autoscroll::fit(), cx);
19726 }
19727
19728 cx.notify();
19729
19730 self.scrollbar_marker_state.dirty = true;
19731 self.folds_did_change(cx);
19732 }
19733
19734 /// Removes any folds whose ranges intersect any of the given ranges.
19735 pub fn unfold_ranges<T: ToOffset + Clone>(
19736 &mut self,
19737 ranges: &[Range<T>],
19738 inclusive: bool,
19739 auto_scroll: bool,
19740 cx: &mut Context<Self>,
19741 ) {
19742 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19743 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19744 });
19745 self.folds_did_change(cx);
19746 }
19747
19748 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19749 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19750 return;
19751 }
19752
19753 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19754 self.display_map.update(cx, |display_map, cx| {
19755 display_map.fold_buffers([buffer_id], cx)
19756 });
19757
19758 let snapshot = self.display_snapshot(cx);
19759 self.selections.change_with(&snapshot, |selections| {
19760 selections.remove_selections_from_buffer(buffer_id);
19761 });
19762
19763 cx.emit(EditorEvent::BufferFoldToggled {
19764 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19765 folded: true,
19766 });
19767 cx.notify();
19768 }
19769
19770 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19771 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19772 return;
19773 }
19774 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19775 self.display_map.update(cx, |display_map, cx| {
19776 display_map.unfold_buffers([buffer_id], cx);
19777 });
19778 cx.emit(EditorEvent::BufferFoldToggled {
19779 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19780 folded: false,
19781 });
19782 cx.notify();
19783 }
19784
19785 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19786 self.display_map.read(cx).is_buffer_folded(buffer)
19787 }
19788
19789 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19790 self.display_map.read(cx).folded_buffers()
19791 }
19792
19793 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19794 self.display_map.update(cx, |display_map, cx| {
19795 display_map.disable_header_for_buffer(buffer_id, cx);
19796 });
19797 cx.notify();
19798 }
19799
19800 /// Removes any folds with the given ranges.
19801 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19802 &mut self,
19803 ranges: &[Range<T>],
19804 type_id: TypeId,
19805 auto_scroll: bool,
19806 cx: &mut Context<Self>,
19807 ) {
19808 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19809 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19810 });
19811 self.folds_did_change(cx);
19812 }
19813
19814 fn remove_folds_with<T: ToOffset + Clone>(
19815 &mut self,
19816 ranges: &[Range<T>],
19817 auto_scroll: bool,
19818 cx: &mut Context<Self>,
19819 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19820 ) {
19821 if ranges.is_empty() {
19822 return;
19823 }
19824
19825 let mut buffers_affected = HashSet::default();
19826 let multi_buffer = self.buffer().read(cx);
19827 for range in ranges {
19828 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19829 buffers_affected.insert(buffer.read(cx).remote_id());
19830 };
19831 }
19832
19833 self.display_map.update(cx, update);
19834
19835 if auto_scroll {
19836 self.request_autoscroll(Autoscroll::fit(), cx);
19837 }
19838
19839 cx.notify();
19840 self.scrollbar_marker_state.dirty = true;
19841 self.active_indent_guides_state.dirty = true;
19842 }
19843
19844 pub fn update_renderer_widths(
19845 &mut self,
19846 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19847 cx: &mut Context<Self>,
19848 ) -> bool {
19849 self.display_map
19850 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19851 }
19852
19853 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19854 self.display_map.read(cx).fold_placeholder.clone()
19855 }
19856
19857 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19858 self.buffer.update(cx, |buffer, cx| {
19859 buffer.set_all_diff_hunks_expanded(cx);
19860 });
19861 }
19862
19863 pub fn expand_all_diff_hunks(
19864 &mut self,
19865 _: &ExpandAllDiffHunks,
19866 _window: &mut Window,
19867 cx: &mut Context<Self>,
19868 ) {
19869 self.buffer.update(cx, |buffer, cx| {
19870 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19871 });
19872 }
19873
19874 pub fn collapse_all_diff_hunks(
19875 &mut self,
19876 _: &CollapseAllDiffHunks,
19877 _window: &mut Window,
19878 cx: &mut Context<Self>,
19879 ) {
19880 self.buffer.update(cx, |buffer, cx| {
19881 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19882 });
19883 }
19884
19885 pub fn toggle_selected_diff_hunks(
19886 &mut self,
19887 _: &ToggleSelectedDiffHunks,
19888 _window: &mut Window,
19889 cx: &mut Context<Self>,
19890 ) {
19891 let ranges: Vec<_> = self
19892 .selections
19893 .disjoint_anchors()
19894 .iter()
19895 .map(|s| s.range())
19896 .collect();
19897 self.toggle_diff_hunks_in_ranges(ranges, cx);
19898 }
19899
19900 pub fn diff_hunks_in_ranges<'a>(
19901 &'a self,
19902 ranges: &'a [Range<Anchor>],
19903 buffer: &'a MultiBufferSnapshot,
19904 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19905 ranges.iter().flat_map(move |range| {
19906 let end_excerpt_id = range.end.excerpt_id;
19907 let range = range.to_point(buffer);
19908 let mut peek_end = range.end;
19909 if range.end.row < buffer.max_row().0 {
19910 peek_end = Point::new(range.end.row + 1, 0);
19911 }
19912 buffer
19913 .diff_hunks_in_range(range.start..peek_end)
19914 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19915 })
19916 }
19917
19918 pub fn has_stageable_diff_hunks_in_ranges(
19919 &self,
19920 ranges: &[Range<Anchor>],
19921 snapshot: &MultiBufferSnapshot,
19922 ) -> bool {
19923 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19924 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19925 }
19926
19927 pub fn toggle_staged_selected_diff_hunks(
19928 &mut self,
19929 _: &::git::ToggleStaged,
19930 _: &mut Window,
19931 cx: &mut Context<Self>,
19932 ) {
19933 let snapshot = self.buffer.read(cx).snapshot(cx);
19934 let ranges: Vec<_> = self
19935 .selections
19936 .disjoint_anchors()
19937 .iter()
19938 .map(|s| s.range())
19939 .collect();
19940 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19941 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19942 }
19943
19944 pub fn set_render_diff_hunk_controls(
19945 &mut self,
19946 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19947 cx: &mut Context<Self>,
19948 ) {
19949 self.render_diff_hunk_controls = render_diff_hunk_controls;
19950 cx.notify();
19951 }
19952
19953 pub fn stage_and_next(
19954 &mut self,
19955 _: &::git::StageAndNext,
19956 window: &mut Window,
19957 cx: &mut Context<Self>,
19958 ) {
19959 self.do_stage_or_unstage_and_next(true, window, cx);
19960 }
19961
19962 pub fn unstage_and_next(
19963 &mut self,
19964 _: &::git::UnstageAndNext,
19965 window: &mut Window,
19966 cx: &mut Context<Self>,
19967 ) {
19968 self.do_stage_or_unstage_and_next(false, window, cx);
19969 }
19970
19971 pub fn stage_or_unstage_diff_hunks(
19972 &mut self,
19973 stage: bool,
19974 ranges: Vec<Range<Anchor>>,
19975 cx: &mut Context<Self>,
19976 ) {
19977 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19978 cx.spawn(async move |this, cx| {
19979 task.await?;
19980 this.update(cx, |this, cx| {
19981 let snapshot = this.buffer.read(cx).snapshot(cx);
19982 let chunk_by = this
19983 .diff_hunks_in_ranges(&ranges, &snapshot)
19984 .chunk_by(|hunk| hunk.buffer_id);
19985 for (buffer_id, hunks) in &chunk_by {
19986 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19987 }
19988 })
19989 })
19990 .detach_and_log_err(cx);
19991 }
19992
19993 fn save_buffers_for_ranges_if_needed(
19994 &mut self,
19995 ranges: &[Range<Anchor>],
19996 cx: &mut Context<Editor>,
19997 ) -> Task<Result<()>> {
19998 let multibuffer = self.buffer.read(cx);
19999 let snapshot = multibuffer.read(cx);
20000 let buffer_ids: HashSet<_> = ranges
20001 .iter()
20002 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
20003 .collect();
20004 drop(snapshot);
20005
20006 let mut buffers = HashSet::default();
20007 for buffer_id in buffer_ids {
20008 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
20009 let buffer = buffer_entity.read(cx);
20010 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
20011 {
20012 buffers.insert(buffer_entity);
20013 }
20014 }
20015 }
20016
20017 if let Some(project) = &self.project {
20018 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
20019 } else {
20020 Task::ready(Ok(()))
20021 }
20022 }
20023
20024 fn do_stage_or_unstage_and_next(
20025 &mut self,
20026 stage: bool,
20027 window: &mut Window,
20028 cx: &mut Context<Self>,
20029 ) {
20030 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
20031
20032 if ranges.iter().any(|range| range.start != range.end) {
20033 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20034 return;
20035 }
20036
20037 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20038 let snapshot = self.snapshot(window, cx);
20039 let position = self
20040 .selections
20041 .newest::<Point>(&snapshot.display_snapshot)
20042 .head();
20043 let mut row = snapshot
20044 .buffer_snapshot()
20045 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20046 .find(|hunk| hunk.row_range.start.0 > position.row)
20047 .map(|hunk| hunk.row_range.start);
20048
20049 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20050 // Outside of the project diff editor, wrap around to the beginning.
20051 if !all_diff_hunks_expanded {
20052 row = row.or_else(|| {
20053 snapshot
20054 .buffer_snapshot()
20055 .diff_hunks_in_range(Point::zero()..position)
20056 .find(|hunk| hunk.row_range.end.0 < position.row)
20057 .map(|hunk| hunk.row_range.start)
20058 });
20059 }
20060
20061 if let Some(row) = row {
20062 let destination = Point::new(row.0, 0);
20063 let autoscroll = Autoscroll::center();
20064
20065 self.unfold_ranges(&[destination..destination], false, false, cx);
20066 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20067 s.select_ranges([destination..destination]);
20068 });
20069 }
20070 }
20071
20072 fn do_stage_or_unstage(
20073 &self,
20074 stage: bool,
20075 buffer_id: BufferId,
20076 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20077 cx: &mut App,
20078 ) -> Option<()> {
20079 let project = self.project()?;
20080 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20081 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20082 let buffer_snapshot = buffer.read(cx).snapshot();
20083 let file_exists = buffer_snapshot
20084 .file()
20085 .is_some_and(|file| file.disk_state().exists());
20086 diff.update(cx, |diff, cx| {
20087 diff.stage_or_unstage_hunks(
20088 stage,
20089 &hunks
20090 .map(|hunk| buffer_diff::DiffHunk {
20091 buffer_range: hunk.buffer_range,
20092 // We don't need to pass in word diffs here because they're only used for rendering and
20093 // this function changes internal state
20094 base_word_diffs: Vec::default(),
20095 buffer_word_diffs: Vec::default(),
20096 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20097 ..hunk.diff_base_byte_range.end.0,
20098 secondary_status: hunk.status.secondary,
20099 range: Point::zero()..Point::zero(), // unused
20100 })
20101 .collect::<Vec<_>>(),
20102 &buffer_snapshot,
20103 file_exists,
20104 cx,
20105 )
20106 });
20107 None
20108 }
20109
20110 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20111 let ranges: Vec<_> = self
20112 .selections
20113 .disjoint_anchors()
20114 .iter()
20115 .map(|s| s.range())
20116 .collect();
20117 self.buffer
20118 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20119 }
20120
20121 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20122 self.buffer.update(cx, |buffer, cx| {
20123 let ranges = vec![Anchor::min()..Anchor::max()];
20124 if !buffer.all_diff_hunks_expanded()
20125 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20126 {
20127 buffer.collapse_diff_hunks(ranges, cx);
20128 true
20129 } else {
20130 false
20131 }
20132 })
20133 }
20134
20135 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20136 if self.buffer.read(cx).all_diff_hunks_expanded() {
20137 return true;
20138 }
20139 let ranges = vec![Anchor::min()..Anchor::max()];
20140 self.buffer
20141 .read(cx)
20142 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20143 }
20144
20145 fn toggle_diff_hunks_in_ranges(
20146 &mut self,
20147 ranges: Vec<Range<Anchor>>,
20148 cx: &mut Context<Editor>,
20149 ) {
20150 self.buffer.update(cx, |buffer, cx| {
20151 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20152 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20153 })
20154 }
20155
20156 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20157 self.buffer.update(cx, |buffer, cx| {
20158 let snapshot = buffer.snapshot(cx);
20159 let excerpt_id = range.end.excerpt_id;
20160 let point_range = range.to_point(&snapshot);
20161 let expand = !buffer.single_hunk_is_expanded(range, cx);
20162 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20163 })
20164 }
20165
20166 pub(crate) fn apply_all_diff_hunks(
20167 &mut self,
20168 _: &ApplyAllDiffHunks,
20169 window: &mut Window,
20170 cx: &mut Context<Self>,
20171 ) {
20172 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20173
20174 let buffers = self.buffer.read(cx).all_buffers();
20175 for branch_buffer in buffers {
20176 branch_buffer.update(cx, |branch_buffer, cx| {
20177 branch_buffer.merge_into_base(Vec::new(), cx);
20178 });
20179 }
20180
20181 if let Some(project) = self.project.clone() {
20182 self.save(
20183 SaveOptions {
20184 format: true,
20185 autosave: false,
20186 },
20187 project,
20188 window,
20189 cx,
20190 )
20191 .detach_and_log_err(cx);
20192 }
20193 }
20194
20195 pub(crate) fn apply_selected_diff_hunks(
20196 &mut self,
20197 _: &ApplyDiffHunk,
20198 window: &mut Window,
20199 cx: &mut Context<Self>,
20200 ) {
20201 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20202 let snapshot = self.snapshot(window, cx);
20203 let hunks = snapshot.hunks_for_ranges(
20204 self.selections
20205 .all(&snapshot.display_snapshot)
20206 .into_iter()
20207 .map(|selection| selection.range()),
20208 );
20209 let mut ranges_by_buffer = HashMap::default();
20210 self.transact(window, cx, |editor, _window, cx| {
20211 for hunk in hunks {
20212 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20213 ranges_by_buffer
20214 .entry(buffer.clone())
20215 .or_insert_with(Vec::new)
20216 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20217 }
20218 }
20219
20220 for (buffer, ranges) in ranges_by_buffer {
20221 buffer.update(cx, |buffer, cx| {
20222 buffer.merge_into_base(ranges, cx);
20223 });
20224 }
20225 });
20226
20227 if let Some(project) = self.project.clone() {
20228 self.save(
20229 SaveOptions {
20230 format: true,
20231 autosave: false,
20232 },
20233 project,
20234 window,
20235 cx,
20236 )
20237 .detach_and_log_err(cx);
20238 }
20239 }
20240
20241 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20242 if hovered != self.gutter_hovered {
20243 self.gutter_hovered = hovered;
20244 cx.notify();
20245 }
20246 }
20247
20248 pub fn insert_blocks(
20249 &mut self,
20250 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20251 autoscroll: Option<Autoscroll>,
20252 cx: &mut Context<Self>,
20253 ) -> Vec<CustomBlockId> {
20254 let blocks = self
20255 .display_map
20256 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20257 if let Some(autoscroll) = autoscroll {
20258 self.request_autoscroll(autoscroll, cx);
20259 }
20260 cx.notify();
20261 blocks
20262 }
20263
20264 pub fn resize_blocks(
20265 &mut self,
20266 heights: HashMap<CustomBlockId, u32>,
20267 autoscroll: Option<Autoscroll>,
20268 cx: &mut Context<Self>,
20269 ) {
20270 self.display_map
20271 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20272 if let Some(autoscroll) = autoscroll {
20273 self.request_autoscroll(autoscroll, cx);
20274 }
20275 cx.notify();
20276 }
20277
20278 pub fn replace_blocks(
20279 &mut self,
20280 renderers: HashMap<CustomBlockId, RenderBlock>,
20281 autoscroll: Option<Autoscroll>,
20282 cx: &mut Context<Self>,
20283 ) {
20284 self.display_map
20285 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20286 if let Some(autoscroll) = autoscroll {
20287 self.request_autoscroll(autoscroll, cx);
20288 }
20289 cx.notify();
20290 }
20291
20292 pub fn remove_blocks(
20293 &mut self,
20294 block_ids: HashSet<CustomBlockId>,
20295 autoscroll: Option<Autoscroll>,
20296 cx: &mut Context<Self>,
20297 ) {
20298 self.display_map.update(cx, |display_map, cx| {
20299 display_map.remove_blocks(block_ids, cx)
20300 });
20301 if let Some(autoscroll) = autoscroll {
20302 self.request_autoscroll(autoscroll, cx);
20303 }
20304 cx.notify();
20305 }
20306
20307 pub fn row_for_block(
20308 &self,
20309 block_id: CustomBlockId,
20310 cx: &mut Context<Self>,
20311 ) -> Option<DisplayRow> {
20312 self.display_map
20313 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20314 }
20315
20316 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20317 self.focused_block = Some(focused_block);
20318 }
20319
20320 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20321 self.focused_block.take()
20322 }
20323
20324 pub fn insert_creases(
20325 &mut self,
20326 creases: impl IntoIterator<Item = Crease<Anchor>>,
20327 cx: &mut Context<Self>,
20328 ) -> Vec<CreaseId> {
20329 self.display_map
20330 .update(cx, |map, cx| map.insert_creases(creases, cx))
20331 }
20332
20333 pub fn remove_creases(
20334 &mut self,
20335 ids: impl IntoIterator<Item = CreaseId>,
20336 cx: &mut Context<Self>,
20337 ) -> Vec<(CreaseId, Range<Anchor>)> {
20338 self.display_map
20339 .update(cx, |map, cx| map.remove_creases(ids, cx))
20340 }
20341
20342 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20343 self.display_map
20344 .update(cx, |map, cx| map.snapshot(cx))
20345 .longest_row()
20346 }
20347
20348 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20349 self.display_map
20350 .update(cx, |map, cx| map.snapshot(cx))
20351 .max_point()
20352 }
20353
20354 pub fn text(&self, cx: &App) -> String {
20355 self.buffer.read(cx).read(cx).text()
20356 }
20357
20358 pub fn is_empty(&self, cx: &App) -> bool {
20359 self.buffer.read(cx).read(cx).is_empty()
20360 }
20361
20362 pub fn text_option(&self, cx: &App) -> Option<String> {
20363 let text = self.text(cx);
20364 let text = text.trim();
20365
20366 if text.is_empty() {
20367 return None;
20368 }
20369
20370 Some(text.to_string())
20371 }
20372
20373 pub fn set_text(
20374 &mut self,
20375 text: impl Into<Arc<str>>,
20376 window: &mut Window,
20377 cx: &mut Context<Self>,
20378 ) {
20379 self.transact(window, cx, |this, _, cx| {
20380 this.buffer
20381 .read(cx)
20382 .as_singleton()
20383 .expect("you can only call set_text on editors for singleton buffers")
20384 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20385 });
20386 }
20387
20388 pub fn display_text(&self, cx: &mut App) -> String {
20389 self.display_map
20390 .update(cx, |map, cx| map.snapshot(cx))
20391 .text()
20392 }
20393
20394 fn create_minimap(
20395 &self,
20396 minimap_settings: MinimapSettings,
20397 window: &mut Window,
20398 cx: &mut Context<Self>,
20399 ) -> Option<Entity<Self>> {
20400 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20401 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20402 }
20403
20404 fn initialize_new_minimap(
20405 &self,
20406 minimap_settings: MinimapSettings,
20407 window: &mut Window,
20408 cx: &mut Context<Self>,
20409 ) -> Entity<Self> {
20410 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20411 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20412
20413 let mut minimap = Editor::new_internal(
20414 EditorMode::Minimap {
20415 parent: cx.weak_entity(),
20416 },
20417 self.buffer.clone(),
20418 None,
20419 Some(self.display_map.clone()),
20420 window,
20421 cx,
20422 );
20423 minimap.scroll_manager.clone_state(&self.scroll_manager);
20424 minimap.set_text_style_refinement(TextStyleRefinement {
20425 font_size: Some(MINIMAP_FONT_SIZE),
20426 font_weight: Some(MINIMAP_FONT_WEIGHT),
20427 font_family: Some(MINIMAP_FONT_FAMILY),
20428 ..Default::default()
20429 });
20430 minimap.update_minimap_configuration(minimap_settings, cx);
20431 cx.new(|_| minimap)
20432 }
20433
20434 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20435 let current_line_highlight = minimap_settings
20436 .current_line_highlight
20437 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20438 self.set_current_line_highlight(Some(current_line_highlight));
20439 }
20440
20441 pub fn minimap(&self) -> Option<&Entity<Self>> {
20442 self.minimap
20443 .as_ref()
20444 .filter(|_| self.minimap_visibility.visible())
20445 }
20446
20447 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20448 let mut wrap_guides = smallvec![];
20449
20450 if self.show_wrap_guides == Some(false) {
20451 return wrap_guides;
20452 }
20453
20454 let settings = self.buffer.read(cx).language_settings(cx);
20455 if settings.show_wrap_guides {
20456 match self.soft_wrap_mode(cx) {
20457 SoftWrap::Column(soft_wrap) => {
20458 wrap_guides.push((soft_wrap as usize, true));
20459 }
20460 SoftWrap::Bounded(soft_wrap) => {
20461 wrap_guides.push((soft_wrap as usize, true));
20462 }
20463 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20464 }
20465 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20466 }
20467
20468 wrap_guides
20469 }
20470
20471 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20472 let settings = self.buffer.read(cx).language_settings(cx);
20473 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20474 match mode {
20475 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20476 SoftWrap::None
20477 }
20478 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20479 language_settings::SoftWrap::PreferredLineLength => {
20480 SoftWrap::Column(settings.preferred_line_length)
20481 }
20482 language_settings::SoftWrap::Bounded => {
20483 SoftWrap::Bounded(settings.preferred_line_length)
20484 }
20485 }
20486 }
20487
20488 pub fn set_soft_wrap_mode(
20489 &mut self,
20490 mode: language_settings::SoftWrap,
20491
20492 cx: &mut Context<Self>,
20493 ) {
20494 self.soft_wrap_mode_override = Some(mode);
20495 cx.notify();
20496 }
20497
20498 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20499 self.hard_wrap = hard_wrap;
20500 cx.notify();
20501 }
20502
20503 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20504 self.text_style_refinement = Some(style);
20505 }
20506
20507 /// called by the Element so we know what style we were most recently rendered with.
20508 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20509 // We intentionally do not inform the display map about the minimap style
20510 // so that wrapping is not recalculated and stays consistent for the editor
20511 // and its linked minimap.
20512 if !self.mode.is_minimap() {
20513 let font = style.text.font();
20514 let font_size = style.text.font_size.to_pixels(window.rem_size());
20515 let display_map = self
20516 .placeholder_display_map
20517 .as_ref()
20518 .filter(|_| self.is_empty(cx))
20519 .unwrap_or(&self.display_map);
20520
20521 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20522 }
20523 self.style = Some(style);
20524 }
20525
20526 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20527 if self.style.is_none() {
20528 self.style = Some(self.create_style(cx));
20529 }
20530 self.style.as_ref().unwrap()
20531 }
20532
20533 // Called by the element. This method is not designed to be called outside of the editor
20534 // element's layout code because it does not notify when rewrapping is computed synchronously.
20535 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20536 if self.is_empty(cx) {
20537 self.placeholder_display_map
20538 .as_ref()
20539 .map_or(false, |display_map| {
20540 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20541 })
20542 } else {
20543 self.display_map
20544 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20545 }
20546 }
20547
20548 pub fn set_soft_wrap(&mut self) {
20549 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20550 }
20551
20552 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20553 if self.soft_wrap_mode_override.is_some() {
20554 self.soft_wrap_mode_override.take();
20555 } else {
20556 let soft_wrap = match self.soft_wrap_mode(cx) {
20557 SoftWrap::GitDiff => return,
20558 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20559 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20560 language_settings::SoftWrap::None
20561 }
20562 };
20563 self.soft_wrap_mode_override = Some(soft_wrap);
20564 }
20565 cx.notify();
20566 }
20567
20568 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20569 let Some(workspace) = self.workspace() else {
20570 return;
20571 };
20572 let fs = workspace.read(cx).app_state().fs.clone();
20573 let current_show = TabBarSettings::get_global(cx).show;
20574 update_settings_file(fs, cx, move |setting, _| {
20575 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20576 });
20577 }
20578
20579 pub fn toggle_indent_guides(
20580 &mut self,
20581 _: &ToggleIndentGuides,
20582 _: &mut Window,
20583 cx: &mut Context<Self>,
20584 ) {
20585 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20586 self.buffer
20587 .read(cx)
20588 .language_settings(cx)
20589 .indent_guides
20590 .enabled
20591 });
20592 self.show_indent_guides = Some(!currently_enabled);
20593 cx.notify();
20594 }
20595
20596 fn should_show_indent_guides(&self) -> Option<bool> {
20597 self.show_indent_guides
20598 }
20599
20600 pub fn disable_indent_guides_for_buffer(
20601 &mut self,
20602 buffer_id: BufferId,
20603 cx: &mut Context<Self>,
20604 ) {
20605 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20606 cx.notify();
20607 }
20608
20609 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20610 self.buffers_with_disabled_indent_guides
20611 .contains(&buffer_id)
20612 }
20613
20614 pub fn toggle_line_numbers(
20615 &mut self,
20616 _: &ToggleLineNumbers,
20617 _: &mut Window,
20618 cx: &mut Context<Self>,
20619 ) {
20620 let mut editor_settings = EditorSettings::get_global(cx).clone();
20621 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20622 EditorSettings::override_global(editor_settings, cx);
20623 }
20624
20625 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20626 if let Some(show_line_numbers) = self.show_line_numbers {
20627 return show_line_numbers;
20628 }
20629 EditorSettings::get_global(cx).gutter.line_numbers
20630 }
20631
20632 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
20633 match (
20634 self.use_relative_line_numbers,
20635 EditorSettings::get_global(cx).relative_line_numbers,
20636 ) {
20637 (None, setting) => setting,
20638 (Some(false), _) => RelativeLineNumbers::Disabled,
20639 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20640 (Some(true), _) => RelativeLineNumbers::Enabled,
20641 }
20642 }
20643
20644 pub fn toggle_relative_line_numbers(
20645 &mut self,
20646 _: &ToggleRelativeLineNumbers,
20647 _: &mut Window,
20648 cx: &mut Context<Self>,
20649 ) {
20650 let is_relative = self.relative_line_numbers(cx);
20651 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20652 }
20653
20654 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20655 self.use_relative_line_numbers = is_relative;
20656 cx.notify();
20657 }
20658
20659 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20660 self.show_gutter = show_gutter;
20661 cx.notify();
20662 }
20663
20664 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20665 self.show_scrollbars = ScrollbarAxes {
20666 horizontal: show,
20667 vertical: show,
20668 };
20669 cx.notify();
20670 }
20671
20672 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20673 self.show_scrollbars.vertical = show;
20674 cx.notify();
20675 }
20676
20677 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20678 self.show_scrollbars.horizontal = show;
20679 cx.notify();
20680 }
20681
20682 pub fn set_minimap_visibility(
20683 &mut self,
20684 minimap_visibility: MinimapVisibility,
20685 window: &mut Window,
20686 cx: &mut Context<Self>,
20687 ) {
20688 if self.minimap_visibility != minimap_visibility {
20689 if minimap_visibility.visible() && self.minimap.is_none() {
20690 let minimap_settings = EditorSettings::get_global(cx).minimap;
20691 self.minimap =
20692 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20693 }
20694 self.minimap_visibility = minimap_visibility;
20695 cx.notify();
20696 }
20697 }
20698
20699 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20700 self.set_show_scrollbars(false, cx);
20701 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20702 }
20703
20704 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20705 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20706 }
20707
20708 /// Normally the text in full mode and auto height editors is padded on the
20709 /// left side by roughly half a character width for improved hit testing.
20710 ///
20711 /// Use this method to disable this for cases where this is not wanted (e.g.
20712 /// if you want to align the editor text with some other text above or below)
20713 /// or if you want to add this padding to single-line editors.
20714 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20715 self.offset_content = offset_content;
20716 cx.notify();
20717 }
20718
20719 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20720 self.show_line_numbers = Some(show_line_numbers);
20721 cx.notify();
20722 }
20723
20724 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20725 self.disable_expand_excerpt_buttons = true;
20726 cx.notify();
20727 }
20728
20729 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
20730 self.delegate_expand_excerpts = delegate;
20731 }
20732
20733 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20734 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20735 cx.notify();
20736 }
20737
20738 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20739 self.show_code_actions = Some(show_code_actions);
20740 cx.notify();
20741 }
20742
20743 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20744 self.show_runnables = Some(show_runnables);
20745 cx.notify();
20746 }
20747
20748 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20749 self.show_breakpoints = Some(show_breakpoints);
20750 cx.notify();
20751 }
20752
20753 pub fn set_show_diff_review_button(&mut self, show: bool, cx: &mut Context<Self>) {
20754 self.show_diff_review_button = show;
20755 cx.notify();
20756 }
20757
20758 pub fn show_diff_review_button(&self) -> bool {
20759 self.show_diff_review_button
20760 }
20761
20762 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20763 if self.display_map.read(cx).masked != masked {
20764 self.display_map.update(cx, |map, _| map.masked = masked);
20765 }
20766 cx.notify()
20767 }
20768
20769 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20770 self.show_wrap_guides = Some(show_wrap_guides);
20771 cx.notify();
20772 }
20773
20774 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20775 self.show_indent_guides = Some(show_indent_guides);
20776 cx.notify();
20777 }
20778
20779 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20780 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20781 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20782 && let Some(dir) = file.abs_path(cx).parent()
20783 {
20784 return Some(dir.to_owned());
20785 }
20786 }
20787
20788 None
20789 }
20790
20791 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20792 self.active_excerpt(cx)?
20793 .1
20794 .read(cx)
20795 .file()
20796 .and_then(|f| f.as_local())
20797 }
20798
20799 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20800 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20801 let buffer = buffer.read(cx);
20802 if let Some(project_path) = buffer.project_path(cx) {
20803 let project = self.project()?.read(cx);
20804 project.absolute_path(&project_path, cx)
20805 } else {
20806 buffer
20807 .file()
20808 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20809 }
20810 })
20811 }
20812
20813 pub fn reveal_in_finder(
20814 &mut self,
20815 _: &RevealInFileManager,
20816 _window: &mut Window,
20817 cx: &mut Context<Self>,
20818 ) {
20819 if let Some(target) = self.target_file(cx) {
20820 cx.reveal_path(&target.abs_path(cx));
20821 }
20822 }
20823
20824 pub fn copy_path(
20825 &mut self,
20826 _: &zed_actions::workspace::CopyPath,
20827 _window: &mut Window,
20828 cx: &mut Context<Self>,
20829 ) {
20830 if let Some(path) = self.target_file_abs_path(cx)
20831 && let Some(path) = path.to_str()
20832 {
20833 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20834 } else {
20835 cx.propagate();
20836 }
20837 }
20838
20839 pub fn copy_relative_path(
20840 &mut self,
20841 _: &zed_actions::workspace::CopyRelativePath,
20842 _window: &mut Window,
20843 cx: &mut Context<Self>,
20844 ) {
20845 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20846 let project = self.project()?.read(cx);
20847 let path = buffer.read(cx).file()?.path();
20848 let path = path.display(project.path_style(cx));
20849 Some(path)
20850 }) {
20851 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20852 } else {
20853 cx.propagate();
20854 }
20855 }
20856
20857 /// Returns the project path for the editor's buffer, if any buffer is
20858 /// opened in the editor.
20859 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20860 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20861 buffer.read(cx).project_path(cx)
20862 } else {
20863 None
20864 }
20865 }
20866
20867 // Returns true if the editor handled a go-to-line request
20868 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20869 maybe!({
20870 let breakpoint_store = self.breakpoint_store.as_ref()?;
20871
20872 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20873 else {
20874 self.clear_row_highlights::<ActiveDebugLine>();
20875 return None;
20876 };
20877
20878 let position = active_stack_frame.position;
20879 let buffer_id = position.buffer_id?;
20880 let snapshot = self
20881 .project
20882 .as_ref()?
20883 .read(cx)
20884 .buffer_for_id(buffer_id, cx)?
20885 .read(cx)
20886 .snapshot();
20887
20888 let mut handled = false;
20889 for (id, ExcerptRange { context, .. }) in
20890 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20891 {
20892 if context.start.cmp(&position, &snapshot).is_ge()
20893 || context.end.cmp(&position, &snapshot).is_lt()
20894 {
20895 continue;
20896 }
20897 let snapshot = self.buffer.read(cx).snapshot(cx);
20898 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20899
20900 handled = true;
20901 self.clear_row_highlights::<ActiveDebugLine>();
20902
20903 self.go_to_line::<ActiveDebugLine>(
20904 multibuffer_anchor,
20905 Some(cx.theme().colors().editor_debugger_active_line_background),
20906 window,
20907 cx,
20908 );
20909
20910 cx.notify();
20911 }
20912
20913 handled.then_some(())
20914 })
20915 .is_some()
20916 }
20917
20918 pub fn copy_file_name_without_extension(
20919 &mut self,
20920 _: &CopyFileNameWithoutExtension,
20921 _: &mut Window,
20922 cx: &mut Context<Self>,
20923 ) {
20924 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20925 let file = buffer.read(cx).file()?;
20926 file.path().file_stem()
20927 }) {
20928 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20929 }
20930 }
20931
20932 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20933 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20934 let file = buffer.read(cx).file()?;
20935 Some(file.file_name(cx))
20936 }) {
20937 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20938 }
20939 }
20940
20941 pub fn toggle_git_blame(
20942 &mut self,
20943 _: &::git::Blame,
20944 window: &mut Window,
20945 cx: &mut Context<Self>,
20946 ) {
20947 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20948
20949 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20950 self.start_git_blame(true, window, cx);
20951 }
20952
20953 cx.notify();
20954 }
20955
20956 pub fn toggle_git_blame_inline(
20957 &mut self,
20958 _: &ToggleGitBlameInline,
20959 window: &mut Window,
20960 cx: &mut Context<Self>,
20961 ) {
20962 self.toggle_git_blame_inline_internal(true, window, cx);
20963 cx.notify();
20964 }
20965
20966 pub fn open_git_blame_commit(
20967 &mut self,
20968 _: &OpenGitBlameCommit,
20969 window: &mut Window,
20970 cx: &mut Context<Self>,
20971 ) {
20972 self.open_git_blame_commit_internal(window, cx);
20973 }
20974
20975 fn open_git_blame_commit_internal(
20976 &mut self,
20977 window: &mut Window,
20978 cx: &mut Context<Self>,
20979 ) -> Option<()> {
20980 let blame = self.blame.as_ref()?;
20981 let snapshot = self.snapshot(window, cx);
20982 let cursor = self
20983 .selections
20984 .newest::<Point>(&snapshot.display_snapshot)
20985 .head();
20986 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20987 let (_, blame_entry) = blame
20988 .update(cx, |blame, cx| {
20989 blame
20990 .blame_for_rows(
20991 &[RowInfo {
20992 buffer_id: Some(buffer.remote_id()),
20993 buffer_row: Some(point.row),
20994 ..Default::default()
20995 }],
20996 cx,
20997 )
20998 .next()
20999 })
21000 .flatten()?;
21001 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
21002 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
21003 let workspace = self.workspace()?.downgrade();
21004 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
21005 None
21006 }
21007
21008 pub fn git_blame_inline_enabled(&self) -> bool {
21009 self.git_blame_inline_enabled
21010 }
21011
21012 pub fn toggle_selection_menu(
21013 &mut self,
21014 _: &ToggleSelectionMenu,
21015 _: &mut Window,
21016 cx: &mut Context<Self>,
21017 ) {
21018 self.show_selection_menu = self
21019 .show_selection_menu
21020 .map(|show_selections_menu| !show_selections_menu)
21021 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
21022
21023 cx.notify();
21024 }
21025
21026 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
21027 self.show_selection_menu
21028 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
21029 }
21030
21031 fn start_git_blame(
21032 &mut self,
21033 user_triggered: bool,
21034 window: &mut Window,
21035 cx: &mut Context<Self>,
21036 ) {
21037 if let Some(project) = self.project() {
21038 if let Some(buffer) = self.buffer().read(cx).as_singleton()
21039 && buffer.read(cx).file().is_none()
21040 {
21041 return;
21042 }
21043
21044 let focused = self.focus_handle(cx).contains_focused(window, cx);
21045
21046 let project = project.clone();
21047 let blame = cx
21048 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
21049 self.blame_subscription =
21050 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
21051 self.blame = Some(blame);
21052 }
21053 }
21054
21055 fn toggle_git_blame_inline_internal(
21056 &mut self,
21057 user_triggered: bool,
21058 window: &mut Window,
21059 cx: &mut Context<Self>,
21060 ) {
21061 if self.git_blame_inline_enabled {
21062 self.git_blame_inline_enabled = false;
21063 self.show_git_blame_inline = false;
21064 self.show_git_blame_inline_delay_task.take();
21065 } else {
21066 self.git_blame_inline_enabled = true;
21067 self.start_git_blame_inline(user_triggered, window, cx);
21068 }
21069
21070 cx.notify();
21071 }
21072
21073 fn start_git_blame_inline(
21074 &mut self,
21075 user_triggered: bool,
21076 window: &mut Window,
21077 cx: &mut Context<Self>,
21078 ) {
21079 self.start_git_blame(user_triggered, window, cx);
21080
21081 if ProjectSettings::get_global(cx)
21082 .git
21083 .inline_blame_delay()
21084 .is_some()
21085 {
21086 self.start_inline_blame_timer(window, cx);
21087 } else {
21088 self.show_git_blame_inline = true
21089 }
21090 }
21091
21092 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
21093 self.blame.as_ref()
21094 }
21095
21096 pub fn show_git_blame_gutter(&self) -> bool {
21097 self.show_git_blame_gutter
21098 }
21099
21100 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
21101 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
21102 }
21103
21104 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
21105 self.show_git_blame_inline
21106 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
21107 && !self.newest_selection_head_on_empty_line(cx)
21108 && self.has_blame_entries(cx)
21109 }
21110
21111 fn has_blame_entries(&self, cx: &App) -> bool {
21112 self.blame()
21113 .is_some_and(|blame| blame.read(cx).has_generated_entries())
21114 }
21115
21116 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
21117 let cursor_anchor = self.selections.newest_anchor().head();
21118
21119 let snapshot = self.buffer.read(cx).snapshot(cx);
21120 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
21121
21122 snapshot.line_len(buffer_row) == 0
21123 }
21124
21125 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
21126 let buffer_and_selection = maybe!({
21127 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
21128 let selection_range = selection.range();
21129
21130 let multi_buffer = self.buffer().read(cx);
21131 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21132 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
21133
21134 let (buffer, range, _) = if selection.reversed {
21135 buffer_ranges.first()
21136 } else {
21137 buffer_ranges.last()
21138 }?;
21139
21140 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
21141 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
21142
21143 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
21144 let selection = start_row_in_buffer..end_row_in_buffer;
21145
21146 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
21147 };
21148
21149 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
21150
21151 Some((
21152 multi_buffer.buffer(buffer.remote_id()).unwrap(),
21153 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, Bias::Left, buffer)
21154 ..buffer_diff_snapshot.row_to_base_text_row(
21155 end_row_in_buffer,
21156 Bias::Left,
21157 buffer,
21158 ),
21159 ))
21160 });
21161
21162 let Some((buffer, selection)) = buffer_and_selection else {
21163 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
21164 };
21165
21166 let Some(project) = self.project() else {
21167 return Task::ready(Err(anyhow!("editor does not have project")));
21168 };
21169
21170 project.update(cx, |project, cx| {
21171 project.get_permalink_to_line(&buffer, selection, cx)
21172 })
21173 }
21174
21175 pub fn copy_permalink_to_line(
21176 &mut self,
21177 _: &CopyPermalinkToLine,
21178 window: &mut Window,
21179 cx: &mut Context<Self>,
21180 ) {
21181 let permalink_task = self.get_permalink_to_line(cx);
21182 let workspace = self.workspace();
21183
21184 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21185 Ok(permalink) => {
21186 cx.update(|_, cx| {
21187 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21188 })
21189 .ok();
21190 }
21191 Err(err) => {
21192 let message = format!("Failed to copy permalink: {err}");
21193
21194 anyhow::Result::<()>::Err(err).log_err();
21195
21196 if let Some(workspace) = workspace {
21197 workspace
21198 .update_in(cx, |workspace, _, cx| {
21199 struct CopyPermalinkToLine;
21200
21201 workspace.show_toast(
21202 Toast::new(
21203 NotificationId::unique::<CopyPermalinkToLine>(),
21204 message,
21205 ),
21206 cx,
21207 )
21208 })
21209 .ok();
21210 }
21211 }
21212 })
21213 .detach();
21214 }
21215
21216 pub fn copy_file_location(
21217 &mut self,
21218 _: &CopyFileLocation,
21219 _: &mut Window,
21220 cx: &mut Context<Self>,
21221 ) {
21222 let selection = self
21223 .selections
21224 .newest::<Point>(&self.display_snapshot(cx))
21225 .start
21226 .row
21227 + 1;
21228 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21229 let project = self.project()?.read(cx);
21230 let file = buffer.read(cx).file()?;
21231 let path = file.path().display(project.path_style(cx));
21232
21233 Some(format!("{path}:{selection}"))
21234 }) {
21235 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21236 }
21237 }
21238
21239 pub fn open_permalink_to_line(
21240 &mut self,
21241 _: &OpenPermalinkToLine,
21242 window: &mut Window,
21243 cx: &mut Context<Self>,
21244 ) {
21245 let permalink_task = self.get_permalink_to_line(cx);
21246 let workspace = self.workspace();
21247
21248 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21249 Ok(permalink) => {
21250 cx.update(|_, cx| {
21251 cx.open_url(permalink.as_ref());
21252 })
21253 .ok();
21254 }
21255 Err(err) => {
21256 let message = format!("Failed to open permalink: {err}");
21257
21258 anyhow::Result::<()>::Err(err).log_err();
21259
21260 if let Some(workspace) = workspace {
21261 workspace.update(cx, |workspace, cx| {
21262 struct OpenPermalinkToLine;
21263
21264 workspace.show_toast(
21265 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
21266 cx,
21267 )
21268 });
21269 }
21270 }
21271 })
21272 .detach();
21273 }
21274
21275 pub fn insert_uuid_v4(
21276 &mut self,
21277 _: &InsertUuidV4,
21278 window: &mut Window,
21279 cx: &mut Context<Self>,
21280 ) {
21281 self.insert_uuid(UuidVersion::V4, window, cx);
21282 }
21283
21284 pub fn insert_uuid_v7(
21285 &mut self,
21286 _: &InsertUuidV7,
21287 window: &mut Window,
21288 cx: &mut Context<Self>,
21289 ) {
21290 self.insert_uuid(UuidVersion::V7, window, cx);
21291 }
21292
21293 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21294 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21295 self.transact(window, cx, |this, window, cx| {
21296 let edits = this
21297 .selections
21298 .all::<Point>(&this.display_snapshot(cx))
21299 .into_iter()
21300 .map(|selection| {
21301 let uuid = match version {
21302 UuidVersion::V4 => uuid::Uuid::new_v4(),
21303 UuidVersion::V7 => uuid::Uuid::now_v7(),
21304 };
21305
21306 (selection.range(), uuid.to_string())
21307 });
21308 this.edit(edits, cx);
21309 this.refresh_edit_prediction(true, false, window, cx);
21310 });
21311 }
21312
21313 pub fn open_selections_in_multibuffer(
21314 &mut self,
21315 _: &OpenSelectionsInMultibuffer,
21316 window: &mut Window,
21317 cx: &mut Context<Self>,
21318 ) {
21319 let multibuffer = self.buffer.read(cx);
21320
21321 let Some(buffer) = multibuffer.as_singleton() else {
21322 return;
21323 };
21324
21325 let Some(workspace) = self.workspace() else {
21326 return;
21327 };
21328
21329 let title = multibuffer.title(cx).to_string();
21330
21331 let locations = self
21332 .selections
21333 .all_anchors(&self.display_snapshot(cx))
21334 .iter()
21335 .map(|selection| {
21336 (
21337 buffer.clone(),
21338 (selection.start.text_anchor..selection.end.text_anchor)
21339 .to_point(buffer.read(cx)),
21340 )
21341 })
21342 .into_group_map();
21343
21344 cx.spawn_in(window, async move |_, cx| {
21345 workspace.update_in(cx, |workspace, window, cx| {
21346 Self::open_locations_in_multibuffer(
21347 workspace,
21348 locations,
21349 format!("Selections for '{title}'"),
21350 false,
21351 false,
21352 MultibufferSelectionMode::All,
21353 window,
21354 cx,
21355 );
21356 })
21357 })
21358 .detach();
21359 }
21360
21361 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21362 /// last highlight added will be used.
21363 ///
21364 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21365 pub fn highlight_rows<T: 'static>(
21366 &mut self,
21367 range: Range<Anchor>,
21368 color: Hsla,
21369 options: RowHighlightOptions,
21370 cx: &mut Context<Self>,
21371 ) {
21372 let snapshot = self.buffer().read(cx).snapshot(cx);
21373 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21374 let ix = row_highlights.binary_search_by(|highlight| {
21375 Ordering::Equal
21376 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21377 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21378 });
21379
21380 if let Err(mut ix) = ix {
21381 let index = post_inc(&mut self.highlight_order);
21382
21383 // If this range intersects with the preceding highlight, then merge it with
21384 // the preceding highlight. Otherwise insert a new highlight.
21385 let mut merged = false;
21386 if ix > 0 {
21387 let prev_highlight = &mut row_highlights[ix - 1];
21388 if prev_highlight
21389 .range
21390 .end
21391 .cmp(&range.start, &snapshot)
21392 .is_ge()
21393 {
21394 ix -= 1;
21395 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21396 prev_highlight.range.end = range.end;
21397 }
21398 merged = true;
21399 prev_highlight.index = index;
21400 prev_highlight.color = color;
21401 prev_highlight.options = options;
21402 }
21403 }
21404
21405 if !merged {
21406 row_highlights.insert(
21407 ix,
21408 RowHighlight {
21409 range,
21410 index,
21411 color,
21412 options,
21413 type_id: TypeId::of::<T>(),
21414 },
21415 );
21416 }
21417
21418 // If any of the following highlights intersect with this one, merge them.
21419 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21420 let highlight = &row_highlights[ix];
21421 if next_highlight
21422 .range
21423 .start
21424 .cmp(&highlight.range.end, &snapshot)
21425 .is_le()
21426 {
21427 if next_highlight
21428 .range
21429 .end
21430 .cmp(&highlight.range.end, &snapshot)
21431 .is_gt()
21432 {
21433 row_highlights[ix].range.end = next_highlight.range.end;
21434 }
21435 row_highlights.remove(ix + 1);
21436 } else {
21437 break;
21438 }
21439 }
21440 }
21441 }
21442
21443 /// Remove any highlighted row ranges of the given type that intersect the
21444 /// given ranges.
21445 pub fn remove_highlighted_rows<T: 'static>(
21446 &mut self,
21447 ranges_to_remove: Vec<Range<Anchor>>,
21448 cx: &mut Context<Self>,
21449 ) {
21450 let snapshot = self.buffer().read(cx).snapshot(cx);
21451 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21452 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21453 row_highlights.retain(|highlight| {
21454 while let Some(range_to_remove) = ranges_to_remove.peek() {
21455 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21456 Ordering::Less | Ordering::Equal => {
21457 ranges_to_remove.next();
21458 }
21459 Ordering::Greater => {
21460 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21461 Ordering::Less | Ordering::Equal => {
21462 return false;
21463 }
21464 Ordering::Greater => break,
21465 }
21466 }
21467 }
21468 }
21469
21470 true
21471 })
21472 }
21473
21474 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21475 pub fn clear_row_highlights<T: 'static>(&mut self) {
21476 self.highlighted_rows.remove(&TypeId::of::<T>());
21477 }
21478
21479 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21480 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21481 self.highlighted_rows
21482 .get(&TypeId::of::<T>())
21483 .map_or(&[] as &[_], |vec| vec.as_slice())
21484 .iter()
21485 .map(|highlight| (highlight.range.clone(), highlight.color))
21486 }
21487
21488 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21489 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21490 /// Allows to ignore certain kinds of highlights.
21491 pub fn highlighted_display_rows(
21492 &self,
21493 window: &mut Window,
21494 cx: &mut App,
21495 ) -> BTreeMap<DisplayRow, LineHighlight> {
21496 let snapshot = self.snapshot(window, cx);
21497 let mut used_highlight_orders = HashMap::default();
21498 self.highlighted_rows
21499 .iter()
21500 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21501 .fold(
21502 BTreeMap::<DisplayRow, LineHighlight>::new(),
21503 |mut unique_rows, highlight| {
21504 let start = highlight.range.start.to_display_point(&snapshot);
21505 let end = highlight.range.end.to_display_point(&snapshot);
21506 let start_row = start.row().0;
21507 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21508 {
21509 end.row().0.saturating_sub(1)
21510 } else {
21511 end.row().0
21512 };
21513 for row in start_row..=end_row {
21514 let used_index =
21515 used_highlight_orders.entry(row).or_insert(highlight.index);
21516 if highlight.index >= *used_index {
21517 *used_index = highlight.index;
21518 unique_rows.insert(
21519 DisplayRow(row),
21520 LineHighlight {
21521 include_gutter: highlight.options.include_gutter,
21522 border: None,
21523 background: highlight.color.into(),
21524 type_id: Some(highlight.type_id),
21525 },
21526 );
21527 }
21528 }
21529 unique_rows
21530 },
21531 )
21532 }
21533
21534 pub fn highlighted_display_row_for_autoscroll(
21535 &self,
21536 snapshot: &DisplaySnapshot,
21537 ) -> Option<DisplayRow> {
21538 self.highlighted_rows
21539 .values()
21540 .flat_map(|highlighted_rows| highlighted_rows.iter())
21541 .filter_map(|highlight| {
21542 if highlight.options.autoscroll {
21543 Some(highlight.range.start.to_display_point(snapshot).row())
21544 } else {
21545 None
21546 }
21547 })
21548 .min()
21549 }
21550
21551 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21552 self.highlight_background::<SearchWithinRange>(
21553 ranges,
21554 |_, colors| colors.colors().editor_document_highlight_read_background,
21555 cx,
21556 )
21557 }
21558
21559 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21560 self.breadcrumb_header = Some(new_header);
21561 }
21562
21563 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21564 self.clear_background_highlights::<SearchWithinRange>(cx);
21565 }
21566
21567 pub fn highlight_background<T: 'static>(
21568 &mut self,
21569 ranges: &[Range<Anchor>],
21570 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21571 cx: &mut Context<Self>,
21572 ) {
21573 self.background_highlights.insert(
21574 HighlightKey::Type(TypeId::of::<T>()),
21575 (Arc::new(color_fetcher), Arc::from(ranges)),
21576 );
21577 self.scrollbar_marker_state.dirty = true;
21578 cx.notify();
21579 }
21580
21581 pub fn highlight_background_key<T: 'static>(
21582 &mut self,
21583 key: usize,
21584 ranges: &[Range<Anchor>],
21585 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21586 cx: &mut Context<Self>,
21587 ) {
21588 self.background_highlights.insert(
21589 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21590 (Arc::new(color_fetcher), Arc::from(ranges)),
21591 );
21592 self.scrollbar_marker_state.dirty = true;
21593 cx.notify();
21594 }
21595
21596 pub fn clear_background_highlights<T: 'static>(
21597 &mut self,
21598 cx: &mut Context<Self>,
21599 ) -> Option<BackgroundHighlight> {
21600 let text_highlights = self
21601 .background_highlights
21602 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21603 if !text_highlights.1.is_empty() {
21604 self.scrollbar_marker_state.dirty = true;
21605 cx.notify();
21606 }
21607 Some(text_highlights)
21608 }
21609
21610 pub fn highlight_gutter<T: 'static>(
21611 &mut self,
21612 ranges: impl Into<Vec<Range<Anchor>>>,
21613 color_fetcher: fn(&App) -> Hsla,
21614 cx: &mut Context<Self>,
21615 ) {
21616 self.gutter_highlights
21617 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21618 cx.notify();
21619 }
21620
21621 pub fn clear_gutter_highlights<T: 'static>(
21622 &mut self,
21623 cx: &mut Context<Self>,
21624 ) -> Option<GutterHighlight> {
21625 cx.notify();
21626 self.gutter_highlights.remove(&TypeId::of::<T>())
21627 }
21628
21629 pub fn insert_gutter_highlight<T: 'static>(
21630 &mut self,
21631 range: Range<Anchor>,
21632 color_fetcher: fn(&App) -> Hsla,
21633 cx: &mut Context<Self>,
21634 ) {
21635 let snapshot = self.buffer().read(cx).snapshot(cx);
21636 let mut highlights = self
21637 .gutter_highlights
21638 .remove(&TypeId::of::<T>())
21639 .map(|(_, highlights)| highlights)
21640 .unwrap_or_default();
21641 let ix = highlights.binary_search_by(|highlight| {
21642 Ordering::Equal
21643 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21644 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21645 });
21646 if let Err(ix) = ix {
21647 highlights.insert(ix, range);
21648 }
21649 self.gutter_highlights
21650 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21651 }
21652
21653 pub fn remove_gutter_highlights<T: 'static>(
21654 &mut self,
21655 ranges_to_remove: Vec<Range<Anchor>>,
21656 cx: &mut Context<Self>,
21657 ) {
21658 let snapshot = self.buffer().read(cx).snapshot(cx);
21659 let Some((color_fetcher, mut gutter_highlights)) =
21660 self.gutter_highlights.remove(&TypeId::of::<T>())
21661 else {
21662 return;
21663 };
21664 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21665 gutter_highlights.retain(|highlight| {
21666 while let Some(range_to_remove) = ranges_to_remove.peek() {
21667 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21668 Ordering::Less | Ordering::Equal => {
21669 ranges_to_remove.next();
21670 }
21671 Ordering::Greater => {
21672 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21673 Ordering::Less | Ordering::Equal => {
21674 return false;
21675 }
21676 Ordering::Greater => break,
21677 }
21678 }
21679 }
21680 }
21681
21682 true
21683 });
21684 self.gutter_highlights
21685 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21686 }
21687
21688 #[cfg(feature = "test-support")]
21689 pub fn all_text_highlights(
21690 &self,
21691 window: &mut Window,
21692 cx: &mut Context<Self>,
21693 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21694 let snapshot = self.snapshot(window, cx);
21695 self.display_map.update(cx, |display_map, _| {
21696 display_map
21697 .all_text_highlights()
21698 .map(|highlight| {
21699 let (style, ranges) = highlight.as_ref();
21700 (
21701 *style,
21702 ranges
21703 .iter()
21704 .map(|range| range.clone().to_display_points(&snapshot))
21705 .collect(),
21706 )
21707 })
21708 .collect()
21709 })
21710 }
21711
21712 #[cfg(feature = "test-support")]
21713 pub fn all_text_background_highlights(
21714 &self,
21715 window: &mut Window,
21716 cx: &mut Context<Self>,
21717 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21718 let snapshot = self.snapshot(window, cx);
21719 let buffer = &snapshot.buffer_snapshot();
21720 let start = buffer.anchor_before(MultiBufferOffset(0));
21721 let end = buffer.anchor_after(buffer.len());
21722 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21723 }
21724
21725 #[cfg(any(test, feature = "test-support"))]
21726 pub fn sorted_background_highlights_in_range(
21727 &self,
21728 search_range: Range<Anchor>,
21729 display_snapshot: &DisplaySnapshot,
21730 theme: &Theme,
21731 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21732 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21733 res.sort_by(|a, b| {
21734 a.0.start
21735 .cmp(&b.0.start)
21736 .then_with(|| a.0.end.cmp(&b.0.end))
21737 .then_with(|| a.1.cmp(&b.1))
21738 });
21739 res
21740 }
21741
21742 #[cfg(feature = "test-support")]
21743 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21744 let snapshot = self.buffer().read(cx).snapshot(cx);
21745
21746 let highlights = self
21747 .background_highlights
21748 .get(&HighlightKey::Type(TypeId::of::<
21749 items::BufferSearchHighlights,
21750 >()));
21751
21752 if let Some((_color, ranges)) = highlights {
21753 ranges
21754 .iter()
21755 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21756 .collect_vec()
21757 } else {
21758 vec![]
21759 }
21760 }
21761
21762 fn document_highlights_for_position<'a>(
21763 &'a self,
21764 position: Anchor,
21765 buffer: &'a MultiBufferSnapshot,
21766 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21767 let read_highlights = self
21768 .background_highlights
21769 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21770 .map(|h| &h.1);
21771 let write_highlights = self
21772 .background_highlights
21773 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21774 .map(|h| &h.1);
21775 let left_position = position.bias_left(buffer);
21776 let right_position = position.bias_right(buffer);
21777 read_highlights
21778 .into_iter()
21779 .chain(write_highlights)
21780 .flat_map(move |ranges| {
21781 let start_ix = match ranges.binary_search_by(|probe| {
21782 let cmp = probe.end.cmp(&left_position, buffer);
21783 if cmp.is_ge() {
21784 Ordering::Greater
21785 } else {
21786 Ordering::Less
21787 }
21788 }) {
21789 Ok(i) | Err(i) => i,
21790 };
21791
21792 ranges[start_ix..]
21793 .iter()
21794 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21795 })
21796 }
21797
21798 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21799 self.background_highlights
21800 .get(&HighlightKey::Type(TypeId::of::<T>()))
21801 .is_some_and(|(_, highlights)| !highlights.is_empty())
21802 }
21803
21804 /// Returns all background highlights for a given range.
21805 ///
21806 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21807 pub fn background_highlights_in_range(
21808 &self,
21809 search_range: Range<Anchor>,
21810 display_snapshot: &DisplaySnapshot,
21811 theme: &Theme,
21812 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21813 let mut results = Vec::new();
21814 for (color_fetcher, ranges) in self.background_highlights.values() {
21815 let start_ix = match ranges.binary_search_by(|probe| {
21816 let cmp = probe
21817 .end
21818 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21819 if cmp.is_gt() {
21820 Ordering::Greater
21821 } else {
21822 Ordering::Less
21823 }
21824 }) {
21825 Ok(i) | Err(i) => i,
21826 };
21827 for (index, range) in ranges[start_ix..].iter().enumerate() {
21828 if range
21829 .start
21830 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21831 .is_ge()
21832 {
21833 break;
21834 }
21835
21836 let color = color_fetcher(&(start_ix + index), theme);
21837 let start = range.start.to_display_point(display_snapshot);
21838 let end = range.end.to_display_point(display_snapshot);
21839 results.push((start..end, color))
21840 }
21841 }
21842 results
21843 }
21844
21845 pub fn gutter_highlights_in_range(
21846 &self,
21847 search_range: Range<Anchor>,
21848 display_snapshot: &DisplaySnapshot,
21849 cx: &App,
21850 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21851 let mut results = Vec::new();
21852 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21853 let color = color_fetcher(cx);
21854 let start_ix = match ranges.binary_search_by(|probe| {
21855 let cmp = probe
21856 .end
21857 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21858 if cmp.is_gt() {
21859 Ordering::Greater
21860 } else {
21861 Ordering::Less
21862 }
21863 }) {
21864 Ok(i) | Err(i) => i,
21865 };
21866 for range in &ranges[start_ix..] {
21867 if range
21868 .start
21869 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21870 .is_ge()
21871 {
21872 break;
21873 }
21874
21875 let start = range.start.to_display_point(display_snapshot);
21876 let end = range.end.to_display_point(display_snapshot);
21877 results.push((start..end, color))
21878 }
21879 }
21880 results
21881 }
21882
21883 /// Get the text ranges corresponding to the redaction query
21884 pub fn redacted_ranges(
21885 &self,
21886 search_range: Range<Anchor>,
21887 display_snapshot: &DisplaySnapshot,
21888 cx: &App,
21889 ) -> Vec<Range<DisplayPoint>> {
21890 display_snapshot
21891 .buffer_snapshot()
21892 .redacted_ranges(search_range, |file| {
21893 if let Some(file) = file {
21894 file.is_private()
21895 && EditorSettings::get(
21896 Some(SettingsLocation {
21897 worktree_id: file.worktree_id(cx),
21898 path: file.path().as_ref(),
21899 }),
21900 cx,
21901 )
21902 .redact_private_values
21903 } else {
21904 false
21905 }
21906 })
21907 .map(|range| {
21908 range.start.to_display_point(display_snapshot)
21909 ..range.end.to_display_point(display_snapshot)
21910 })
21911 .collect()
21912 }
21913
21914 pub fn highlight_text_key<T: 'static>(
21915 &mut self,
21916 key: usize,
21917 ranges: Vec<Range<Anchor>>,
21918 style: HighlightStyle,
21919 merge: bool,
21920 cx: &mut Context<Self>,
21921 ) {
21922 self.display_map.update(cx, |map, cx| {
21923 map.highlight_text(
21924 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21925 ranges,
21926 style,
21927 merge,
21928 cx,
21929 );
21930 });
21931 cx.notify();
21932 }
21933
21934 pub fn highlight_text<T: 'static>(
21935 &mut self,
21936 ranges: Vec<Range<Anchor>>,
21937 style: HighlightStyle,
21938 cx: &mut Context<Self>,
21939 ) {
21940 self.display_map.update(cx, |map, cx| {
21941 map.highlight_text(
21942 HighlightKey::Type(TypeId::of::<T>()),
21943 ranges,
21944 style,
21945 false,
21946 cx,
21947 )
21948 });
21949 cx.notify();
21950 }
21951
21952 pub fn text_highlights<'a, T: 'static>(
21953 &'a self,
21954 cx: &'a App,
21955 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21956 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21957 }
21958
21959 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21960 let cleared = self
21961 .display_map
21962 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21963 if cleared {
21964 cx.notify();
21965 }
21966 }
21967
21968 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21969 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21970 && self.focus_handle.is_focused(window)
21971 }
21972
21973 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21974 self.show_cursor_when_unfocused = is_enabled;
21975 cx.notify();
21976 }
21977
21978 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21979 cx.notify();
21980 }
21981
21982 fn on_debug_session_event(
21983 &mut self,
21984 _session: Entity<Session>,
21985 event: &SessionEvent,
21986 cx: &mut Context<Self>,
21987 ) {
21988 if let SessionEvent::InvalidateInlineValue = event {
21989 self.refresh_inline_values(cx);
21990 }
21991 }
21992
21993 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21994 let Some(project) = self.project.clone() else {
21995 return;
21996 };
21997
21998 if !self.inline_value_cache.enabled {
21999 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
22000 self.splice_inlays(&inlays, Vec::new(), cx);
22001 return;
22002 }
22003
22004 let current_execution_position = self
22005 .highlighted_rows
22006 .get(&TypeId::of::<ActiveDebugLine>())
22007 .and_then(|lines| lines.last().map(|line| line.range.end));
22008
22009 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
22010 let inline_values = editor
22011 .update(cx, |editor, cx| {
22012 let Some(current_execution_position) = current_execution_position else {
22013 return Some(Task::ready(Ok(Vec::new())));
22014 };
22015
22016 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
22017 let snapshot = buffer.snapshot(cx);
22018
22019 let excerpt = snapshot.excerpt_containing(
22020 current_execution_position..current_execution_position,
22021 )?;
22022
22023 editor.buffer.read(cx).buffer(excerpt.buffer_id())
22024 })?;
22025
22026 let range =
22027 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
22028
22029 project.inline_values(buffer, range, cx)
22030 })
22031 .ok()
22032 .flatten()?
22033 .await
22034 .context("refreshing debugger inlays")
22035 .log_err()?;
22036
22037 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
22038
22039 for (buffer_id, inline_value) in inline_values
22040 .into_iter()
22041 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
22042 {
22043 buffer_inline_values
22044 .entry(buffer_id)
22045 .or_default()
22046 .push(inline_value);
22047 }
22048
22049 editor
22050 .update(cx, |editor, cx| {
22051 let snapshot = editor.buffer.read(cx).snapshot(cx);
22052 let mut new_inlays = Vec::default();
22053
22054 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
22055 let buffer_id = buffer_snapshot.remote_id();
22056 buffer_inline_values
22057 .get(&buffer_id)
22058 .into_iter()
22059 .flatten()
22060 .for_each(|hint| {
22061 let inlay = Inlay::debugger(
22062 post_inc(&mut editor.next_inlay_id),
22063 Anchor::in_buffer(excerpt_id, hint.position),
22064 hint.text(),
22065 );
22066 if !inlay.text().chars().contains(&'\n') {
22067 new_inlays.push(inlay);
22068 }
22069 });
22070 }
22071
22072 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
22073 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
22074
22075 editor.splice_inlays(&inlay_ids, new_inlays, cx);
22076 })
22077 .ok()?;
22078 Some(())
22079 });
22080 }
22081
22082 fn on_buffer_event(
22083 &mut self,
22084 multibuffer: &Entity<MultiBuffer>,
22085 event: &multi_buffer::Event,
22086 window: &mut Window,
22087 cx: &mut Context<Self>,
22088 ) {
22089 match event {
22090 multi_buffer::Event::Edited { edited_buffer } => {
22091 self.scrollbar_marker_state.dirty = true;
22092 self.active_indent_guides_state.dirty = true;
22093 self.refresh_active_diagnostics(cx);
22094 self.refresh_code_actions(window, cx);
22095 self.refresh_single_line_folds(window, cx);
22096 self.refresh_matching_bracket_highlights(window, cx);
22097 if self.has_active_edit_prediction() {
22098 self.update_visible_edit_prediction(window, cx);
22099 }
22100
22101 if let Some(buffer) = edited_buffer {
22102 if buffer.read(cx).file().is_none() {
22103 cx.emit(EditorEvent::TitleChanged);
22104 }
22105
22106 if self.project.is_some() {
22107 let buffer_id = buffer.read(cx).remote_id();
22108 self.register_buffer(buffer_id, cx);
22109 self.update_lsp_data(Some(buffer_id), window, cx);
22110 self.refresh_inlay_hints(
22111 InlayHintRefreshReason::BufferEdited(buffer_id),
22112 cx,
22113 );
22114 }
22115 }
22116
22117 cx.emit(EditorEvent::BufferEdited);
22118 cx.emit(SearchEvent::MatchesInvalidated);
22119
22120 let Some(project) = &self.project else { return };
22121 let (telemetry, is_via_ssh) = {
22122 let project = project.read(cx);
22123 let telemetry = project.client().telemetry().clone();
22124 let is_via_ssh = project.is_via_remote_server();
22125 (telemetry, is_via_ssh)
22126 };
22127 telemetry.log_edit_event("editor", is_via_ssh);
22128 }
22129 multi_buffer::Event::ExcerptsAdded {
22130 buffer,
22131 predecessor,
22132 excerpts,
22133 } => {
22134 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22135 let buffer_id = buffer.read(cx).remote_id();
22136 if self.buffer.read(cx).diff_for(buffer_id).is_none()
22137 && let Some(project) = &self.project
22138 {
22139 update_uncommitted_diff_for_buffer(
22140 cx.entity(),
22141 project,
22142 [buffer.clone()],
22143 self.buffer.clone(),
22144 cx,
22145 )
22146 .detach();
22147 }
22148 self.update_lsp_data(Some(buffer_id), window, cx);
22149 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22150 self.colorize_brackets(false, cx);
22151 cx.emit(EditorEvent::ExcerptsAdded {
22152 buffer: buffer.clone(),
22153 predecessor: *predecessor,
22154 excerpts: excerpts.clone(),
22155 });
22156 }
22157 multi_buffer::Event::ExcerptsRemoved {
22158 ids,
22159 removed_buffer_ids,
22160 } => {
22161 if let Some(inlay_hints) = &mut self.inlay_hints {
22162 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
22163 }
22164 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
22165 for buffer_id in removed_buffer_ids {
22166 self.registered_buffers.remove(buffer_id);
22167 }
22168 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22169 cx.emit(EditorEvent::ExcerptsRemoved {
22170 ids: ids.clone(),
22171 removed_buffer_ids: removed_buffer_ids.clone(),
22172 });
22173 }
22174 multi_buffer::Event::ExcerptsEdited {
22175 excerpt_ids,
22176 buffer_ids,
22177 } => {
22178 self.display_map.update(cx, |map, cx| {
22179 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22180 });
22181 cx.emit(EditorEvent::ExcerptsEdited {
22182 ids: excerpt_ids.clone(),
22183 });
22184 }
22185 multi_buffer::Event::ExcerptsExpanded { ids } => {
22186 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22187 self.refresh_document_highlights(cx);
22188 for id in ids {
22189 self.fetched_tree_sitter_chunks.remove(id);
22190 }
22191 self.colorize_brackets(false, cx);
22192 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22193 }
22194 multi_buffer::Event::Reparsed(buffer_id) => {
22195 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22196 self.refresh_selected_text_highlights(true, window, cx);
22197 self.colorize_brackets(true, cx);
22198 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22199
22200 cx.emit(EditorEvent::Reparsed(*buffer_id));
22201 }
22202 multi_buffer::Event::DiffHunksToggled => {
22203 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22204 }
22205 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22206 if !is_fresh_language {
22207 self.registered_buffers.remove(&buffer_id);
22208 }
22209 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22210 cx.emit(EditorEvent::Reparsed(*buffer_id));
22211 cx.notify();
22212 }
22213 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22214 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22215 multi_buffer::Event::FileHandleChanged
22216 | multi_buffer::Event::Reloaded
22217 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22218 multi_buffer::Event::DiagnosticsUpdated => {
22219 self.update_diagnostics_state(window, cx);
22220 }
22221 _ => {}
22222 };
22223 }
22224
22225 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22226 if !self.diagnostics_enabled() {
22227 return;
22228 }
22229 self.refresh_active_diagnostics(cx);
22230 self.refresh_inline_diagnostics(true, window, cx);
22231 self.scrollbar_marker_state.dirty = true;
22232 cx.notify();
22233 }
22234
22235 pub fn start_temporary_diff_override(&mut self) {
22236 self.load_diff_task.take();
22237 self.temporary_diff_override = true;
22238 }
22239
22240 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22241 self.temporary_diff_override = false;
22242 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22243 self.buffer.update(cx, |buffer, cx| {
22244 buffer.set_all_diff_hunks_collapsed(cx);
22245 });
22246
22247 if let Some(project) = self.project.clone() {
22248 self.load_diff_task = Some(
22249 update_uncommitted_diff_for_buffer(
22250 cx.entity(),
22251 &project,
22252 self.buffer.read(cx).all_buffers(),
22253 self.buffer.clone(),
22254 cx,
22255 )
22256 .shared(),
22257 );
22258 }
22259 }
22260
22261 fn on_display_map_changed(
22262 &mut self,
22263 _: Entity<DisplayMap>,
22264 _: &mut Window,
22265 cx: &mut Context<Self>,
22266 ) {
22267 cx.notify();
22268 }
22269
22270 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22271 if !self.mode.is_full() {
22272 return None;
22273 }
22274
22275 let theme_settings = theme::ThemeSettings::get_global(cx);
22276 let theme = cx.theme();
22277 let accent_colors = theme.accents().clone();
22278
22279 let accent_overrides = theme_settings
22280 .theme_overrides
22281 .get(theme.name.as_ref())
22282 .map(|theme_style| &theme_style.accents)
22283 .into_iter()
22284 .flatten()
22285 .chain(
22286 theme_settings
22287 .experimental_theme_overrides
22288 .as_ref()
22289 .map(|overrides| &overrides.accents)
22290 .into_iter()
22291 .flatten(),
22292 )
22293 .flat_map(|accent| accent.0.clone())
22294 .collect();
22295
22296 Some(AccentData {
22297 colors: accent_colors,
22298 overrides: accent_overrides,
22299 })
22300 }
22301
22302 fn fetch_applicable_language_settings(
22303 &self,
22304 cx: &App,
22305 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22306 if !self.mode.is_full() {
22307 return HashMap::default();
22308 }
22309
22310 self.buffer().read(cx).all_buffers().into_iter().fold(
22311 HashMap::default(),
22312 |mut acc, buffer| {
22313 let buffer = buffer.read(cx);
22314 let language = buffer.language().map(|language| language.name());
22315 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22316 let file = buffer.file();
22317 v.insert(language_settings(language, file, cx).into_owned());
22318 }
22319 acc
22320 },
22321 )
22322 }
22323
22324 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22325 let new_language_settings = self.fetch_applicable_language_settings(cx);
22326 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22327 self.applicable_language_settings = new_language_settings;
22328
22329 let new_accents = self.fetch_accent_data(cx);
22330 let accents_changed = new_accents != self.accent_data;
22331 self.accent_data = new_accents;
22332
22333 if self.diagnostics_enabled() {
22334 let new_severity = EditorSettings::get_global(cx)
22335 .diagnostics_max_severity
22336 .unwrap_or(DiagnosticSeverity::Hint);
22337 self.set_max_diagnostics_severity(new_severity, cx);
22338 }
22339 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22340 self.update_edit_prediction_settings(cx);
22341 self.refresh_edit_prediction(true, false, window, cx);
22342 self.refresh_inline_values(cx);
22343 self.refresh_inlay_hints(
22344 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22345 self.selections.newest_anchor().head(),
22346 &self.buffer.read(cx).snapshot(cx),
22347 cx,
22348 )),
22349 cx,
22350 );
22351
22352 let old_cursor_shape = self.cursor_shape;
22353 let old_show_breadcrumbs = self.show_breadcrumbs;
22354
22355 {
22356 let editor_settings = EditorSettings::get_global(cx);
22357 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22358 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22359 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22360 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22361 }
22362
22363 if old_cursor_shape != self.cursor_shape {
22364 cx.emit(EditorEvent::CursorShapeChanged);
22365 }
22366
22367 if old_show_breadcrumbs != self.show_breadcrumbs {
22368 cx.emit(EditorEvent::BreadcrumbsChanged);
22369 }
22370
22371 let project_settings = ProjectSettings::get_global(cx);
22372 self.buffer_serialization = self
22373 .should_serialize_buffer()
22374 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22375
22376 if self.mode.is_full() {
22377 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22378 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22379 if self.show_inline_diagnostics != show_inline_diagnostics {
22380 self.show_inline_diagnostics = show_inline_diagnostics;
22381 self.refresh_inline_diagnostics(false, window, cx);
22382 }
22383
22384 if self.git_blame_inline_enabled != inline_blame_enabled {
22385 self.toggle_git_blame_inline_internal(false, window, cx);
22386 }
22387
22388 let minimap_settings = EditorSettings::get_global(cx).minimap;
22389 if self.minimap_visibility != MinimapVisibility::Disabled {
22390 if self.minimap_visibility.settings_visibility()
22391 != minimap_settings.minimap_enabled()
22392 {
22393 self.set_minimap_visibility(
22394 MinimapVisibility::for_mode(self.mode(), cx),
22395 window,
22396 cx,
22397 );
22398 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22399 minimap_entity.update(cx, |minimap_editor, cx| {
22400 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22401 })
22402 }
22403 }
22404
22405 if language_settings_changed || accents_changed {
22406 self.colorize_brackets(true, cx);
22407 }
22408
22409 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22410 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22411 }) {
22412 if !inlay_splice.is_empty() {
22413 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22414 }
22415 self.refresh_colors_for_visible_range(None, window, cx);
22416 }
22417 }
22418
22419 cx.notify();
22420 }
22421
22422 pub fn set_searchable(&mut self, searchable: bool) {
22423 self.searchable = searchable;
22424 }
22425
22426 pub fn searchable(&self) -> bool {
22427 self.searchable
22428 }
22429
22430 pub fn open_excerpts_in_split(
22431 &mut self,
22432 _: &OpenExcerptsSplit,
22433 window: &mut Window,
22434 cx: &mut Context<Self>,
22435 ) {
22436 self.open_excerpts_common(None, true, window, cx)
22437 }
22438
22439 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22440 self.open_excerpts_common(None, false, window, cx)
22441 }
22442
22443 fn open_excerpts_common(
22444 &mut self,
22445 jump_data: Option<JumpData>,
22446 split: bool,
22447 window: &mut Window,
22448 cx: &mut Context<Self>,
22449 ) {
22450 let Some(workspace) = self.workspace() else {
22451 cx.propagate();
22452 return;
22453 };
22454
22455 if self.buffer.read(cx).is_singleton() {
22456 cx.propagate();
22457 return;
22458 }
22459
22460 let mut new_selections_by_buffer = HashMap::default();
22461 match &jump_data {
22462 Some(JumpData::MultiBufferPoint {
22463 excerpt_id,
22464 position,
22465 anchor,
22466 line_offset_from_top,
22467 }) => {
22468 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22469 if let Some(buffer) = multi_buffer_snapshot
22470 .buffer_id_for_excerpt(*excerpt_id)
22471 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22472 {
22473 let buffer_snapshot = buffer.read(cx).snapshot();
22474 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22475 language::ToPoint::to_point(anchor, &buffer_snapshot)
22476 } else {
22477 buffer_snapshot.clip_point(*position, Bias::Left)
22478 };
22479 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22480 new_selections_by_buffer.insert(
22481 buffer,
22482 (
22483 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22484 Some(*line_offset_from_top),
22485 ),
22486 );
22487 }
22488 }
22489 Some(JumpData::MultiBufferRow {
22490 row,
22491 line_offset_from_top,
22492 }) => {
22493 let point = MultiBufferPoint::new(row.0, 0);
22494 if let Some((buffer, buffer_point, _)) =
22495 self.buffer.read(cx).point_to_buffer_point(point, cx)
22496 {
22497 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22498 new_selections_by_buffer
22499 .entry(buffer)
22500 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22501 .0
22502 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22503 }
22504 }
22505 None => {
22506 let selections = self
22507 .selections
22508 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22509 let multi_buffer = self.buffer.read(cx);
22510 for selection in selections {
22511 for (snapshot, range, _, anchor) in multi_buffer
22512 .snapshot(cx)
22513 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22514 {
22515 if let Some(anchor) = anchor {
22516 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22517 else {
22518 continue;
22519 };
22520 let offset = text::ToOffset::to_offset(
22521 &anchor.text_anchor,
22522 &buffer_handle.read(cx).snapshot(),
22523 );
22524 let range = BufferOffset(offset)..BufferOffset(offset);
22525 new_selections_by_buffer
22526 .entry(buffer_handle)
22527 .or_insert((Vec::new(), None))
22528 .0
22529 .push(range)
22530 } else {
22531 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22532 else {
22533 continue;
22534 };
22535 new_selections_by_buffer
22536 .entry(buffer_handle)
22537 .or_insert((Vec::new(), None))
22538 .0
22539 .push(range)
22540 }
22541 }
22542 }
22543 }
22544 }
22545
22546 new_selections_by_buffer
22547 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
22548
22549 if new_selections_by_buffer.is_empty() {
22550 return;
22551 }
22552
22553 // We defer the pane interaction because we ourselves are a workspace item
22554 // and activating a new item causes the pane to call a method on us reentrantly,
22555 // which panics if we're on the stack.
22556 window.defer(cx, move |window, cx| {
22557 workspace.update(cx, |workspace, cx| {
22558 let pane = if split {
22559 workspace.adjacent_pane(window, cx)
22560 } else {
22561 workspace.active_pane().clone()
22562 };
22563
22564 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22565 let buffer_read = buffer.read(cx);
22566 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22567 (true, project::File::from_dyn(Some(file)).is_some())
22568 } else {
22569 (false, false)
22570 };
22571
22572 // If project file is none workspace.open_project_item will fail to open the excerpt
22573 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22574 // so we check if there's a tab match in that case first
22575 let editor = (!has_file || !is_project_file)
22576 .then(|| {
22577 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22578 // so `workspace.open_project_item` will never find them, always opening a new editor.
22579 // Instead, we try to activate the existing editor in the pane first.
22580 let (editor, pane_item_index, pane_item_id) =
22581 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22582 let editor = item.downcast::<Editor>()?;
22583 let singleton_buffer =
22584 editor.read(cx).buffer().read(cx).as_singleton()?;
22585 if singleton_buffer == buffer {
22586 Some((editor, i, item.item_id()))
22587 } else {
22588 None
22589 }
22590 })?;
22591 pane.update(cx, |pane, cx| {
22592 pane.activate_item(pane_item_index, true, true, window, cx);
22593 if !PreviewTabsSettings::get_global(cx)
22594 .enable_preview_from_multibuffer
22595 {
22596 pane.unpreview_item_if_preview(pane_item_id);
22597 }
22598 });
22599 Some(editor)
22600 })
22601 .flatten()
22602 .unwrap_or_else(|| {
22603 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22604 .enable_keep_preview_on_code_navigation;
22605 let allow_new_preview =
22606 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22607 workspace.open_project_item::<Self>(
22608 pane.clone(),
22609 buffer,
22610 true,
22611 true,
22612 keep_old_preview,
22613 allow_new_preview,
22614 window,
22615 cx,
22616 )
22617 });
22618
22619 editor.update(cx, |editor, cx| {
22620 if has_file && !is_project_file {
22621 editor.set_read_only(true);
22622 }
22623 let autoscroll = match scroll_offset {
22624 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22625 None => Autoscroll::newest(),
22626 };
22627 let nav_history = editor.nav_history.take();
22628 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22629 let Some((&excerpt_id, _, buffer_snapshot)) =
22630 multibuffer_snapshot.as_singleton()
22631 else {
22632 return;
22633 };
22634 editor.change_selections(
22635 SelectionEffects::scroll(autoscroll),
22636 window,
22637 cx,
22638 |s| {
22639 s.select_ranges(ranges.into_iter().map(|range| {
22640 let range = buffer_snapshot.anchor_before(range.start)
22641 ..buffer_snapshot.anchor_after(range.end);
22642 multibuffer_snapshot
22643 .anchor_range_in_excerpt(excerpt_id, range)
22644 .unwrap()
22645 }));
22646 },
22647 );
22648 editor.nav_history = nav_history;
22649 });
22650 }
22651 })
22652 });
22653 }
22654
22655 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22656 let snapshot = self.buffer.read(cx).read(cx);
22657 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22658 Some(
22659 ranges
22660 .iter()
22661 .map(move |range| {
22662 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22663 })
22664 .collect(),
22665 )
22666 }
22667
22668 fn selection_replacement_ranges(
22669 &self,
22670 range: Range<MultiBufferOffsetUtf16>,
22671 cx: &mut App,
22672 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22673 let selections = self
22674 .selections
22675 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22676 let newest_selection = selections
22677 .iter()
22678 .max_by_key(|selection| selection.id)
22679 .unwrap();
22680 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22681 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22682 let snapshot = self.buffer.read(cx).read(cx);
22683 selections
22684 .into_iter()
22685 .map(|mut selection| {
22686 selection.start.0.0 =
22687 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22688 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22689 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22690 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22691 })
22692 .collect()
22693 }
22694
22695 fn report_editor_event(
22696 &self,
22697 reported_event: ReportEditorEvent,
22698 file_extension: Option<String>,
22699 cx: &App,
22700 ) {
22701 if cfg!(any(test, feature = "test-support")) {
22702 return;
22703 }
22704
22705 let Some(project) = &self.project else { return };
22706
22707 // If None, we are in a file without an extension
22708 let file = self
22709 .buffer
22710 .read(cx)
22711 .as_singleton()
22712 .and_then(|b| b.read(cx).file());
22713 let file_extension = file_extension.or(file
22714 .as_ref()
22715 .and_then(|file| Path::new(file.file_name(cx)).extension())
22716 .and_then(|e| e.to_str())
22717 .map(|a| a.to_string()));
22718
22719 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22720 .map(|vim_mode| vim_mode.0)
22721 .unwrap_or(false);
22722
22723 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22724 let copilot_enabled = edit_predictions_provider
22725 == language::language_settings::EditPredictionProvider::Copilot;
22726 let copilot_enabled_for_language = self
22727 .buffer
22728 .read(cx)
22729 .language_settings(cx)
22730 .show_edit_predictions;
22731
22732 let project = project.read(cx);
22733 let event_type = reported_event.event_type();
22734
22735 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22736 telemetry::event!(
22737 event_type,
22738 type = if auto_saved {"autosave"} else {"manual"},
22739 file_extension,
22740 vim_mode,
22741 copilot_enabled,
22742 copilot_enabled_for_language,
22743 edit_predictions_provider,
22744 is_via_ssh = project.is_via_remote_server(),
22745 );
22746 } else {
22747 telemetry::event!(
22748 event_type,
22749 file_extension,
22750 vim_mode,
22751 copilot_enabled,
22752 copilot_enabled_for_language,
22753 edit_predictions_provider,
22754 is_via_ssh = project.is_via_remote_server(),
22755 );
22756 };
22757 }
22758
22759 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22760 /// with each line being an array of {text, highlight} objects.
22761 fn copy_highlight_json(
22762 &mut self,
22763 _: &CopyHighlightJson,
22764 window: &mut Window,
22765 cx: &mut Context<Self>,
22766 ) {
22767 #[derive(Serialize)]
22768 struct Chunk<'a> {
22769 text: String,
22770 highlight: Option<&'a str>,
22771 }
22772
22773 let snapshot = self.buffer.read(cx).snapshot(cx);
22774 let range = self
22775 .selected_text_range(false, window, cx)
22776 .and_then(|selection| {
22777 if selection.range.is_empty() {
22778 None
22779 } else {
22780 Some(
22781 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22782 selection.range.start,
22783 )))
22784 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22785 selection.range.end,
22786 ))),
22787 )
22788 }
22789 })
22790 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22791
22792 let chunks = snapshot.chunks(range, true);
22793 let mut lines = Vec::new();
22794 let mut line: VecDeque<Chunk> = VecDeque::new();
22795
22796 let Some(style) = self.style.as_ref() else {
22797 return;
22798 };
22799
22800 for chunk in chunks {
22801 let highlight = chunk
22802 .syntax_highlight_id
22803 .and_then(|id| id.name(&style.syntax));
22804 let mut chunk_lines = chunk.text.split('\n').peekable();
22805 while let Some(text) = chunk_lines.next() {
22806 let mut merged_with_last_token = false;
22807 if let Some(last_token) = line.back_mut()
22808 && last_token.highlight == highlight
22809 {
22810 last_token.text.push_str(text);
22811 merged_with_last_token = true;
22812 }
22813
22814 if !merged_with_last_token {
22815 line.push_back(Chunk {
22816 text: text.into(),
22817 highlight,
22818 });
22819 }
22820
22821 if chunk_lines.peek().is_some() {
22822 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22823 line.pop_front();
22824 }
22825 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22826 line.pop_back();
22827 }
22828
22829 lines.push(mem::take(&mut line));
22830 }
22831 }
22832 }
22833
22834 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22835 return;
22836 };
22837 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22838 }
22839
22840 pub fn open_context_menu(
22841 &mut self,
22842 _: &OpenContextMenu,
22843 window: &mut Window,
22844 cx: &mut Context<Self>,
22845 ) {
22846 self.request_autoscroll(Autoscroll::newest(), cx);
22847 let position = self
22848 .selections
22849 .newest_display(&self.display_snapshot(cx))
22850 .start;
22851 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22852 }
22853
22854 pub fn replay_insert_event(
22855 &mut self,
22856 text: &str,
22857 relative_utf16_range: Option<Range<isize>>,
22858 window: &mut Window,
22859 cx: &mut Context<Self>,
22860 ) {
22861 if !self.input_enabled {
22862 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22863 return;
22864 }
22865 if let Some(relative_utf16_range) = relative_utf16_range {
22866 let selections = self
22867 .selections
22868 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22869 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22870 let new_ranges = selections.into_iter().map(|range| {
22871 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22872 range
22873 .head()
22874 .0
22875 .0
22876 .saturating_add_signed(relative_utf16_range.start),
22877 ));
22878 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22879 range
22880 .head()
22881 .0
22882 .0
22883 .saturating_add_signed(relative_utf16_range.end),
22884 ));
22885 start..end
22886 });
22887 s.select_ranges(new_ranges);
22888 });
22889 }
22890
22891 self.handle_input(text, window, cx);
22892 }
22893
22894 pub fn is_focused(&self, window: &Window) -> bool {
22895 self.focus_handle.is_focused(window)
22896 }
22897
22898 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22899 cx.emit(EditorEvent::Focused);
22900
22901 if let Some(descendant) = self
22902 .last_focused_descendant
22903 .take()
22904 .and_then(|descendant| descendant.upgrade())
22905 {
22906 window.focus(&descendant, cx);
22907 } else {
22908 if let Some(blame) = self.blame.as_ref() {
22909 blame.update(cx, GitBlame::focus)
22910 }
22911
22912 self.blink_manager.update(cx, BlinkManager::enable);
22913 self.show_cursor_names(window, cx);
22914 self.buffer.update(cx, |buffer, cx| {
22915 buffer.finalize_last_transaction(cx);
22916 if self.leader_id.is_none() {
22917 buffer.set_active_selections(
22918 &self.selections.disjoint_anchors_arc(),
22919 self.selections.line_mode(),
22920 self.cursor_shape,
22921 cx,
22922 );
22923 }
22924 });
22925
22926 if let Some(position_map) = self.last_position_map.clone() {
22927 EditorElement::mouse_moved(
22928 self,
22929 &MouseMoveEvent {
22930 position: window.mouse_position(),
22931 pressed_button: None,
22932 modifiers: window.modifiers(),
22933 },
22934 &position_map,
22935 window,
22936 cx,
22937 );
22938 }
22939 }
22940 }
22941
22942 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22943 cx.emit(EditorEvent::FocusedIn)
22944 }
22945
22946 fn handle_focus_out(
22947 &mut self,
22948 event: FocusOutEvent,
22949 _window: &mut Window,
22950 cx: &mut Context<Self>,
22951 ) {
22952 if event.blurred != self.focus_handle {
22953 self.last_focused_descendant = Some(event.blurred);
22954 }
22955 self.selection_drag_state = SelectionDragState::None;
22956 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22957 }
22958
22959 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22960 self.blink_manager.update(cx, BlinkManager::disable);
22961 self.buffer
22962 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22963
22964 if let Some(blame) = self.blame.as_ref() {
22965 blame.update(cx, GitBlame::blur)
22966 }
22967 if !self.hover_state.focused(window, cx) {
22968 hide_hover(self, cx);
22969 }
22970 if !self
22971 .context_menu
22972 .borrow()
22973 .as_ref()
22974 .is_some_and(|context_menu| context_menu.focused(window, cx))
22975 {
22976 self.hide_context_menu(window, cx);
22977 }
22978 self.take_active_edit_prediction(cx);
22979 cx.emit(EditorEvent::Blurred);
22980 cx.notify();
22981 }
22982
22983 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22984 let mut pending: String = window
22985 .pending_input_keystrokes()
22986 .into_iter()
22987 .flatten()
22988 .filter_map(|keystroke| keystroke.key_char.clone())
22989 .collect();
22990
22991 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22992 pending = "".to_string();
22993 }
22994
22995 let existing_pending = self
22996 .text_highlights::<PendingInput>(cx)
22997 .map(|(_, ranges)| ranges.to_vec());
22998 if existing_pending.is_none() && pending.is_empty() {
22999 return;
23000 }
23001 let transaction =
23002 self.transact(window, cx, |this, window, cx| {
23003 let selections = this
23004 .selections
23005 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
23006 let edits = selections
23007 .iter()
23008 .map(|selection| (selection.end..selection.end, pending.clone()));
23009 this.edit(edits, cx);
23010 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23011 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
23012 sel.start + ix * pending.len()..sel.end + ix * pending.len()
23013 }));
23014 });
23015 if let Some(existing_ranges) = existing_pending {
23016 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
23017 this.edit(edits, cx);
23018 }
23019 });
23020
23021 let snapshot = self.snapshot(window, cx);
23022 let ranges = self
23023 .selections
23024 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
23025 .into_iter()
23026 .map(|selection| {
23027 snapshot.buffer_snapshot().anchor_after(selection.end)
23028 ..snapshot
23029 .buffer_snapshot()
23030 .anchor_before(selection.end + pending.len())
23031 })
23032 .collect();
23033
23034 if pending.is_empty() {
23035 self.clear_highlights::<PendingInput>(cx);
23036 } else {
23037 self.highlight_text::<PendingInput>(
23038 ranges,
23039 HighlightStyle {
23040 underline: Some(UnderlineStyle {
23041 thickness: px(1.),
23042 color: None,
23043 wavy: false,
23044 }),
23045 ..Default::default()
23046 },
23047 cx,
23048 );
23049 }
23050
23051 self.ime_transaction = self.ime_transaction.or(transaction);
23052 if let Some(transaction) = self.ime_transaction {
23053 self.buffer.update(cx, |buffer, cx| {
23054 buffer.group_until_transaction(transaction, cx);
23055 });
23056 }
23057
23058 if self.text_highlights::<PendingInput>(cx).is_none() {
23059 self.ime_transaction.take();
23060 }
23061 }
23062
23063 pub fn register_action_renderer(
23064 &mut self,
23065 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
23066 ) -> Subscription {
23067 let id = self.next_editor_action_id.post_inc();
23068 self.editor_actions
23069 .borrow_mut()
23070 .insert(id, Box::new(listener));
23071
23072 let editor_actions = self.editor_actions.clone();
23073 Subscription::new(move || {
23074 editor_actions.borrow_mut().remove(&id);
23075 })
23076 }
23077
23078 pub fn register_action<A: Action>(
23079 &mut self,
23080 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
23081 ) -> Subscription {
23082 let id = self.next_editor_action_id.post_inc();
23083 let listener = Arc::new(listener);
23084 self.editor_actions.borrow_mut().insert(
23085 id,
23086 Box::new(move |_, window, _| {
23087 let listener = listener.clone();
23088 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
23089 let action = action.downcast_ref().unwrap();
23090 if phase == DispatchPhase::Bubble {
23091 listener(action, window, cx)
23092 }
23093 })
23094 }),
23095 );
23096
23097 let editor_actions = self.editor_actions.clone();
23098 Subscription::new(move || {
23099 editor_actions.borrow_mut().remove(&id);
23100 })
23101 }
23102
23103 pub fn file_header_size(&self) -> u32 {
23104 FILE_HEADER_HEIGHT
23105 }
23106
23107 pub fn restore(
23108 &mut self,
23109 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
23110 window: &mut Window,
23111 cx: &mut Context<Self>,
23112 ) {
23113 self.buffer().update(cx, |multi_buffer, cx| {
23114 for (buffer_id, changes) in revert_changes {
23115 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
23116 buffer.update(cx, |buffer, cx| {
23117 buffer.edit(
23118 changes
23119 .into_iter()
23120 .map(|(range, text)| (range, text.to_string())),
23121 None,
23122 cx,
23123 );
23124 });
23125 }
23126 }
23127 });
23128 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23129 selections.refresh()
23130 });
23131 }
23132
23133 pub fn to_pixel_point(
23134 &mut self,
23135 source: multi_buffer::Anchor,
23136 editor_snapshot: &EditorSnapshot,
23137 window: &mut Window,
23138 cx: &App,
23139 ) -> Option<gpui::Point<Pixels>> {
23140 let source_point = source.to_display_point(editor_snapshot);
23141 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23142 }
23143
23144 pub fn display_to_pixel_point(
23145 &mut self,
23146 source: DisplayPoint,
23147 editor_snapshot: &EditorSnapshot,
23148 window: &mut Window,
23149 cx: &App,
23150 ) -> Option<gpui::Point<Pixels>> {
23151 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23152 let text_layout_details = self.text_layout_details(window);
23153 let scroll_top = text_layout_details
23154 .scroll_anchor
23155 .scroll_position(editor_snapshot)
23156 .y;
23157
23158 if source.row().as_f64() < scroll_top.floor() {
23159 return None;
23160 }
23161 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23162 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23163 Some(gpui::Point::new(source_x, source_y))
23164 }
23165
23166 pub fn has_visible_completions_menu(&self) -> bool {
23167 !self.edit_prediction_preview_is_active()
23168 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23169 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23170 })
23171 }
23172
23173 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23174 if self.mode.is_minimap() {
23175 return;
23176 }
23177 self.addons
23178 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23179 }
23180
23181 pub fn unregister_addon<T: Addon>(&mut self) {
23182 self.addons.remove(&std::any::TypeId::of::<T>());
23183 }
23184
23185 pub fn addon<T: Addon>(&self) -> Option<&T> {
23186 let type_id = std::any::TypeId::of::<T>();
23187 self.addons
23188 .get(&type_id)
23189 .and_then(|item| item.to_any().downcast_ref::<T>())
23190 }
23191
23192 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23193 let type_id = std::any::TypeId::of::<T>();
23194 self.addons
23195 .get_mut(&type_id)
23196 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23197 }
23198
23199 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23200 let text_layout_details = self.text_layout_details(window);
23201 let style = &text_layout_details.editor_style;
23202 let font_id = window.text_system().resolve_font(&style.text.font());
23203 let font_size = style.text.font_size.to_pixels(window.rem_size());
23204 let line_height = style.text.line_height_in_pixels(window.rem_size());
23205 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23206 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23207
23208 CharacterDimensions {
23209 em_width,
23210 em_advance,
23211 line_height,
23212 }
23213 }
23214
23215 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23216 self.load_diff_task.clone()
23217 }
23218
23219 fn read_metadata_from_db(
23220 &mut self,
23221 item_id: u64,
23222 workspace_id: WorkspaceId,
23223 window: &mut Window,
23224 cx: &mut Context<Editor>,
23225 ) {
23226 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23227 && !self.mode.is_minimap()
23228 && WorkspaceSettings::get(None, cx).restore_on_startup
23229 != RestoreOnStartupBehavior::EmptyTab
23230 {
23231 let buffer_snapshot = OnceCell::new();
23232
23233 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23234 && !folds.is_empty()
23235 {
23236 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23237 let snapshot_len = snapshot.len().0;
23238
23239 // Helper: search for fingerprint in buffer, return offset if found
23240 let find_fingerprint = |fingerprint: &str, search_start: usize| -> Option<usize> {
23241 // Ensure we start at a character boundary (defensive)
23242 let search_start = snapshot
23243 .clip_offset(MultiBufferOffset(search_start), Bias::Left)
23244 .0;
23245 let search_end = snapshot_len.saturating_sub(fingerprint.len());
23246
23247 let mut byte_offset = search_start;
23248 for ch in snapshot.chars_at(MultiBufferOffset(search_start)) {
23249 if byte_offset > search_end {
23250 break;
23251 }
23252 if snapshot.contains_str_at(MultiBufferOffset(byte_offset), fingerprint) {
23253 return Some(byte_offset);
23254 }
23255 byte_offset += ch.len_utf8();
23256 }
23257 None
23258 };
23259
23260 // Track search position to handle duplicate fingerprints correctly.
23261 // Folds are stored in document order, so we advance after each match.
23262 let mut search_start = 0usize;
23263
23264 let valid_folds: Vec<_> = folds
23265 .into_iter()
23266 .filter_map(|(stored_start, stored_end, start_fp, end_fp)| {
23267 // Skip folds without fingerprints (old data before migration)
23268 let sfp = start_fp?;
23269 let efp = end_fp?;
23270 let efp_len = efp.len();
23271
23272 // Fast path: check if fingerprints match at stored offsets
23273 // Note: end_fp is content BEFORE fold end, so check at (stored_end - efp_len)
23274 let start_matches = stored_start < snapshot_len
23275 && snapshot.contains_str_at(MultiBufferOffset(stored_start), &sfp);
23276 let efp_check_pos = stored_end.saturating_sub(efp_len);
23277 let end_matches = efp_check_pos >= stored_start
23278 && stored_end <= snapshot_len
23279 && snapshot.contains_str_at(MultiBufferOffset(efp_check_pos), &efp);
23280
23281 let (new_start, new_end) = if start_matches && end_matches {
23282 // Offsets unchanged, use stored values
23283 (stored_start, stored_end)
23284 } else if sfp == efp {
23285 // Short fold: identical fingerprints can only match once per search
23286 // Use stored fold length to compute new_end
23287 let new_start = find_fingerprint(&sfp, search_start)?;
23288 let fold_len = stored_end - stored_start;
23289 let new_end = new_start + fold_len;
23290 (new_start, new_end)
23291 } else {
23292 // Slow path: search for fingerprints in buffer
23293 let new_start = find_fingerprint(&sfp, search_start)?;
23294 // Search for end_fp after start, then add efp_len to get actual fold end
23295 let efp_pos = find_fingerprint(&efp, new_start + sfp.len())?;
23296 let new_end = efp_pos + efp_len;
23297 (new_start, new_end)
23298 };
23299
23300 // Advance search position for next fold
23301 search_start = new_end;
23302
23303 // Validate fold makes sense (end must be after start)
23304 if new_end <= new_start {
23305 return None;
23306 }
23307
23308 Some(
23309 snapshot.clip_offset(MultiBufferOffset(new_start), Bias::Left)
23310 ..snapshot.clip_offset(MultiBufferOffset(new_end), Bias::Right),
23311 )
23312 })
23313 .collect();
23314
23315 if !valid_folds.is_empty() {
23316 self.fold_ranges(valid_folds, false, window, cx);
23317
23318 // Migrate folds to current entity_id before workspace cleanup runs.
23319 // Entity IDs change between sessions, but workspace cleanup deletes
23320 // old editor rows (cascading to folds) based on current entity IDs.
23321 let new_editor_id = cx.entity().entity_id().as_u64() as ItemId;
23322 if new_editor_id != item_id {
23323 cx.spawn(async move |_, _| {
23324 DB.migrate_editor_folds(item_id, new_editor_id, workspace_id)
23325 .await
23326 .log_err();
23327 })
23328 .detach();
23329 }
23330 }
23331 }
23332
23333 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23334 && !selections.is_empty()
23335 {
23336 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23337 // skip adding the initial selection to selection history
23338 self.selection_history.mode = SelectionHistoryMode::Skipping;
23339 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23340 s.select_ranges(selections.into_iter().map(|(start, end)| {
23341 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23342 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23343 }));
23344 });
23345 self.selection_history.mode = SelectionHistoryMode::Normal;
23346 };
23347 }
23348
23349 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23350 }
23351
23352 fn update_lsp_data(
23353 &mut self,
23354 for_buffer: Option<BufferId>,
23355 window: &mut Window,
23356 cx: &mut Context<'_, Self>,
23357 ) {
23358 self.pull_diagnostics(for_buffer, window, cx);
23359 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23360 }
23361
23362 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23363 if self.ignore_lsp_data() {
23364 return;
23365 }
23366 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23367 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23368 }
23369 }
23370
23371 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23372 if self.ignore_lsp_data() {
23373 return;
23374 }
23375
23376 if !self.registered_buffers.contains_key(&buffer_id)
23377 && let Some(project) = self.project.as_ref()
23378 {
23379 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23380 project.update(cx, |project, cx| {
23381 self.registered_buffers.insert(
23382 buffer_id,
23383 project.register_buffer_with_language_servers(&buffer, cx),
23384 );
23385 });
23386 } else {
23387 self.registered_buffers.remove(&buffer_id);
23388 }
23389 }
23390 }
23391
23392 fn ignore_lsp_data(&self) -> bool {
23393 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23394 // skip any LSP updates for it.
23395 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23396 }
23397
23398 fn create_style(&self, cx: &App) -> EditorStyle {
23399 let settings = ThemeSettings::get_global(cx);
23400
23401 let mut text_style = match self.mode {
23402 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23403 color: cx.theme().colors().editor_foreground,
23404 font_family: settings.ui_font.family.clone(),
23405 font_features: settings.ui_font.features.clone(),
23406 font_fallbacks: settings.ui_font.fallbacks.clone(),
23407 font_size: rems(0.875).into(),
23408 font_weight: settings.ui_font.weight,
23409 line_height: relative(settings.buffer_line_height.value()),
23410 ..Default::default()
23411 },
23412 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23413 color: cx.theme().colors().editor_foreground,
23414 font_family: settings.buffer_font.family.clone(),
23415 font_features: settings.buffer_font.features.clone(),
23416 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23417 font_size: settings.buffer_font_size(cx).into(),
23418 font_weight: settings.buffer_font.weight,
23419 line_height: relative(settings.buffer_line_height.value()),
23420 ..Default::default()
23421 },
23422 };
23423 if let Some(text_style_refinement) = &self.text_style_refinement {
23424 text_style.refine(text_style_refinement)
23425 }
23426
23427 let background = match self.mode {
23428 EditorMode::SingleLine => cx.theme().system().transparent,
23429 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23430 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23431 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23432 };
23433
23434 EditorStyle {
23435 background,
23436 border: cx.theme().colors().border,
23437 local_player: cx.theme().players().local(),
23438 text: text_style,
23439 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23440 syntax: cx.theme().syntax().clone(),
23441 status: cx.theme().status().clone(),
23442 inlay_hints_style: make_inlay_hints_style(cx),
23443 edit_prediction_styles: make_suggestion_styles(cx),
23444 unnecessary_code_fade: settings.unnecessary_code_fade,
23445 show_underlines: self.diagnostics_enabled(),
23446 }
23447 }
23448 fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
23449 let cursor = self.selections.newest_anchor().head();
23450 let multibuffer = self.buffer().read(cx);
23451 let is_singleton = multibuffer.is_singleton();
23452 let (buffer_id, symbols) = multibuffer
23453 .read(cx)
23454 .symbols_containing(cursor, Some(variant.syntax()))?;
23455 let buffer = multibuffer.buffer(buffer_id)?;
23456
23457 let buffer = buffer.read(cx);
23458 let settings = ThemeSettings::get_global(cx);
23459 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
23460 let mut breadcrumbs = if is_singleton {
23461 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
23462 buffer
23463 .snapshot()
23464 .resolve_file_path(
23465 self.project
23466 .as_ref()
23467 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
23468 .unwrap_or_default(),
23469 cx,
23470 )
23471 .unwrap_or_else(|| {
23472 if multibuffer.is_singleton() {
23473 multibuffer.title(cx).to_string()
23474 } else {
23475 "untitled".to_string()
23476 }
23477 })
23478 });
23479 vec![BreadcrumbText {
23480 text,
23481 highlights: None,
23482 font: Some(settings.buffer_font.clone()),
23483 }]
23484 } else {
23485 vec![]
23486 };
23487
23488 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
23489 text: symbol.text,
23490 highlights: Some(symbol.highlight_ranges),
23491 font: Some(settings.buffer_font.clone()),
23492 }));
23493 Some(breadcrumbs)
23494 }
23495}
23496
23497fn edit_for_markdown_paste<'a>(
23498 buffer: &MultiBufferSnapshot,
23499 range: Range<MultiBufferOffset>,
23500 to_insert: &'a str,
23501 url: Option<url::Url>,
23502) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23503 if url.is_none() {
23504 return (range, Cow::Borrowed(to_insert));
23505 };
23506
23507 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23508
23509 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23510 Cow::Borrowed(to_insert)
23511 } else {
23512 Cow::Owned(format!("[{old_text}]({to_insert})"))
23513 };
23514 (range, new_text)
23515}
23516
23517fn process_completion_for_edit(
23518 completion: &Completion,
23519 intent: CompletionIntent,
23520 buffer: &Entity<Buffer>,
23521 cursor_position: &text::Anchor,
23522 cx: &mut Context<Editor>,
23523) -> CompletionEdit {
23524 let buffer = buffer.read(cx);
23525 let buffer_snapshot = buffer.snapshot();
23526 let (snippet, new_text) = if completion.is_snippet() {
23527 let mut snippet_source = completion.new_text.clone();
23528 // Workaround for typescript language server issues so that methods don't expand within
23529 // strings and functions with type expressions. The previous point is used because the query
23530 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23531 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23532 let previous_point = if previous_point.column > 0 {
23533 cursor_position.to_previous_offset(&buffer_snapshot)
23534 } else {
23535 cursor_position.to_offset(&buffer_snapshot)
23536 };
23537 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23538 && scope.prefers_label_for_snippet_in_completion()
23539 && let Some(label) = completion.label()
23540 && matches!(
23541 completion.kind(),
23542 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23543 )
23544 {
23545 snippet_source = label;
23546 }
23547 match Snippet::parse(&snippet_source).log_err() {
23548 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23549 None => (None, completion.new_text.clone()),
23550 }
23551 } else {
23552 (None, completion.new_text.clone())
23553 };
23554
23555 let mut range_to_replace = {
23556 let replace_range = &completion.replace_range;
23557 if let CompletionSource::Lsp {
23558 insert_range: Some(insert_range),
23559 ..
23560 } = &completion.source
23561 {
23562 debug_assert_eq!(
23563 insert_range.start, replace_range.start,
23564 "insert_range and replace_range should start at the same position"
23565 );
23566 debug_assert!(
23567 insert_range
23568 .start
23569 .cmp(cursor_position, &buffer_snapshot)
23570 .is_le(),
23571 "insert_range should start before or at cursor position"
23572 );
23573 debug_assert!(
23574 replace_range
23575 .start
23576 .cmp(cursor_position, &buffer_snapshot)
23577 .is_le(),
23578 "replace_range should start before or at cursor position"
23579 );
23580
23581 let should_replace = match intent {
23582 CompletionIntent::CompleteWithInsert => false,
23583 CompletionIntent::CompleteWithReplace => true,
23584 CompletionIntent::Complete | CompletionIntent::Compose => {
23585 let insert_mode =
23586 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23587 .completions
23588 .lsp_insert_mode;
23589 match insert_mode {
23590 LspInsertMode::Insert => false,
23591 LspInsertMode::Replace => true,
23592 LspInsertMode::ReplaceSubsequence => {
23593 let mut text_to_replace = buffer.chars_for_range(
23594 buffer.anchor_before(replace_range.start)
23595 ..buffer.anchor_after(replace_range.end),
23596 );
23597 let mut current_needle = text_to_replace.next();
23598 for haystack_ch in completion.label.text.chars() {
23599 if let Some(needle_ch) = current_needle
23600 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23601 {
23602 current_needle = text_to_replace.next();
23603 }
23604 }
23605 current_needle.is_none()
23606 }
23607 LspInsertMode::ReplaceSuffix => {
23608 if replace_range
23609 .end
23610 .cmp(cursor_position, &buffer_snapshot)
23611 .is_gt()
23612 {
23613 let range_after_cursor = *cursor_position..replace_range.end;
23614 let text_after_cursor = buffer
23615 .text_for_range(
23616 buffer.anchor_before(range_after_cursor.start)
23617 ..buffer.anchor_after(range_after_cursor.end),
23618 )
23619 .collect::<String>()
23620 .to_ascii_lowercase();
23621 completion
23622 .label
23623 .text
23624 .to_ascii_lowercase()
23625 .ends_with(&text_after_cursor)
23626 } else {
23627 true
23628 }
23629 }
23630 }
23631 }
23632 };
23633
23634 if should_replace {
23635 replace_range.clone()
23636 } else {
23637 insert_range.clone()
23638 }
23639 } else {
23640 replace_range.clone()
23641 }
23642 };
23643
23644 if range_to_replace
23645 .end
23646 .cmp(cursor_position, &buffer_snapshot)
23647 .is_lt()
23648 {
23649 range_to_replace.end = *cursor_position;
23650 }
23651
23652 let replace_range = range_to_replace.to_offset(buffer);
23653 CompletionEdit {
23654 new_text,
23655 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23656 snippet,
23657 }
23658}
23659
23660struct CompletionEdit {
23661 new_text: String,
23662 replace_range: Range<BufferOffset>,
23663 snippet: Option<Snippet>,
23664}
23665
23666fn comment_delimiter_for_newline(
23667 start_point: &Point,
23668 buffer: &MultiBufferSnapshot,
23669 language: &LanguageScope,
23670) -> Option<Arc<str>> {
23671 let delimiters = language.line_comment_prefixes();
23672 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
23673 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23674
23675 let num_of_whitespaces = snapshot
23676 .chars_for_range(range.clone())
23677 .take_while(|c| c.is_whitespace())
23678 .count();
23679 let comment_candidate = snapshot
23680 .chars_for_range(range.clone())
23681 .skip(num_of_whitespaces)
23682 .take(max_len_of_delimiter)
23683 .collect::<String>();
23684 let (delimiter, trimmed_len) = delimiters
23685 .iter()
23686 .filter_map(|delimiter| {
23687 let prefix = delimiter.trim_end();
23688 if comment_candidate.starts_with(prefix) {
23689 Some((delimiter, prefix.len()))
23690 } else {
23691 None
23692 }
23693 })
23694 .max_by_key(|(_, len)| *len)?;
23695
23696 if let Some(BlockCommentConfig {
23697 start: block_start, ..
23698 }) = language.block_comment()
23699 {
23700 let block_start_trimmed = block_start.trim_end();
23701 if block_start_trimmed.starts_with(delimiter.trim_end()) {
23702 let line_content = snapshot
23703 .chars_for_range(range)
23704 .skip(num_of_whitespaces)
23705 .take(block_start_trimmed.len())
23706 .collect::<String>();
23707
23708 if line_content.starts_with(block_start_trimmed) {
23709 return None;
23710 }
23711 }
23712 }
23713
23714 let cursor_is_placed_after_comment_marker =
23715 num_of_whitespaces + trimmed_len <= start_point.column as usize;
23716 if cursor_is_placed_after_comment_marker {
23717 Some(delimiter.clone())
23718 } else {
23719 None
23720 }
23721}
23722
23723fn documentation_delimiter_for_newline(
23724 start_point: &Point,
23725 buffer: &MultiBufferSnapshot,
23726 language: &LanguageScope,
23727 newline_config: &mut NewlineConfig,
23728) -> Option<Arc<str>> {
23729 let BlockCommentConfig {
23730 start: start_tag,
23731 end: end_tag,
23732 prefix: delimiter,
23733 tab_size: len,
23734 } = language.documentation_comment()?;
23735 let is_within_block_comment = buffer
23736 .language_scope_at(*start_point)
23737 .is_some_and(|scope| scope.override_name() == Some("comment"));
23738 if !is_within_block_comment {
23739 return None;
23740 }
23741
23742 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23743
23744 let num_of_whitespaces = snapshot
23745 .chars_for_range(range.clone())
23746 .take_while(|c| c.is_whitespace())
23747 .count();
23748
23749 // 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.
23750 let column = start_point.column;
23751 let cursor_is_after_start_tag = {
23752 let start_tag_len = start_tag.len();
23753 let start_tag_line = snapshot
23754 .chars_for_range(range.clone())
23755 .skip(num_of_whitespaces)
23756 .take(start_tag_len)
23757 .collect::<String>();
23758 if start_tag_line.starts_with(start_tag.as_ref()) {
23759 num_of_whitespaces + start_tag_len <= column as usize
23760 } else {
23761 false
23762 }
23763 };
23764
23765 let cursor_is_after_delimiter = {
23766 let delimiter_trim = delimiter.trim_end();
23767 let delimiter_line = snapshot
23768 .chars_for_range(range.clone())
23769 .skip(num_of_whitespaces)
23770 .take(delimiter_trim.len())
23771 .collect::<String>();
23772 if delimiter_line.starts_with(delimiter_trim) {
23773 num_of_whitespaces + delimiter_trim.len() <= column as usize
23774 } else {
23775 false
23776 }
23777 };
23778
23779 let mut needs_extra_line = false;
23780 let mut extra_line_additional_indent = IndentSize::spaces(0);
23781
23782 let cursor_is_before_end_tag_if_exists = {
23783 let mut char_position = 0u32;
23784 let mut end_tag_offset = None;
23785
23786 'outer: for chunk in snapshot.text_for_range(range) {
23787 if let Some(byte_pos) = chunk.find(&**end_tag) {
23788 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
23789 end_tag_offset = Some(char_position + chars_before_match);
23790 break 'outer;
23791 }
23792 char_position += chunk.chars().count() as u32;
23793 }
23794
23795 if let Some(end_tag_offset) = end_tag_offset {
23796 let cursor_is_before_end_tag = column <= end_tag_offset;
23797 if cursor_is_after_start_tag {
23798 if cursor_is_before_end_tag {
23799 needs_extra_line = true;
23800 }
23801 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
23802 if cursor_is_at_start_of_end_tag {
23803 extra_line_additional_indent.len = *len;
23804 }
23805 }
23806 cursor_is_before_end_tag
23807 } else {
23808 true
23809 }
23810 };
23811
23812 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
23813 && cursor_is_before_end_tag_if_exists
23814 {
23815 let additional_indent = if cursor_is_after_start_tag {
23816 IndentSize::spaces(*len)
23817 } else {
23818 IndentSize::spaces(0)
23819 };
23820
23821 *newline_config = NewlineConfig::Newline {
23822 additional_indent,
23823 extra_line_additional_indent: if needs_extra_line {
23824 Some(extra_line_additional_indent)
23825 } else {
23826 None
23827 },
23828 prevent_auto_indent: true,
23829 };
23830 Some(delimiter.clone())
23831 } else {
23832 None
23833 }
23834}
23835
23836const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
23837
23838fn list_delimiter_for_newline(
23839 start_point: &Point,
23840 buffer: &MultiBufferSnapshot,
23841 language: &LanguageScope,
23842 newline_config: &mut NewlineConfig,
23843) -> Option<Arc<str>> {
23844 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23845
23846 let num_of_whitespaces = snapshot
23847 .chars_for_range(range.clone())
23848 .take_while(|c| c.is_whitespace())
23849 .count();
23850
23851 let task_list_entries: Vec<_> = language
23852 .task_list()
23853 .into_iter()
23854 .flat_map(|config| {
23855 config
23856 .prefixes
23857 .iter()
23858 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
23859 })
23860 .collect();
23861 let unordered_list_entries: Vec<_> = language
23862 .unordered_list()
23863 .iter()
23864 .map(|marker| (marker.as_ref(), marker.as_ref()))
23865 .collect();
23866
23867 let all_entries: Vec<_> = task_list_entries
23868 .into_iter()
23869 .chain(unordered_list_entries)
23870 .collect();
23871
23872 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
23873 let candidate: String = snapshot
23874 .chars_for_range(range.clone())
23875 .skip(num_of_whitespaces)
23876 .take(max_prefix_len)
23877 .collect();
23878
23879 if let Some((prefix, continuation)) = all_entries
23880 .iter()
23881 .filter(|(prefix, _)| candidate.starts_with(*prefix))
23882 .max_by_key(|(prefix, _)| prefix.len())
23883 {
23884 let end_of_prefix = num_of_whitespaces + prefix.len();
23885 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
23886 let has_content_after_marker = snapshot
23887 .chars_for_range(range)
23888 .skip(end_of_prefix)
23889 .any(|c| !c.is_whitespace());
23890
23891 if has_content_after_marker && cursor_is_after_prefix {
23892 return Some((*continuation).into());
23893 }
23894
23895 if start_point.column as usize == end_of_prefix {
23896 if num_of_whitespaces == 0 {
23897 *newline_config = NewlineConfig::ClearCurrentLine;
23898 } else {
23899 *newline_config = NewlineConfig::UnindentCurrentLine {
23900 continuation: (*continuation).into(),
23901 };
23902 }
23903 }
23904
23905 return None;
23906 }
23907 }
23908
23909 let candidate: String = snapshot
23910 .chars_for_range(range.clone())
23911 .skip(num_of_whitespaces)
23912 .take(ORDERED_LIST_MAX_MARKER_LEN)
23913 .collect();
23914
23915 for ordered_config in language.ordered_list() {
23916 let regex = match Regex::new(&ordered_config.pattern) {
23917 Ok(r) => r,
23918 Err(_) => continue,
23919 };
23920
23921 if let Some(captures) = regex.captures(&candidate) {
23922 let full_match = captures.get(0)?;
23923 let marker_len = full_match.len();
23924 let end_of_prefix = num_of_whitespaces + marker_len;
23925 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
23926
23927 let has_content_after_marker = snapshot
23928 .chars_for_range(range)
23929 .skip(end_of_prefix)
23930 .any(|c| !c.is_whitespace());
23931
23932 if has_content_after_marker && cursor_is_after_prefix {
23933 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
23934 let continuation = ordered_config
23935 .format
23936 .replace("{1}", &(number + 1).to_string());
23937 return Some(continuation.into());
23938 }
23939
23940 if start_point.column as usize == end_of_prefix {
23941 let continuation = ordered_config.format.replace("{1}", "1");
23942 if num_of_whitespaces == 0 {
23943 *newline_config = NewlineConfig::ClearCurrentLine;
23944 } else {
23945 *newline_config = NewlineConfig::UnindentCurrentLine {
23946 continuation: continuation.into(),
23947 };
23948 }
23949 }
23950
23951 return None;
23952 }
23953 }
23954
23955 None
23956}
23957
23958fn is_list_prefix_row(
23959 row: MultiBufferRow,
23960 buffer: &MultiBufferSnapshot,
23961 language: &LanguageScope,
23962) -> bool {
23963 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
23964 return false;
23965 };
23966
23967 let num_of_whitespaces = snapshot
23968 .chars_for_range(range.clone())
23969 .take_while(|c| c.is_whitespace())
23970 .count();
23971
23972 let task_list_prefixes: Vec<_> = language
23973 .task_list()
23974 .into_iter()
23975 .flat_map(|config| {
23976 config
23977 .prefixes
23978 .iter()
23979 .map(|p| p.as_ref())
23980 .collect::<Vec<_>>()
23981 })
23982 .collect();
23983 let unordered_list_markers: Vec<_> = language
23984 .unordered_list()
23985 .iter()
23986 .map(|marker| marker.as_ref())
23987 .collect();
23988 let all_prefixes: Vec<_> = task_list_prefixes
23989 .into_iter()
23990 .chain(unordered_list_markers)
23991 .collect();
23992 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
23993 let candidate: String = snapshot
23994 .chars_for_range(range.clone())
23995 .skip(num_of_whitespaces)
23996 .take(max_prefix_len)
23997 .collect();
23998 if all_prefixes
23999 .iter()
24000 .any(|prefix| candidate.starts_with(*prefix))
24001 {
24002 return true;
24003 }
24004 }
24005
24006 let ordered_list_candidate: String = snapshot
24007 .chars_for_range(range)
24008 .skip(num_of_whitespaces)
24009 .take(ORDERED_LIST_MAX_MARKER_LEN)
24010 .collect();
24011 for ordered_config in language.ordered_list() {
24012 let regex = match Regex::new(&ordered_config.pattern) {
24013 Ok(r) => r,
24014 Err(_) => continue,
24015 };
24016 if let Some(captures) = regex.captures(&ordered_list_candidate) {
24017 return captures.get(0).is_some();
24018 }
24019 }
24020
24021 false
24022}
24023
24024#[derive(Debug)]
24025enum NewlineConfig {
24026 /// Insert newline with optional additional indent and optional extra blank line
24027 Newline {
24028 additional_indent: IndentSize,
24029 extra_line_additional_indent: Option<IndentSize>,
24030 prevent_auto_indent: bool,
24031 },
24032 /// Clear the current line
24033 ClearCurrentLine,
24034 /// Unindent the current line and add continuation
24035 UnindentCurrentLine { continuation: Arc<str> },
24036}
24037
24038impl NewlineConfig {
24039 fn has_extra_line(&self) -> bool {
24040 matches!(
24041 self,
24042 Self::Newline {
24043 extra_line_additional_indent: Some(_),
24044 ..
24045 }
24046 )
24047 }
24048
24049 fn insert_extra_newline_brackets(
24050 buffer: &MultiBufferSnapshot,
24051 range: Range<MultiBufferOffset>,
24052 language: &language::LanguageScope,
24053 ) -> bool {
24054 let leading_whitespace_len = buffer
24055 .reversed_chars_at(range.start)
24056 .take_while(|c| c.is_whitespace() && *c != '\n')
24057 .map(|c| c.len_utf8())
24058 .sum::<usize>();
24059 let trailing_whitespace_len = buffer
24060 .chars_at(range.end)
24061 .take_while(|c| c.is_whitespace() && *c != '\n')
24062 .map(|c| c.len_utf8())
24063 .sum::<usize>();
24064 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
24065
24066 language.brackets().any(|(pair, enabled)| {
24067 let pair_start = pair.start.trim_end();
24068 let pair_end = pair.end.trim_start();
24069
24070 enabled
24071 && pair.newline
24072 && buffer.contains_str_at(range.end, pair_end)
24073 && buffer.contains_str_at(
24074 range.start.saturating_sub_usize(pair_start.len()),
24075 pair_start,
24076 )
24077 })
24078 }
24079
24080 fn insert_extra_newline_tree_sitter(
24081 buffer: &MultiBufferSnapshot,
24082 range: Range<MultiBufferOffset>,
24083 ) -> bool {
24084 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
24085 [(buffer, range, _)] => (*buffer, range.clone()),
24086 _ => return false,
24087 };
24088 let pair = {
24089 let mut result: Option<BracketMatch<usize>> = None;
24090
24091 for pair in buffer
24092 .all_bracket_ranges(range.start.0..range.end.0)
24093 .filter(move |pair| {
24094 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
24095 })
24096 {
24097 let len = pair.close_range.end - pair.open_range.start;
24098
24099 if let Some(existing) = &result {
24100 let existing_len = existing.close_range.end - existing.open_range.start;
24101 if len > existing_len {
24102 continue;
24103 }
24104 }
24105
24106 result = Some(pair);
24107 }
24108
24109 result
24110 };
24111 let Some(pair) = pair else {
24112 return false;
24113 };
24114 pair.newline_only
24115 && buffer
24116 .chars_for_range(pair.open_range.end..range.start.0)
24117 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
24118 .all(|c| c.is_whitespace() && c != '\n')
24119 }
24120}
24121
24122fn update_uncommitted_diff_for_buffer(
24123 editor: Entity<Editor>,
24124 project: &Entity<Project>,
24125 buffers: impl IntoIterator<Item = Entity<Buffer>>,
24126 buffer: Entity<MultiBuffer>,
24127 cx: &mut App,
24128) -> Task<()> {
24129 let mut tasks = Vec::new();
24130 project.update(cx, |project, cx| {
24131 for buffer in buffers {
24132 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
24133 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
24134 }
24135 }
24136 });
24137 cx.spawn(async move |cx| {
24138 let diffs = future::join_all(tasks).await;
24139 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
24140 return;
24141 }
24142
24143 buffer.update(cx, |buffer, cx| {
24144 for diff in diffs.into_iter().flatten() {
24145 buffer.add_diff(diff, cx);
24146 }
24147 });
24148 })
24149}
24150
24151fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
24152 let tab_size = tab_size.get() as usize;
24153 let mut width = offset;
24154
24155 for ch in text.chars() {
24156 width += if ch == '\t' {
24157 tab_size - (width % tab_size)
24158 } else {
24159 1
24160 };
24161 }
24162
24163 width - offset
24164}
24165
24166#[cfg(test)]
24167mod tests {
24168 use super::*;
24169
24170 #[test]
24171 fn test_string_size_with_expanded_tabs() {
24172 let nz = |val| NonZeroU32::new(val).unwrap();
24173 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
24174 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
24175 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
24176 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
24177 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
24178 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
24179 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
24180 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
24181 }
24182}
24183
24184/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
24185struct WordBreakingTokenizer<'a> {
24186 input: &'a str,
24187}
24188
24189impl<'a> WordBreakingTokenizer<'a> {
24190 fn new(input: &'a str) -> Self {
24191 Self { input }
24192 }
24193}
24194
24195fn is_char_ideographic(ch: char) -> bool {
24196 use unicode_script::Script::*;
24197 use unicode_script::UnicodeScript;
24198 matches!(ch.script(), Han | Tangut | Yi)
24199}
24200
24201fn is_grapheme_ideographic(text: &str) -> bool {
24202 text.chars().any(is_char_ideographic)
24203}
24204
24205fn is_grapheme_whitespace(text: &str) -> bool {
24206 text.chars().any(|x| x.is_whitespace())
24207}
24208
24209fn should_stay_with_preceding_ideograph(text: &str) -> bool {
24210 text.chars()
24211 .next()
24212 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
24213}
24214
24215#[derive(PartialEq, Eq, Debug, Clone, Copy)]
24216enum WordBreakToken<'a> {
24217 Word { token: &'a str, grapheme_len: usize },
24218 InlineWhitespace { token: &'a str, grapheme_len: usize },
24219 Newline,
24220}
24221
24222impl<'a> Iterator for WordBreakingTokenizer<'a> {
24223 /// Yields a span, the count of graphemes in the token, and whether it was
24224 /// whitespace. Note that it also breaks at word boundaries.
24225 type Item = WordBreakToken<'a>;
24226
24227 fn next(&mut self) -> Option<Self::Item> {
24228 use unicode_segmentation::UnicodeSegmentation;
24229 if self.input.is_empty() {
24230 return None;
24231 }
24232
24233 let mut iter = self.input.graphemes(true).peekable();
24234 let mut offset = 0;
24235 let mut grapheme_len = 0;
24236 if let Some(first_grapheme) = iter.next() {
24237 let is_newline = first_grapheme == "\n";
24238 let is_whitespace = is_grapheme_whitespace(first_grapheme);
24239 offset += first_grapheme.len();
24240 grapheme_len += 1;
24241 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
24242 if let Some(grapheme) = iter.peek().copied()
24243 && should_stay_with_preceding_ideograph(grapheme)
24244 {
24245 offset += grapheme.len();
24246 grapheme_len += 1;
24247 }
24248 } else {
24249 let mut words = self.input[offset..].split_word_bound_indices().peekable();
24250 let mut next_word_bound = words.peek().copied();
24251 if next_word_bound.is_some_and(|(i, _)| i == 0) {
24252 next_word_bound = words.next();
24253 }
24254 while let Some(grapheme) = iter.peek().copied() {
24255 if next_word_bound.is_some_and(|(i, _)| i == offset) {
24256 break;
24257 };
24258 if is_grapheme_whitespace(grapheme) != is_whitespace
24259 || (grapheme == "\n") != is_newline
24260 {
24261 break;
24262 };
24263 offset += grapheme.len();
24264 grapheme_len += 1;
24265 iter.next();
24266 }
24267 }
24268 let token = &self.input[..offset];
24269 self.input = &self.input[offset..];
24270 if token == "\n" {
24271 Some(WordBreakToken::Newline)
24272 } else if is_whitespace {
24273 Some(WordBreakToken::InlineWhitespace {
24274 token,
24275 grapheme_len,
24276 })
24277 } else {
24278 Some(WordBreakToken::Word {
24279 token,
24280 grapheme_len,
24281 })
24282 }
24283 } else {
24284 None
24285 }
24286 }
24287}
24288
24289#[test]
24290fn test_word_breaking_tokenizer() {
24291 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
24292 ("", &[]),
24293 (" ", &[whitespace(" ", 2)]),
24294 ("Ʒ", &[word("Ʒ", 1)]),
24295 ("Ǽ", &[word("Ǽ", 1)]),
24296 ("⋑", &[word("⋑", 1)]),
24297 ("⋑⋑", &[word("⋑⋑", 2)]),
24298 (
24299 "原理,进而",
24300 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
24301 ),
24302 (
24303 "hello world",
24304 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
24305 ),
24306 (
24307 "hello, world",
24308 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
24309 ),
24310 (
24311 " hello world",
24312 &[
24313 whitespace(" ", 2),
24314 word("hello", 5),
24315 whitespace(" ", 1),
24316 word("world", 5),
24317 ],
24318 ),
24319 (
24320 "这是什么 \n 钢笔",
24321 &[
24322 word("这", 1),
24323 word("是", 1),
24324 word("什", 1),
24325 word("么", 1),
24326 whitespace(" ", 1),
24327 newline(),
24328 whitespace(" ", 1),
24329 word("钢", 1),
24330 word("笔", 1),
24331 ],
24332 ),
24333 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
24334 ];
24335
24336 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24337 WordBreakToken::Word {
24338 token,
24339 grapheme_len,
24340 }
24341 }
24342
24343 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24344 WordBreakToken::InlineWhitespace {
24345 token,
24346 grapheme_len,
24347 }
24348 }
24349
24350 fn newline() -> WordBreakToken<'static> {
24351 WordBreakToken::Newline
24352 }
24353
24354 for (input, result) in tests {
24355 assert_eq!(
24356 WordBreakingTokenizer::new(input)
24357 .collect::<Vec<_>>()
24358 .as_slice(),
24359 *result,
24360 );
24361 }
24362}
24363
24364fn wrap_with_prefix(
24365 first_line_prefix: String,
24366 subsequent_lines_prefix: String,
24367 unwrapped_text: String,
24368 wrap_column: usize,
24369 tab_size: NonZeroU32,
24370 preserve_existing_whitespace: bool,
24371) -> String {
24372 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
24373 let subsequent_lines_prefix_len =
24374 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
24375 let mut wrapped_text = String::new();
24376 let mut current_line = first_line_prefix;
24377 let mut is_first_line = true;
24378
24379 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
24380 let mut current_line_len = first_line_prefix_len;
24381 let mut in_whitespace = false;
24382 for token in tokenizer {
24383 let have_preceding_whitespace = in_whitespace;
24384 match token {
24385 WordBreakToken::Word {
24386 token,
24387 grapheme_len,
24388 } => {
24389 in_whitespace = false;
24390 let current_prefix_len = if is_first_line {
24391 first_line_prefix_len
24392 } else {
24393 subsequent_lines_prefix_len
24394 };
24395 if current_line_len + grapheme_len > wrap_column
24396 && current_line_len != current_prefix_len
24397 {
24398 wrapped_text.push_str(current_line.trim_end());
24399 wrapped_text.push('\n');
24400 is_first_line = false;
24401 current_line = subsequent_lines_prefix.clone();
24402 current_line_len = subsequent_lines_prefix_len;
24403 }
24404 current_line.push_str(token);
24405 current_line_len += grapheme_len;
24406 }
24407 WordBreakToken::InlineWhitespace {
24408 mut token,
24409 mut grapheme_len,
24410 } => {
24411 in_whitespace = true;
24412 if have_preceding_whitespace && !preserve_existing_whitespace {
24413 continue;
24414 }
24415 if !preserve_existing_whitespace {
24416 // Keep a single whitespace grapheme as-is
24417 if let Some(first) =
24418 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
24419 {
24420 token = first;
24421 } else {
24422 token = " ";
24423 }
24424 grapheme_len = 1;
24425 }
24426 let current_prefix_len = if is_first_line {
24427 first_line_prefix_len
24428 } else {
24429 subsequent_lines_prefix_len
24430 };
24431 if current_line_len + grapheme_len > wrap_column {
24432 wrapped_text.push_str(current_line.trim_end());
24433 wrapped_text.push('\n');
24434 is_first_line = false;
24435 current_line = subsequent_lines_prefix.clone();
24436 current_line_len = subsequent_lines_prefix_len;
24437 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
24438 current_line.push_str(token);
24439 current_line_len += grapheme_len;
24440 }
24441 }
24442 WordBreakToken::Newline => {
24443 in_whitespace = true;
24444 let current_prefix_len = if is_first_line {
24445 first_line_prefix_len
24446 } else {
24447 subsequent_lines_prefix_len
24448 };
24449 if preserve_existing_whitespace {
24450 wrapped_text.push_str(current_line.trim_end());
24451 wrapped_text.push('\n');
24452 is_first_line = false;
24453 current_line = subsequent_lines_prefix.clone();
24454 current_line_len = subsequent_lines_prefix_len;
24455 } else if have_preceding_whitespace {
24456 continue;
24457 } else if current_line_len + 1 > wrap_column
24458 && current_line_len != current_prefix_len
24459 {
24460 wrapped_text.push_str(current_line.trim_end());
24461 wrapped_text.push('\n');
24462 is_first_line = false;
24463 current_line = subsequent_lines_prefix.clone();
24464 current_line_len = subsequent_lines_prefix_len;
24465 } else if current_line_len != current_prefix_len {
24466 current_line.push(' ');
24467 current_line_len += 1;
24468 }
24469 }
24470 }
24471 }
24472
24473 if !current_line.is_empty() {
24474 wrapped_text.push_str(¤t_line);
24475 }
24476 wrapped_text
24477}
24478
24479#[test]
24480fn test_wrap_with_prefix() {
24481 assert_eq!(
24482 wrap_with_prefix(
24483 "# ".to_string(),
24484 "# ".to_string(),
24485 "abcdefg".to_string(),
24486 4,
24487 NonZeroU32::new(4).unwrap(),
24488 false,
24489 ),
24490 "# abcdefg"
24491 );
24492 assert_eq!(
24493 wrap_with_prefix(
24494 "".to_string(),
24495 "".to_string(),
24496 "\thello world".to_string(),
24497 8,
24498 NonZeroU32::new(4).unwrap(),
24499 false,
24500 ),
24501 "hello\nworld"
24502 );
24503 assert_eq!(
24504 wrap_with_prefix(
24505 "// ".to_string(),
24506 "// ".to_string(),
24507 "xx \nyy zz aa bb cc".to_string(),
24508 12,
24509 NonZeroU32::new(4).unwrap(),
24510 false,
24511 ),
24512 "// xx yy zz\n// aa bb cc"
24513 );
24514 assert_eq!(
24515 wrap_with_prefix(
24516 String::new(),
24517 String::new(),
24518 "这是什么 \n 钢笔".to_string(),
24519 3,
24520 NonZeroU32::new(4).unwrap(),
24521 false,
24522 ),
24523 "这是什\n么 钢\n笔"
24524 );
24525 assert_eq!(
24526 wrap_with_prefix(
24527 String::new(),
24528 String::new(),
24529 format!("foo{}bar", '\u{2009}'), // thin space
24530 80,
24531 NonZeroU32::new(4).unwrap(),
24532 false,
24533 ),
24534 format!("foo{}bar", '\u{2009}')
24535 );
24536}
24537
24538pub trait CollaborationHub {
24539 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
24540 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
24541 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
24542}
24543
24544impl CollaborationHub for Entity<Project> {
24545 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
24546 self.read(cx).collaborators()
24547 }
24548
24549 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
24550 self.read(cx).user_store().read(cx).participant_indices()
24551 }
24552
24553 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
24554 let this = self.read(cx);
24555 let user_ids = this.collaborators().values().map(|c| c.user_id);
24556 this.user_store().read(cx).participant_names(user_ids, cx)
24557 }
24558}
24559
24560pub trait SemanticsProvider {
24561 fn hover(
24562 &self,
24563 buffer: &Entity<Buffer>,
24564 position: text::Anchor,
24565 cx: &mut App,
24566 ) -> Option<Task<Option<Vec<project::Hover>>>>;
24567
24568 fn inline_values(
24569 &self,
24570 buffer_handle: Entity<Buffer>,
24571 range: Range<text::Anchor>,
24572 cx: &mut App,
24573 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24574
24575 fn applicable_inlay_chunks(
24576 &self,
24577 buffer: &Entity<Buffer>,
24578 ranges: &[Range<text::Anchor>],
24579 cx: &mut App,
24580 ) -> Vec<Range<BufferRow>>;
24581
24582 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
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
24593 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24594
24595 fn document_highlights(
24596 &self,
24597 buffer: &Entity<Buffer>,
24598 position: text::Anchor,
24599 cx: &mut App,
24600 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24601
24602 fn definitions(
24603 &self,
24604 buffer: &Entity<Buffer>,
24605 position: text::Anchor,
24606 kind: GotoDefinitionKind,
24607 cx: &mut App,
24608 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24609
24610 fn range_for_rename(
24611 &self,
24612 buffer: &Entity<Buffer>,
24613 position: text::Anchor,
24614 cx: &mut App,
24615 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24616
24617 fn perform_rename(
24618 &self,
24619 buffer: &Entity<Buffer>,
24620 position: text::Anchor,
24621 new_name: String,
24622 cx: &mut App,
24623 ) -> Option<Task<Result<ProjectTransaction>>>;
24624}
24625
24626pub trait CompletionProvider {
24627 fn completions(
24628 &self,
24629 excerpt_id: ExcerptId,
24630 buffer: &Entity<Buffer>,
24631 buffer_position: text::Anchor,
24632 trigger: CompletionContext,
24633 window: &mut Window,
24634 cx: &mut Context<Editor>,
24635 ) -> Task<Result<Vec<CompletionResponse>>>;
24636
24637 fn resolve_completions(
24638 &self,
24639 _buffer: Entity<Buffer>,
24640 _completion_indices: Vec<usize>,
24641 _completions: Rc<RefCell<Box<[Completion]>>>,
24642 _cx: &mut Context<Editor>,
24643 ) -> Task<Result<bool>> {
24644 Task::ready(Ok(false))
24645 }
24646
24647 fn apply_additional_edits_for_completion(
24648 &self,
24649 _buffer: Entity<Buffer>,
24650 _completions: Rc<RefCell<Box<[Completion]>>>,
24651 _completion_index: usize,
24652 _push_to_history: bool,
24653 _cx: &mut Context<Editor>,
24654 ) -> Task<Result<Option<language::Transaction>>> {
24655 Task::ready(Ok(None))
24656 }
24657
24658 fn is_completion_trigger(
24659 &self,
24660 buffer: &Entity<Buffer>,
24661 position: language::Anchor,
24662 text: &str,
24663 trigger_in_words: bool,
24664 cx: &mut Context<Editor>,
24665 ) -> bool;
24666
24667 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24668
24669 fn sort_completions(&self) -> bool {
24670 true
24671 }
24672
24673 fn filter_completions(&self) -> bool {
24674 true
24675 }
24676
24677 fn show_snippets(&self) -> bool {
24678 false
24679 }
24680}
24681
24682pub trait CodeActionProvider {
24683 fn id(&self) -> Arc<str>;
24684
24685 fn code_actions(
24686 &self,
24687 buffer: &Entity<Buffer>,
24688 range: Range<text::Anchor>,
24689 window: &mut Window,
24690 cx: &mut App,
24691 ) -> Task<Result<Vec<CodeAction>>>;
24692
24693 fn apply_code_action(
24694 &self,
24695 buffer_handle: Entity<Buffer>,
24696 action: CodeAction,
24697 excerpt_id: ExcerptId,
24698 push_to_history: bool,
24699 window: &mut Window,
24700 cx: &mut App,
24701 ) -> Task<Result<ProjectTransaction>>;
24702}
24703
24704impl CodeActionProvider for Entity<Project> {
24705 fn id(&self) -> Arc<str> {
24706 "project".into()
24707 }
24708
24709 fn code_actions(
24710 &self,
24711 buffer: &Entity<Buffer>,
24712 range: Range<text::Anchor>,
24713 _window: &mut Window,
24714 cx: &mut App,
24715 ) -> Task<Result<Vec<CodeAction>>> {
24716 self.update(cx, |project, cx| {
24717 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24718 let code_actions = project.code_actions(buffer, range, None, cx);
24719 cx.background_spawn(async move {
24720 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24721 Ok(code_lens_actions
24722 .context("code lens fetch")?
24723 .into_iter()
24724 .flatten()
24725 .chain(
24726 code_actions
24727 .context("code action fetch")?
24728 .into_iter()
24729 .flatten(),
24730 )
24731 .collect())
24732 })
24733 })
24734 }
24735
24736 fn apply_code_action(
24737 &self,
24738 buffer_handle: Entity<Buffer>,
24739 action: CodeAction,
24740 _excerpt_id: ExcerptId,
24741 push_to_history: bool,
24742 _window: &mut Window,
24743 cx: &mut App,
24744 ) -> Task<Result<ProjectTransaction>> {
24745 self.update(cx, |project, cx| {
24746 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24747 })
24748 }
24749}
24750
24751fn snippet_completions(
24752 project: &Project,
24753 buffer: &Entity<Buffer>,
24754 buffer_anchor: text::Anchor,
24755 classifier: CharClassifier,
24756 cx: &mut App,
24757) -> Task<Result<CompletionResponse>> {
24758 let languages = buffer.read(cx).languages_at(buffer_anchor);
24759 let snippet_store = project.snippets().read(cx);
24760
24761 let scopes: Vec<_> = languages
24762 .iter()
24763 .filter_map(|language| {
24764 let language_name = language.lsp_id();
24765 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24766
24767 if snippets.is_empty() {
24768 None
24769 } else {
24770 Some((language.default_scope(), snippets))
24771 }
24772 })
24773 .collect();
24774
24775 if scopes.is_empty() {
24776 return Task::ready(Ok(CompletionResponse {
24777 completions: vec![],
24778 display_options: CompletionDisplayOptions::default(),
24779 is_incomplete: false,
24780 }));
24781 }
24782
24783 let snapshot = buffer.read(cx).text_snapshot();
24784 let executor = cx.background_executor().clone();
24785
24786 cx.background_spawn(async move {
24787 let is_word_char = |c| classifier.is_word(c);
24788
24789 let mut is_incomplete = false;
24790 let mut completions: Vec<Completion> = Vec::new();
24791
24792 const MAX_PREFIX_LEN: usize = 128;
24793 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24794 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24795 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24796
24797 let max_buffer_window: String = snapshot
24798 .text_for_range(window_start..buffer_offset)
24799 .collect();
24800
24801 if max_buffer_window.is_empty() {
24802 return Ok(CompletionResponse {
24803 completions: vec![],
24804 display_options: CompletionDisplayOptions::default(),
24805 is_incomplete: true,
24806 });
24807 }
24808
24809 for (_scope, snippets) in scopes.into_iter() {
24810 // Sort snippets by word count to match longer snippet prefixes first.
24811 let mut sorted_snippet_candidates = snippets
24812 .iter()
24813 .enumerate()
24814 .flat_map(|(snippet_ix, snippet)| {
24815 snippet
24816 .prefix
24817 .iter()
24818 .enumerate()
24819 .map(move |(prefix_ix, prefix)| {
24820 let word_count =
24821 snippet_candidate_suffixes(prefix, is_word_char).count();
24822 ((snippet_ix, prefix_ix), prefix, word_count)
24823 })
24824 })
24825 .collect_vec();
24826 sorted_snippet_candidates
24827 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24828
24829 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24830
24831 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24832 .take(
24833 sorted_snippet_candidates
24834 .first()
24835 .map(|(_, _, word_count)| *word_count)
24836 .unwrap_or_default(),
24837 )
24838 .collect_vec();
24839
24840 const MAX_RESULTS: usize = 100;
24841 // Each match also remembers how many characters from the buffer it consumed
24842 let mut matches: Vec<(StringMatch, usize)> = vec![];
24843
24844 let mut snippet_list_cutoff_index = 0;
24845 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24846 let word_count = buffer_index + 1;
24847 // Increase `snippet_list_cutoff_index` until we have all of the
24848 // snippets with sufficiently many words.
24849 while sorted_snippet_candidates
24850 .get(snippet_list_cutoff_index)
24851 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24852 *snippet_word_count >= word_count
24853 })
24854 {
24855 snippet_list_cutoff_index += 1;
24856 }
24857
24858 // Take only the candidates with at least `word_count` many words
24859 let snippet_candidates_at_word_len =
24860 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24861
24862 let candidates = snippet_candidates_at_word_len
24863 .iter()
24864 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24865 .enumerate() // index in `sorted_snippet_candidates`
24866 // First char must match
24867 .filter(|(_ix, prefix)| {
24868 itertools::equal(
24869 prefix
24870 .chars()
24871 .next()
24872 .into_iter()
24873 .flat_map(|c| c.to_lowercase()),
24874 buffer_window
24875 .chars()
24876 .next()
24877 .into_iter()
24878 .flat_map(|c| c.to_lowercase()),
24879 )
24880 })
24881 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24882 .collect::<Vec<StringMatchCandidate>>();
24883
24884 matches.extend(
24885 fuzzy::match_strings(
24886 &candidates,
24887 &buffer_window,
24888 buffer_window.chars().any(|c| c.is_uppercase()),
24889 true,
24890 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24891 &Default::default(),
24892 executor.clone(),
24893 )
24894 .await
24895 .into_iter()
24896 .map(|string_match| (string_match, buffer_window.len())),
24897 );
24898
24899 if matches.len() >= MAX_RESULTS {
24900 break;
24901 }
24902 }
24903
24904 let to_lsp = |point: &text::Anchor| {
24905 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24906 point_to_lsp(end)
24907 };
24908 let lsp_end = to_lsp(&buffer_anchor);
24909
24910 if matches.len() >= MAX_RESULTS {
24911 is_incomplete = true;
24912 }
24913
24914 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24915 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24916 sorted_snippet_candidates[string_match.candidate_id];
24917 let snippet = &snippets[snippet_index];
24918 let start = buffer_offset - buffer_window_len;
24919 let start = snapshot.anchor_before(start);
24920 let range = start..buffer_anchor;
24921 let lsp_start = to_lsp(&start);
24922 let lsp_range = lsp::Range {
24923 start: lsp_start,
24924 end: lsp_end,
24925 };
24926 Completion {
24927 replace_range: range,
24928 new_text: snippet.body.clone(),
24929 source: CompletionSource::Lsp {
24930 insert_range: None,
24931 server_id: LanguageServerId(usize::MAX),
24932 resolved: true,
24933 lsp_completion: Box::new(lsp::CompletionItem {
24934 label: snippet.prefix.first().unwrap().clone(),
24935 kind: Some(CompletionItemKind::SNIPPET),
24936 label_details: snippet.description.as_ref().map(|description| {
24937 lsp::CompletionItemLabelDetails {
24938 detail: Some(description.clone()),
24939 description: None,
24940 }
24941 }),
24942 insert_text_format: Some(InsertTextFormat::SNIPPET),
24943 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24944 lsp::InsertReplaceEdit {
24945 new_text: snippet.body.clone(),
24946 insert: lsp_range,
24947 replace: lsp_range,
24948 },
24949 )),
24950 filter_text: Some(snippet.body.clone()),
24951 sort_text: Some(char::MAX.to_string()),
24952 ..lsp::CompletionItem::default()
24953 }),
24954 lsp_defaults: None,
24955 },
24956 label: CodeLabel {
24957 text: matching_prefix.clone(),
24958 runs: Vec::new(),
24959 filter_range: 0..matching_prefix.len(),
24960 },
24961 icon_path: None,
24962 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24963 single_line: snippet.name.clone().into(),
24964 plain_text: snippet
24965 .description
24966 .clone()
24967 .map(|description| description.into()),
24968 }),
24969 insert_text_mode: None,
24970 confirm: None,
24971 match_start: Some(start),
24972 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24973 }
24974 }));
24975 }
24976
24977 Ok(CompletionResponse {
24978 completions,
24979 display_options: CompletionDisplayOptions::default(),
24980 is_incomplete,
24981 })
24982 })
24983}
24984
24985impl CompletionProvider for Entity<Project> {
24986 fn completions(
24987 &self,
24988 _excerpt_id: ExcerptId,
24989 buffer: &Entity<Buffer>,
24990 buffer_position: text::Anchor,
24991 options: CompletionContext,
24992 _window: &mut Window,
24993 cx: &mut Context<Editor>,
24994 ) -> Task<Result<Vec<CompletionResponse>>> {
24995 self.update(cx, |project, cx| {
24996 let task = project.completions(buffer, buffer_position, options, cx);
24997 cx.background_spawn(task)
24998 })
24999 }
25000
25001 fn resolve_completions(
25002 &self,
25003 buffer: Entity<Buffer>,
25004 completion_indices: Vec<usize>,
25005 completions: Rc<RefCell<Box<[Completion]>>>,
25006 cx: &mut Context<Editor>,
25007 ) -> Task<Result<bool>> {
25008 self.update(cx, |project, cx| {
25009 project.lsp_store().update(cx, |lsp_store, cx| {
25010 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
25011 })
25012 })
25013 }
25014
25015 fn apply_additional_edits_for_completion(
25016 &self,
25017 buffer: Entity<Buffer>,
25018 completions: Rc<RefCell<Box<[Completion]>>>,
25019 completion_index: usize,
25020 push_to_history: bool,
25021 cx: &mut Context<Editor>,
25022 ) -> Task<Result<Option<language::Transaction>>> {
25023 self.update(cx, |project, cx| {
25024 project.lsp_store().update(cx, |lsp_store, cx| {
25025 lsp_store.apply_additional_edits_for_completion(
25026 buffer,
25027 completions,
25028 completion_index,
25029 push_to_history,
25030 cx,
25031 )
25032 })
25033 })
25034 }
25035
25036 fn is_completion_trigger(
25037 &self,
25038 buffer: &Entity<Buffer>,
25039 position: language::Anchor,
25040 text: &str,
25041 trigger_in_words: bool,
25042 cx: &mut Context<Editor>,
25043 ) -> bool {
25044 let mut chars = text.chars();
25045 let char = if let Some(char) = chars.next() {
25046 char
25047 } else {
25048 return false;
25049 };
25050 if chars.next().is_some() {
25051 return false;
25052 }
25053
25054 let buffer = buffer.read(cx);
25055 let snapshot = buffer.snapshot();
25056 let classifier = snapshot
25057 .char_classifier_at(position)
25058 .scope_context(Some(CharScopeContext::Completion));
25059 if trigger_in_words && classifier.is_word(char) {
25060 return true;
25061 }
25062
25063 buffer.completion_triggers().contains(text)
25064 }
25065
25066 fn show_snippets(&self) -> bool {
25067 true
25068 }
25069}
25070
25071impl SemanticsProvider for Entity<Project> {
25072 fn hover(
25073 &self,
25074 buffer: &Entity<Buffer>,
25075 position: text::Anchor,
25076 cx: &mut App,
25077 ) -> Option<Task<Option<Vec<project::Hover>>>> {
25078 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
25079 }
25080
25081 fn document_highlights(
25082 &self,
25083 buffer: &Entity<Buffer>,
25084 position: text::Anchor,
25085 cx: &mut App,
25086 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
25087 Some(self.update(cx, |project, cx| {
25088 project.document_highlights(buffer, position, cx)
25089 }))
25090 }
25091
25092 fn definitions(
25093 &self,
25094 buffer: &Entity<Buffer>,
25095 position: text::Anchor,
25096 kind: GotoDefinitionKind,
25097 cx: &mut App,
25098 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
25099 Some(self.update(cx, |project, cx| match kind {
25100 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
25101 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
25102 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
25103 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
25104 }))
25105 }
25106
25107 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
25108 self.update(cx, |project, cx| {
25109 if project
25110 .active_debug_session(cx)
25111 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
25112 {
25113 return true;
25114 }
25115
25116 buffer.update(cx, |buffer, cx| {
25117 project.any_language_server_supports_inlay_hints(buffer, cx)
25118 })
25119 })
25120 }
25121
25122 fn inline_values(
25123 &self,
25124 buffer_handle: Entity<Buffer>,
25125 range: Range<text::Anchor>,
25126 cx: &mut App,
25127 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
25128 self.update(cx, |project, cx| {
25129 let (session, active_stack_frame) = project.active_debug_session(cx)?;
25130
25131 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
25132 })
25133 }
25134
25135 fn applicable_inlay_chunks(
25136 &self,
25137 buffer: &Entity<Buffer>,
25138 ranges: &[Range<text::Anchor>],
25139 cx: &mut App,
25140 ) -> Vec<Range<BufferRow>> {
25141 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
25142 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
25143 })
25144 }
25145
25146 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
25147 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
25148 lsp_store.invalidate_inlay_hints(for_buffers)
25149 });
25150 }
25151
25152 fn inlay_hints(
25153 &self,
25154 invalidate: InvalidationStrategy,
25155 buffer: Entity<Buffer>,
25156 ranges: Vec<Range<text::Anchor>>,
25157 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
25158 cx: &mut App,
25159 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
25160 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
25161 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
25162 }))
25163 }
25164
25165 fn range_for_rename(
25166 &self,
25167 buffer: &Entity<Buffer>,
25168 position: text::Anchor,
25169 cx: &mut App,
25170 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
25171 Some(self.update(cx, |project, cx| {
25172 let buffer = buffer.clone();
25173 let task = project.prepare_rename(buffer.clone(), position, cx);
25174 cx.spawn(async move |_, cx| {
25175 Ok(match task.await? {
25176 PrepareRenameResponse::Success(range) => Some(range),
25177 PrepareRenameResponse::InvalidPosition => None,
25178 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
25179 // Fallback on using TreeSitter info to determine identifier range
25180 buffer.read_with(cx, |buffer, _| {
25181 let snapshot = buffer.snapshot();
25182 let (range, kind) = snapshot.surrounding_word(position, None);
25183 if kind != Some(CharKind::Word) {
25184 return None;
25185 }
25186 Some(
25187 snapshot.anchor_before(range.start)
25188 ..snapshot.anchor_after(range.end),
25189 )
25190 })
25191 }
25192 })
25193 })
25194 }))
25195 }
25196
25197 fn perform_rename(
25198 &self,
25199 buffer: &Entity<Buffer>,
25200 position: text::Anchor,
25201 new_name: String,
25202 cx: &mut App,
25203 ) -> Option<Task<Result<ProjectTransaction>>> {
25204 Some(self.update(cx, |project, cx| {
25205 project.perform_rename(buffer.clone(), position, new_name, cx)
25206 }))
25207 }
25208}
25209
25210fn consume_contiguous_rows(
25211 contiguous_row_selections: &mut Vec<Selection<Point>>,
25212 selection: &Selection<Point>,
25213 display_map: &DisplaySnapshot,
25214 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
25215) -> (MultiBufferRow, MultiBufferRow) {
25216 contiguous_row_selections.push(selection.clone());
25217 let start_row = starting_row(selection, display_map);
25218 let mut end_row = ending_row(selection, display_map);
25219
25220 while let Some(next_selection) = selections.peek() {
25221 if next_selection.start.row <= end_row.0 {
25222 end_row = ending_row(next_selection, display_map);
25223 contiguous_row_selections.push(selections.next().unwrap().clone());
25224 } else {
25225 break;
25226 }
25227 }
25228 (start_row, end_row)
25229}
25230
25231fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
25232 if selection.start.column > 0 {
25233 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
25234 } else {
25235 MultiBufferRow(selection.start.row)
25236 }
25237}
25238
25239fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
25240 if next_selection.end.column > 0 || next_selection.is_empty() {
25241 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
25242 } else {
25243 MultiBufferRow(next_selection.end.row)
25244 }
25245}
25246
25247impl EditorSnapshot {
25248 pub fn remote_selections_in_range<'a>(
25249 &'a self,
25250 range: &'a Range<Anchor>,
25251 collaboration_hub: &dyn CollaborationHub,
25252 cx: &'a App,
25253 ) -> impl 'a + Iterator<Item = RemoteSelection> {
25254 let participant_names = collaboration_hub.user_names(cx);
25255 let participant_indices = collaboration_hub.user_participant_indices(cx);
25256 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
25257 let collaborators_by_replica_id = collaborators_by_peer_id
25258 .values()
25259 .map(|collaborator| (collaborator.replica_id, collaborator))
25260 .collect::<HashMap<_, _>>();
25261 self.buffer_snapshot()
25262 .selections_in_range(range, false)
25263 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
25264 if replica_id == ReplicaId::AGENT {
25265 Some(RemoteSelection {
25266 replica_id,
25267 selection,
25268 cursor_shape,
25269 line_mode,
25270 collaborator_id: CollaboratorId::Agent,
25271 user_name: Some("Agent".into()),
25272 color: cx.theme().players().agent(),
25273 })
25274 } else {
25275 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
25276 let participant_index = participant_indices.get(&collaborator.user_id).copied();
25277 let user_name = participant_names.get(&collaborator.user_id).cloned();
25278 Some(RemoteSelection {
25279 replica_id,
25280 selection,
25281 cursor_shape,
25282 line_mode,
25283 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
25284 user_name,
25285 color: if let Some(index) = participant_index {
25286 cx.theme().players().color_for_participant(index.0)
25287 } else {
25288 cx.theme().players().absent()
25289 },
25290 })
25291 }
25292 })
25293 }
25294
25295 pub fn hunks_for_ranges(
25296 &self,
25297 ranges: impl IntoIterator<Item = Range<Point>>,
25298 ) -> Vec<MultiBufferDiffHunk> {
25299 let mut hunks = Vec::new();
25300 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
25301 HashMap::default();
25302 for query_range in ranges {
25303 let query_rows =
25304 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
25305 for hunk in self.buffer_snapshot().diff_hunks_in_range(
25306 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
25307 ) {
25308 // Include deleted hunks that are adjacent to the query range, because
25309 // otherwise they would be missed.
25310 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
25311 if hunk.status().is_deleted() {
25312 intersects_range |= hunk.row_range.start == query_rows.end;
25313 intersects_range |= hunk.row_range.end == query_rows.start;
25314 }
25315 if intersects_range {
25316 if !processed_buffer_rows
25317 .entry(hunk.buffer_id)
25318 .or_default()
25319 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
25320 {
25321 continue;
25322 }
25323 hunks.push(hunk);
25324 }
25325 }
25326 }
25327
25328 hunks
25329 }
25330
25331 fn display_diff_hunks_for_rows<'a>(
25332 &'a self,
25333 display_rows: Range<DisplayRow>,
25334 folded_buffers: &'a HashSet<BufferId>,
25335 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
25336 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
25337 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
25338
25339 self.buffer_snapshot()
25340 .diff_hunks_in_range(buffer_start..buffer_end)
25341 .filter_map(|hunk| {
25342 if folded_buffers.contains(&hunk.buffer_id) {
25343 return None;
25344 }
25345
25346 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
25347 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
25348
25349 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
25350 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
25351
25352 let display_hunk = if hunk_display_start.column() != 0 {
25353 DisplayDiffHunk::Folded {
25354 display_row: hunk_display_start.row(),
25355 }
25356 } else {
25357 let mut end_row = hunk_display_end.row();
25358 if hunk_display_end.column() > 0 {
25359 end_row.0 += 1;
25360 }
25361 let is_created_file = hunk.is_created_file();
25362
25363 DisplayDiffHunk::Unfolded {
25364 status: hunk.status(),
25365 diff_base_byte_range: hunk.diff_base_byte_range.start.0
25366 ..hunk.diff_base_byte_range.end.0,
25367 word_diffs: hunk.word_diffs,
25368 display_row_range: hunk_display_start.row()..end_row,
25369 multi_buffer_range: Anchor::range_in_buffer(
25370 hunk.excerpt_id,
25371 hunk.buffer_range,
25372 ),
25373 is_created_file,
25374 }
25375 };
25376
25377 Some(display_hunk)
25378 })
25379 }
25380
25381 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
25382 self.display_snapshot
25383 .buffer_snapshot()
25384 .language_at(position)
25385 }
25386
25387 pub fn is_focused(&self) -> bool {
25388 self.is_focused
25389 }
25390
25391 pub fn placeholder_text(&self) -> Option<String> {
25392 self.placeholder_display_snapshot
25393 .as_ref()
25394 .map(|display_map| display_map.text())
25395 }
25396
25397 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
25398 self.scroll_anchor.scroll_position(&self.display_snapshot)
25399 }
25400
25401 pub fn gutter_dimensions(
25402 &self,
25403 font_id: FontId,
25404 font_size: Pixels,
25405 style: &EditorStyle,
25406 window: &mut Window,
25407 cx: &App,
25408 ) -> GutterDimensions {
25409 if self.show_gutter
25410 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
25411 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
25412 {
25413 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
25414 matches!(
25415 ProjectSettings::get_global(cx).git.git_gutter,
25416 GitGutterSetting::TrackedFiles
25417 )
25418 });
25419 let gutter_settings = EditorSettings::get_global(cx).gutter;
25420 let show_line_numbers = self
25421 .show_line_numbers
25422 .unwrap_or(gutter_settings.line_numbers);
25423 let line_gutter_width = if show_line_numbers {
25424 // Avoid flicker-like gutter resizes when the line number gains another digit by
25425 // only resizing the gutter on files with > 10**min_line_number_digits lines.
25426 let min_width_for_number_on_gutter =
25427 ch_advance * gutter_settings.min_line_number_digits as f32;
25428 self.max_line_number_width(style, window)
25429 .max(min_width_for_number_on_gutter)
25430 } else {
25431 0.0.into()
25432 };
25433
25434 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
25435 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
25436
25437 let git_blame_entries_width =
25438 self.git_blame_gutter_max_author_length
25439 .map(|max_author_length| {
25440 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
25441 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
25442
25443 /// The number of characters to dedicate to gaps and margins.
25444 const SPACING_WIDTH: usize = 4;
25445
25446 let max_char_count = max_author_length.min(renderer.max_author_length())
25447 + ::git::SHORT_SHA_LENGTH
25448 + MAX_RELATIVE_TIMESTAMP.len()
25449 + SPACING_WIDTH;
25450
25451 ch_advance * max_char_count
25452 });
25453
25454 let is_singleton = self.buffer_snapshot().is_singleton();
25455
25456 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
25457 left_padding += if !is_singleton {
25458 ch_width * 4.0
25459 } else if show_runnables || show_breakpoints {
25460 ch_width * 3.0
25461 } else if show_git_gutter && show_line_numbers {
25462 ch_width * 2.0
25463 } else if show_git_gutter || show_line_numbers {
25464 ch_width
25465 } else {
25466 px(0.)
25467 };
25468
25469 let shows_folds = is_singleton && gutter_settings.folds;
25470
25471 let right_padding = if shows_folds && show_line_numbers {
25472 ch_width * 4.0
25473 } else if shows_folds || (!is_singleton && show_line_numbers) {
25474 ch_width * 3.0
25475 } else if show_line_numbers {
25476 ch_width
25477 } else {
25478 px(0.)
25479 };
25480
25481 GutterDimensions {
25482 left_padding,
25483 right_padding,
25484 width: line_gutter_width + left_padding + right_padding,
25485 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
25486 git_blame_entries_width,
25487 }
25488 } else if self.offset_content {
25489 GutterDimensions::default_with_margin(font_id, font_size, cx)
25490 } else {
25491 GutterDimensions::default()
25492 }
25493 }
25494
25495 pub fn render_crease_toggle(
25496 &self,
25497 buffer_row: MultiBufferRow,
25498 row_contains_cursor: bool,
25499 editor: Entity<Editor>,
25500 window: &mut Window,
25501 cx: &mut App,
25502 ) -> Option<AnyElement> {
25503 let folded = self.is_line_folded(buffer_row);
25504 let mut is_foldable = false;
25505
25506 if let Some(crease) = self
25507 .crease_snapshot
25508 .query_row(buffer_row, self.buffer_snapshot())
25509 {
25510 is_foldable = true;
25511 match crease {
25512 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
25513 if let Some(render_toggle) = render_toggle {
25514 let toggle_callback =
25515 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
25516 if folded {
25517 editor.update(cx, |editor, cx| {
25518 editor.fold_at(buffer_row, window, cx)
25519 });
25520 } else {
25521 editor.update(cx, |editor, cx| {
25522 editor.unfold_at(buffer_row, window, cx)
25523 });
25524 }
25525 });
25526 return Some((render_toggle)(
25527 buffer_row,
25528 folded,
25529 toggle_callback,
25530 window,
25531 cx,
25532 ));
25533 }
25534 }
25535 }
25536 }
25537
25538 is_foldable |= self.starts_indent(buffer_row);
25539
25540 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
25541 Some(
25542 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
25543 .toggle_state(folded)
25544 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
25545 if folded {
25546 this.unfold_at(buffer_row, window, cx);
25547 } else {
25548 this.fold_at(buffer_row, window, cx);
25549 }
25550 }))
25551 .into_any_element(),
25552 )
25553 } else {
25554 None
25555 }
25556 }
25557
25558 pub fn render_crease_trailer(
25559 &self,
25560 buffer_row: MultiBufferRow,
25561 window: &mut Window,
25562 cx: &mut App,
25563 ) -> Option<AnyElement> {
25564 let folded = self.is_line_folded(buffer_row);
25565 if let Crease::Inline { render_trailer, .. } = self
25566 .crease_snapshot
25567 .query_row(buffer_row, self.buffer_snapshot())?
25568 {
25569 let render_trailer = render_trailer.as_ref()?;
25570 Some(render_trailer(buffer_row, folded, window, cx))
25571 } else {
25572 None
25573 }
25574 }
25575
25576 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25577 let digit_count = self.widest_line_number().ilog10() + 1;
25578 column_pixels(style, digit_count as usize, window)
25579 }
25580
25581 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
25582 ///
25583 /// This is positive if `base` is before `line`.
25584 fn relative_line_delta(
25585 &self,
25586 current_selection_head: DisplayRow,
25587 first_visible_row: DisplayRow,
25588 consider_wrapped_lines: bool,
25589 ) -> i64 {
25590 let current_selection_head = current_selection_head.as_display_point().to_point(self);
25591 let first_visible_row = first_visible_row.as_display_point().to_point(self);
25592
25593 if consider_wrapped_lines {
25594 let wrap_snapshot = self.wrap_snapshot();
25595 let base_wrap_row = wrap_snapshot
25596 .make_wrap_point(current_selection_head, Bias::Left)
25597 .row();
25598 let wrap_row = wrap_snapshot
25599 .make_wrap_point(first_visible_row, Bias::Left)
25600 .row();
25601 wrap_row.0 as i64 - base_wrap_row.0 as i64
25602 } else {
25603 let folds = if current_selection_head < first_visible_row {
25604 self.folds_in_range(current_selection_head..first_visible_row)
25605 } else {
25606 self.folds_in_range(first_visible_row..current_selection_head)
25607 };
25608
25609 let folded_lines = folds
25610 .map(|fold| {
25611 let range = fold.range.0.to_point(self);
25612 range.end.row.saturating_sub(range.start.row)
25613 })
25614 .sum::<u32>() as i64;
25615
25616 first_visible_row.row as i64 - current_selection_head.row as i64 + folded_lines
25617 }
25618 }
25619
25620 /// Returns the unsigned relative line number to display for each row in `rows`.
25621 ///
25622 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
25623 pub fn calculate_relative_line_numbers(
25624 &self,
25625 rows: &Range<DisplayRow>,
25626 current_selection_head: DisplayRow,
25627 count_wrapped_lines: bool,
25628 ) -> HashMap<DisplayRow, u32> {
25629 let initial_offset =
25630 self.relative_line_delta(current_selection_head, rows.start, count_wrapped_lines);
25631 let current_selection_point = current_selection_head.as_display_point().to_point(self);
25632
25633 self.row_infos(rows.start)
25634 .take(rows.len())
25635 .enumerate()
25636 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
25637 .filter(|(_row, row_info)| {
25638 row_info.buffer_row.is_some()
25639 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
25640 })
25641 .enumerate()
25642 .filter(|(_, (row, row_info))| {
25643 // We want to check here that
25644 // - the row is not the current selection head to ensure the current
25645 // line has absolute numbering
25646 // - similarly, should the selection head live in a soft-wrapped line
25647 // and we are not counting those, that the parent line keeps its
25648 // absolute number
25649 // - lastly, if we are in a deleted line, it is fine to number this
25650 // relative with 0, as otherwise it would have no line number at all
25651 (*row != current_selection_head
25652 && (count_wrapped_lines
25653 || row_info.buffer_row != Some(current_selection_point.row)))
25654 || row_info
25655 .diff_status
25656 .is_some_and(|status| status.is_deleted())
25657 })
25658 .map(|(i, (row, _))| (row, (initial_offset + i as i64).unsigned_abs() as u32))
25659 .collect()
25660 }
25661}
25662
25663pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25664 let font_size = style.text.font_size.to_pixels(window.rem_size());
25665 let layout = window.text_system().shape_line(
25666 SharedString::from(" ".repeat(column)),
25667 font_size,
25668 &[TextRun {
25669 len: column,
25670 font: style.text.font(),
25671 color: Hsla::default(),
25672 ..Default::default()
25673 }],
25674 None,
25675 );
25676
25677 layout.width
25678}
25679
25680impl Deref for EditorSnapshot {
25681 type Target = DisplaySnapshot;
25682
25683 fn deref(&self) -> &Self::Target {
25684 &self.display_snapshot
25685 }
25686}
25687
25688#[derive(Clone, Debug, PartialEq, Eq)]
25689pub enum EditorEvent {
25690 InputIgnored {
25691 text: Arc<str>,
25692 },
25693 InputHandled {
25694 utf16_range_to_replace: Option<Range<isize>>,
25695 text: Arc<str>,
25696 },
25697 ExcerptsAdded {
25698 buffer: Entity<Buffer>,
25699 predecessor: ExcerptId,
25700 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25701 },
25702 ExcerptsRemoved {
25703 ids: Vec<ExcerptId>,
25704 removed_buffer_ids: Vec<BufferId>,
25705 },
25706 BufferFoldToggled {
25707 ids: Vec<ExcerptId>,
25708 folded: bool,
25709 },
25710 ExcerptsEdited {
25711 ids: Vec<ExcerptId>,
25712 },
25713 ExcerptsExpanded {
25714 ids: Vec<ExcerptId>,
25715 },
25716 ExpandExcerptsRequested {
25717 excerpt_ids: Vec<ExcerptId>,
25718 lines: u32,
25719 direction: ExpandExcerptDirection,
25720 },
25721 BufferEdited,
25722 Edited {
25723 transaction_id: clock::Lamport,
25724 },
25725 Reparsed(BufferId),
25726 Focused,
25727 FocusedIn,
25728 Blurred,
25729 DirtyChanged,
25730 Saved,
25731 TitleChanged,
25732 SelectionsChanged {
25733 local: bool,
25734 },
25735 ScrollPositionChanged {
25736 local: bool,
25737 autoscroll: bool,
25738 },
25739 TransactionUndone {
25740 transaction_id: clock::Lamport,
25741 },
25742 TransactionBegun {
25743 transaction_id: clock::Lamport,
25744 },
25745 CursorShapeChanged,
25746 BreadcrumbsChanged,
25747 PushedToNavHistory {
25748 anchor: Anchor,
25749 is_deactivate: bool,
25750 },
25751}
25752
25753impl EventEmitter<EditorEvent> for Editor {}
25754
25755impl Focusable for Editor {
25756 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25757 self.focus_handle.clone()
25758 }
25759}
25760
25761impl Render for Editor {
25762 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25763 EditorElement::new(&cx.entity(), self.create_style(cx))
25764 }
25765}
25766
25767impl EntityInputHandler for Editor {
25768 fn text_for_range(
25769 &mut self,
25770 range_utf16: Range<usize>,
25771 adjusted_range: &mut Option<Range<usize>>,
25772 _: &mut Window,
25773 cx: &mut Context<Self>,
25774 ) -> Option<String> {
25775 let snapshot = self.buffer.read(cx).read(cx);
25776 let start = snapshot.clip_offset_utf16(
25777 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25778 Bias::Left,
25779 );
25780 let end = snapshot.clip_offset_utf16(
25781 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25782 Bias::Right,
25783 );
25784 if (start.0.0..end.0.0) != range_utf16 {
25785 adjusted_range.replace(start.0.0..end.0.0);
25786 }
25787 Some(snapshot.text_for_range(start..end).collect())
25788 }
25789
25790 fn selected_text_range(
25791 &mut self,
25792 ignore_disabled_input: bool,
25793 _: &mut Window,
25794 cx: &mut Context<Self>,
25795 ) -> Option<UTF16Selection> {
25796 // Prevent the IME menu from appearing when holding down an alphabetic key
25797 // while input is disabled.
25798 if !ignore_disabled_input && !self.input_enabled {
25799 return None;
25800 }
25801
25802 let selection = self
25803 .selections
25804 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25805 let range = selection.range();
25806
25807 Some(UTF16Selection {
25808 range: range.start.0.0..range.end.0.0,
25809 reversed: selection.reversed,
25810 })
25811 }
25812
25813 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25814 let snapshot = self.buffer.read(cx).read(cx);
25815 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25816 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25817 }
25818
25819 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25820 self.clear_highlights::<InputComposition>(cx);
25821 self.ime_transaction.take();
25822 }
25823
25824 fn replace_text_in_range(
25825 &mut self,
25826 range_utf16: Option<Range<usize>>,
25827 text: &str,
25828 window: &mut Window,
25829 cx: &mut Context<Self>,
25830 ) {
25831 if !self.input_enabled {
25832 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25833 return;
25834 }
25835
25836 self.transact(window, cx, |this, window, cx| {
25837 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25838 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25839 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25840 Some(this.selection_replacement_ranges(range_utf16, cx))
25841 } else {
25842 this.marked_text_ranges(cx)
25843 };
25844
25845 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25846 let newest_selection_id = this.selections.newest_anchor().id;
25847 this.selections
25848 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25849 .iter()
25850 .zip(ranges_to_replace.iter())
25851 .find_map(|(selection, range)| {
25852 if selection.id == newest_selection_id {
25853 Some(
25854 (range.start.0.0 as isize - selection.head().0.0 as isize)
25855 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25856 )
25857 } else {
25858 None
25859 }
25860 })
25861 });
25862
25863 cx.emit(EditorEvent::InputHandled {
25864 utf16_range_to_replace: range_to_replace,
25865 text: text.into(),
25866 });
25867
25868 if let Some(new_selected_ranges) = new_selected_ranges {
25869 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25870 selections.select_ranges(new_selected_ranges)
25871 });
25872 this.backspace(&Default::default(), window, cx);
25873 }
25874
25875 this.handle_input(text, window, cx);
25876 });
25877
25878 if let Some(transaction) = self.ime_transaction {
25879 self.buffer.update(cx, |buffer, cx| {
25880 buffer.group_until_transaction(transaction, cx);
25881 });
25882 }
25883
25884 self.unmark_text(window, cx);
25885 }
25886
25887 fn replace_and_mark_text_in_range(
25888 &mut self,
25889 range_utf16: Option<Range<usize>>,
25890 text: &str,
25891 new_selected_range_utf16: Option<Range<usize>>,
25892 window: &mut Window,
25893 cx: &mut Context<Self>,
25894 ) {
25895 if !self.input_enabled {
25896 return;
25897 }
25898
25899 let transaction = self.transact(window, cx, |this, window, cx| {
25900 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25901 let snapshot = this.buffer.read(cx).read(cx);
25902 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25903 for marked_range in &mut marked_ranges {
25904 marked_range.end = marked_range.start + relative_range_utf16.end;
25905 marked_range.start += relative_range_utf16.start;
25906 marked_range.start =
25907 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25908 marked_range.end =
25909 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25910 }
25911 }
25912 Some(marked_ranges)
25913 } else if let Some(range_utf16) = range_utf16 {
25914 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25915 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25916 Some(this.selection_replacement_ranges(range_utf16, cx))
25917 } else {
25918 None
25919 };
25920
25921 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25922 let newest_selection_id = this.selections.newest_anchor().id;
25923 this.selections
25924 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25925 .iter()
25926 .zip(ranges_to_replace.iter())
25927 .find_map(|(selection, range)| {
25928 if selection.id == newest_selection_id {
25929 Some(
25930 (range.start.0.0 as isize - selection.head().0.0 as isize)
25931 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25932 )
25933 } else {
25934 None
25935 }
25936 })
25937 });
25938
25939 cx.emit(EditorEvent::InputHandled {
25940 utf16_range_to_replace: range_to_replace,
25941 text: text.into(),
25942 });
25943
25944 if let Some(ranges) = ranges_to_replace {
25945 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25946 s.select_ranges(ranges)
25947 });
25948 }
25949
25950 let marked_ranges = {
25951 let snapshot = this.buffer.read(cx).read(cx);
25952 this.selections
25953 .disjoint_anchors_arc()
25954 .iter()
25955 .map(|selection| {
25956 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25957 })
25958 .collect::<Vec<_>>()
25959 };
25960
25961 if text.is_empty() {
25962 this.unmark_text(window, cx);
25963 } else {
25964 this.highlight_text::<InputComposition>(
25965 marked_ranges.clone(),
25966 HighlightStyle {
25967 underline: Some(UnderlineStyle {
25968 thickness: px(1.),
25969 color: None,
25970 wavy: false,
25971 }),
25972 ..Default::default()
25973 },
25974 cx,
25975 );
25976 }
25977
25978 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25979 let use_autoclose = this.use_autoclose;
25980 let use_auto_surround = this.use_auto_surround;
25981 this.set_use_autoclose(false);
25982 this.set_use_auto_surround(false);
25983 this.handle_input(text, window, cx);
25984 this.set_use_autoclose(use_autoclose);
25985 this.set_use_auto_surround(use_auto_surround);
25986
25987 if let Some(new_selected_range) = new_selected_range_utf16 {
25988 let snapshot = this.buffer.read(cx).read(cx);
25989 let new_selected_ranges = marked_ranges
25990 .into_iter()
25991 .map(|marked_range| {
25992 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25993 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25994 insertion_start.0 + new_selected_range.start,
25995 ));
25996 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25997 insertion_start.0 + new_selected_range.end,
25998 ));
25999 snapshot.clip_offset_utf16(new_start, Bias::Left)
26000 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
26001 })
26002 .collect::<Vec<_>>();
26003
26004 drop(snapshot);
26005 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
26006 selections.select_ranges(new_selected_ranges)
26007 });
26008 }
26009 });
26010
26011 self.ime_transaction = self.ime_transaction.or(transaction);
26012 if let Some(transaction) = self.ime_transaction {
26013 self.buffer.update(cx, |buffer, cx| {
26014 buffer.group_until_transaction(transaction, cx);
26015 });
26016 }
26017
26018 if self.text_highlights::<InputComposition>(cx).is_none() {
26019 self.ime_transaction.take();
26020 }
26021 }
26022
26023 fn bounds_for_range(
26024 &mut self,
26025 range_utf16: Range<usize>,
26026 element_bounds: gpui::Bounds<Pixels>,
26027 window: &mut Window,
26028 cx: &mut Context<Self>,
26029 ) -> Option<gpui::Bounds<Pixels>> {
26030 let text_layout_details = self.text_layout_details(window);
26031 let CharacterDimensions {
26032 em_width,
26033 em_advance,
26034 line_height,
26035 } = self.character_dimensions(window);
26036
26037 let snapshot = self.snapshot(window, cx);
26038 let scroll_position = snapshot.scroll_position();
26039 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
26040
26041 let start =
26042 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
26043 let x = Pixels::from(
26044 ScrollOffset::from(
26045 snapshot.x_for_display_point(start, &text_layout_details)
26046 + self.gutter_dimensions.full_width(),
26047 ) - scroll_left,
26048 );
26049 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
26050
26051 Some(Bounds {
26052 origin: element_bounds.origin + point(x, y),
26053 size: size(em_width, line_height),
26054 })
26055 }
26056
26057 fn character_index_for_point(
26058 &mut self,
26059 point: gpui::Point<Pixels>,
26060 _window: &mut Window,
26061 _cx: &mut Context<Self>,
26062 ) -> Option<usize> {
26063 let position_map = self.last_position_map.as_ref()?;
26064 if !position_map.text_hitbox.contains(&point) {
26065 return None;
26066 }
26067 let display_point = position_map.point_for_position(point).previous_valid;
26068 let anchor = position_map
26069 .snapshot
26070 .display_point_to_anchor(display_point, Bias::Left);
26071 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
26072 Some(utf16_offset.0.0)
26073 }
26074
26075 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
26076 self.input_enabled
26077 }
26078}
26079
26080trait SelectionExt {
26081 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
26082 fn spanned_rows(
26083 &self,
26084 include_end_if_at_line_start: bool,
26085 map: &DisplaySnapshot,
26086 ) -> Range<MultiBufferRow>;
26087}
26088
26089impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
26090 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
26091 let start = self
26092 .start
26093 .to_point(map.buffer_snapshot())
26094 .to_display_point(map);
26095 let end = self
26096 .end
26097 .to_point(map.buffer_snapshot())
26098 .to_display_point(map);
26099 if self.reversed {
26100 end..start
26101 } else {
26102 start..end
26103 }
26104 }
26105
26106 fn spanned_rows(
26107 &self,
26108 include_end_if_at_line_start: bool,
26109 map: &DisplaySnapshot,
26110 ) -> Range<MultiBufferRow> {
26111 let start = self.start.to_point(map.buffer_snapshot());
26112 let mut end = self.end.to_point(map.buffer_snapshot());
26113 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
26114 end.row -= 1;
26115 }
26116
26117 let buffer_start = map.prev_line_boundary(start).0;
26118 let buffer_end = map.next_line_boundary(end).0;
26119 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
26120 }
26121}
26122
26123impl<T: InvalidationRegion> InvalidationStack<T> {
26124 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
26125 where
26126 S: Clone + ToOffset,
26127 {
26128 while let Some(region) = self.last() {
26129 let all_selections_inside_invalidation_ranges =
26130 if selections.len() == region.ranges().len() {
26131 selections
26132 .iter()
26133 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
26134 .all(|(selection, invalidation_range)| {
26135 let head = selection.head().to_offset(buffer);
26136 invalidation_range.start <= head && invalidation_range.end >= head
26137 })
26138 } else {
26139 false
26140 };
26141
26142 if all_selections_inside_invalidation_ranges {
26143 break;
26144 } else {
26145 self.pop();
26146 }
26147 }
26148 }
26149}
26150
26151impl<T> Default for InvalidationStack<T> {
26152 fn default() -> Self {
26153 Self(Default::default())
26154 }
26155}
26156
26157impl<T> Deref for InvalidationStack<T> {
26158 type Target = Vec<T>;
26159
26160 fn deref(&self) -> &Self::Target {
26161 &self.0
26162 }
26163}
26164
26165impl<T> DerefMut for InvalidationStack<T> {
26166 fn deref_mut(&mut self) -> &mut Self::Target {
26167 &mut self.0
26168 }
26169}
26170
26171impl InvalidationRegion for SnippetState {
26172 fn ranges(&self) -> &[Range<Anchor>] {
26173 &self.ranges[self.active_index]
26174 }
26175}
26176
26177fn edit_prediction_edit_text(
26178 current_snapshot: &BufferSnapshot,
26179 edits: &[(Range<Anchor>, impl AsRef<str>)],
26180 edit_preview: &EditPreview,
26181 include_deletions: bool,
26182 cx: &App,
26183) -> HighlightedText {
26184 let edits = edits
26185 .iter()
26186 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
26187 .collect::<Vec<_>>();
26188
26189 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
26190}
26191
26192fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
26193 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
26194 // Just show the raw edit text with basic styling
26195 let mut text = String::new();
26196 let mut highlights = Vec::new();
26197
26198 let insertion_highlight_style = HighlightStyle {
26199 color: Some(cx.theme().colors().text),
26200 ..Default::default()
26201 };
26202
26203 for (_, edit_text) in edits {
26204 let start_offset = text.len();
26205 text.push_str(edit_text);
26206 let end_offset = text.len();
26207
26208 if start_offset < end_offset {
26209 highlights.push((start_offset..end_offset, insertion_highlight_style));
26210 }
26211 }
26212
26213 HighlightedText {
26214 text: text.into(),
26215 highlights,
26216 }
26217}
26218
26219pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
26220 match severity {
26221 lsp::DiagnosticSeverity::ERROR => colors.error,
26222 lsp::DiagnosticSeverity::WARNING => colors.warning,
26223 lsp::DiagnosticSeverity::INFORMATION => colors.info,
26224 lsp::DiagnosticSeverity::HINT => colors.info,
26225 _ => colors.ignored,
26226 }
26227}
26228
26229pub fn styled_runs_for_code_label<'a>(
26230 label: &'a CodeLabel,
26231 syntax_theme: &'a theme::SyntaxTheme,
26232 local_player: &'a theme::PlayerColor,
26233) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
26234 let fade_out = HighlightStyle {
26235 fade_out: Some(0.35),
26236 ..Default::default()
26237 };
26238
26239 let mut prev_end = label.filter_range.end;
26240 label
26241 .runs
26242 .iter()
26243 .enumerate()
26244 .flat_map(move |(ix, (range, highlight_id))| {
26245 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
26246 HighlightStyle {
26247 color: Some(local_player.cursor),
26248 ..Default::default()
26249 }
26250 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
26251 HighlightStyle {
26252 background_color: Some(local_player.selection),
26253 ..Default::default()
26254 }
26255 } else if let Some(style) = highlight_id.style(syntax_theme) {
26256 style
26257 } else {
26258 return Default::default();
26259 };
26260 let muted_style = style.highlight(fade_out);
26261
26262 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
26263 if range.start >= label.filter_range.end {
26264 if range.start > prev_end {
26265 runs.push((prev_end..range.start, fade_out));
26266 }
26267 runs.push((range.clone(), muted_style));
26268 } else if range.end <= label.filter_range.end {
26269 runs.push((range.clone(), style));
26270 } else {
26271 runs.push((range.start..label.filter_range.end, style));
26272 runs.push((label.filter_range.end..range.end, muted_style));
26273 }
26274 prev_end = cmp::max(prev_end, range.end);
26275
26276 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
26277 runs.push((prev_end..label.text.len(), fade_out));
26278 }
26279
26280 runs
26281 })
26282}
26283
26284pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
26285 let mut prev_index = 0;
26286 let mut prev_codepoint: Option<char> = None;
26287 text.char_indices()
26288 .chain([(text.len(), '\0')])
26289 .filter_map(move |(index, codepoint)| {
26290 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26291 let is_boundary = index == text.len()
26292 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
26293 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
26294 if is_boundary {
26295 let chunk = &text[prev_index..index];
26296 prev_index = index;
26297 Some(chunk)
26298 } else {
26299 None
26300 }
26301 })
26302}
26303
26304/// Given a string of text immediately before the cursor, iterates over possible
26305/// strings a snippet could match to. More precisely: returns an iterator over
26306/// suffixes of `text` created by splitting at word boundaries (before & after
26307/// every non-word character).
26308///
26309/// Shorter suffixes are returned first.
26310pub(crate) fn snippet_candidate_suffixes(
26311 text: &str,
26312 is_word_char: impl Fn(char) -> bool,
26313) -> impl std::iter::Iterator<Item = &str> {
26314 let mut prev_index = text.len();
26315 let mut prev_codepoint = None;
26316 text.char_indices()
26317 .rev()
26318 .chain([(0, '\0')])
26319 .filter_map(move |(index, codepoint)| {
26320 let prev_index = std::mem::replace(&mut prev_index, index);
26321 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26322 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
26323 None
26324 } else {
26325 let chunk = &text[prev_index..]; // go to end of string
26326 Some(chunk)
26327 }
26328 })
26329}
26330
26331pub trait RangeToAnchorExt: Sized {
26332 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
26333
26334 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
26335 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
26336 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
26337 }
26338}
26339
26340impl<T: ToOffset> RangeToAnchorExt for Range<T> {
26341 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
26342 let start_offset = self.start.to_offset(snapshot);
26343 let end_offset = self.end.to_offset(snapshot);
26344 if start_offset == end_offset {
26345 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
26346 } else {
26347 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
26348 }
26349 }
26350}
26351
26352pub trait RowExt {
26353 fn as_f64(&self) -> f64;
26354
26355 fn next_row(&self) -> Self;
26356
26357 fn previous_row(&self) -> Self;
26358
26359 fn minus(&self, other: Self) -> u32;
26360}
26361
26362impl RowExt for DisplayRow {
26363 fn as_f64(&self) -> f64 {
26364 self.0 as _
26365 }
26366
26367 fn next_row(&self) -> Self {
26368 Self(self.0 + 1)
26369 }
26370
26371 fn previous_row(&self) -> Self {
26372 Self(self.0.saturating_sub(1))
26373 }
26374
26375 fn minus(&self, other: Self) -> u32 {
26376 self.0 - other.0
26377 }
26378}
26379
26380impl RowExt for MultiBufferRow {
26381 fn as_f64(&self) -> f64 {
26382 self.0 as _
26383 }
26384
26385 fn next_row(&self) -> Self {
26386 Self(self.0 + 1)
26387 }
26388
26389 fn previous_row(&self) -> Self {
26390 Self(self.0.saturating_sub(1))
26391 }
26392
26393 fn minus(&self, other: Self) -> u32 {
26394 self.0 - other.0
26395 }
26396}
26397
26398trait RowRangeExt {
26399 type Row;
26400
26401 fn len(&self) -> usize;
26402
26403 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
26404}
26405
26406impl RowRangeExt for Range<MultiBufferRow> {
26407 type Row = MultiBufferRow;
26408
26409 fn len(&self) -> usize {
26410 (self.end.0 - self.start.0) as usize
26411 }
26412
26413 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
26414 (self.start.0..self.end.0).map(MultiBufferRow)
26415 }
26416}
26417
26418impl RowRangeExt for Range<DisplayRow> {
26419 type Row = DisplayRow;
26420
26421 fn len(&self) -> usize {
26422 (self.end.0 - self.start.0) as usize
26423 }
26424
26425 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
26426 (self.start.0..self.end.0).map(DisplayRow)
26427 }
26428}
26429
26430/// If select range has more than one line, we
26431/// just point the cursor to range.start.
26432fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
26433 if range.start.row == range.end.row {
26434 range
26435 } else {
26436 range.start..range.start
26437 }
26438}
26439pub struct KillRing(ClipboardItem);
26440impl Global for KillRing {}
26441
26442const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
26443
26444enum BreakpointPromptEditAction {
26445 Log,
26446 Condition,
26447 HitCondition,
26448}
26449
26450struct BreakpointPromptEditor {
26451 pub(crate) prompt: Entity<Editor>,
26452 editor: WeakEntity<Editor>,
26453 breakpoint_anchor: Anchor,
26454 breakpoint: Breakpoint,
26455 edit_action: BreakpointPromptEditAction,
26456 block_ids: HashSet<CustomBlockId>,
26457 editor_margins: Arc<Mutex<EditorMargins>>,
26458 _subscriptions: Vec<Subscription>,
26459}
26460
26461impl BreakpointPromptEditor {
26462 const MAX_LINES: u8 = 4;
26463
26464 fn new(
26465 editor: WeakEntity<Editor>,
26466 breakpoint_anchor: Anchor,
26467 breakpoint: Breakpoint,
26468 edit_action: BreakpointPromptEditAction,
26469 window: &mut Window,
26470 cx: &mut Context<Self>,
26471 ) -> Self {
26472 let base_text = match edit_action {
26473 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
26474 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
26475 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
26476 }
26477 .map(|msg| msg.to_string())
26478 .unwrap_or_default();
26479
26480 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
26481 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26482
26483 let prompt = cx.new(|cx| {
26484 let mut prompt = Editor::new(
26485 EditorMode::AutoHeight {
26486 min_lines: 1,
26487 max_lines: Some(Self::MAX_LINES as usize),
26488 },
26489 buffer,
26490 None,
26491 window,
26492 cx,
26493 );
26494 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
26495 prompt.set_show_cursor_when_unfocused(false, cx);
26496 prompt.set_placeholder_text(
26497 match edit_action {
26498 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
26499 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
26500 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
26501 },
26502 window,
26503 cx,
26504 );
26505
26506 prompt
26507 });
26508
26509 Self {
26510 prompt,
26511 editor,
26512 breakpoint_anchor,
26513 breakpoint,
26514 edit_action,
26515 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
26516 block_ids: Default::default(),
26517 _subscriptions: vec![],
26518 }
26519 }
26520
26521 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
26522 self.block_ids.extend(block_ids)
26523 }
26524
26525 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
26526 if let Some(editor) = self.editor.upgrade() {
26527 let message = self
26528 .prompt
26529 .read(cx)
26530 .buffer
26531 .read(cx)
26532 .as_singleton()
26533 .expect("A multi buffer in breakpoint prompt isn't possible")
26534 .read(cx)
26535 .as_rope()
26536 .to_string();
26537
26538 editor.update(cx, |editor, cx| {
26539 editor.edit_breakpoint_at_anchor(
26540 self.breakpoint_anchor,
26541 self.breakpoint.clone(),
26542 match self.edit_action {
26543 BreakpointPromptEditAction::Log => {
26544 BreakpointEditAction::EditLogMessage(message.into())
26545 }
26546 BreakpointPromptEditAction::Condition => {
26547 BreakpointEditAction::EditCondition(message.into())
26548 }
26549 BreakpointPromptEditAction::HitCondition => {
26550 BreakpointEditAction::EditHitCondition(message.into())
26551 }
26552 },
26553 cx,
26554 );
26555
26556 editor.remove_blocks(self.block_ids.clone(), None, cx);
26557 cx.focus_self(window);
26558 });
26559 }
26560 }
26561
26562 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
26563 self.editor
26564 .update(cx, |editor, cx| {
26565 editor.remove_blocks(self.block_ids.clone(), None, cx);
26566 window.focus(&editor.focus_handle, cx);
26567 })
26568 .log_err();
26569 }
26570
26571 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
26572 let settings = ThemeSettings::get_global(cx);
26573 let text_style = TextStyle {
26574 color: if self.prompt.read(cx).read_only(cx) {
26575 cx.theme().colors().text_disabled
26576 } else {
26577 cx.theme().colors().text
26578 },
26579 font_family: settings.buffer_font.family.clone(),
26580 font_fallbacks: settings.buffer_font.fallbacks.clone(),
26581 font_size: settings.buffer_font_size(cx).into(),
26582 font_weight: settings.buffer_font.weight,
26583 line_height: relative(settings.buffer_line_height.value()),
26584 ..Default::default()
26585 };
26586 EditorElement::new(
26587 &self.prompt,
26588 EditorStyle {
26589 background: cx.theme().colors().editor_background,
26590 local_player: cx.theme().players().local(),
26591 text: text_style,
26592 ..Default::default()
26593 },
26594 )
26595 }
26596}
26597
26598impl Render for BreakpointPromptEditor {
26599 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26600 let editor_margins = *self.editor_margins.lock();
26601 let gutter_dimensions = editor_margins.gutter;
26602 h_flex()
26603 .key_context("Editor")
26604 .bg(cx.theme().colors().editor_background)
26605 .border_y_1()
26606 .border_color(cx.theme().status().info_border)
26607 .size_full()
26608 .py(window.line_height() / 2.5)
26609 .on_action(cx.listener(Self::confirm))
26610 .on_action(cx.listener(Self::cancel))
26611 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
26612 .child(div().flex_1().child(self.render_prompt_editor(cx)))
26613 }
26614}
26615
26616impl Focusable for BreakpointPromptEditor {
26617 fn focus_handle(&self, cx: &App) -> FocusHandle {
26618 self.prompt.focus_handle(cx)
26619 }
26620}
26621
26622fn all_edits_insertions_or_deletions(
26623 edits: &Vec<(Range<Anchor>, Arc<str>)>,
26624 snapshot: &MultiBufferSnapshot,
26625) -> bool {
26626 let mut all_insertions = true;
26627 let mut all_deletions = true;
26628
26629 for (range, new_text) in edits.iter() {
26630 let range_is_empty = range.to_offset(snapshot).is_empty();
26631 let text_is_empty = new_text.is_empty();
26632
26633 if range_is_empty != text_is_empty {
26634 if range_is_empty {
26635 all_deletions = false;
26636 } else {
26637 all_insertions = false;
26638 }
26639 } else {
26640 return false;
26641 }
26642
26643 if !all_insertions && !all_deletions {
26644 return false;
26645 }
26646 }
26647 all_insertions || all_deletions
26648}
26649
26650struct MissingEditPredictionKeybindingTooltip;
26651
26652impl Render for MissingEditPredictionKeybindingTooltip {
26653 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26654 ui::tooltip_container(cx, |container, cx| {
26655 container
26656 .flex_shrink_0()
26657 .max_w_80()
26658 .min_h(rems_from_px(124.))
26659 .justify_between()
26660 .child(
26661 v_flex()
26662 .flex_1()
26663 .text_ui_sm(cx)
26664 .child(Label::new("Conflict with Accept Keybinding"))
26665 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26666 )
26667 .child(
26668 h_flex()
26669 .pb_1()
26670 .gap_1()
26671 .items_end()
26672 .w_full()
26673 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26674 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26675 }))
26676 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26677 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26678 })),
26679 )
26680 })
26681 }
26682}
26683
26684#[derive(Debug, Clone, Copy, PartialEq)]
26685pub struct LineHighlight {
26686 pub background: Background,
26687 pub border: Option<gpui::Hsla>,
26688 pub include_gutter: bool,
26689 pub type_id: Option<TypeId>,
26690}
26691
26692struct LineManipulationResult {
26693 pub new_text: String,
26694 pub line_count_before: usize,
26695 pub line_count_after: usize,
26696}
26697
26698fn render_diff_hunk_controls(
26699 row: u32,
26700 status: &DiffHunkStatus,
26701 hunk_range: Range<Anchor>,
26702 is_created_file: bool,
26703 line_height: Pixels,
26704 editor: &Entity<Editor>,
26705 _window: &mut Window,
26706 cx: &mut App,
26707) -> AnyElement {
26708 h_flex()
26709 .h(line_height)
26710 .mr_1()
26711 .gap_1()
26712 .px_0p5()
26713 .pb_1()
26714 .border_x_1()
26715 .border_b_1()
26716 .border_color(cx.theme().colors().border_variant)
26717 .rounded_b_lg()
26718 .bg(cx.theme().colors().editor_background)
26719 .gap_1()
26720 .block_mouse_except_scroll()
26721 .shadow_md()
26722 .child(if status.has_secondary_hunk() {
26723 Button::new(("stage", row as u64), "Stage")
26724 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26725 .tooltip({
26726 let focus_handle = editor.focus_handle(cx);
26727 move |_window, cx| {
26728 Tooltip::for_action_in(
26729 "Stage Hunk",
26730 &::git::ToggleStaged,
26731 &focus_handle,
26732 cx,
26733 )
26734 }
26735 })
26736 .on_click({
26737 let editor = editor.clone();
26738 move |_event, _window, cx| {
26739 editor.update(cx, |editor, cx| {
26740 editor.stage_or_unstage_diff_hunks(
26741 true,
26742 vec![hunk_range.start..hunk_range.start],
26743 cx,
26744 );
26745 });
26746 }
26747 })
26748 } else {
26749 Button::new(("unstage", row as u64), "Unstage")
26750 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26751 .tooltip({
26752 let focus_handle = editor.focus_handle(cx);
26753 move |_window, cx| {
26754 Tooltip::for_action_in(
26755 "Unstage Hunk",
26756 &::git::ToggleStaged,
26757 &focus_handle,
26758 cx,
26759 )
26760 }
26761 })
26762 .on_click({
26763 let editor = editor.clone();
26764 move |_event, _window, cx| {
26765 editor.update(cx, |editor, cx| {
26766 editor.stage_or_unstage_diff_hunks(
26767 false,
26768 vec![hunk_range.start..hunk_range.start],
26769 cx,
26770 );
26771 });
26772 }
26773 })
26774 })
26775 .child(
26776 Button::new(("restore", row as u64), "Restore")
26777 .tooltip({
26778 let focus_handle = editor.focus_handle(cx);
26779 move |_window, cx| {
26780 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26781 }
26782 })
26783 .on_click({
26784 let editor = editor.clone();
26785 move |_event, window, cx| {
26786 editor.update(cx, |editor, cx| {
26787 let snapshot = editor.snapshot(window, cx);
26788 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26789 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26790 });
26791 }
26792 })
26793 .disabled(is_created_file),
26794 )
26795 .when(
26796 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26797 |el| {
26798 el.child(
26799 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26800 .shape(IconButtonShape::Square)
26801 .icon_size(IconSize::Small)
26802 // .disabled(!has_multiple_hunks)
26803 .tooltip({
26804 let focus_handle = editor.focus_handle(cx);
26805 move |_window, cx| {
26806 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26807 }
26808 })
26809 .on_click({
26810 let editor = editor.clone();
26811 move |_event, window, cx| {
26812 editor.update(cx, |editor, cx| {
26813 let snapshot = editor.snapshot(window, cx);
26814 let position =
26815 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26816 editor.go_to_hunk_before_or_after_position(
26817 &snapshot,
26818 position,
26819 Direction::Next,
26820 window,
26821 cx,
26822 );
26823 editor.expand_selected_diff_hunks(cx);
26824 });
26825 }
26826 }),
26827 )
26828 .child(
26829 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26830 .shape(IconButtonShape::Square)
26831 .icon_size(IconSize::Small)
26832 // .disabled(!has_multiple_hunks)
26833 .tooltip({
26834 let focus_handle = editor.focus_handle(cx);
26835 move |_window, cx| {
26836 Tooltip::for_action_in(
26837 "Previous Hunk",
26838 &GoToPreviousHunk,
26839 &focus_handle,
26840 cx,
26841 )
26842 }
26843 })
26844 .on_click({
26845 let editor = editor.clone();
26846 move |_event, window, cx| {
26847 editor.update(cx, |editor, cx| {
26848 let snapshot = editor.snapshot(window, cx);
26849 let point =
26850 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26851 editor.go_to_hunk_before_or_after_position(
26852 &snapshot,
26853 point,
26854 Direction::Prev,
26855 window,
26856 cx,
26857 );
26858 editor.expand_selected_diff_hunks(cx);
26859 });
26860 }
26861 }),
26862 )
26863 },
26864 )
26865 .into_any_element()
26866}
26867
26868pub fn multibuffer_context_lines(cx: &App) -> u32 {
26869 EditorSettings::try_get(cx)
26870 .map(|settings| settings.excerpt_context_lines)
26871 .unwrap_or(2)
26872 .min(32)
26873}