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/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1025///
1026/// See the [module level documentation](self) for more information.
1027pub struct Editor {
1028 focus_handle: FocusHandle,
1029 last_focused_descendant: Option<WeakFocusHandle>,
1030 /// The text buffer being edited
1031 buffer: Entity<MultiBuffer>,
1032 /// Map of how text in the buffer should be displayed.
1033 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1034 pub display_map: Entity<DisplayMap>,
1035 placeholder_display_map: Option<Entity<DisplayMap>>,
1036 pub selections: SelectionsCollection,
1037 pub scroll_manager: ScrollManager,
1038 /// When inline assist editors are linked, they all render cursors because
1039 /// typing enters text into each of them, even the ones that aren't focused.
1040 pub(crate) show_cursor_when_unfocused: bool,
1041 columnar_selection_state: Option<ColumnarSelectionState>,
1042 add_selections_state: Option<AddSelectionsState>,
1043 select_next_state: Option<SelectNextState>,
1044 select_prev_state: Option<SelectNextState>,
1045 selection_history: SelectionHistory,
1046 defer_selection_effects: bool,
1047 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1048 autoclose_regions: Vec<AutocloseRegion>,
1049 snippet_stack: InvalidationStack<SnippetState>,
1050 select_syntax_node_history: SelectSyntaxNodeHistory,
1051 ime_transaction: Option<TransactionId>,
1052 pub diagnostics_max_severity: DiagnosticSeverity,
1053 active_diagnostics: ActiveDiagnostic,
1054 show_inline_diagnostics: bool,
1055 inline_diagnostics_update: Task<()>,
1056 inline_diagnostics_enabled: bool,
1057 diagnostics_enabled: bool,
1058 word_completions_enabled: bool,
1059 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1060 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1061 hard_wrap: Option<usize>,
1062 project: Option<Entity<Project>>,
1063 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1064 completion_provider: Option<Rc<dyn CompletionProvider>>,
1065 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1066 blink_manager: Entity<BlinkManager>,
1067 show_cursor_names: bool,
1068 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1069 pub show_local_selections: bool,
1070 mode: EditorMode,
1071 show_breadcrumbs: bool,
1072 show_gutter: bool,
1073 show_scrollbars: ScrollbarAxes,
1074 minimap_visibility: MinimapVisibility,
1075 offset_content: bool,
1076 disable_expand_excerpt_buttons: bool,
1077 delegate_expand_excerpts: bool,
1078 show_line_numbers: Option<bool>,
1079 use_relative_line_numbers: Option<bool>,
1080 show_git_diff_gutter: Option<bool>,
1081 show_code_actions: Option<bool>,
1082 show_runnables: Option<bool>,
1083 show_breakpoints: Option<bool>,
1084 show_wrap_guides: Option<bool>,
1085 show_indent_guides: Option<bool>,
1086 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1087 highlight_order: usize,
1088 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1089 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1090 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1091 scrollbar_marker_state: ScrollbarMarkerState,
1092 active_indent_guides_state: ActiveIndentGuidesState,
1093 nav_history: Option<ItemNavHistory>,
1094 context_menu: RefCell<Option<CodeContextMenu>>,
1095 context_menu_options: Option<ContextMenuOptions>,
1096 mouse_context_menu: Option<MouseContextMenu>,
1097 completion_tasks: Vec<(CompletionId, Task<()>)>,
1098 inline_blame_popover: Option<InlineBlamePopover>,
1099 inline_blame_popover_show_task: Option<Task<()>>,
1100 signature_help_state: SignatureHelpState,
1101 auto_signature_help: Option<bool>,
1102 find_all_references_task_sources: Vec<Anchor>,
1103 next_completion_id: CompletionId,
1104 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1105 code_actions_task: Option<Task<Result<()>>>,
1106 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1107 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1108 document_highlights_task: Option<Task<()>>,
1109 linked_editing_range_task: Option<Task<Option<()>>>,
1110 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1111 pending_rename: Option<RenameState>,
1112 searchable: bool,
1113 cursor_shape: CursorShape,
1114 /// Whether the cursor is offset one character to the left when something is
1115 /// selected (needed for vim visual mode)
1116 cursor_offset_on_selection: bool,
1117 current_line_highlight: Option<CurrentLineHighlight>,
1118 pub collapse_matches: bool,
1119 autoindent_mode: Option<AutoindentMode>,
1120 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1121 input_enabled: bool,
1122 use_modal_editing: bool,
1123 read_only: bool,
1124 leader_id: Option<CollaboratorId>,
1125 remote_id: Option<ViewId>,
1126 pub hover_state: HoverState,
1127 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1128 prev_pressure_stage: Option<PressureStage>,
1129 gutter_hovered: bool,
1130 hovered_link_state: Option<HoveredLinkState>,
1131 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1132 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1133 active_edit_prediction: Option<EditPredictionState>,
1134 /// Used to prevent flickering as the user types while the menu is open
1135 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1136 edit_prediction_settings: EditPredictionSettings,
1137 edit_predictions_hidden_for_vim_mode: bool,
1138 show_edit_predictions_override: Option<bool>,
1139 show_completions_on_input_override: Option<bool>,
1140 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1141 edit_prediction_preview: EditPredictionPreview,
1142 edit_prediction_indent_conflict: bool,
1143 edit_prediction_requires_modifier_in_indent_conflict: bool,
1144 next_inlay_id: usize,
1145 next_color_inlay_id: usize,
1146 _subscriptions: Vec<Subscription>,
1147 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1148 gutter_dimensions: GutterDimensions,
1149 style: Option<EditorStyle>,
1150 text_style_refinement: Option<TextStyleRefinement>,
1151 next_editor_action_id: EditorActionId,
1152 editor_actions: Rc<
1153 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1154 >,
1155 use_autoclose: bool,
1156 use_auto_surround: bool,
1157 auto_replace_emoji_shortcode: bool,
1158 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1159 show_git_blame_gutter: bool,
1160 show_git_blame_inline: bool,
1161 show_git_blame_inline_delay_task: Option<Task<()>>,
1162 git_blame_inline_enabled: bool,
1163 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1164 buffer_serialization: Option<BufferSerialization>,
1165 show_selection_menu: Option<bool>,
1166 blame: Option<Entity<GitBlame>>,
1167 blame_subscription: Option<Subscription>,
1168 custom_context_menu: Option<
1169 Box<
1170 dyn 'static
1171 + Fn(
1172 &mut Self,
1173 DisplayPoint,
1174 &mut Window,
1175 &mut Context<Self>,
1176 ) -> Option<Entity<ui::ContextMenu>>,
1177 >,
1178 >,
1179 last_bounds: Option<Bounds<Pixels>>,
1180 last_position_map: Option<Rc<PositionMap>>,
1181 expect_bounds_change: Option<Bounds<Pixels>>,
1182 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1183 tasks_update_task: Option<Task<()>>,
1184 breakpoint_store: Option<Entity<BreakpointStore>>,
1185 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1186 hovered_diff_hunk_row: Option<DisplayRow>,
1187 pull_diagnostics_task: Task<()>,
1188 pull_diagnostics_background_task: Task<()>,
1189 in_project_search: bool,
1190 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1191 breadcrumb_header: Option<String>,
1192 focused_block: Option<FocusedBlock>,
1193 next_scroll_position: NextScrollCursorCenterTopBottom,
1194 addons: HashMap<TypeId, Box<dyn Addon>>,
1195 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1196 load_diff_task: Option<Shared<Task<()>>>,
1197 /// Whether we are temporarily displaying a diff other than git's
1198 temporary_diff_override: bool,
1199 selection_mark_mode: bool,
1200 toggle_fold_multiple_buffers: Task<()>,
1201 _scroll_cursor_center_top_bottom_task: Task<()>,
1202 serialize_selections: Task<()>,
1203 serialize_folds: Task<()>,
1204 mouse_cursor_hidden: bool,
1205 minimap: Option<Entity<Self>>,
1206 hide_mouse_mode: HideMouseMode,
1207 pub change_list: ChangeList,
1208 inline_value_cache: InlineValueCache,
1209 number_deleted_lines: bool,
1210
1211 selection_drag_state: SelectionDragState,
1212 colors: Option<LspColorData>,
1213 post_scroll_update: Task<()>,
1214 refresh_colors_task: Task<()>,
1215 inlay_hints: Option<LspInlayHintData>,
1216 folding_newlines: Task<()>,
1217 select_next_is_case_sensitive: Option<bool>,
1218 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1219 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1220 accent_data: Option<AccentData>,
1221 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1222}
1223
1224#[derive(Debug, PartialEq)]
1225struct AccentData {
1226 colors: AccentColors,
1227 overrides: Vec<SharedString>,
1228}
1229
1230fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1231 if debounce_ms > 0 {
1232 Some(Duration::from_millis(debounce_ms))
1233 } else {
1234 None
1235 }
1236}
1237
1238#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1239enum NextScrollCursorCenterTopBottom {
1240 #[default]
1241 Center,
1242 Top,
1243 Bottom,
1244}
1245
1246impl NextScrollCursorCenterTopBottom {
1247 fn next(&self) -> Self {
1248 match self {
1249 Self::Center => Self::Top,
1250 Self::Top => Self::Bottom,
1251 Self::Bottom => Self::Center,
1252 }
1253 }
1254}
1255
1256#[derive(Clone)]
1257pub struct EditorSnapshot {
1258 pub mode: EditorMode,
1259 show_gutter: bool,
1260 offset_content: bool,
1261 show_line_numbers: Option<bool>,
1262 number_deleted_lines: bool,
1263 show_git_diff_gutter: Option<bool>,
1264 show_code_actions: Option<bool>,
1265 show_runnables: Option<bool>,
1266 show_breakpoints: Option<bool>,
1267 git_blame_gutter_max_author_length: Option<usize>,
1268 pub display_snapshot: DisplaySnapshot,
1269 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1270 is_focused: bool,
1271 scroll_anchor: ScrollAnchor,
1272 ongoing_scroll: OngoingScroll,
1273 current_line_highlight: CurrentLineHighlight,
1274 gutter_hovered: bool,
1275}
1276
1277#[derive(Default, Debug, Clone, Copy)]
1278pub struct GutterDimensions {
1279 pub left_padding: Pixels,
1280 pub right_padding: Pixels,
1281 pub width: Pixels,
1282 pub margin: Pixels,
1283 pub git_blame_entries_width: Option<Pixels>,
1284}
1285
1286impl GutterDimensions {
1287 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1288 Self {
1289 margin: Self::default_gutter_margin(font_id, font_size, cx),
1290 ..Default::default()
1291 }
1292 }
1293
1294 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1295 -cx.text_system().descent(font_id, font_size)
1296 }
1297 /// The full width of the space taken up by the gutter.
1298 pub fn full_width(&self) -> Pixels {
1299 self.margin + self.width
1300 }
1301
1302 /// The width of the space reserved for the fold indicators,
1303 /// use alongside 'justify_end' and `gutter_width` to
1304 /// right align content with the line numbers
1305 pub fn fold_area_width(&self) -> Pixels {
1306 self.margin + self.right_padding
1307 }
1308}
1309
1310struct CharacterDimensions {
1311 em_width: Pixels,
1312 em_advance: Pixels,
1313 line_height: Pixels,
1314}
1315
1316#[derive(Debug)]
1317pub struct RemoteSelection {
1318 pub replica_id: ReplicaId,
1319 pub selection: Selection<Anchor>,
1320 pub cursor_shape: CursorShape,
1321 pub collaborator_id: CollaboratorId,
1322 pub line_mode: bool,
1323 pub user_name: Option<SharedString>,
1324 pub color: PlayerColor,
1325}
1326
1327#[derive(Clone, Debug)]
1328struct SelectionHistoryEntry {
1329 selections: Arc<[Selection<Anchor>]>,
1330 select_next_state: Option<SelectNextState>,
1331 select_prev_state: Option<SelectNextState>,
1332 add_selections_state: Option<AddSelectionsState>,
1333}
1334
1335#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1336enum SelectionHistoryMode {
1337 #[default]
1338 Normal,
1339 Undoing,
1340 Redoing,
1341 Skipping,
1342}
1343
1344#[derive(Clone, PartialEq, Eq, Hash)]
1345struct HoveredCursor {
1346 replica_id: ReplicaId,
1347 selection_id: usize,
1348}
1349
1350#[derive(Debug)]
1351/// SelectionEffects controls the side-effects of updating the selection.
1352///
1353/// The default behaviour does "what you mostly want":
1354/// - it pushes to the nav history if the cursor moved by >10 lines
1355/// - it re-triggers completion requests
1356/// - it scrolls to fit
1357///
1358/// You might want to modify these behaviours. For example when doing a "jump"
1359/// like go to definition, we always want to add to nav history; but when scrolling
1360/// in vim mode we never do.
1361///
1362/// Similarly, you might want to disable scrolling if you don't want the viewport to
1363/// move.
1364#[derive(Clone)]
1365pub struct SelectionEffects {
1366 nav_history: Option<bool>,
1367 completions: bool,
1368 scroll: Option<Autoscroll>,
1369}
1370
1371impl Default for SelectionEffects {
1372 fn default() -> Self {
1373 Self {
1374 nav_history: None,
1375 completions: true,
1376 scroll: Some(Autoscroll::fit()),
1377 }
1378 }
1379}
1380impl SelectionEffects {
1381 pub fn scroll(scroll: Autoscroll) -> Self {
1382 Self {
1383 scroll: Some(scroll),
1384 ..Default::default()
1385 }
1386 }
1387
1388 pub fn no_scroll() -> Self {
1389 Self {
1390 scroll: None,
1391 ..Default::default()
1392 }
1393 }
1394
1395 pub fn completions(self, completions: bool) -> Self {
1396 Self {
1397 completions,
1398 ..self
1399 }
1400 }
1401
1402 pub fn nav_history(self, nav_history: bool) -> Self {
1403 Self {
1404 nav_history: Some(nav_history),
1405 ..self
1406 }
1407 }
1408}
1409
1410struct DeferredSelectionEffectsState {
1411 changed: bool,
1412 effects: SelectionEffects,
1413 old_cursor_position: Anchor,
1414 history_entry: SelectionHistoryEntry,
1415}
1416
1417#[derive(Default)]
1418struct SelectionHistory {
1419 #[allow(clippy::type_complexity)]
1420 selections_by_transaction:
1421 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1422 mode: SelectionHistoryMode,
1423 undo_stack: VecDeque<SelectionHistoryEntry>,
1424 redo_stack: VecDeque<SelectionHistoryEntry>,
1425}
1426
1427impl SelectionHistory {
1428 #[track_caller]
1429 fn insert_transaction(
1430 &mut self,
1431 transaction_id: TransactionId,
1432 selections: Arc<[Selection<Anchor>]>,
1433 ) {
1434 if selections.is_empty() {
1435 log::error!(
1436 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1437 std::panic::Location::caller()
1438 );
1439 return;
1440 }
1441 self.selections_by_transaction
1442 .insert(transaction_id, (selections, None));
1443 }
1444
1445 #[allow(clippy::type_complexity)]
1446 fn transaction(
1447 &self,
1448 transaction_id: TransactionId,
1449 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1450 self.selections_by_transaction.get(&transaction_id)
1451 }
1452
1453 #[allow(clippy::type_complexity)]
1454 fn transaction_mut(
1455 &mut self,
1456 transaction_id: TransactionId,
1457 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1458 self.selections_by_transaction.get_mut(&transaction_id)
1459 }
1460
1461 fn push(&mut self, entry: SelectionHistoryEntry) {
1462 if !entry.selections.is_empty() {
1463 match self.mode {
1464 SelectionHistoryMode::Normal => {
1465 self.push_undo(entry);
1466 self.redo_stack.clear();
1467 }
1468 SelectionHistoryMode::Undoing => self.push_redo(entry),
1469 SelectionHistoryMode::Redoing => self.push_undo(entry),
1470 SelectionHistoryMode::Skipping => {}
1471 }
1472 }
1473 }
1474
1475 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1476 if self
1477 .undo_stack
1478 .back()
1479 .is_none_or(|e| e.selections != entry.selections)
1480 {
1481 self.undo_stack.push_back(entry);
1482 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1483 self.undo_stack.pop_front();
1484 }
1485 }
1486 }
1487
1488 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1489 if self
1490 .redo_stack
1491 .back()
1492 .is_none_or(|e| e.selections != entry.selections)
1493 {
1494 self.redo_stack.push_back(entry);
1495 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1496 self.redo_stack.pop_front();
1497 }
1498 }
1499 }
1500}
1501
1502#[derive(Clone, Copy)]
1503pub struct RowHighlightOptions {
1504 pub autoscroll: bool,
1505 pub include_gutter: bool,
1506}
1507
1508impl Default for RowHighlightOptions {
1509 fn default() -> Self {
1510 Self {
1511 autoscroll: Default::default(),
1512 include_gutter: true,
1513 }
1514 }
1515}
1516
1517struct RowHighlight {
1518 index: usize,
1519 range: Range<Anchor>,
1520 color: Hsla,
1521 options: RowHighlightOptions,
1522 type_id: TypeId,
1523}
1524
1525#[derive(Clone, Debug)]
1526struct AddSelectionsState {
1527 groups: Vec<AddSelectionsGroup>,
1528}
1529
1530#[derive(Clone, Debug)]
1531struct AddSelectionsGroup {
1532 above: bool,
1533 stack: Vec<usize>,
1534}
1535
1536#[derive(Clone)]
1537struct SelectNextState {
1538 query: AhoCorasick,
1539 wordwise: bool,
1540 done: bool,
1541}
1542
1543impl std::fmt::Debug for SelectNextState {
1544 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1545 f.debug_struct(std::any::type_name::<Self>())
1546 .field("wordwise", &self.wordwise)
1547 .field("done", &self.done)
1548 .finish()
1549 }
1550}
1551
1552#[derive(Debug)]
1553struct AutocloseRegion {
1554 selection_id: usize,
1555 range: Range<Anchor>,
1556 pair: BracketPair,
1557}
1558
1559#[derive(Debug)]
1560struct SnippetState {
1561 ranges: Vec<Vec<Range<Anchor>>>,
1562 active_index: usize,
1563 choices: Vec<Option<Vec<String>>>,
1564}
1565
1566#[doc(hidden)]
1567pub struct RenameState {
1568 pub range: Range<Anchor>,
1569 pub old_name: Arc<str>,
1570 pub editor: Entity<Editor>,
1571 block_id: CustomBlockId,
1572}
1573
1574struct InvalidationStack<T>(Vec<T>);
1575
1576struct RegisteredEditPredictionDelegate {
1577 provider: Arc<dyn EditPredictionDelegateHandle>,
1578 _subscription: Subscription,
1579}
1580
1581#[derive(Debug, PartialEq, Eq)]
1582pub struct ActiveDiagnosticGroup {
1583 pub active_range: Range<Anchor>,
1584 pub active_message: String,
1585 pub group_id: usize,
1586 pub blocks: HashSet<CustomBlockId>,
1587}
1588
1589#[derive(Debug, PartialEq, Eq)]
1590
1591pub(crate) enum ActiveDiagnostic {
1592 None,
1593 All,
1594 Group(ActiveDiagnosticGroup),
1595}
1596
1597#[derive(Serialize, Deserialize, Clone, Debug)]
1598pub struct ClipboardSelection {
1599 /// The number of bytes in this selection.
1600 pub len: usize,
1601 /// Whether this was a full-line selection.
1602 pub is_entire_line: bool,
1603 /// The indentation of the first line when this content was originally copied.
1604 pub first_line_indent: u32,
1605 #[serde(default)]
1606 pub file_path: Option<PathBuf>,
1607 #[serde(default)]
1608 pub line_range: Option<RangeInclusive<u32>>,
1609}
1610
1611impl ClipboardSelection {
1612 pub fn for_buffer(
1613 len: usize,
1614 is_entire_line: bool,
1615 range: Range<Point>,
1616 buffer: &MultiBufferSnapshot,
1617 project: Option<&Entity<Project>>,
1618 cx: &App,
1619 ) -> Self {
1620 let first_line_indent = buffer
1621 .indent_size_for_line(MultiBufferRow(range.start.row))
1622 .len;
1623
1624 let file_path = util::maybe!({
1625 let project = project?.read(cx);
1626 let file = buffer.file_at(range.start)?;
1627 let project_path = ProjectPath {
1628 worktree_id: file.worktree_id(cx),
1629 path: file.path().clone(),
1630 };
1631 project.absolute_path(&project_path, cx)
1632 });
1633
1634 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1635
1636 Self {
1637 len,
1638 is_entire_line,
1639 first_line_indent,
1640 file_path,
1641 line_range,
1642 }
1643 }
1644}
1645
1646// selections, scroll behavior, was newest selection reversed
1647type SelectSyntaxNodeHistoryState = (
1648 Box<[Selection<MultiBufferOffset>]>,
1649 SelectSyntaxNodeScrollBehavior,
1650 bool,
1651);
1652
1653#[derive(Default)]
1654struct SelectSyntaxNodeHistory {
1655 stack: Vec<SelectSyntaxNodeHistoryState>,
1656 // disable temporarily to allow changing selections without losing the stack
1657 pub disable_clearing: bool,
1658}
1659
1660impl SelectSyntaxNodeHistory {
1661 pub fn try_clear(&mut self) {
1662 if !self.disable_clearing {
1663 self.stack.clear();
1664 }
1665 }
1666
1667 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1668 self.stack.push(selection);
1669 }
1670
1671 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1672 self.stack.pop()
1673 }
1674}
1675
1676enum SelectSyntaxNodeScrollBehavior {
1677 CursorTop,
1678 FitSelection,
1679 CursorBottom,
1680}
1681
1682#[derive(Debug)]
1683pub(crate) struct NavigationData {
1684 cursor_anchor: Anchor,
1685 cursor_position: Point,
1686 scroll_anchor: ScrollAnchor,
1687 scroll_top_row: u32,
1688}
1689
1690#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1691pub enum GotoDefinitionKind {
1692 Symbol,
1693 Declaration,
1694 Type,
1695 Implementation,
1696}
1697
1698pub enum FormatTarget {
1699 Buffers(HashSet<Entity<Buffer>>),
1700 Ranges(Vec<Range<MultiBufferPoint>>),
1701}
1702
1703pub(crate) struct FocusedBlock {
1704 id: BlockId,
1705 focus_handle: WeakFocusHandle,
1706}
1707
1708#[derive(Clone, Debug)]
1709enum JumpData {
1710 MultiBufferRow {
1711 row: MultiBufferRow,
1712 line_offset_from_top: u32,
1713 },
1714 MultiBufferPoint {
1715 excerpt_id: ExcerptId,
1716 position: Point,
1717 anchor: text::Anchor,
1718 line_offset_from_top: u32,
1719 },
1720}
1721
1722pub enum MultibufferSelectionMode {
1723 First,
1724 All,
1725}
1726
1727#[derive(Clone, Copy, Debug, Default)]
1728pub struct RewrapOptions {
1729 pub override_language_settings: bool,
1730 pub preserve_existing_whitespace: bool,
1731}
1732
1733impl Editor {
1734 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1735 let buffer = cx.new(|cx| Buffer::local("", cx));
1736 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1737 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1738 }
1739
1740 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1741 let buffer = cx.new(|cx| Buffer::local("", cx));
1742 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1743 Self::new(EditorMode::full(), buffer, None, window, cx)
1744 }
1745
1746 pub fn auto_height(
1747 min_lines: usize,
1748 max_lines: usize,
1749 window: &mut Window,
1750 cx: &mut Context<Self>,
1751 ) -> Self {
1752 let buffer = cx.new(|cx| Buffer::local("", cx));
1753 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1754 Self::new(
1755 EditorMode::AutoHeight {
1756 min_lines,
1757 max_lines: Some(max_lines),
1758 },
1759 buffer,
1760 None,
1761 window,
1762 cx,
1763 )
1764 }
1765
1766 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1767 /// The editor grows as tall as needed to fit its content.
1768 pub fn auto_height_unbounded(
1769 min_lines: usize,
1770 window: &mut Window,
1771 cx: &mut Context<Self>,
1772 ) -> Self {
1773 let buffer = cx.new(|cx| Buffer::local("", cx));
1774 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1775 Self::new(
1776 EditorMode::AutoHeight {
1777 min_lines,
1778 max_lines: None,
1779 },
1780 buffer,
1781 None,
1782 window,
1783 cx,
1784 )
1785 }
1786
1787 pub fn for_buffer(
1788 buffer: Entity<Buffer>,
1789 project: Option<Entity<Project>>,
1790 window: &mut Window,
1791 cx: &mut Context<Self>,
1792 ) -> Self {
1793 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1794 Self::new(EditorMode::full(), buffer, project, window, cx)
1795 }
1796
1797 pub fn for_multibuffer(
1798 buffer: Entity<MultiBuffer>,
1799 project: Option<Entity<Project>>,
1800 window: &mut Window,
1801 cx: &mut Context<Self>,
1802 ) -> Self {
1803 Self::new(EditorMode::full(), buffer, project, window, cx)
1804 }
1805
1806 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1807 let mut clone = Self::new(
1808 self.mode.clone(),
1809 self.buffer.clone(),
1810 self.project.clone(),
1811 window,
1812 cx,
1813 );
1814 self.display_map.update(cx, |display_map, cx| {
1815 let snapshot = display_map.snapshot(cx);
1816 clone.display_map.update(cx, |display_map, cx| {
1817 display_map.set_state(&snapshot, cx);
1818 });
1819 });
1820 clone.folds_did_change(cx);
1821 clone.selections.clone_state(&self.selections);
1822 clone.scroll_manager.clone_state(&self.scroll_manager);
1823 clone.searchable = self.searchable;
1824 clone.read_only = self.read_only;
1825 clone
1826 }
1827
1828 pub fn new(
1829 mode: EditorMode,
1830 buffer: Entity<MultiBuffer>,
1831 project: Option<Entity<Project>>,
1832 window: &mut Window,
1833 cx: &mut Context<Self>,
1834 ) -> Self {
1835 Editor::new_internal(mode, buffer, project, None, window, cx)
1836 }
1837
1838 pub fn sticky_headers(
1839 &self,
1840 style: &EditorStyle,
1841 cx: &App,
1842 ) -> Option<Vec<OutlineItem<Anchor>>> {
1843 let multi_buffer = self.buffer().read(cx);
1844 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1845 let multi_buffer_visible_start = self
1846 .scroll_manager
1847 .anchor()
1848 .anchor
1849 .to_point(&multi_buffer_snapshot);
1850 let max_row = multi_buffer_snapshot.max_point().row;
1851
1852 let start_row = (multi_buffer_visible_start.row).min(max_row);
1853 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1854
1855 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1856 let outline_items = buffer
1857 .outline_items_containing(
1858 Point::new(start_row, 0)..Point::new(end_row, 0),
1859 true,
1860 Some(style.syntax.as_ref()),
1861 )
1862 .into_iter()
1863 .map(|outline_item| OutlineItem {
1864 depth: outline_item.depth,
1865 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1866 source_range_for_text: Anchor::range_in_buffer(
1867 *excerpt_id,
1868 outline_item.source_range_for_text,
1869 ),
1870 text: outline_item.text,
1871 highlight_ranges: outline_item.highlight_ranges,
1872 name_ranges: outline_item.name_ranges,
1873 body_range: outline_item
1874 .body_range
1875 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1876 annotation_range: outline_item
1877 .annotation_range
1878 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1879 });
1880 return Some(outline_items.collect());
1881 }
1882
1883 None
1884 }
1885
1886 fn new_internal(
1887 mode: EditorMode,
1888 multi_buffer: Entity<MultiBuffer>,
1889 project: Option<Entity<Project>>,
1890 display_map: Option<Entity<DisplayMap>>,
1891 window: &mut Window,
1892 cx: &mut Context<Self>,
1893 ) -> Self {
1894 debug_assert!(
1895 display_map.is_none() || mode.is_minimap(),
1896 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1897 );
1898
1899 let full_mode = mode.is_full();
1900 let is_minimap = mode.is_minimap();
1901 let diagnostics_max_severity = if full_mode {
1902 EditorSettings::get_global(cx)
1903 .diagnostics_max_severity
1904 .unwrap_or(DiagnosticSeverity::Hint)
1905 } else {
1906 DiagnosticSeverity::Off
1907 };
1908 let style = window.text_style();
1909 let font_size = style.font_size.to_pixels(window.rem_size());
1910 let editor = cx.entity().downgrade();
1911 let fold_placeholder = FoldPlaceholder {
1912 constrain_width: false,
1913 render: Arc::new(move |fold_id, fold_range, cx| {
1914 let editor = editor.clone();
1915 div()
1916 .id(fold_id)
1917 .bg(cx.theme().colors().ghost_element_background)
1918 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1919 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1920 .rounded_xs()
1921 .size_full()
1922 .cursor_pointer()
1923 .child("⋯")
1924 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1925 .on_click(move |_, _window, cx| {
1926 editor
1927 .update(cx, |editor, cx| {
1928 editor.unfold_ranges(
1929 &[fold_range.start..fold_range.end],
1930 true,
1931 false,
1932 cx,
1933 );
1934 cx.stop_propagation();
1935 })
1936 .ok();
1937 })
1938 .into_any()
1939 }),
1940 merge_adjacent: true,
1941 ..FoldPlaceholder::default()
1942 };
1943 let display_map = display_map.unwrap_or_else(|| {
1944 cx.new(|cx| {
1945 DisplayMap::new(
1946 multi_buffer.clone(),
1947 style.font(),
1948 font_size,
1949 None,
1950 FILE_HEADER_HEIGHT,
1951 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1952 fold_placeholder,
1953 diagnostics_max_severity,
1954 cx,
1955 )
1956 })
1957 });
1958
1959 let selections = SelectionsCollection::new();
1960
1961 let blink_manager = cx.new(|cx| {
1962 let mut blink_manager = BlinkManager::new(
1963 CURSOR_BLINK_INTERVAL,
1964 |cx| EditorSettings::get_global(cx).cursor_blink,
1965 cx,
1966 );
1967 if is_minimap {
1968 blink_manager.disable(cx);
1969 }
1970 blink_manager
1971 });
1972
1973 let soft_wrap_mode_override =
1974 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1975
1976 let mut project_subscriptions = Vec::new();
1977 if full_mode && let Some(project) = project.as_ref() {
1978 project_subscriptions.push(cx.subscribe_in(
1979 project,
1980 window,
1981 |editor, _, event, window, cx| match event {
1982 project::Event::RefreshCodeLens => {
1983 // we always query lens with actions, without storing them, always refreshing them
1984 }
1985 project::Event::RefreshInlayHints {
1986 server_id,
1987 request_id,
1988 } => {
1989 editor.refresh_inlay_hints(
1990 InlayHintRefreshReason::RefreshRequested {
1991 server_id: *server_id,
1992 request_id: *request_id,
1993 },
1994 cx,
1995 );
1996 }
1997 project::Event::LanguageServerRemoved(..) => {
1998 if editor.tasks_update_task.is_none() {
1999 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2000 }
2001 editor.registered_buffers.clear();
2002 editor.register_visible_buffers(cx);
2003 }
2004 project::Event::LanguageServerAdded(..) => {
2005 if editor.tasks_update_task.is_none() {
2006 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2007 }
2008 }
2009 project::Event::SnippetEdit(id, snippet_edits) => {
2010 // todo(lw): Non singletons
2011 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2012 let snapshot = buffer.read(cx).snapshot();
2013 let focus_handle = editor.focus_handle(cx);
2014 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2015 for (range, snippet) in snippet_edits {
2016 let buffer_range =
2017 language::range_from_lsp(*range).to_offset(&snapshot);
2018 editor
2019 .insert_snippet(
2020 &[MultiBufferOffset(buffer_range.start)
2021 ..MultiBufferOffset(buffer_range.end)],
2022 snippet.clone(),
2023 window,
2024 cx,
2025 )
2026 .ok();
2027 }
2028 }
2029 }
2030 }
2031 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2032 let buffer_id = *buffer_id;
2033 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2034 editor.register_buffer(buffer_id, cx);
2035 editor.update_lsp_data(Some(buffer_id), window, cx);
2036 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2037 refresh_linked_ranges(editor, window, cx);
2038 editor.refresh_code_actions(window, cx);
2039 editor.refresh_document_highlights(cx);
2040 }
2041 }
2042
2043 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2044 let Some(workspace) = editor.workspace() else {
2045 return;
2046 };
2047 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2048 else {
2049 return;
2050 };
2051
2052 if active_editor.entity_id() == cx.entity_id() {
2053 let entity_id = cx.entity_id();
2054 workspace.update(cx, |this, cx| {
2055 this.panes_mut()
2056 .iter_mut()
2057 .filter(|pane| pane.entity_id() != entity_id)
2058 .for_each(|p| {
2059 p.update(cx, |pane, _| {
2060 pane.nav_history_mut().rename_item(
2061 entity_id,
2062 project_path.clone(),
2063 abs_path.clone().into(),
2064 );
2065 })
2066 });
2067 });
2068
2069 Self::open_transaction_for_hidden_buffers(
2070 workspace,
2071 transaction.clone(),
2072 "Rename".to_string(),
2073 window,
2074 cx,
2075 );
2076 }
2077 }
2078
2079 project::Event::WorkspaceEditApplied(transaction) => {
2080 let Some(workspace) = editor.workspace() else {
2081 return;
2082 };
2083 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2084 else {
2085 return;
2086 };
2087
2088 if active_editor.entity_id() == cx.entity_id() {
2089 Self::open_transaction_for_hidden_buffers(
2090 workspace,
2091 transaction.clone(),
2092 "LSP Edit".to_string(),
2093 window,
2094 cx,
2095 );
2096 }
2097 }
2098
2099 _ => {}
2100 },
2101 ));
2102 if let Some(task_inventory) = project
2103 .read(cx)
2104 .task_store()
2105 .read(cx)
2106 .task_inventory()
2107 .cloned()
2108 {
2109 project_subscriptions.push(cx.observe_in(
2110 &task_inventory,
2111 window,
2112 |editor, _, window, cx| {
2113 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2114 },
2115 ));
2116 };
2117
2118 project_subscriptions.push(cx.subscribe_in(
2119 &project.read(cx).breakpoint_store(),
2120 window,
2121 |editor, _, event, window, cx| match event {
2122 BreakpointStoreEvent::ClearDebugLines => {
2123 editor.clear_row_highlights::<ActiveDebugLine>();
2124 editor.refresh_inline_values(cx);
2125 }
2126 BreakpointStoreEvent::SetDebugLine => {
2127 if editor.go_to_active_debug_line(window, cx) {
2128 cx.stop_propagation();
2129 }
2130
2131 editor.refresh_inline_values(cx);
2132 }
2133 _ => {}
2134 },
2135 ));
2136 let git_store = project.read(cx).git_store().clone();
2137 let project = project.clone();
2138 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2139 if let GitStoreEvent::RepositoryAdded = event {
2140 this.load_diff_task = Some(
2141 update_uncommitted_diff_for_buffer(
2142 cx.entity(),
2143 &project,
2144 this.buffer.read(cx).all_buffers(),
2145 this.buffer.clone(),
2146 cx,
2147 )
2148 .shared(),
2149 );
2150 }
2151 }));
2152 }
2153
2154 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2155
2156 let inlay_hint_settings =
2157 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2158 let focus_handle = cx.focus_handle();
2159 if !is_minimap {
2160 cx.on_focus(&focus_handle, window, Self::handle_focus)
2161 .detach();
2162 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2163 .detach();
2164 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2165 .detach();
2166 cx.on_blur(&focus_handle, window, Self::handle_blur)
2167 .detach();
2168 cx.observe_pending_input(window, Self::observe_pending_input)
2169 .detach();
2170 }
2171
2172 let show_indent_guides =
2173 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2174 Some(false)
2175 } else {
2176 None
2177 };
2178
2179 let breakpoint_store = match (&mode, project.as_ref()) {
2180 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2181 _ => None,
2182 };
2183
2184 let mut code_action_providers = Vec::new();
2185 let mut load_uncommitted_diff = None;
2186 if let Some(project) = project.clone() {
2187 load_uncommitted_diff = Some(
2188 update_uncommitted_diff_for_buffer(
2189 cx.entity(),
2190 &project,
2191 multi_buffer.read(cx).all_buffers(),
2192 multi_buffer.clone(),
2193 cx,
2194 )
2195 .shared(),
2196 );
2197 code_action_providers.push(Rc::new(project) as Rc<_>);
2198 }
2199
2200 let mut editor = Self {
2201 focus_handle,
2202 show_cursor_when_unfocused: false,
2203 last_focused_descendant: None,
2204 buffer: multi_buffer.clone(),
2205 display_map: display_map.clone(),
2206 placeholder_display_map: None,
2207 selections,
2208 scroll_manager: ScrollManager::new(cx),
2209 columnar_selection_state: None,
2210 add_selections_state: None,
2211 select_next_state: None,
2212 select_prev_state: None,
2213 selection_history: SelectionHistory::default(),
2214 defer_selection_effects: false,
2215 deferred_selection_effects_state: None,
2216 autoclose_regions: Vec::new(),
2217 snippet_stack: InvalidationStack::default(),
2218 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2219 ime_transaction: None,
2220 active_diagnostics: ActiveDiagnostic::None,
2221 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2222 inline_diagnostics_update: Task::ready(()),
2223 inline_diagnostics: Vec::new(),
2224 soft_wrap_mode_override,
2225 diagnostics_max_severity,
2226 hard_wrap: None,
2227 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2228 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2229 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2230 project,
2231 blink_manager: blink_manager.clone(),
2232 show_local_selections: true,
2233 show_scrollbars: ScrollbarAxes {
2234 horizontal: full_mode,
2235 vertical: full_mode,
2236 },
2237 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2238 offset_content: !matches!(mode, EditorMode::SingleLine),
2239 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2240 show_gutter: full_mode,
2241 show_line_numbers: (!full_mode).then_some(false),
2242 use_relative_line_numbers: None,
2243 disable_expand_excerpt_buttons: !full_mode,
2244 delegate_expand_excerpts: false,
2245 show_git_diff_gutter: None,
2246 show_code_actions: None,
2247 show_runnables: None,
2248 show_breakpoints: None,
2249 show_wrap_guides: None,
2250 show_indent_guides,
2251 buffers_with_disabled_indent_guides: HashSet::default(),
2252 highlight_order: 0,
2253 highlighted_rows: HashMap::default(),
2254 background_highlights: HashMap::default(),
2255 gutter_highlights: HashMap::default(),
2256 scrollbar_marker_state: ScrollbarMarkerState::default(),
2257 active_indent_guides_state: ActiveIndentGuidesState::default(),
2258 nav_history: None,
2259 context_menu: RefCell::new(None),
2260 context_menu_options: None,
2261 mouse_context_menu: None,
2262 completion_tasks: Vec::new(),
2263 inline_blame_popover: None,
2264 inline_blame_popover_show_task: None,
2265 signature_help_state: SignatureHelpState::default(),
2266 auto_signature_help: None,
2267 find_all_references_task_sources: Vec::new(),
2268 next_completion_id: 0,
2269 next_inlay_id: 0,
2270 code_action_providers,
2271 available_code_actions: None,
2272 code_actions_task: None,
2273 quick_selection_highlight_task: None,
2274 debounced_selection_highlight_task: None,
2275 document_highlights_task: None,
2276 linked_editing_range_task: None,
2277 pending_rename: None,
2278 searchable: !is_minimap,
2279 cursor_shape: EditorSettings::get_global(cx)
2280 .cursor_shape
2281 .unwrap_or_default(),
2282 cursor_offset_on_selection: false,
2283 current_line_highlight: None,
2284 autoindent_mode: Some(AutoindentMode::EachLine),
2285 collapse_matches: false,
2286 workspace: None,
2287 input_enabled: !is_minimap,
2288 use_modal_editing: full_mode,
2289 read_only: is_minimap,
2290 use_autoclose: true,
2291 use_auto_surround: true,
2292 auto_replace_emoji_shortcode: false,
2293 jsx_tag_auto_close_enabled_in_any_buffer: false,
2294 leader_id: None,
2295 remote_id: None,
2296 hover_state: HoverState::default(),
2297 pending_mouse_down: None,
2298 prev_pressure_stage: None,
2299 hovered_link_state: None,
2300 edit_prediction_provider: None,
2301 active_edit_prediction: None,
2302 stale_edit_prediction_in_menu: None,
2303 edit_prediction_preview: EditPredictionPreview::Inactive {
2304 released_too_fast: false,
2305 },
2306 inline_diagnostics_enabled: full_mode,
2307 diagnostics_enabled: full_mode,
2308 word_completions_enabled: full_mode,
2309 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2310 gutter_hovered: false,
2311 pixel_position_of_newest_cursor: None,
2312 last_bounds: None,
2313 last_position_map: None,
2314 expect_bounds_change: None,
2315 gutter_dimensions: GutterDimensions::default(),
2316 style: None,
2317 show_cursor_names: false,
2318 hovered_cursors: HashMap::default(),
2319 next_editor_action_id: EditorActionId::default(),
2320 editor_actions: Rc::default(),
2321 edit_predictions_hidden_for_vim_mode: false,
2322 show_edit_predictions_override: None,
2323 show_completions_on_input_override: None,
2324 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2325 edit_prediction_settings: EditPredictionSettings::Disabled,
2326 edit_prediction_indent_conflict: false,
2327 edit_prediction_requires_modifier_in_indent_conflict: true,
2328 custom_context_menu: None,
2329 show_git_blame_gutter: false,
2330 show_git_blame_inline: false,
2331 show_selection_menu: None,
2332 show_git_blame_inline_delay_task: None,
2333 git_blame_inline_enabled: full_mode
2334 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2335 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2336 buffer_serialization: is_minimap.not().then(|| {
2337 BufferSerialization::new(
2338 ProjectSettings::get_global(cx)
2339 .session
2340 .restore_unsaved_buffers,
2341 )
2342 }),
2343 blame: None,
2344 blame_subscription: None,
2345 tasks: BTreeMap::default(),
2346
2347 breakpoint_store,
2348 gutter_breakpoint_indicator: (None, None),
2349 hovered_diff_hunk_row: None,
2350 _subscriptions: (!is_minimap)
2351 .then(|| {
2352 vec![
2353 cx.observe(&multi_buffer, Self::on_buffer_changed),
2354 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2355 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2356 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2357 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2358 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2359 cx.observe_window_activation(window, |editor, window, cx| {
2360 let active = window.is_window_active();
2361 editor.blink_manager.update(cx, |blink_manager, cx| {
2362 if active {
2363 blink_manager.enable(cx);
2364 } else {
2365 blink_manager.disable(cx);
2366 }
2367 });
2368 if active {
2369 editor.show_mouse_cursor(cx);
2370 }
2371 }),
2372 ]
2373 })
2374 .unwrap_or_default(),
2375 tasks_update_task: None,
2376 pull_diagnostics_task: Task::ready(()),
2377 pull_diagnostics_background_task: Task::ready(()),
2378 colors: None,
2379 refresh_colors_task: Task::ready(()),
2380 inlay_hints: None,
2381 next_color_inlay_id: 0,
2382 post_scroll_update: Task::ready(()),
2383 linked_edit_ranges: Default::default(),
2384 in_project_search: false,
2385 previous_search_ranges: None,
2386 breadcrumb_header: None,
2387 focused_block: None,
2388 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2389 addons: HashMap::default(),
2390 registered_buffers: HashMap::default(),
2391 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2392 selection_mark_mode: false,
2393 toggle_fold_multiple_buffers: Task::ready(()),
2394 serialize_selections: Task::ready(()),
2395 serialize_folds: Task::ready(()),
2396 text_style_refinement: None,
2397 load_diff_task: load_uncommitted_diff,
2398 temporary_diff_override: false,
2399 mouse_cursor_hidden: false,
2400 minimap: None,
2401 hide_mouse_mode: EditorSettings::get_global(cx)
2402 .hide_mouse
2403 .unwrap_or_default(),
2404 change_list: ChangeList::new(),
2405 mode,
2406 selection_drag_state: SelectionDragState::None,
2407 folding_newlines: Task::ready(()),
2408 lookup_key: None,
2409 select_next_is_case_sensitive: None,
2410 applicable_language_settings: HashMap::default(),
2411 accent_data: None,
2412 fetched_tree_sitter_chunks: HashMap::default(),
2413 number_deleted_lines: false,
2414 };
2415
2416 if is_minimap {
2417 return editor;
2418 }
2419
2420 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2421 editor.accent_data = editor.fetch_accent_data(cx);
2422
2423 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2424 editor
2425 ._subscriptions
2426 .push(cx.observe(breakpoints, |_, _, cx| {
2427 cx.notify();
2428 }));
2429 }
2430 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2431 editor._subscriptions.extend(project_subscriptions);
2432
2433 editor._subscriptions.push(cx.subscribe_in(
2434 &cx.entity(),
2435 window,
2436 |editor, _, e: &EditorEvent, window, cx| match e {
2437 EditorEvent::ScrollPositionChanged { local, .. } => {
2438 if *local {
2439 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2440 editor.inline_blame_popover.take();
2441 let new_anchor = editor.scroll_manager.anchor();
2442 let snapshot = editor.snapshot(window, cx);
2443 editor.update_restoration_data(cx, move |data| {
2444 data.scroll_position = (
2445 new_anchor.top_row(snapshot.buffer_snapshot()),
2446 new_anchor.offset,
2447 );
2448 });
2449
2450 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2451 cx.background_executor()
2452 .timer(Duration::from_millis(50))
2453 .await;
2454 editor
2455 .update_in(cx, |editor, window, cx| {
2456 editor.register_visible_buffers(cx);
2457 editor.refresh_colors_for_visible_range(None, window, cx);
2458 editor.refresh_inlay_hints(
2459 InlayHintRefreshReason::NewLinesShown,
2460 cx,
2461 );
2462 editor.colorize_brackets(false, cx);
2463 })
2464 .ok();
2465 });
2466 }
2467 }
2468 EditorEvent::Edited { .. } => {
2469 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2470 .map(|vim_mode| vim_mode.0)
2471 .unwrap_or(false);
2472 if !vim_mode {
2473 let display_map = editor.display_snapshot(cx);
2474 let selections = editor.selections.all_adjusted_display(&display_map);
2475 let pop_state = editor
2476 .change_list
2477 .last()
2478 .map(|previous| {
2479 previous.len() == selections.len()
2480 && previous.iter().enumerate().all(|(ix, p)| {
2481 p.to_display_point(&display_map).row()
2482 == selections[ix].head().row()
2483 })
2484 })
2485 .unwrap_or(false);
2486 let new_positions = selections
2487 .into_iter()
2488 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2489 .collect();
2490 editor
2491 .change_list
2492 .push_to_change_list(pop_state, new_positions);
2493 }
2494 }
2495 _ => (),
2496 },
2497 ));
2498
2499 if let Some(dap_store) = editor
2500 .project
2501 .as_ref()
2502 .map(|project| project.read(cx).dap_store())
2503 {
2504 let weak_editor = cx.weak_entity();
2505
2506 editor
2507 ._subscriptions
2508 .push(
2509 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2510 let session_entity = cx.entity();
2511 weak_editor
2512 .update(cx, |editor, cx| {
2513 editor._subscriptions.push(
2514 cx.subscribe(&session_entity, Self::on_debug_session_event),
2515 );
2516 })
2517 .ok();
2518 }),
2519 );
2520
2521 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2522 editor
2523 ._subscriptions
2524 .push(cx.subscribe(&session, Self::on_debug_session_event));
2525 }
2526 }
2527
2528 // skip adding the initial selection to selection history
2529 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2530 editor.end_selection(window, cx);
2531 editor.selection_history.mode = SelectionHistoryMode::Normal;
2532
2533 editor.scroll_manager.show_scrollbars(window, cx);
2534 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2535
2536 if full_mode {
2537 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2538 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2539
2540 if editor.git_blame_inline_enabled {
2541 editor.start_git_blame_inline(false, window, cx);
2542 }
2543
2544 editor.go_to_active_debug_line(window, cx);
2545
2546 editor.minimap =
2547 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2548 editor.colors = Some(LspColorData::new(cx));
2549 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2550
2551 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2552 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2553 }
2554 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2555 }
2556
2557 editor
2558 }
2559
2560 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2561 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2562 }
2563
2564 pub fn deploy_mouse_context_menu(
2565 &mut self,
2566 position: gpui::Point<Pixels>,
2567 context_menu: Entity<ContextMenu>,
2568 window: &mut Window,
2569 cx: &mut Context<Self>,
2570 ) {
2571 self.mouse_context_menu = Some(MouseContextMenu::new(
2572 self,
2573 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2574 context_menu,
2575 window,
2576 cx,
2577 ));
2578 }
2579
2580 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2581 self.mouse_context_menu
2582 .as_ref()
2583 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2584 }
2585
2586 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2587 if self
2588 .selections
2589 .pending_anchor()
2590 .is_some_and(|pending_selection| {
2591 let snapshot = self.buffer().read(cx).snapshot(cx);
2592 pending_selection.range().includes(range, &snapshot)
2593 })
2594 {
2595 return true;
2596 }
2597
2598 self.selections
2599 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2600 .into_iter()
2601 .any(|selection| {
2602 // This is needed to cover a corner case, if we just check for an existing
2603 // selection in the fold range, having a cursor at the start of the fold
2604 // marks it as selected. Non-empty selections don't cause this.
2605 let length = selection.end - selection.start;
2606 length > 0
2607 })
2608 }
2609
2610 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2611 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2612 }
2613
2614 fn key_context_internal(
2615 &self,
2616 has_active_edit_prediction: bool,
2617 window: &mut Window,
2618 cx: &mut App,
2619 ) -> KeyContext {
2620 let mut key_context = KeyContext::new_with_defaults();
2621 key_context.add("Editor");
2622 let mode = match self.mode {
2623 EditorMode::SingleLine => "single_line",
2624 EditorMode::AutoHeight { .. } => "auto_height",
2625 EditorMode::Minimap { .. } => "minimap",
2626 EditorMode::Full { .. } => "full",
2627 };
2628
2629 if EditorSettings::jupyter_enabled(cx) {
2630 key_context.add("jupyter");
2631 }
2632
2633 key_context.set("mode", mode);
2634 if self.pending_rename.is_some() {
2635 key_context.add("renaming");
2636 }
2637
2638 if let Some(snippet_stack) = self.snippet_stack.last() {
2639 key_context.add("in_snippet");
2640
2641 if snippet_stack.active_index > 0 {
2642 key_context.add("has_previous_tabstop");
2643 }
2644
2645 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2646 key_context.add("has_next_tabstop");
2647 }
2648 }
2649
2650 match self.context_menu.borrow().as_ref() {
2651 Some(CodeContextMenu::Completions(menu)) => {
2652 if menu.visible() {
2653 key_context.add("menu");
2654 key_context.add("showing_completions");
2655 }
2656 }
2657 Some(CodeContextMenu::CodeActions(menu)) => {
2658 if menu.visible() {
2659 key_context.add("menu");
2660 key_context.add("showing_code_actions")
2661 }
2662 }
2663 None => {}
2664 }
2665
2666 if self.signature_help_state.has_multiple_signatures() {
2667 key_context.add("showing_signature_help");
2668 }
2669
2670 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2671 if !self.focus_handle(cx).contains_focused(window, cx)
2672 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2673 {
2674 for addon in self.addons.values() {
2675 addon.extend_key_context(&mut key_context, cx)
2676 }
2677 }
2678
2679 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2680 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2681 Some(
2682 file.full_path(cx)
2683 .extension()?
2684 .to_string_lossy()
2685 .to_lowercase(),
2686 )
2687 }) {
2688 key_context.set("extension", extension);
2689 }
2690 } else {
2691 key_context.add("multibuffer");
2692 }
2693
2694 if has_active_edit_prediction {
2695 if self.edit_prediction_in_conflict() {
2696 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2697 } else {
2698 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2699 key_context.add("copilot_suggestion");
2700 }
2701 }
2702
2703 if self.selection_mark_mode {
2704 key_context.add("selection_mode");
2705 }
2706
2707 let disjoint = self.selections.disjoint_anchors();
2708 let snapshot = self.snapshot(window, cx);
2709 let snapshot = snapshot.buffer_snapshot();
2710 if self.mode == EditorMode::SingleLine
2711 && let [selection] = disjoint
2712 && selection.start == selection.end
2713 && selection.end.to_offset(snapshot) == snapshot.len()
2714 {
2715 key_context.add("end_of_input");
2716 }
2717
2718 if self.has_any_expanded_diff_hunks(cx) {
2719 key_context.add("diffs_expanded");
2720 }
2721
2722 key_context
2723 }
2724
2725 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2726 self.last_bounds.as_ref()
2727 }
2728
2729 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2730 if self.mouse_cursor_hidden {
2731 self.mouse_cursor_hidden = false;
2732 cx.notify();
2733 }
2734 }
2735
2736 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2737 let hide_mouse_cursor = match origin {
2738 HideMouseCursorOrigin::TypingAction => {
2739 matches!(
2740 self.hide_mouse_mode,
2741 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2742 )
2743 }
2744 HideMouseCursorOrigin::MovementAction => {
2745 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2746 }
2747 };
2748 if self.mouse_cursor_hidden != hide_mouse_cursor {
2749 self.mouse_cursor_hidden = hide_mouse_cursor;
2750 cx.notify();
2751 }
2752 }
2753
2754 pub fn edit_prediction_in_conflict(&self) -> bool {
2755 if !self.show_edit_predictions_in_menu() {
2756 return false;
2757 }
2758
2759 let showing_completions = self
2760 .context_menu
2761 .borrow()
2762 .as_ref()
2763 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2764
2765 showing_completions
2766 || self.edit_prediction_requires_modifier()
2767 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2768 // bindings to insert tab characters.
2769 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2770 }
2771
2772 pub fn accept_edit_prediction_keybind(
2773 &self,
2774 granularity: EditPredictionGranularity,
2775 window: &mut Window,
2776 cx: &mut App,
2777 ) -> AcceptEditPredictionBinding {
2778 let key_context = self.key_context_internal(true, window, cx);
2779 let in_conflict = self.edit_prediction_in_conflict();
2780
2781 let bindings =
2782 match granularity {
2783 EditPredictionGranularity::Word => window
2784 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2785 EditPredictionGranularity::Line => window
2786 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2787 EditPredictionGranularity::Full => {
2788 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2789 }
2790 };
2791
2792 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2793 !in_conflict
2794 || binding
2795 .keystrokes()
2796 .first()
2797 .is_some_and(|keystroke| keystroke.modifiers().modified())
2798 }))
2799 }
2800
2801 pub fn new_file(
2802 workspace: &mut Workspace,
2803 _: &workspace::NewFile,
2804 window: &mut Window,
2805 cx: &mut Context<Workspace>,
2806 ) {
2807 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2808 "Failed to create buffer",
2809 window,
2810 cx,
2811 |e, _, _| match e.error_code() {
2812 ErrorCode::RemoteUpgradeRequired => Some(format!(
2813 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2814 e.error_tag("required").unwrap_or("the latest version")
2815 )),
2816 _ => None,
2817 },
2818 );
2819 }
2820
2821 pub fn new_in_workspace(
2822 workspace: &mut Workspace,
2823 window: &mut Window,
2824 cx: &mut Context<Workspace>,
2825 ) -> Task<Result<Entity<Editor>>> {
2826 let project = workspace.project().clone();
2827 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2828
2829 cx.spawn_in(window, async move |workspace, cx| {
2830 let buffer = create.await?;
2831 workspace.update_in(cx, |workspace, window, cx| {
2832 let editor =
2833 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2834 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2835 editor
2836 })
2837 })
2838 }
2839
2840 fn new_file_vertical(
2841 workspace: &mut Workspace,
2842 _: &workspace::NewFileSplitVertical,
2843 window: &mut Window,
2844 cx: &mut Context<Workspace>,
2845 ) {
2846 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2847 }
2848
2849 fn new_file_horizontal(
2850 workspace: &mut Workspace,
2851 _: &workspace::NewFileSplitHorizontal,
2852 window: &mut Window,
2853 cx: &mut Context<Workspace>,
2854 ) {
2855 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2856 }
2857
2858 fn new_file_split(
2859 workspace: &mut Workspace,
2860 action: &workspace::NewFileSplit,
2861 window: &mut Window,
2862 cx: &mut Context<Workspace>,
2863 ) {
2864 Self::new_file_in_direction(workspace, action.0, window, cx)
2865 }
2866
2867 fn new_file_in_direction(
2868 workspace: &mut Workspace,
2869 direction: SplitDirection,
2870 window: &mut Window,
2871 cx: &mut Context<Workspace>,
2872 ) {
2873 let project = workspace.project().clone();
2874 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2875
2876 cx.spawn_in(window, async move |workspace, cx| {
2877 let buffer = create.await?;
2878 workspace.update_in(cx, move |workspace, window, cx| {
2879 workspace.split_item(
2880 direction,
2881 Box::new(
2882 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2883 ),
2884 window,
2885 cx,
2886 )
2887 })?;
2888 anyhow::Ok(())
2889 })
2890 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2891 match e.error_code() {
2892 ErrorCode::RemoteUpgradeRequired => Some(format!(
2893 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2894 e.error_tag("required").unwrap_or("the latest version")
2895 )),
2896 _ => None,
2897 }
2898 });
2899 }
2900
2901 pub fn leader_id(&self) -> Option<CollaboratorId> {
2902 self.leader_id
2903 }
2904
2905 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2906 &self.buffer
2907 }
2908
2909 pub fn project(&self) -> Option<&Entity<Project>> {
2910 self.project.as_ref()
2911 }
2912
2913 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2914 self.workspace.as_ref()?.0.upgrade()
2915 }
2916
2917 /// Returns the workspace serialization ID if this editor should be serialized.
2918 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2919 self.workspace
2920 .as_ref()
2921 .filter(|_| self.should_serialize_buffer())
2922 .and_then(|workspace| workspace.1)
2923 }
2924
2925 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2926 self.buffer().read(cx).title(cx)
2927 }
2928
2929 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2930 let git_blame_gutter_max_author_length = self
2931 .render_git_blame_gutter(cx)
2932 .then(|| {
2933 if let Some(blame) = self.blame.as_ref() {
2934 let max_author_length =
2935 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2936 Some(max_author_length)
2937 } else {
2938 None
2939 }
2940 })
2941 .flatten();
2942
2943 EditorSnapshot {
2944 mode: self.mode.clone(),
2945 show_gutter: self.show_gutter,
2946 offset_content: self.offset_content,
2947 show_line_numbers: self.show_line_numbers,
2948 number_deleted_lines: self.number_deleted_lines,
2949 show_git_diff_gutter: self.show_git_diff_gutter,
2950 show_code_actions: self.show_code_actions,
2951 show_runnables: self.show_runnables,
2952 show_breakpoints: self.show_breakpoints,
2953 git_blame_gutter_max_author_length,
2954 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2955 placeholder_display_snapshot: self
2956 .placeholder_display_map
2957 .as_ref()
2958 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2959 scroll_anchor: self.scroll_manager.anchor(),
2960 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2961 is_focused: self.focus_handle.is_focused(window),
2962 current_line_highlight: self
2963 .current_line_highlight
2964 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2965 gutter_hovered: self.gutter_hovered,
2966 }
2967 }
2968
2969 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2970 self.buffer.read(cx).language_at(point, cx)
2971 }
2972
2973 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2974 self.buffer.read(cx).read(cx).file_at(point).cloned()
2975 }
2976
2977 pub fn active_excerpt(
2978 &self,
2979 cx: &App,
2980 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2981 self.buffer
2982 .read(cx)
2983 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2984 }
2985
2986 pub fn mode(&self) -> &EditorMode {
2987 &self.mode
2988 }
2989
2990 pub fn set_mode(&mut self, mode: EditorMode) {
2991 self.mode = mode;
2992 }
2993
2994 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2995 self.collaboration_hub.as_deref()
2996 }
2997
2998 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2999 self.collaboration_hub = Some(hub);
3000 }
3001
3002 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3003 self.in_project_search = in_project_search;
3004 }
3005
3006 pub fn set_custom_context_menu(
3007 &mut self,
3008 f: impl 'static
3009 + Fn(
3010 &mut Self,
3011 DisplayPoint,
3012 &mut Window,
3013 &mut Context<Self>,
3014 ) -> Option<Entity<ui::ContextMenu>>,
3015 ) {
3016 self.custom_context_menu = Some(Box::new(f))
3017 }
3018
3019 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3020 self.completion_provider = provider;
3021 }
3022
3023 #[cfg(any(test, feature = "test-support"))]
3024 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3025 self.completion_provider.clone()
3026 }
3027
3028 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3029 self.semantics_provider.clone()
3030 }
3031
3032 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3033 self.semantics_provider = provider;
3034 }
3035
3036 pub fn set_edit_prediction_provider<T>(
3037 &mut self,
3038 provider: Option<Entity<T>>,
3039 window: &mut Window,
3040 cx: &mut Context<Self>,
3041 ) where
3042 T: EditPredictionDelegate,
3043 {
3044 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3045 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3046 if this.focus_handle.is_focused(window) {
3047 this.update_visible_edit_prediction(window, cx);
3048 }
3049 }),
3050 provider: Arc::new(provider),
3051 });
3052 self.update_edit_prediction_settings(cx);
3053 self.refresh_edit_prediction(false, false, window, cx);
3054 }
3055
3056 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3057 self.placeholder_display_map
3058 .as_ref()
3059 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3060 }
3061
3062 pub fn set_placeholder_text(
3063 &mut self,
3064 placeholder_text: &str,
3065 window: &mut Window,
3066 cx: &mut Context<Self>,
3067 ) {
3068 let multibuffer = cx
3069 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3070
3071 let style = window.text_style();
3072
3073 self.placeholder_display_map = Some(cx.new(|cx| {
3074 DisplayMap::new(
3075 multibuffer,
3076 style.font(),
3077 style.font_size.to_pixels(window.rem_size()),
3078 None,
3079 FILE_HEADER_HEIGHT,
3080 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3081 Default::default(),
3082 DiagnosticSeverity::Off,
3083 cx,
3084 )
3085 }));
3086 cx.notify();
3087 }
3088
3089 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3090 self.cursor_shape = cursor_shape;
3091
3092 // Disrupt blink for immediate user feedback that the cursor shape has changed
3093 self.blink_manager.update(cx, BlinkManager::show_cursor);
3094
3095 cx.notify();
3096 }
3097
3098 pub fn cursor_shape(&self) -> CursorShape {
3099 self.cursor_shape
3100 }
3101
3102 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3103 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3104 }
3105
3106 pub fn set_current_line_highlight(
3107 &mut self,
3108 current_line_highlight: Option<CurrentLineHighlight>,
3109 ) {
3110 self.current_line_highlight = current_line_highlight;
3111 }
3112
3113 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3114 self.collapse_matches = collapse_matches;
3115 }
3116
3117 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3118 if self.collapse_matches {
3119 return range.start..range.start;
3120 }
3121 range.clone()
3122 }
3123
3124 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3125 self.display_map.read(cx).clip_at_line_ends
3126 }
3127
3128 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3129 if self.display_map.read(cx).clip_at_line_ends != clip {
3130 self.display_map
3131 .update(cx, |map, _| map.clip_at_line_ends = clip);
3132 }
3133 }
3134
3135 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3136 self.input_enabled = input_enabled;
3137 }
3138
3139 pub fn set_edit_predictions_hidden_for_vim_mode(
3140 &mut self,
3141 hidden: bool,
3142 window: &mut Window,
3143 cx: &mut Context<Self>,
3144 ) {
3145 if hidden != self.edit_predictions_hidden_for_vim_mode {
3146 self.edit_predictions_hidden_for_vim_mode = hidden;
3147 if hidden {
3148 self.update_visible_edit_prediction(window, cx);
3149 } else {
3150 self.refresh_edit_prediction(true, false, window, cx);
3151 }
3152 }
3153 }
3154
3155 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3156 self.menu_edit_predictions_policy = value;
3157 }
3158
3159 pub fn set_autoindent(&mut self, autoindent: bool) {
3160 if autoindent {
3161 self.autoindent_mode = Some(AutoindentMode::EachLine);
3162 } else {
3163 self.autoindent_mode = None;
3164 }
3165 }
3166
3167 pub fn capability(&self, cx: &App) -> Capability {
3168 if self.read_only {
3169 Capability::ReadOnly
3170 } else {
3171 self.buffer.read(cx).capability()
3172 }
3173 }
3174
3175 pub fn read_only(&self, cx: &App) -> bool {
3176 self.read_only || self.buffer.read(cx).read_only()
3177 }
3178
3179 pub fn set_read_only(&mut self, read_only: bool) {
3180 self.read_only = read_only;
3181 }
3182
3183 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3184 self.use_autoclose = autoclose;
3185 }
3186
3187 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3188 self.use_auto_surround = auto_surround;
3189 }
3190
3191 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3192 self.auto_replace_emoji_shortcode = auto_replace;
3193 }
3194
3195 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3196 self.buffer_serialization = should_serialize.then(|| {
3197 BufferSerialization::new(
3198 ProjectSettings::get_global(cx)
3199 .session
3200 .restore_unsaved_buffers,
3201 )
3202 })
3203 }
3204
3205 fn should_serialize_buffer(&self) -> bool {
3206 self.buffer_serialization.is_some()
3207 }
3208
3209 pub fn toggle_edit_predictions(
3210 &mut self,
3211 _: &ToggleEditPrediction,
3212 window: &mut Window,
3213 cx: &mut Context<Self>,
3214 ) {
3215 if self.show_edit_predictions_override.is_some() {
3216 self.set_show_edit_predictions(None, window, cx);
3217 } else {
3218 let show_edit_predictions = !self.edit_predictions_enabled();
3219 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3220 }
3221 }
3222
3223 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3224 self.show_completions_on_input_override = show_completions_on_input;
3225 }
3226
3227 pub fn set_show_edit_predictions(
3228 &mut self,
3229 show_edit_predictions: Option<bool>,
3230 window: &mut Window,
3231 cx: &mut Context<Self>,
3232 ) {
3233 self.show_edit_predictions_override = show_edit_predictions;
3234 self.update_edit_prediction_settings(cx);
3235
3236 if let Some(false) = show_edit_predictions {
3237 self.discard_edit_prediction(false, cx);
3238 } else {
3239 self.refresh_edit_prediction(false, true, window, cx);
3240 }
3241 }
3242
3243 fn edit_predictions_disabled_in_scope(
3244 &self,
3245 buffer: &Entity<Buffer>,
3246 buffer_position: language::Anchor,
3247 cx: &App,
3248 ) -> bool {
3249 let snapshot = buffer.read(cx).snapshot();
3250 let settings = snapshot.settings_at(buffer_position, cx);
3251
3252 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3253 return false;
3254 };
3255
3256 scope.override_name().is_some_and(|scope_name| {
3257 settings
3258 .edit_predictions_disabled_in
3259 .iter()
3260 .any(|s| s == scope_name)
3261 })
3262 }
3263
3264 pub fn set_use_modal_editing(&mut self, to: bool) {
3265 self.use_modal_editing = to;
3266 }
3267
3268 pub fn use_modal_editing(&self) -> bool {
3269 self.use_modal_editing
3270 }
3271
3272 fn selections_did_change(
3273 &mut self,
3274 local: bool,
3275 old_cursor_position: &Anchor,
3276 effects: SelectionEffects,
3277 window: &mut Window,
3278 cx: &mut Context<Self>,
3279 ) {
3280 window.invalidate_character_coordinates();
3281
3282 // Copy selections to primary selection buffer
3283 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3284 if local {
3285 let selections = self
3286 .selections
3287 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3288 let buffer_handle = self.buffer.read(cx).read(cx);
3289
3290 let mut text = String::new();
3291 for (index, selection) in selections.iter().enumerate() {
3292 let text_for_selection = buffer_handle
3293 .text_for_range(selection.start..selection.end)
3294 .collect::<String>();
3295
3296 text.push_str(&text_for_selection);
3297 if index != selections.len() - 1 {
3298 text.push('\n');
3299 }
3300 }
3301
3302 if !text.is_empty() {
3303 cx.write_to_primary(ClipboardItem::new_string(text));
3304 }
3305 }
3306
3307 let selection_anchors = self.selections.disjoint_anchors_arc();
3308
3309 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3310 self.buffer.update(cx, |buffer, cx| {
3311 buffer.set_active_selections(
3312 &selection_anchors,
3313 self.selections.line_mode(),
3314 self.cursor_shape,
3315 cx,
3316 )
3317 });
3318 }
3319 let display_map = self
3320 .display_map
3321 .update(cx, |display_map, cx| display_map.snapshot(cx));
3322 let buffer = display_map.buffer_snapshot();
3323 if self.selections.count() == 1 {
3324 self.add_selections_state = None;
3325 }
3326 self.select_next_state = None;
3327 self.select_prev_state = None;
3328 self.select_syntax_node_history.try_clear();
3329 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3330 self.snippet_stack.invalidate(&selection_anchors, buffer);
3331 self.take_rename(false, window, cx);
3332
3333 let newest_selection = self.selections.newest_anchor();
3334 let new_cursor_position = newest_selection.head();
3335 let selection_start = newest_selection.start;
3336
3337 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3338 self.push_to_nav_history(
3339 *old_cursor_position,
3340 Some(new_cursor_position.to_point(buffer)),
3341 false,
3342 effects.nav_history == Some(true),
3343 cx,
3344 );
3345 }
3346
3347 if local {
3348 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3349 self.register_buffer(buffer_id, cx);
3350 }
3351
3352 let mut context_menu = self.context_menu.borrow_mut();
3353 let completion_menu = match context_menu.as_ref() {
3354 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3355 Some(CodeContextMenu::CodeActions(_)) => {
3356 *context_menu = None;
3357 None
3358 }
3359 None => None,
3360 };
3361 let completion_position = completion_menu.map(|menu| menu.initial_position);
3362 drop(context_menu);
3363
3364 if effects.completions
3365 && let Some(completion_position) = completion_position
3366 {
3367 let start_offset = selection_start.to_offset(buffer);
3368 let position_matches = start_offset == completion_position.to_offset(buffer);
3369 let continue_showing = if let Some((snap, ..)) =
3370 buffer.point_to_buffer_offset(completion_position)
3371 && !snap.capability.editable()
3372 {
3373 false
3374 } else if position_matches {
3375 if self.snippet_stack.is_empty() {
3376 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3377 == Some(CharKind::Word)
3378 } else {
3379 // Snippet choices can be shown even when the cursor is in whitespace.
3380 // Dismissing the menu with actions like backspace is handled by
3381 // invalidation regions.
3382 true
3383 }
3384 } else {
3385 false
3386 };
3387
3388 if continue_showing {
3389 self.open_or_update_completions_menu(None, None, false, window, cx);
3390 } else {
3391 self.hide_context_menu(window, cx);
3392 }
3393 }
3394
3395 hide_hover(self, cx);
3396
3397 if old_cursor_position.to_display_point(&display_map).row()
3398 != new_cursor_position.to_display_point(&display_map).row()
3399 {
3400 self.available_code_actions.take();
3401 }
3402 self.refresh_code_actions(window, cx);
3403 self.refresh_document_highlights(cx);
3404 refresh_linked_ranges(self, window, cx);
3405
3406 self.refresh_selected_text_highlights(false, window, cx);
3407 self.refresh_matching_bracket_highlights(window, cx);
3408 self.update_visible_edit_prediction(window, cx);
3409 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3410 self.inline_blame_popover.take();
3411 if self.git_blame_inline_enabled {
3412 self.start_inline_blame_timer(window, cx);
3413 }
3414 }
3415
3416 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3417 cx.emit(EditorEvent::SelectionsChanged { local });
3418
3419 let selections = &self.selections.disjoint_anchors_arc();
3420 if selections.len() == 1 {
3421 cx.emit(SearchEvent::ActiveMatchChanged)
3422 }
3423 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3424 let inmemory_selections = selections
3425 .iter()
3426 .map(|s| {
3427 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3428 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3429 })
3430 .collect();
3431 self.update_restoration_data(cx, |data| {
3432 data.selections = inmemory_selections;
3433 });
3434
3435 if WorkspaceSettings::get(None, cx).restore_on_startup
3436 != RestoreOnStartupBehavior::EmptyTab
3437 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3438 {
3439 let snapshot = self.buffer().read(cx).snapshot(cx);
3440 let selections = selections.clone();
3441 let background_executor = cx.background_executor().clone();
3442 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3443 self.serialize_selections = cx.background_spawn(async move {
3444 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3445 let db_selections = selections
3446 .iter()
3447 .map(|selection| {
3448 (
3449 selection.start.to_offset(&snapshot).0,
3450 selection.end.to_offset(&snapshot).0,
3451 )
3452 })
3453 .collect();
3454
3455 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3456 .await
3457 .with_context(|| {
3458 format!(
3459 "persisting editor selections for editor {editor_id}, \
3460 workspace {workspace_id:?}"
3461 )
3462 })
3463 .log_err();
3464 });
3465 }
3466 }
3467
3468 cx.notify();
3469 }
3470
3471 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3472 use text::ToOffset as _;
3473 use text::ToPoint as _;
3474
3475 if self.mode.is_minimap()
3476 || WorkspaceSettings::get(None, cx).restore_on_startup
3477 == RestoreOnStartupBehavior::EmptyTab
3478 {
3479 return;
3480 }
3481
3482 if !self.buffer().read(cx).is_singleton() {
3483 return;
3484 }
3485
3486 let display_snapshot = self
3487 .display_map
3488 .update(cx, |display_map, cx| display_map.snapshot(cx));
3489 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3490 return;
3491 };
3492 let inmemory_folds = display_snapshot
3493 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3494 .map(|fold| {
3495 fold.range.start.text_anchor.to_point(&snapshot)
3496 ..fold.range.end.text_anchor.to_point(&snapshot)
3497 })
3498 .collect();
3499 self.update_restoration_data(cx, |data| {
3500 data.folds = inmemory_folds;
3501 });
3502
3503 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3504 return;
3505 };
3506 let background_executor = cx.background_executor().clone();
3507 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3508 let db_folds = display_snapshot
3509 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3510 .map(|fold| {
3511 (
3512 fold.range.start.text_anchor.to_offset(&snapshot),
3513 fold.range.end.text_anchor.to_offset(&snapshot),
3514 )
3515 })
3516 .collect();
3517 self.serialize_folds = cx.background_spawn(async move {
3518 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3519 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3520 .await
3521 .with_context(|| {
3522 format!(
3523 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3524 )
3525 })
3526 .log_err();
3527 });
3528 }
3529
3530 pub fn sync_selections(
3531 &mut self,
3532 other: Entity<Editor>,
3533 cx: &mut Context<Self>,
3534 ) -> gpui::Subscription {
3535 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3536 if !other_selections.is_empty() {
3537 self.selections
3538 .change_with(&self.display_snapshot(cx), |selections| {
3539 selections.select_anchors(other_selections);
3540 });
3541 }
3542
3543 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3544 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3545 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3546 if other_selections.is_empty() {
3547 return;
3548 }
3549 let snapshot = this.display_snapshot(cx);
3550 this.selections.change_with(&snapshot, |selections| {
3551 selections.select_anchors(other_selections);
3552 });
3553 }
3554 });
3555
3556 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3557 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3558 let these_selections = this.selections.disjoint_anchors().to_vec();
3559 if these_selections.is_empty() {
3560 return;
3561 }
3562 other.update(cx, |other_editor, cx| {
3563 let snapshot = other_editor.display_snapshot(cx);
3564 other_editor
3565 .selections
3566 .change_with(&snapshot, |selections| {
3567 selections.select_anchors(these_selections);
3568 })
3569 });
3570 }
3571 });
3572
3573 Subscription::join(other_subscription, this_subscription)
3574 }
3575
3576 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3577 if self.buffer().read(cx).is_singleton() {
3578 return;
3579 }
3580 let snapshot = self.buffer.read(cx).snapshot(cx);
3581 let buffer_ids: HashSet<BufferId> = self
3582 .selections
3583 .disjoint_anchor_ranges()
3584 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3585 .collect();
3586 for buffer_id in buffer_ids {
3587 self.unfold_buffer(buffer_id, cx);
3588 }
3589 }
3590
3591 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3592 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3593 /// effects of selection change occur at the end of the transaction.
3594 pub fn change_selections<R>(
3595 &mut self,
3596 effects: SelectionEffects,
3597 window: &mut Window,
3598 cx: &mut Context<Self>,
3599 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3600 ) -> R {
3601 let snapshot = self.display_snapshot(cx);
3602 if let Some(state) = &mut self.deferred_selection_effects_state {
3603 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3604 state.effects.completions = effects.completions;
3605 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3606 let (changed, result) = self.selections.change_with(&snapshot, change);
3607 state.changed |= changed;
3608 return result;
3609 }
3610 let mut state = DeferredSelectionEffectsState {
3611 changed: false,
3612 effects,
3613 old_cursor_position: self.selections.newest_anchor().head(),
3614 history_entry: SelectionHistoryEntry {
3615 selections: self.selections.disjoint_anchors_arc(),
3616 select_next_state: self.select_next_state.clone(),
3617 select_prev_state: self.select_prev_state.clone(),
3618 add_selections_state: self.add_selections_state.clone(),
3619 },
3620 };
3621 let (changed, result) = self.selections.change_with(&snapshot, change);
3622 state.changed = state.changed || changed;
3623 if self.defer_selection_effects {
3624 self.deferred_selection_effects_state = Some(state);
3625 } else {
3626 self.apply_selection_effects(state, window, cx);
3627 }
3628 result
3629 }
3630
3631 /// Defers the effects of selection change, so that the effects of multiple calls to
3632 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3633 /// to selection history and the state of popovers based on selection position aren't
3634 /// erroneously updated.
3635 pub fn with_selection_effects_deferred<R>(
3636 &mut self,
3637 window: &mut Window,
3638 cx: &mut Context<Self>,
3639 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3640 ) -> R {
3641 let already_deferred = self.defer_selection_effects;
3642 self.defer_selection_effects = true;
3643 let result = update(self, window, cx);
3644 if !already_deferred {
3645 self.defer_selection_effects = false;
3646 if let Some(state) = self.deferred_selection_effects_state.take() {
3647 self.apply_selection_effects(state, window, cx);
3648 }
3649 }
3650 result
3651 }
3652
3653 fn apply_selection_effects(
3654 &mut self,
3655 state: DeferredSelectionEffectsState,
3656 window: &mut Window,
3657 cx: &mut Context<Self>,
3658 ) {
3659 if state.changed {
3660 self.selection_history.push(state.history_entry);
3661
3662 if let Some(autoscroll) = state.effects.scroll {
3663 self.request_autoscroll(autoscroll, cx);
3664 }
3665
3666 let old_cursor_position = &state.old_cursor_position;
3667
3668 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3669
3670 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3671 self.show_signature_help(&ShowSignatureHelp, window, cx);
3672 }
3673 }
3674 }
3675
3676 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3677 where
3678 I: IntoIterator<Item = (Range<S>, T)>,
3679 S: ToOffset,
3680 T: Into<Arc<str>>,
3681 {
3682 if self.read_only(cx) {
3683 return;
3684 }
3685
3686 self.buffer
3687 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3688 }
3689
3690 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3691 where
3692 I: IntoIterator<Item = (Range<S>, T)>,
3693 S: ToOffset,
3694 T: Into<Arc<str>>,
3695 {
3696 if self.read_only(cx) {
3697 return;
3698 }
3699
3700 self.buffer.update(cx, |buffer, cx| {
3701 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3702 });
3703 }
3704
3705 pub fn edit_with_block_indent<I, S, T>(
3706 &mut self,
3707 edits: I,
3708 original_indent_columns: Vec<Option<u32>>,
3709 cx: &mut Context<Self>,
3710 ) where
3711 I: IntoIterator<Item = (Range<S>, T)>,
3712 S: ToOffset,
3713 T: Into<Arc<str>>,
3714 {
3715 if self.read_only(cx) {
3716 return;
3717 }
3718
3719 self.buffer.update(cx, |buffer, cx| {
3720 buffer.edit(
3721 edits,
3722 Some(AutoindentMode::Block {
3723 original_indent_columns,
3724 }),
3725 cx,
3726 )
3727 });
3728 }
3729
3730 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3731 self.hide_context_menu(window, cx);
3732
3733 match phase {
3734 SelectPhase::Begin {
3735 position,
3736 add,
3737 click_count,
3738 } => self.begin_selection(position, add, click_count, window, cx),
3739 SelectPhase::BeginColumnar {
3740 position,
3741 goal_column,
3742 reset,
3743 mode,
3744 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3745 SelectPhase::Extend {
3746 position,
3747 click_count,
3748 } => self.extend_selection(position, click_count, window, cx),
3749 SelectPhase::Update {
3750 position,
3751 goal_column,
3752 scroll_delta,
3753 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3754 SelectPhase::End => self.end_selection(window, cx),
3755 }
3756 }
3757
3758 fn extend_selection(
3759 &mut self,
3760 position: DisplayPoint,
3761 click_count: usize,
3762 window: &mut Window,
3763 cx: &mut Context<Self>,
3764 ) {
3765 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3766 let tail = self
3767 .selections
3768 .newest::<MultiBufferOffset>(&display_map)
3769 .tail();
3770 let click_count = click_count.max(match self.selections.select_mode() {
3771 SelectMode::Character => 1,
3772 SelectMode::Word(_) => 2,
3773 SelectMode::Line(_) => 3,
3774 SelectMode::All => 4,
3775 });
3776 self.begin_selection(position, false, click_count, window, cx);
3777
3778 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3779
3780 let current_selection = match self.selections.select_mode() {
3781 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3782 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3783 };
3784
3785 let mut pending_selection = self
3786 .selections
3787 .pending_anchor()
3788 .cloned()
3789 .expect("extend_selection not called with pending selection");
3790
3791 if pending_selection
3792 .start
3793 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3794 == Ordering::Greater
3795 {
3796 pending_selection.start = current_selection.start;
3797 }
3798 if pending_selection
3799 .end
3800 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3801 == Ordering::Less
3802 {
3803 pending_selection.end = current_selection.end;
3804 pending_selection.reversed = true;
3805 }
3806
3807 let mut pending_mode = self.selections.pending_mode().unwrap();
3808 match &mut pending_mode {
3809 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3810 _ => {}
3811 }
3812
3813 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3814 SelectionEffects::scroll(Autoscroll::fit())
3815 } else {
3816 SelectionEffects::no_scroll()
3817 };
3818
3819 self.change_selections(effects, window, cx, |s| {
3820 s.set_pending(pending_selection.clone(), pending_mode);
3821 s.set_is_extending(true);
3822 });
3823 }
3824
3825 fn begin_selection(
3826 &mut self,
3827 position: DisplayPoint,
3828 add: bool,
3829 click_count: usize,
3830 window: &mut Window,
3831 cx: &mut Context<Self>,
3832 ) {
3833 if !self.focus_handle.is_focused(window) {
3834 self.last_focused_descendant = None;
3835 window.focus(&self.focus_handle, cx);
3836 }
3837
3838 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3839 let buffer = display_map.buffer_snapshot();
3840 let position = display_map.clip_point(position, Bias::Left);
3841
3842 let start;
3843 let end;
3844 let mode;
3845 let mut auto_scroll;
3846 match click_count {
3847 1 => {
3848 start = buffer.anchor_before(position.to_point(&display_map));
3849 end = start;
3850 mode = SelectMode::Character;
3851 auto_scroll = true;
3852 }
3853 2 => {
3854 let position = display_map
3855 .clip_point(position, Bias::Left)
3856 .to_offset(&display_map, Bias::Left);
3857 let (range, _) = buffer.surrounding_word(position, None);
3858 start = buffer.anchor_before(range.start);
3859 end = buffer.anchor_before(range.end);
3860 mode = SelectMode::Word(start..end);
3861 auto_scroll = true;
3862 }
3863 3 => {
3864 let position = display_map
3865 .clip_point(position, Bias::Left)
3866 .to_point(&display_map);
3867 let line_start = display_map.prev_line_boundary(position).0;
3868 let next_line_start = buffer.clip_point(
3869 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3870 Bias::Left,
3871 );
3872 start = buffer.anchor_before(line_start);
3873 end = buffer.anchor_before(next_line_start);
3874 mode = SelectMode::Line(start..end);
3875 auto_scroll = true;
3876 }
3877 _ => {
3878 start = buffer.anchor_before(MultiBufferOffset(0));
3879 end = buffer.anchor_before(buffer.len());
3880 mode = SelectMode::All;
3881 auto_scroll = false;
3882 }
3883 }
3884 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3885
3886 let point_to_delete: Option<usize> = {
3887 let selected_points: Vec<Selection<Point>> =
3888 self.selections.disjoint_in_range(start..end, &display_map);
3889
3890 if !add || click_count > 1 {
3891 None
3892 } else if !selected_points.is_empty() {
3893 Some(selected_points[0].id)
3894 } else {
3895 let clicked_point_already_selected =
3896 self.selections.disjoint_anchors().iter().find(|selection| {
3897 selection.start.to_point(buffer) == start.to_point(buffer)
3898 || selection.end.to_point(buffer) == end.to_point(buffer)
3899 });
3900
3901 clicked_point_already_selected.map(|selection| selection.id)
3902 }
3903 };
3904
3905 let selections_count = self.selections.count();
3906 let effects = if auto_scroll {
3907 SelectionEffects::default()
3908 } else {
3909 SelectionEffects::no_scroll()
3910 };
3911
3912 self.change_selections(effects, window, cx, |s| {
3913 if let Some(point_to_delete) = point_to_delete {
3914 s.delete(point_to_delete);
3915
3916 if selections_count == 1 {
3917 s.set_pending_anchor_range(start..end, mode);
3918 }
3919 } else {
3920 if !add {
3921 s.clear_disjoint();
3922 }
3923
3924 s.set_pending_anchor_range(start..end, mode);
3925 }
3926 });
3927 }
3928
3929 fn begin_columnar_selection(
3930 &mut self,
3931 position: DisplayPoint,
3932 goal_column: u32,
3933 reset: bool,
3934 mode: ColumnarMode,
3935 window: &mut Window,
3936 cx: &mut Context<Self>,
3937 ) {
3938 if !self.focus_handle.is_focused(window) {
3939 self.last_focused_descendant = None;
3940 window.focus(&self.focus_handle, cx);
3941 }
3942
3943 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3944
3945 if reset {
3946 let pointer_position = display_map
3947 .buffer_snapshot()
3948 .anchor_before(position.to_point(&display_map));
3949
3950 self.change_selections(
3951 SelectionEffects::scroll(Autoscroll::newest()),
3952 window,
3953 cx,
3954 |s| {
3955 s.clear_disjoint();
3956 s.set_pending_anchor_range(
3957 pointer_position..pointer_position,
3958 SelectMode::Character,
3959 );
3960 },
3961 );
3962 };
3963
3964 let tail = self.selections.newest::<Point>(&display_map).tail();
3965 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3966 self.columnar_selection_state = match mode {
3967 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3968 selection_tail: selection_anchor,
3969 display_point: if reset {
3970 if position.column() != goal_column {
3971 Some(DisplayPoint::new(position.row(), goal_column))
3972 } else {
3973 None
3974 }
3975 } else {
3976 None
3977 },
3978 }),
3979 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3980 selection_tail: selection_anchor,
3981 }),
3982 };
3983
3984 if !reset {
3985 self.select_columns(position, goal_column, &display_map, window, cx);
3986 }
3987 }
3988
3989 fn update_selection(
3990 &mut self,
3991 position: DisplayPoint,
3992 goal_column: u32,
3993 scroll_delta: gpui::Point<f32>,
3994 window: &mut Window,
3995 cx: &mut Context<Self>,
3996 ) {
3997 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3998
3999 if self.columnar_selection_state.is_some() {
4000 self.select_columns(position, goal_column, &display_map, window, cx);
4001 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4002 let buffer = display_map.buffer_snapshot();
4003 let head;
4004 let tail;
4005 let mode = self.selections.pending_mode().unwrap();
4006 match &mode {
4007 SelectMode::Character => {
4008 head = position.to_point(&display_map);
4009 tail = pending.tail().to_point(buffer);
4010 }
4011 SelectMode::Word(original_range) => {
4012 let offset = display_map
4013 .clip_point(position, Bias::Left)
4014 .to_offset(&display_map, Bias::Left);
4015 let original_range = original_range.to_offset(buffer);
4016
4017 let head_offset = if buffer.is_inside_word(offset, None)
4018 || original_range.contains(&offset)
4019 {
4020 let (word_range, _) = buffer.surrounding_word(offset, None);
4021 if word_range.start < original_range.start {
4022 word_range.start
4023 } else {
4024 word_range.end
4025 }
4026 } else {
4027 offset
4028 };
4029
4030 head = head_offset.to_point(buffer);
4031 if head_offset <= original_range.start {
4032 tail = original_range.end.to_point(buffer);
4033 } else {
4034 tail = original_range.start.to_point(buffer);
4035 }
4036 }
4037 SelectMode::Line(original_range) => {
4038 let original_range = original_range.to_point(display_map.buffer_snapshot());
4039
4040 let position = display_map
4041 .clip_point(position, Bias::Left)
4042 .to_point(&display_map);
4043 let line_start = display_map.prev_line_boundary(position).0;
4044 let next_line_start = buffer.clip_point(
4045 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4046 Bias::Left,
4047 );
4048
4049 if line_start < original_range.start {
4050 head = line_start
4051 } else {
4052 head = next_line_start
4053 }
4054
4055 if head <= original_range.start {
4056 tail = original_range.end;
4057 } else {
4058 tail = original_range.start;
4059 }
4060 }
4061 SelectMode::All => {
4062 return;
4063 }
4064 };
4065
4066 if head < tail {
4067 pending.start = buffer.anchor_before(head);
4068 pending.end = buffer.anchor_before(tail);
4069 pending.reversed = true;
4070 } else {
4071 pending.start = buffer.anchor_before(tail);
4072 pending.end = buffer.anchor_before(head);
4073 pending.reversed = false;
4074 }
4075
4076 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4077 s.set_pending(pending.clone(), mode);
4078 });
4079 } else {
4080 log::error!("update_selection dispatched with no pending selection");
4081 return;
4082 }
4083
4084 self.apply_scroll_delta(scroll_delta, window, cx);
4085 cx.notify();
4086 }
4087
4088 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4089 self.columnar_selection_state.take();
4090 if let Some(pending_mode) = self.selections.pending_mode() {
4091 let selections = self
4092 .selections
4093 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4094 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4095 s.select(selections);
4096 s.clear_pending();
4097 if s.is_extending() {
4098 s.set_is_extending(false);
4099 } else {
4100 s.set_select_mode(pending_mode);
4101 }
4102 });
4103 }
4104 }
4105
4106 fn select_columns(
4107 &mut self,
4108 head: DisplayPoint,
4109 goal_column: u32,
4110 display_map: &DisplaySnapshot,
4111 window: &mut Window,
4112 cx: &mut Context<Self>,
4113 ) {
4114 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4115 return;
4116 };
4117
4118 let tail = match columnar_state {
4119 ColumnarSelectionState::FromMouse {
4120 selection_tail,
4121 display_point,
4122 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4123 ColumnarSelectionState::FromSelection { selection_tail } => {
4124 selection_tail.to_display_point(display_map)
4125 }
4126 };
4127
4128 let start_row = cmp::min(tail.row(), head.row());
4129 let end_row = cmp::max(tail.row(), head.row());
4130 let start_column = cmp::min(tail.column(), goal_column);
4131 let end_column = cmp::max(tail.column(), goal_column);
4132 let reversed = start_column < tail.column();
4133
4134 let selection_ranges = (start_row.0..=end_row.0)
4135 .map(DisplayRow)
4136 .filter_map(|row| {
4137 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4138 || start_column <= display_map.line_len(row))
4139 && !display_map.is_block_line(row)
4140 {
4141 let start = display_map
4142 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4143 .to_point(display_map);
4144 let end = display_map
4145 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4146 .to_point(display_map);
4147 if reversed {
4148 Some(end..start)
4149 } else {
4150 Some(start..end)
4151 }
4152 } else {
4153 None
4154 }
4155 })
4156 .collect::<Vec<_>>();
4157 if selection_ranges.is_empty() {
4158 return;
4159 }
4160
4161 let ranges = match columnar_state {
4162 ColumnarSelectionState::FromMouse { .. } => {
4163 let mut non_empty_ranges = selection_ranges
4164 .iter()
4165 .filter(|selection_range| selection_range.start != selection_range.end)
4166 .peekable();
4167 if non_empty_ranges.peek().is_some() {
4168 non_empty_ranges.cloned().collect()
4169 } else {
4170 selection_ranges
4171 }
4172 }
4173 _ => selection_ranges,
4174 };
4175
4176 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4177 s.select_ranges(ranges);
4178 });
4179 cx.notify();
4180 }
4181
4182 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4183 self.selections
4184 .all_adjusted(snapshot)
4185 .iter()
4186 .any(|selection| !selection.is_empty())
4187 }
4188
4189 pub fn has_pending_nonempty_selection(&self) -> bool {
4190 let pending_nonempty_selection = match self.selections.pending_anchor() {
4191 Some(Selection { start, end, .. }) => start != end,
4192 None => false,
4193 };
4194
4195 pending_nonempty_selection
4196 || (self.columnar_selection_state.is_some()
4197 && self.selections.disjoint_anchors().len() > 1)
4198 }
4199
4200 pub fn has_pending_selection(&self) -> bool {
4201 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4202 }
4203
4204 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4205 self.selection_mark_mode = false;
4206 self.selection_drag_state = SelectionDragState::None;
4207
4208 if self.dismiss_menus_and_popups(true, window, cx) {
4209 cx.notify();
4210 return;
4211 }
4212 if self.clear_expanded_diff_hunks(cx) {
4213 cx.notify();
4214 return;
4215 }
4216 if self.show_git_blame_gutter {
4217 self.show_git_blame_gutter = false;
4218 cx.notify();
4219 return;
4220 }
4221
4222 if self.mode.is_full()
4223 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4224 {
4225 cx.notify();
4226 return;
4227 }
4228
4229 cx.propagate();
4230 }
4231
4232 pub fn dismiss_menus_and_popups(
4233 &mut self,
4234 is_user_requested: bool,
4235 window: &mut Window,
4236 cx: &mut Context<Self>,
4237 ) -> bool {
4238 let mut dismissed = false;
4239
4240 dismissed |= self.take_rename(false, window, cx).is_some();
4241 dismissed |= self.hide_blame_popover(true, cx);
4242 dismissed |= hide_hover(self, cx);
4243 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4244 dismissed |= self.hide_context_menu(window, cx).is_some();
4245 dismissed |= self.mouse_context_menu.take().is_some();
4246 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4247 dismissed |= self.snippet_stack.pop().is_some();
4248
4249 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4250 self.dismiss_diagnostics(cx);
4251 dismissed = true;
4252 }
4253
4254 dismissed
4255 }
4256
4257 fn linked_editing_ranges_for(
4258 &self,
4259 selection: Range<text::Anchor>,
4260 cx: &App,
4261 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4262 if self.linked_edit_ranges.is_empty() {
4263 return None;
4264 }
4265 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4266 selection.end.buffer_id.and_then(|end_buffer_id| {
4267 if selection.start.buffer_id != Some(end_buffer_id) {
4268 return None;
4269 }
4270 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4271 let snapshot = buffer.read(cx).snapshot();
4272 self.linked_edit_ranges
4273 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4274 .map(|ranges| (ranges, snapshot, buffer))
4275 })?;
4276 use text::ToOffset as TO;
4277 // find offset from the start of current range to current cursor position
4278 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4279
4280 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4281 let start_difference = start_offset - start_byte_offset;
4282 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4283 let end_difference = end_offset - start_byte_offset;
4284 // Current range has associated linked ranges.
4285 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4286 for range in linked_ranges.iter() {
4287 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4288 let end_offset = start_offset + end_difference;
4289 let start_offset = start_offset + start_difference;
4290 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4291 continue;
4292 }
4293 if self.selections.disjoint_anchor_ranges().any(|s| {
4294 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4295 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4296 {
4297 return false;
4298 }
4299 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4300 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4301 }) {
4302 continue;
4303 }
4304 let start = buffer_snapshot.anchor_after(start_offset);
4305 let end = buffer_snapshot.anchor_after(end_offset);
4306 linked_edits
4307 .entry(buffer.clone())
4308 .or_default()
4309 .push(start..end);
4310 }
4311 Some(linked_edits)
4312 }
4313
4314 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4315 let text: Arc<str> = text.into();
4316
4317 if self.read_only(cx) {
4318 return;
4319 }
4320
4321 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4322
4323 self.unfold_buffers_with_selections(cx);
4324
4325 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4326 let mut bracket_inserted = false;
4327 let mut edits = Vec::new();
4328 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4329 let mut new_selections = Vec::with_capacity(selections.len());
4330 let mut new_autoclose_regions = Vec::new();
4331 let snapshot = self.buffer.read(cx).read(cx);
4332 let mut clear_linked_edit_ranges = false;
4333 let mut all_selections_read_only = true;
4334 let mut has_adjacent_edits = false;
4335 let mut in_adjacent_group = false;
4336
4337 let mut regions = self
4338 .selections_with_autoclose_regions(selections, &snapshot)
4339 .peekable();
4340
4341 while let Some((selection, autoclose_region)) = regions.next() {
4342 if snapshot
4343 .point_to_buffer_point(selection.head())
4344 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4345 {
4346 continue;
4347 }
4348 if snapshot
4349 .point_to_buffer_point(selection.tail())
4350 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4351 {
4352 // note, ideally we'd clip the tail to the closest writeable region towards the head
4353 continue;
4354 }
4355 all_selections_read_only = false;
4356
4357 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4358 // Determine if the inserted text matches the opening or closing
4359 // bracket of any of this language's bracket pairs.
4360 let mut bracket_pair = None;
4361 let mut is_bracket_pair_start = false;
4362 let mut is_bracket_pair_end = false;
4363 if !text.is_empty() {
4364 let mut bracket_pair_matching_end = None;
4365 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4366 // and they are removing the character that triggered IME popup.
4367 for (pair, enabled) in scope.brackets() {
4368 if !pair.close && !pair.surround {
4369 continue;
4370 }
4371
4372 if enabled && pair.start.ends_with(text.as_ref()) {
4373 let prefix_len = pair.start.len() - text.len();
4374 let preceding_text_matches_prefix = prefix_len == 0
4375 || (selection.start.column >= (prefix_len as u32)
4376 && snapshot.contains_str_at(
4377 Point::new(
4378 selection.start.row,
4379 selection.start.column - (prefix_len as u32),
4380 ),
4381 &pair.start[..prefix_len],
4382 ));
4383 if preceding_text_matches_prefix {
4384 bracket_pair = Some(pair.clone());
4385 is_bracket_pair_start = true;
4386 break;
4387 }
4388 }
4389 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4390 {
4391 // take first bracket pair matching end, but don't break in case a later bracket
4392 // pair matches start
4393 bracket_pair_matching_end = Some(pair.clone());
4394 }
4395 }
4396 if let Some(end) = bracket_pair_matching_end
4397 && bracket_pair.is_none()
4398 {
4399 bracket_pair = Some(end);
4400 is_bracket_pair_end = true;
4401 }
4402 }
4403
4404 if let Some(bracket_pair) = bracket_pair {
4405 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4406 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4407 let auto_surround =
4408 self.use_auto_surround && snapshot_settings.use_auto_surround;
4409 if selection.is_empty() {
4410 if is_bracket_pair_start {
4411 // If the inserted text is a suffix of an opening bracket and the
4412 // selection is preceded by the rest of the opening bracket, then
4413 // insert the closing bracket.
4414 let following_text_allows_autoclose = snapshot
4415 .chars_at(selection.start)
4416 .next()
4417 .is_none_or(|c| scope.should_autoclose_before(c));
4418
4419 let preceding_text_allows_autoclose = selection.start.column == 0
4420 || snapshot
4421 .reversed_chars_at(selection.start)
4422 .next()
4423 .is_none_or(|c| {
4424 bracket_pair.start != bracket_pair.end
4425 || !snapshot
4426 .char_classifier_at(selection.start)
4427 .is_word(c)
4428 });
4429
4430 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4431 && bracket_pair.start.len() == 1
4432 {
4433 let target = bracket_pair.start.chars().next().unwrap();
4434 let mut byte_offset = 0u32;
4435 let current_line_count = snapshot
4436 .reversed_chars_at(selection.start)
4437 .take_while(|&c| c != '\n')
4438 .filter(|c| {
4439 byte_offset += c.len_utf8() as u32;
4440 if *c != target {
4441 return false;
4442 }
4443
4444 let point = Point::new(
4445 selection.start.row,
4446 selection.start.column.saturating_sub(byte_offset),
4447 );
4448
4449 let is_enabled = snapshot
4450 .language_scope_at(point)
4451 .and_then(|scope| {
4452 scope
4453 .brackets()
4454 .find(|(pair, _)| {
4455 pair.start == bracket_pair.start
4456 })
4457 .map(|(_, enabled)| enabled)
4458 })
4459 .unwrap_or(true);
4460
4461 let is_delimiter = snapshot
4462 .language_scope_at(Point::new(
4463 point.row,
4464 point.column + 1,
4465 ))
4466 .and_then(|scope| {
4467 scope
4468 .brackets()
4469 .find(|(pair, _)| {
4470 pair.start == bracket_pair.start
4471 })
4472 .map(|(_, enabled)| !enabled)
4473 })
4474 .unwrap_or(false);
4475
4476 is_enabled && !is_delimiter
4477 })
4478 .count();
4479 current_line_count % 2 == 1
4480 } else {
4481 false
4482 };
4483
4484 if autoclose
4485 && bracket_pair.close
4486 && following_text_allows_autoclose
4487 && preceding_text_allows_autoclose
4488 && !is_closing_quote
4489 {
4490 let anchor = snapshot.anchor_before(selection.end);
4491 new_selections.push((selection.map(|_| anchor), text.len()));
4492 new_autoclose_regions.push((
4493 anchor,
4494 text.len(),
4495 selection.id,
4496 bracket_pair.clone(),
4497 ));
4498 edits.push((
4499 selection.range(),
4500 format!("{}{}", text, bracket_pair.end).into(),
4501 ));
4502 bracket_inserted = true;
4503 continue;
4504 }
4505 }
4506
4507 if let Some(region) = autoclose_region {
4508 // If the selection is followed by an auto-inserted closing bracket,
4509 // then don't insert that closing bracket again; just move the selection
4510 // past the closing bracket.
4511 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4512 && text.as_ref() == region.pair.end.as_str()
4513 && snapshot.contains_str_at(region.range.end, text.as_ref());
4514 if should_skip {
4515 let anchor = snapshot.anchor_after(selection.end);
4516 new_selections
4517 .push((selection.map(|_| anchor), region.pair.end.len()));
4518 continue;
4519 }
4520 }
4521
4522 let always_treat_brackets_as_autoclosed = snapshot
4523 .language_settings_at(selection.start, cx)
4524 .always_treat_brackets_as_autoclosed;
4525 if always_treat_brackets_as_autoclosed
4526 && is_bracket_pair_end
4527 && snapshot.contains_str_at(selection.end, text.as_ref())
4528 {
4529 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4530 // and the inserted text is a closing bracket and the selection is followed
4531 // by the closing bracket then move the selection past the closing bracket.
4532 let anchor = snapshot.anchor_after(selection.end);
4533 new_selections.push((selection.map(|_| anchor), text.len()));
4534 continue;
4535 }
4536 }
4537 // If an opening bracket is 1 character long and is typed while
4538 // text is selected, then surround that text with the bracket pair.
4539 else if auto_surround
4540 && bracket_pair.surround
4541 && is_bracket_pair_start
4542 && bracket_pair.start.chars().count() == 1
4543 {
4544 edits.push((selection.start..selection.start, text.clone()));
4545 edits.push((
4546 selection.end..selection.end,
4547 bracket_pair.end.as_str().into(),
4548 ));
4549 bracket_inserted = true;
4550 new_selections.push((
4551 Selection {
4552 id: selection.id,
4553 start: snapshot.anchor_after(selection.start),
4554 end: snapshot.anchor_before(selection.end),
4555 reversed: selection.reversed,
4556 goal: selection.goal,
4557 },
4558 0,
4559 ));
4560 continue;
4561 }
4562 }
4563 }
4564
4565 if self.auto_replace_emoji_shortcode
4566 && selection.is_empty()
4567 && text.as_ref().ends_with(':')
4568 && let Some(possible_emoji_short_code) =
4569 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4570 && !possible_emoji_short_code.is_empty()
4571 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4572 {
4573 let emoji_shortcode_start = Point::new(
4574 selection.start.row,
4575 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4576 );
4577
4578 // Remove shortcode from buffer
4579 edits.push((
4580 emoji_shortcode_start..selection.start,
4581 "".to_string().into(),
4582 ));
4583 new_selections.push((
4584 Selection {
4585 id: selection.id,
4586 start: snapshot.anchor_after(emoji_shortcode_start),
4587 end: snapshot.anchor_before(selection.start),
4588 reversed: selection.reversed,
4589 goal: selection.goal,
4590 },
4591 0,
4592 ));
4593
4594 // Insert emoji
4595 let selection_start_anchor = snapshot.anchor_after(selection.start);
4596 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4597 edits.push((selection.start..selection.end, emoji.to_string().into()));
4598
4599 continue;
4600 }
4601
4602 let next_is_adjacent = regions
4603 .peek()
4604 .is_some_and(|(next, _)| selection.end == next.start);
4605
4606 // If not handling any auto-close operation, then just replace the selected
4607 // text with the given input and move the selection to the end of the
4608 // newly inserted text.
4609 let anchor = if in_adjacent_group || next_is_adjacent {
4610 // After edits the right bias would shift those anchor to the next visible fragment
4611 // but we want to resolve to the previous one
4612 snapshot.anchor_before(selection.end)
4613 } else {
4614 snapshot.anchor_after(selection.end)
4615 };
4616
4617 if !self.linked_edit_ranges.is_empty() {
4618 let start_anchor = snapshot.anchor_before(selection.start);
4619
4620 let is_word_char = text.chars().next().is_none_or(|char| {
4621 let classifier = snapshot
4622 .char_classifier_at(start_anchor.to_offset(&snapshot))
4623 .scope_context(Some(CharScopeContext::LinkedEdit));
4624 classifier.is_word(char)
4625 });
4626
4627 if is_word_char {
4628 if let Some(ranges) = self
4629 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4630 {
4631 for (buffer, edits) in ranges {
4632 linked_edits
4633 .entry(buffer.clone())
4634 .or_default()
4635 .extend(edits.into_iter().map(|range| (range, text.clone())));
4636 }
4637 }
4638 } else {
4639 clear_linked_edit_ranges = true;
4640 }
4641 }
4642
4643 new_selections.push((selection.map(|_| anchor), 0));
4644 edits.push((selection.start..selection.end, text.clone()));
4645
4646 has_adjacent_edits |= next_is_adjacent;
4647 in_adjacent_group = next_is_adjacent;
4648 }
4649
4650 if all_selections_read_only {
4651 return;
4652 }
4653
4654 drop(regions);
4655 drop(snapshot);
4656
4657 self.transact(window, cx, |this, window, cx| {
4658 if clear_linked_edit_ranges {
4659 this.linked_edit_ranges.clear();
4660 }
4661 let initial_buffer_versions =
4662 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4663
4664 this.buffer.update(cx, |buffer, cx| {
4665 if has_adjacent_edits {
4666 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4667 } else {
4668 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4669 }
4670 });
4671 for (buffer, edits) in linked_edits {
4672 buffer.update(cx, |buffer, cx| {
4673 let snapshot = buffer.snapshot();
4674 let edits = edits
4675 .into_iter()
4676 .map(|(range, text)| {
4677 use text::ToPoint as TP;
4678 let end_point = TP::to_point(&range.end, &snapshot);
4679 let start_point = TP::to_point(&range.start, &snapshot);
4680 (start_point..end_point, text)
4681 })
4682 .sorted_by_key(|(range, _)| range.start);
4683 buffer.edit(edits, None, cx);
4684 })
4685 }
4686 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4687 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4688 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4689 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4690 new_anchor_selections,
4691 &map,
4692 )
4693 .zip(new_selection_deltas)
4694 .map(|(selection, delta)| Selection {
4695 id: selection.id,
4696 start: selection.start + delta,
4697 end: selection.end + delta,
4698 reversed: selection.reversed,
4699 goal: SelectionGoal::None,
4700 })
4701 .collect::<Vec<_>>();
4702
4703 let mut i = 0;
4704 for (position, delta, selection_id, pair) in new_autoclose_regions {
4705 let position = position.to_offset(map.buffer_snapshot()) + delta;
4706 let start = map.buffer_snapshot().anchor_before(position);
4707 let end = map.buffer_snapshot().anchor_after(position);
4708 while let Some(existing_state) = this.autoclose_regions.get(i) {
4709 match existing_state
4710 .range
4711 .start
4712 .cmp(&start, map.buffer_snapshot())
4713 {
4714 Ordering::Less => i += 1,
4715 Ordering::Greater => break,
4716 Ordering::Equal => {
4717 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4718 Ordering::Less => i += 1,
4719 Ordering::Equal => break,
4720 Ordering::Greater => break,
4721 }
4722 }
4723 }
4724 }
4725 this.autoclose_regions.insert(
4726 i,
4727 AutocloseRegion {
4728 selection_id,
4729 range: start..end,
4730 pair,
4731 },
4732 );
4733 }
4734
4735 let had_active_edit_prediction = this.has_active_edit_prediction();
4736 this.change_selections(
4737 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4738 window,
4739 cx,
4740 |s| s.select(new_selections),
4741 );
4742
4743 if !bracket_inserted
4744 && let Some(on_type_format_task) =
4745 this.trigger_on_type_formatting(text.to_string(), window, cx)
4746 {
4747 on_type_format_task.detach_and_log_err(cx);
4748 }
4749
4750 let editor_settings = EditorSettings::get_global(cx);
4751 if bracket_inserted
4752 && (editor_settings.auto_signature_help
4753 || editor_settings.show_signature_help_after_edits)
4754 {
4755 this.show_signature_help(&ShowSignatureHelp, window, cx);
4756 }
4757
4758 let trigger_in_words =
4759 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4760 if this.hard_wrap.is_some() {
4761 let latest: Range<Point> = this.selections.newest(&map).range();
4762 if latest.is_empty()
4763 && this
4764 .buffer()
4765 .read(cx)
4766 .snapshot(cx)
4767 .line_len(MultiBufferRow(latest.start.row))
4768 == latest.start.column
4769 {
4770 this.rewrap_impl(
4771 RewrapOptions {
4772 override_language_settings: true,
4773 preserve_existing_whitespace: true,
4774 },
4775 cx,
4776 )
4777 }
4778 }
4779 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4780 refresh_linked_ranges(this, window, cx);
4781 this.refresh_edit_prediction(true, false, window, cx);
4782 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4783 });
4784 }
4785
4786 fn find_possible_emoji_shortcode_at_position(
4787 snapshot: &MultiBufferSnapshot,
4788 position: Point,
4789 ) -> Option<String> {
4790 let mut chars = Vec::new();
4791 let mut found_colon = false;
4792 for char in snapshot.reversed_chars_at(position).take(100) {
4793 // Found a possible emoji shortcode in the middle of the buffer
4794 if found_colon {
4795 if char.is_whitespace() {
4796 chars.reverse();
4797 return Some(chars.iter().collect());
4798 }
4799 // If the previous character is not a whitespace, we are in the middle of a word
4800 // and we only want to complete the shortcode if the word is made up of other emojis
4801 let mut containing_word = String::new();
4802 for ch in snapshot
4803 .reversed_chars_at(position)
4804 .skip(chars.len() + 1)
4805 .take(100)
4806 {
4807 if ch.is_whitespace() {
4808 break;
4809 }
4810 containing_word.push(ch);
4811 }
4812 let containing_word = containing_word.chars().rev().collect::<String>();
4813 if util::word_consists_of_emojis(containing_word.as_str()) {
4814 chars.reverse();
4815 return Some(chars.iter().collect());
4816 }
4817 }
4818
4819 if char.is_whitespace() || !char.is_ascii() {
4820 return None;
4821 }
4822 if char == ':' {
4823 found_colon = true;
4824 } else {
4825 chars.push(char);
4826 }
4827 }
4828 // Found a possible emoji shortcode at the beginning of the buffer
4829 chars.reverse();
4830 Some(chars.iter().collect())
4831 }
4832
4833 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4834 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4835 self.transact(window, cx, |this, window, cx| {
4836 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4837 let selections = this
4838 .selections
4839 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4840 let multi_buffer = this.buffer.read(cx);
4841 let buffer = multi_buffer.snapshot(cx);
4842 selections
4843 .iter()
4844 .map(|selection| {
4845 let start_point = selection.start.to_point(&buffer);
4846 let mut existing_indent =
4847 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4848 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4849 let start = selection.start;
4850 let end = selection.end;
4851 let selection_is_empty = start == end;
4852 let language_scope = buffer.language_scope_at(start);
4853 let (delimiter, newline_config) = if let Some(language) = &language_scope {
4854 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
4855 &buffer,
4856 start..end,
4857 language,
4858 )
4859 || NewlineConfig::insert_extra_newline_tree_sitter(
4860 &buffer,
4861 start..end,
4862 );
4863
4864 let mut newline_config = NewlineConfig::Newline {
4865 additional_indent: IndentSize::spaces(0),
4866 extra_line_additional_indent: if needs_extra_newline {
4867 Some(IndentSize::spaces(0))
4868 } else {
4869 None
4870 },
4871 prevent_auto_indent: false,
4872 };
4873
4874 let comment_delimiter = maybe!({
4875 if !selection_is_empty {
4876 return None;
4877 }
4878
4879 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4880 return None;
4881 }
4882
4883 return comment_delimiter_for_newline(
4884 &start_point,
4885 &buffer,
4886 language,
4887 );
4888 });
4889
4890 let doc_delimiter = maybe!({
4891 if !selection_is_empty {
4892 return None;
4893 }
4894
4895 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4896 return None;
4897 }
4898
4899 return documentation_delimiter_for_newline(
4900 &start_point,
4901 &buffer,
4902 language,
4903 &mut newline_config,
4904 );
4905 });
4906
4907 let list_delimiter = maybe!({
4908 if !selection_is_empty {
4909 return None;
4910 }
4911
4912 if !multi_buffer.language_settings(cx).extend_list_on_newline {
4913 return None;
4914 }
4915
4916 return list_delimiter_for_newline(
4917 &start_point,
4918 &buffer,
4919 language,
4920 &mut newline_config,
4921 );
4922 });
4923
4924 (
4925 comment_delimiter.or(doc_delimiter).or(list_delimiter),
4926 newline_config,
4927 )
4928 } else {
4929 (
4930 None,
4931 NewlineConfig::Newline {
4932 additional_indent: IndentSize::spaces(0),
4933 extra_line_additional_indent: None,
4934 prevent_auto_indent: false,
4935 },
4936 )
4937 };
4938
4939 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
4940 NewlineConfig::ClearCurrentLine => {
4941 let row_start =
4942 buffer.point_to_offset(Point::new(start_point.row, 0));
4943 (row_start, String::new(), false)
4944 }
4945 NewlineConfig::UnindentCurrentLine { continuation } => {
4946 let row_start =
4947 buffer.point_to_offset(Point::new(start_point.row, 0));
4948 let tab_size = buffer.language_settings_at(start, cx).tab_size;
4949 let tab_size_indent = IndentSize::spaces(tab_size.get());
4950 let reduced_indent =
4951 existing_indent.with_delta(Ordering::Less, tab_size_indent);
4952 let mut new_text = String::new();
4953 new_text.extend(reduced_indent.chars());
4954 new_text.push_str(continuation);
4955 (row_start, new_text, true)
4956 }
4957 NewlineConfig::Newline {
4958 additional_indent,
4959 extra_line_additional_indent,
4960 prevent_auto_indent,
4961 } => {
4962 let capacity_for_delimiter =
4963 delimiter.as_deref().map(str::len).unwrap_or_default();
4964 let extra_line_len = extra_line_additional_indent
4965 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
4966 .unwrap_or(0);
4967 let mut new_text = String::with_capacity(
4968 1 + capacity_for_delimiter
4969 + existing_indent.len as usize
4970 + additional_indent.len as usize
4971 + extra_line_len,
4972 );
4973 new_text.push('\n');
4974 new_text.extend(existing_indent.chars());
4975 new_text.extend(additional_indent.chars());
4976 if let Some(delimiter) = &delimiter {
4977 new_text.push_str(delimiter);
4978 }
4979 if let Some(extra_indent) = extra_line_additional_indent {
4980 new_text.push('\n');
4981 new_text.extend(existing_indent.chars());
4982 new_text.extend(extra_indent.chars());
4983 }
4984 (start, new_text, *prevent_auto_indent)
4985 }
4986 };
4987
4988 let anchor = buffer.anchor_after(end);
4989 let new_selection = selection.map(|_| anchor);
4990 (
4991 ((edit_start..end, new_text), prevent_auto_indent),
4992 (newline_config.has_extra_line(), new_selection),
4993 )
4994 })
4995 .unzip()
4996 };
4997
4998 let mut auto_indent_edits = Vec::new();
4999 let mut edits = Vec::new();
5000 for (edit, prevent_auto_indent) in edits_with_flags {
5001 if prevent_auto_indent {
5002 edits.push(edit);
5003 } else {
5004 auto_indent_edits.push(edit);
5005 }
5006 }
5007 if !edits.is_empty() {
5008 this.edit(edits, cx);
5009 }
5010 if !auto_indent_edits.is_empty() {
5011 this.edit_with_autoindent(auto_indent_edits, cx);
5012 }
5013
5014 let buffer = this.buffer.read(cx).snapshot(cx);
5015 let new_selections = selection_info
5016 .into_iter()
5017 .map(|(extra_newline_inserted, new_selection)| {
5018 let mut cursor = new_selection.end.to_point(&buffer);
5019 if extra_newline_inserted {
5020 cursor.row -= 1;
5021 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5022 }
5023 new_selection.map(|_| cursor)
5024 })
5025 .collect();
5026
5027 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5028 this.refresh_edit_prediction(true, false, window, cx);
5029 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5030 task.detach_and_log_err(cx);
5031 }
5032 });
5033 }
5034
5035 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5036 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5037
5038 let buffer = self.buffer.read(cx);
5039 let snapshot = buffer.snapshot(cx);
5040
5041 let mut edits = Vec::new();
5042 let mut rows = Vec::new();
5043
5044 for (rows_inserted, selection) in self
5045 .selections
5046 .all_adjusted(&self.display_snapshot(cx))
5047 .into_iter()
5048 .enumerate()
5049 {
5050 let cursor = selection.head();
5051 let row = cursor.row;
5052
5053 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5054
5055 let newline = "\n".to_string();
5056 edits.push((start_of_line..start_of_line, newline));
5057
5058 rows.push(row + rows_inserted as u32);
5059 }
5060
5061 self.transact(window, cx, |editor, window, cx| {
5062 editor.edit(edits, cx);
5063
5064 editor.change_selections(Default::default(), window, cx, |s| {
5065 let mut index = 0;
5066 s.move_cursors_with(|map, _, _| {
5067 let row = rows[index];
5068 index += 1;
5069
5070 let point = Point::new(row, 0);
5071 let boundary = map.next_line_boundary(point).1;
5072 let clipped = map.clip_point(boundary, Bias::Left);
5073
5074 (clipped, SelectionGoal::None)
5075 });
5076 });
5077
5078 let mut indent_edits = Vec::new();
5079 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5080 for row in rows {
5081 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5082 for (row, indent) in indents {
5083 if indent.len == 0 {
5084 continue;
5085 }
5086
5087 let text = match indent.kind {
5088 IndentKind::Space => " ".repeat(indent.len as usize),
5089 IndentKind::Tab => "\t".repeat(indent.len as usize),
5090 };
5091 let point = Point::new(row.0, 0);
5092 indent_edits.push((point..point, text));
5093 }
5094 }
5095 editor.edit(indent_edits, cx);
5096 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5097 format.detach_and_log_err(cx);
5098 }
5099 });
5100 }
5101
5102 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5103 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5104
5105 let buffer = self.buffer.read(cx);
5106 let snapshot = buffer.snapshot(cx);
5107
5108 let mut edits = Vec::new();
5109 let mut rows = Vec::new();
5110 let mut rows_inserted = 0;
5111
5112 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5113 let cursor = selection.head();
5114 let row = cursor.row;
5115
5116 let point = Point::new(row + 1, 0);
5117 let start_of_line = snapshot.clip_point(point, Bias::Left);
5118
5119 let newline = "\n".to_string();
5120 edits.push((start_of_line..start_of_line, newline));
5121
5122 rows_inserted += 1;
5123 rows.push(row + rows_inserted);
5124 }
5125
5126 self.transact(window, cx, |editor, window, cx| {
5127 editor.edit(edits, cx);
5128
5129 editor.change_selections(Default::default(), window, cx, |s| {
5130 let mut index = 0;
5131 s.move_cursors_with(|map, _, _| {
5132 let row = rows[index];
5133 index += 1;
5134
5135 let point = Point::new(row, 0);
5136 let boundary = map.next_line_boundary(point).1;
5137 let clipped = map.clip_point(boundary, Bias::Left);
5138
5139 (clipped, SelectionGoal::None)
5140 });
5141 });
5142
5143 let mut indent_edits = Vec::new();
5144 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5145 for row in rows {
5146 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5147 for (row, indent) in indents {
5148 if indent.len == 0 {
5149 continue;
5150 }
5151
5152 let text = match indent.kind {
5153 IndentKind::Space => " ".repeat(indent.len as usize),
5154 IndentKind::Tab => "\t".repeat(indent.len as usize),
5155 };
5156 let point = Point::new(row.0, 0);
5157 indent_edits.push((point..point, text));
5158 }
5159 }
5160 editor.edit(indent_edits, cx);
5161 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5162 format.detach_and_log_err(cx);
5163 }
5164 });
5165 }
5166
5167 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5168 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5169 original_indent_columns: Vec::new(),
5170 });
5171 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5172 }
5173
5174 fn insert_with_autoindent_mode(
5175 &mut self,
5176 text: &str,
5177 autoindent_mode: Option<AutoindentMode>,
5178 window: &mut Window,
5179 cx: &mut Context<Self>,
5180 ) {
5181 if self.read_only(cx) {
5182 return;
5183 }
5184
5185 let text: Arc<str> = text.into();
5186 self.transact(window, cx, |this, window, cx| {
5187 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5188 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5189 let anchors = {
5190 let snapshot = buffer.read(cx);
5191 old_selections
5192 .iter()
5193 .map(|s| {
5194 let anchor = snapshot.anchor_after(s.head());
5195 s.map(|_| anchor)
5196 })
5197 .collect::<Vec<_>>()
5198 };
5199 buffer.edit(
5200 old_selections
5201 .iter()
5202 .map(|s| (s.start..s.end, text.clone())),
5203 autoindent_mode,
5204 cx,
5205 );
5206 anchors
5207 });
5208
5209 this.change_selections(Default::default(), window, cx, |s| {
5210 s.select_anchors(selection_anchors);
5211 });
5212
5213 cx.notify();
5214 });
5215 }
5216
5217 fn trigger_completion_on_input(
5218 &mut self,
5219 text: &str,
5220 trigger_in_words: bool,
5221 window: &mut Window,
5222 cx: &mut Context<Self>,
5223 ) {
5224 let completions_source = self
5225 .context_menu
5226 .borrow()
5227 .as_ref()
5228 .and_then(|menu| match menu {
5229 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5230 CodeContextMenu::CodeActions(_) => None,
5231 });
5232
5233 match completions_source {
5234 Some(CompletionsMenuSource::Words { .. }) => {
5235 self.open_or_update_completions_menu(
5236 Some(CompletionsMenuSource::Words {
5237 ignore_threshold: false,
5238 }),
5239 None,
5240 trigger_in_words,
5241 window,
5242 cx,
5243 );
5244 }
5245 _ => self.open_or_update_completions_menu(
5246 None,
5247 Some(text.to_owned()).filter(|x| !x.is_empty()),
5248 true,
5249 window,
5250 cx,
5251 ),
5252 }
5253 }
5254
5255 /// If any empty selections is touching the start of its innermost containing autoclose
5256 /// region, expand it to select the brackets.
5257 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5258 let selections = self
5259 .selections
5260 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5261 let buffer = self.buffer.read(cx).read(cx);
5262 let new_selections = self
5263 .selections_with_autoclose_regions(selections, &buffer)
5264 .map(|(mut selection, region)| {
5265 if !selection.is_empty() {
5266 return selection;
5267 }
5268
5269 if let Some(region) = region {
5270 let mut range = region.range.to_offset(&buffer);
5271 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5272 range.start -= region.pair.start.len();
5273 if buffer.contains_str_at(range.start, ®ion.pair.start)
5274 && buffer.contains_str_at(range.end, ®ion.pair.end)
5275 {
5276 range.end += region.pair.end.len();
5277 selection.start = range.start;
5278 selection.end = range.end;
5279
5280 return selection;
5281 }
5282 }
5283 }
5284
5285 let always_treat_brackets_as_autoclosed = buffer
5286 .language_settings_at(selection.start, cx)
5287 .always_treat_brackets_as_autoclosed;
5288
5289 if !always_treat_brackets_as_autoclosed {
5290 return selection;
5291 }
5292
5293 if let Some(scope) = buffer.language_scope_at(selection.start) {
5294 for (pair, enabled) in scope.brackets() {
5295 if !enabled || !pair.close {
5296 continue;
5297 }
5298
5299 if buffer.contains_str_at(selection.start, &pair.end) {
5300 let pair_start_len = pair.start.len();
5301 if buffer.contains_str_at(
5302 selection.start.saturating_sub_usize(pair_start_len),
5303 &pair.start,
5304 ) {
5305 selection.start -= pair_start_len;
5306 selection.end += pair.end.len();
5307
5308 return selection;
5309 }
5310 }
5311 }
5312 }
5313
5314 selection
5315 })
5316 .collect();
5317
5318 drop(buffer);
5319 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5320 selections.select(new_selections)
5321 });
5322 }
5323
5324 /// Iterate the given selections, and for each one, find the smallest surrounding
5325 /// autoclose region. This uses the ordering of the selections and the autoclose
5326 /// regions to avoid repeated comparisons.
5327 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5328 &'a self,
5329 selections: impl IntoIterator<Item = Selection<D>>,
5330 buffer: &'a MultiBufferSnapshot,
5331 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5332 let mut i = 0;
5333 let mut regions = self.autoclose_regions.as_slice();
5334 selections.into_iter().map(move |selection| {
5335 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5336
5337 let mut enclosing = None;
5338 while let Some(pair_state) = regions.get(i) {
5339 if pair_state.range.end.to_offset(buffer) < range.start {
5340 regions = ®ions[i + 1..];
5341 i = 0;
5342 } else if pair_state.range.start.to_offset(buffer) > range.end {
5343 break;
5344 } else {
5345 if pair_state.selection_id == selection.id {
5346 enclosing = Some(pair_state);
5347 }
5348 i += 1;
5349 }
5350 }
5351
5352 (selection, enclosing)
5353 })
5354 }
5355
5356 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5357 fn invalidate_autoclose_regions(
5358 &mut self,
5359 mut selections: &[Selection<Anchor>],
5360 buffer: &MultiBufferSnapshot,
5361 ) {
5362 self.autoclose_regions.retain(|state| {
5363 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5364 return false;
5365 }
5366
5367 let mut i = 0;
5368 while let Some(selection) = selections.get(i) {
5369 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5370 selections = &selections[1..];
5371 continue;
5372 }
5373 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5374 break;
5375 }
5376 if selection.id == state.selection_id {
5377 return true;
5378 } else {
5379 i += 1;
5380 }
5381 }
5382 false
5383 });
5384 }
5385
5386 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5387 let offset = position.to_offset(buffer);
5388 let (word_range, kind) =
5389 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5390 if offset > word_range.start && kind == Some(CharKind::Word) {
5391 Some(
5392 buffer
5393 .text_for_range(word_range.start..offset)
5394 .collect::<String>(),
5395 )
5396 } else {
5397 None
5398 }
5399 }
5400
5401 pub fn visible_excerpts(
5402 &self,
5403 lsp_related_only: bool,
5404 cx: &mut Context<Editor>,
5405 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5406 let project = self.project().cloned();
5407 let multi_buffer = self.buffer().read(cx);
5408 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5409 let multi_buffer_visible_start = self
5410 .scroll_manager
5411 .anchor()
5412 .anchor
5413 .to_point(&multi_buffer_snapshot);
5414 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5415 multi_buffer_visible_start
5416 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5417 Bias::Left,
5418 );
5419 multi_buffer_snapshot
5420 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5421 .into_iter()
5422 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5423 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5424 if !lsp_related_only {
5425 return Some((
5426 excerpt_id,
5427 (
5428 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5429 buffer.version().clone(),
5430 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5431 ),
5432 ));
5433 }
5434
5435 let project = project.as_ref()?.read(cx);
5436 let buffer_file = project::File::from_dyn(buffer.file())?;
5437 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5438 let worktree_entry = buffer_worktree
5439 .read(cx)
5440 .entry_for_id(buffer_file.project_entry_id()?)?;
5441 if worktree_entry.is_ignored {
5442 None
5443 } else {
5444 Some((
5445 excerpt_id,
5446 (
5447 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5448 buffer.version().clone(),
5449 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5450 ),
5451 ))
5452 }
5453 })
5454 .collect()
5455 }
5456
5457 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5458 TextLayoutDetails {
5459 text_system: window.text_system().clone(),
5460 editor_style: self.style.clone().unwrap(),
5461 rem_size: window.rem_size(),
5462 scroll_anchor: self.scroll_manager.anchor(),
5463 visible_rows: self.visible_line_count(),
5464 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5465 }
5466 }
5467
5468 fn trigger_on_type_formatting(
5469 &self,
5470 input: String,
5471 window: &mut Window,
5472 cx: &mut Context<Self>,
5473 ) -> Option<Task<Result<()>>> {
5474 if input.chars().count() != 1 {
5475 return None;
5476 }
5477
5478 let project = self.project()?;
5479 let position = self.selections.newest_anchor().head();
5480 let (buffer, buffer_position) = self
5481 .buffer
5482 .read(cx)
5483 .text_anchor_for_position(position, cx)?;
5484
5485 let settings = language_settings::language_settings(
5486 buffer
5487 .read(cx)
5488 .language_at(buffer_position)
5489 .map(|l| l.name()),
5490 buffer.read(cx).file(),
5491 cx,
5492 );
5493 if !settings.use_on_type_format {
5494 return None;
5495 }
5496
5497 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5498 // hence we do LSP request & edit on host side only — add formats to host's history.
5499 let push_to_lsp_host_history = true;
5500 // If this is not the host, append its history with new edits.
5501 let push_to_client_history = project.read(cx).is_via_collab();
5502
5503 let on_type_formatting = project.update(cx, |project, cx| {
5504 project.on_type_format(
5505 buffer.clone(),
5506 buffer_position,
5507 input,
5508 push_to_lsp_host_history,
5509 cx,
5510 )
5511 });
5512 Some(cx.spawn_in(window, async move |editor, cx| {
5513 if let Some(transaction) = on_type_formatting.await? {
5514 if push_to_client_history {
5515 buffer.update(cx, |buffer, _| {
5516 buffer.push_transaction(transaction, Instant::now());
5517 buffer.finalize_last_transaction();
5518 });
5519 }
5520 editor.update(cx, |editor, cx| {
5521 editor.refresh_document_highlights(cx);
5522 })?;
5523 }
5524 Ok(())
5525 }))
5526 }
5527
5528 pub fn show_word_completions(
5529 &mut self,
5530 _: &ShowWordCompletions,
5531 window: &mut Window,
5532 cx: &mut Context<Self>,
5533 ) {
5534 self.open_or_update_completions_menu(
5535 Some(CompletionsMenuSource::Words {
5536 ignore_threshold: true,
5537 }),
5538 None,
5539 false,
5540 window,
5541 cx,
5542 );
5543 }
5544
5545 pub fn show_completions(
5546 &mut self,
5547 _: &ShowCompletions,
5548 window: &mut Window,
5549 cx: &mut Context<Self>,
5550 ) {
5551 self.open_or_update_completions_menu(None, None, false, window, cx);
5552 }
5553
5554 fn open_or_update_completions_menu(
5555 &mut self,
5556 requested_source: Option<CompletionsMenuSource>,
5557 trigger: Option<String>,
5558 trigger_in_words: bool,
5559 window: &mut Window,
5560 cx: &mut Context<Self>,
5561 ) {
5562 if self.pending_rename.is_some() {
5563 return;
5564 }
5565
5566 let completions_source = self
5567 .context_menu
5568 .borrow()
5569 .as_ref()
5570 .and_then(|menu| match menu {
5571 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5572 CodeContextMenu::CodeActions(_) => None,
5573 });
5574
5575 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5576
5577 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5578 // inserted and selected. To handle that case, the start of the selection is used so that
5579 // the menu starts with all choices.
5580 let position = self
5581 .selections
5582 .newest_anchor()
5583 .start
5584 .bias_right(&multibuffer_snapshot);
5585 if position.diff_base_anchor.is_some() {
5586 return;
5587 }
5588 let buffer_position = multibuffer_snapshot.anchor_before(position);
5589 let Some(buffer) = buffer_position
5590 .text_anchor
5591 .buffer_id
5592 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5593 else {
5594 return;
5595 };
5596 let buffer_snapshot = buffer.read(cx).snapshot();
5597
5598 let menu_is_open = matches!(
5599 self.context_menu.borrow().as_ref(),
5600 Some(CodeContextMenu::Completions(_))
5601 );
5602
5603 let language = buffer_snapshot
5604 .language_at(buffer_position.text_anchor)
5605 .map(|language| language.name());
5606
5607 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5608 let completion_settings = language_settings.completions.clone();
5609
5610 let show_completions_on_input = self
5611 .show_completions_on_input_override
5612 .unwrap_or(language_settings.show_completions_on_input);
5613 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5614 return;
5615 }
5616
5617 let query: Option<Arc<String>> =
5618 Self::completion_query(&multibuffer_snapshot, buffer_position)
5619 .map(|query| query.into());
5620
5621 drop(multibuffer_snapshot);
5622
5623 // Hide the current completions menu when query is empty. Without this, cached
5624 // completions from before the trigger char may be reused (#32774).
5625 if query.is_none() && menu_is_open {
5626 self.hide_context_menu(window, cx);
5627 }
5628
5629 let mut ignore_word_threshold = false;
5630 let provider = match requested_source {
5631 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5632 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5633 ignore_word_threshold = ignore_threshold;
5634 None
5635 }
5636 Some(CompletionsMenuSource::SnippetChoices)
5637 | Some(CompletionsMenuSource::SnippetsOnly) => {
5638 log::error!("bug: SnippetChoices requested_source is not handled");
5639 None
5640 }
5641 };
5642
5643 let sort_completions = provider
5644 .as_ref()
5645 .is_some_and(|provider| provider.sort_completions());
5646
5647 let filter_completions = provider
5648 .as_ref()
5649 .is_none_or(|provider| provider.filter_completions());
5650
5651 let was_snippets_only = matches!(
5652 completions_source,
5653 Some(CompletionsMenuSource::SnippetsOnly)
5654 );
5655
5656 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5657 if filter_completions {
5658 menu.filter(
5659 query.clone().unwrap_or_default(),
5660 buffer_position.text_anchor,
5661 &buffer,
5662 provider.clone(),
5663 window,
5664 cx,
5665 );
5666 }
5667 // When `is_incomplete` is false, no need to re-query completions when the current query
5668 // is a suffix of the initial query.
5669 let was_complete = !menu.is_incomplete;
5670 if was_complete && !was_snippets_only {
5671 // If the new query is a suffix of the old query (typing more characters) and
5672 // the previous result was complete, the existing completions can be filtered.
5673 //
5674 // Note that snippet completions are always complete.
5675 let query_matches = match (&menu.initial_query, &query) {
5676 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5677 (None, _) => true,
5678 _ => false,
5679 };
5680 if query_matches {
5681 let position_matches = if menu.initial_position == position {
5682 true
5683 } else {
5684 let snapshot = self.buffer.read(cx).read(cx);
5685 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5686 };
5687 if position_matches {
5688 return;
5689 }
5690 }
5691 }
5692 };
5693
5694 let Anchor {
5695 excerpt_id: buffer_excerpt_id,
5696 text_anchor: buffer_position,
5697 ..
5698 } = buffer_position;
5699
5700 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5701 buffer_snapshot.surrounding_word(buffer_position, None)
5702 {
5703 let word_to_exclude = buffer_snapshot
5704 .text_for_range(word_range.clone())
5705 .collect::<String>();
5706 (
5707 buffer_snapshot.anchor_before(word_range.start)
5708 ..buffer_snapshot.anchor_after(buffer_position),
5709 Some(word_to_exclude),
5710 )
5711 } else {
5712 (buffer_position..buffer_position, None)
5713 };
5714
5715 let show_completion_documentation = buffer_snapshot
5716 .settings_at(buffer_position, cx)
5717 .show_completion_documentation;
5718
5719 // The document can be large, so stay in reasonable bounds when searching for words,
5720 // otherwise completion pop-up might be slow to appear.
5721 const WORD_LOOKUP_ROWS: u32 = 5_000;
5722 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5723 let min_word_search = buffer_snapshot.clip_point(
5724 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5725 Bias::Left,
5726 );
5727 let max_word_search = buffer_snapshot.clip_point(
5728 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5729 Bias::Right,
5730 );
5731 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5732 ..buffer_snapshot.point_to_offset(max_word_search);
5733
5734 let skip_digits = query
5735 .as_ref()
5736 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5737
5738 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5739 trigger.as_ref().is_none_or(|trigger| {
5740 provider.is_completion_trigger(
5741 &buffer,
5742 position.text_anchor,
5743 trigger,
5744 trigger_in_words,
5745 cx,
5746 )
5747 })
5748 });
5749
5750 let provider_responses = if let Some(provider) = &provider
5751 && load_provider_completions
5752 {
5753 let trigger_character =
5754 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5755 let completion_context = CompletionContext {
5756 trigger_kind: match &trigger_character {
5757 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5758 None => CompletionTriggerKind::INVOKED,
5759 },
5760 trigger_character,
5761 };
5762
5763 provider.completions(
5764 buffer_excerpt_id,
5765 &buffer,
5766 buffer_position,
5767 completion_context,
5768 window,
5769 cx,
5770 )
5771 } else {
5772 Task::ready(Ok(Vec::new()))
5773 };
5774
5775 let load_word_completions = if !self.word_completions_enabled {
5776 false
5777 } else if requested_source
5778 == Some(CompletionsMenuSource::Words {
5779 ignore_threshold: true,
5780 })
5781 {
5782 true
5783 } else {
5784 load_provider_completions
5785 && completion_settings.words != WordsCompletionMode::Disabled
5786 && (ignore_word_threshold || {
5787 let words_min_length = completion_settings.words_min_length;
5788 // check whether word has at least `words_min_length` characters
5789 let query_chars = query.iter().flat_map(|q| q.chars());
5790 query_chars.take(words_min_length).count() == words_min_length
5791 })
5792 };
5793
5794 let mut words = if load_word_completions {
5795 cx.background_spawn({
5796 let buffer_snapshot = buffer_snapshot.clone();
5797 async move {
5798 buffer_snapshot.words_in_range(WordsQuery {
5799 fuzzy_contents: None,
5800 range: word_search_range,
5801 skip_digits,
5802 })
5803 }
5804 })
5805 } else {
5806 Task::ready(BTreeMap::default())
5807 };
5808
5809 let snippets = if let Some(provider) = &provider
5810 && provider.show_snippets()
5811 && let Some(project) = self.project()
5812 {
5813 let char_classifier = buffer_snapshot
5814 .char_classifier_at(buffer_position)
5815 .scope_context(Some(CharScopeContext::Completion));
5816 project.update(cx, |project, cx| {
5817 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5818 })
5819 } else {
5820 Task::ready(Ok(CompletionResponse {
5821 completions: Vec::new(),
5822 display_options: Default::default(),
5823 is_incomplete: false,
5824 }))
5825 };
5826
5827 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5828
5829 let id = post_inc(&mut self.next_completion_id);
5830 let task = cx.spawn_in(window, async move |editor, cx| {
5831 let Ok(()) = editor.update(cx, |this, _| {
5832 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5833 }) else {
5834 return;
5835 };
5836
5837 // TODO: Ideally completions from different sources would be selectively re-queried, so
5838 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5839 let mut completions = Vec::new();
5840 let mut is_incomplete = false;
5841 let mut display_options: Option<CompletionDisplayOptions> = None;
5842 if let Some(provider_responses) = provider_responses.await.log_err()
5843 && !provider_responses.is_empty()
5844 {
5845 for response in provider_responses {
5846 completions.extend(response.completions);
5847 is_incomplete = is_incomplete || response.is_incomplete;
5848 match display_options.as_mut() {
5849 None => {
5850 display_options = Some(response.display_options);
5851 }
5852 Some(options) => options.merge(&response.display_options),
5853 }
5854 }
5855 if completion_settings.words == WordsCompletionMode::Fallback {
5856 words = Task::ready(BTreeMap::default());
5857 }
5858 }
5859 let display_options = display_options.unwrap_or_default();
5860
5861 let mut words = words.await;
5862 if let Some(word_to_exclude) = &word_to_exclude {
5863 words.remove(word_to_exclude);
5864 }
5865 for lsp_completion in &completions {
5866 words.remove(&lsp_completion.new_text);
5867 }
5868 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5869 replace_range: word_replace_range.clone(),
5870 new_text: word.clone(),
5871 label: CodeLabel::plain(word, None),
5872 match_start: None,
5873 snippet_deduplication_key: None,
5874 icon_path: None,
5875 documentation: None,
5876 source: CompletionSource::BufferWord {
5877 word_range,
5878 resolved: false,
5879 },
5880 insert_text_mode: Some(InsertTextMode::AS_IS),
5881 confirm: None,
5882 }));
5883
5884 completions.extend(
5885 snippets
5886 .await
5887 .into_iter()
5888 .flat_map(|response| response.completions),
5889 );
5890
5891 let menu = if completions.is_empty() {
5892 None
5893 } else {
5894 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5895 let languages = editor
5896 .workspace
5897 .as_ref()
5898 .and_then(|(workspace, _)| workspace.upgrade())
5899 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5900 let menu = CompletionsMenu::new(
5901 id,
5902 requested_source.unwrap_or(if load_provider_completions {
5903 CompletionsMenuSource::Normal
5904 } else {
5905 CompletionsMenuSource::SnippetsOnly
5906 }),
5907 sort_completions,
5908 show_completion_documentation,
5909 position,
5910 query.clone(),
5911 is_incomplete,
5912 buffer.clone(),
5913 completions.into(),
5914 editor
5915 .context_menu()
5916 .borrow_mut()
5917 .as_ref()
5918 .map(|menu| menu.primary_scroll_handle()),
5919 display_options,
5920 snippet_sort_order,
5921 languages,
5922 language,
5923 cx,
5924 );
5925
5926 let query = if filter_completions { query } else { None };
5927 let matches_task = menu.do_async_filtering(
5928 query.unwrap_or_default(),
5929 buffer_position,
5930 &buffer,
5931 cx,
5932 );
5933 (menu, matches_task)
5934 }) else {
5935 return;
5936 };
5937
5938 let matches = matches_task.await;
5939
5940 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5941 // Newer menu already set, so exit.
5942 if let Some(CodeContextMenu::Completions(prev_menu)) =
5943 editor.context_menu.borrow().as_ref()
5944 && prev_menu.id > id
5945 {
5946 return;
5947 };
5948
5949 // Only valid to take prev_menu because either the new menu is immediately set
5950 // below, or the menu is hidden.
5951 if let Some(CodeContextMenu::Completions(prev_menu)) =
5952 editor.context_menu.borrow_mut().take()
5953 {
5954 let position_matches =
5955 if prev_menu.initial_position == menu.initial_position {
5956 true
5957 } else {
5958 let snapshot = editor.buffer.read(cx).read(cx);
5959 prev_menu.initial_position.to_offset(&snapshot)
5960 == menu.initial_position.to_offset(&snapshot)
5961 };
5962 if position_matches {
5963 // Preserve markdown cache before `set_filter_results` because it will
5964 // try to populate the documentation cache.
5965 menu.preserve_markdown_cache(prev_menu);
5966 }
5967 };
5968
5969 menu.set_filter_results(matches, provider, window, cx);
5970 }) else {
5971 return;
5972 };
5973
5974 menu.visible().then_some(menu)
5975 };
5976
5977 editor
5978 .update_in(cx, |editor, window, cx| {
5979 if editor.focus_handle.is_focused(window)
5980 && let Some(menu) = menu
5981 {
5982 *editor.context_menu.borrow_mut() =
5983 Some(CodeContextMenu::Completions(menu));
5984
5985 crate::hover_popover::hide_hover(editor, cx);
5986 if editor.show_edit_predictions_in_menu() {
5987 editor.update_visible_edit_prediction(window, cx);
5988 } else {
5989 editor.discard_edit_prediction(false, cx);
5990 }
5991
5992 cx.notify();
5993 return;
5994 }
5995
5996 if editor.completion_tasks.len() <= 1 {
5997 // If there are no more completion tasks and the last menu was empty, we should hide it.
5998 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5999 // If it was already hidden and we don't show edit predictions in the menu,
6000 // we should also show the edit prediction when available.
6001 if was_hidden && editor.show_edit_predictions_in_menu() {
6002 editor.update_visible_edit_prediction(window, cx);
6003 }
6004 }
6005 })
6006 .ok();
6007 });
6008
6009 self.completion_tasks.push((id, task));
6010 }
6011
6012 #[cfg(feature = "test-support")]
6013 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6014 let menu = self.context_menu.borrow();
6015 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6016 let completions = menu.completions.borrow();
6017 Some(completions.to_vec())
6018 } else {
6019 None
6020 }
6021 }
6022
6023 pub fn with_completions_menu_matching_id<R>(
6024 &self,
6025 id: CompletionId,
6026 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6027 ) -> R {
6028 let mut context_menu = self.context_menu.borrow_mut();
6029 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6030 return f(None);
6031 };
6032 if completions_menu.id != id {
6033 return f(None);
6034 }
6035 f(Some(completions_menu))
6036 }
6037
6038 pub fn confirm_completion(
6039 &mut self,
6040 action: &ConfirmCompletion,
6041 window: &mut Window,
6042 cx: &mut Context<Self>,
6043 ) -> Option<Task<Result<()>>> {
6044 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6045 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6046 }
6047
6048 pub fn confirm_completion_insert(
6049 &mut self,
6050 _: &ConfirmCompletionInsert,
6051 window: &mut Window,
6052 cx: &mut Context<Self>,
6053 ) -> Option<Task<Result<()>>> {
6054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6055 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6056 }
6057
6058 pub fn confirm_completion_replace(
6059 &mut self,
6060 _: &ConfirmCompletionReplace,
6061 window: &mut Window,
6062 cx: &mut Context<Self>,
6063 ) -> Option<Task<Result<()>>> {
6064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6065 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6066 }
6067
6068 pub fn compose_completion(
6069 &mut self,
6070 action: &ComposeCompletion,
6071 window: &mut Window,
6072 cx: &mut Context<Self>,
6073 ) -> Option<Task<Result<()>>> {
6074 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6075 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6076 }
6077
6078 fn do_completion(
6079 &mut self,
6080 item_ix: Option<usize>,
6081 intent: CompletionIntent,
6082 window: &mut Window,
6083 cx: &mut Context<Editor>,
6084 ) -> Option<Task<Result<()>>> {
6085 use language::ToOffset as _;
6086
6087 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6088 else {
6089 return None;
6090 };
6091
6092 let candidate_id = {
6093 let entries = completions_menu.entries.borrow();
6094 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6095 if self.show_edit_predictions_in_menu() {
6096 self.discard_edit_prediction(true, cx);
6097 }
6098 mat.candidate_id
6099 };
6100
6101 let completion = completions_menu
6102 .completions
6103 .borrow()
6104 .get(candidate_id)?
6105 .clone();
6106 cx.stop_propagation();
6107
6108 let buffer_handle = completions_menu.buffer.clone();
6109
6110 let CompletionEdit {
6111 new_text,
6112 snippet,
6113 replace_range,
6114 } = process_completion_for_edit(
6115 &completion,
6116 intent,
6117 &buffer_handle,
6118 &completions_menu.initial_position.text_anchor,
6119 cx,
6120 );
6121
6122 let buffer = buffer_handle.read(cx);
6123 let snapshot = self.buffer.read(cx).snapshot(cx);
6124 let newest_anchor = self.selections.newest_anchor();
6125 let replace_range_multibuffer = {
6126 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6127 excerpt.map_range_from_buffer(replace_range.clone())
6128 };
6129 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6130 return None;
6131 }
6132
6133 let old_text = buffer
6134 .text_for_range(replace_range.clone())
6135 .collect::<String>();
6136 let lookbehind = newest_anchor
6137 .start
6138 .text_anchor
6139 .to_offset(buffer)
6140 .saturating_sub(replace_range.start.0);
6141 let lookahead = replace_range
6142 .end
6143 .0
6144 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6145 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6146 let suffix = &old_text[lookbehind.min(old_text.len())..];
6147
6148 let selections = self
6149 .selections
6150 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6151 let mut ranges = Vec::new();
6152 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6153
6154 for selection in &selections {
6155 let range = if selection.id == newest_anchor.id {
6156 replace_range_multibuffer.clone()
6157 } else {
6158 let mut range = selection.range();
6159
6160 // if prefix is present, don't duplicate it
6161 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6162 range.start = range.start.saturating_sub_usize(lookbehind);
6163
6164 // if suffix is also present, mimic the newest cursor and replace it
6165 if selection.id != newest_anchor.id
6166 && snapshot.contains_str_at(range.end, suffix)
6167 {
6168 range.end += lookahead;
6169 }
6170 }
6171 range
6172 };
6173
6174 ranges.push(range.clone());
6175
6176 if !self.linked_edit_ranges.is_empty() {
6177 let start_anchor = snapshot.anchor_before(range.start);
6178 let end_anchor = snapshot.anchor_after(range.end);
6179 if let Some(ranges) = self
6180 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6181 {
6182 for (buffer, edits) in ranges {
6183 linked_edits
6184 .entry(buffer.clone())
6185 .or_default()
6186 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6187 }
6188 }
6189 }
6190 }
6191
6192 let common_prefix_len = old_text
6193 .chars()
6194 .zip(new_text.chars())
6195 .take_while(|(a, b)| a == b)
6196 .map(|(a, _)| a.len_utf8())
6197 .sum::<usize>();
6198
6199 cx.emit(EditorEvent::InputHandled {
6200 utf16_range_to_replace: None,
6201 text: new_text[common_prefix_len..].into(),
6202 });
6203
6204 self.transact(window, cx, |editor, window, cx| {
6205 if let Some(mut snippet) = snippet {
6206 snippet.text = new_text.to_string();
6207 editor
6208 .insert_snippet(&ranges, snippet, window, cx)
6209 .log_err();
6210 } else {
6211 editor.buffer.update(cx, |multi_buffer, cx| {
6212 let auto_indent = match completion.insert_text_mode {
6213 Some(InsertTextMode::AS_IS) => None,
6214 _ => editor.autoindent_mode.clone(),
6215 };
6216 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6217 multi_buffer.edit(edits, auto_indent, cx);
6218 });
6219 }
6220 for (buffer, edits) in linked_edits {
6221 buffer.update(cx, |buffer, cx| {
6222 let snapshot = buffer.snapshot();
6223 let edits = edits
6224 .into_iter()
6225 .map(|(range, text)| {
6226 use text::ToPoint as TP;
6227 let end_point = TP::to_point(&range.end, &snapshot);
6228 let start_point = TP::to_point(&range.start, &snapshot);
6229 (start_point..end_point, text)
6230 })
6231 .sorted_by_key(|(range, _)| range.start);
6232 buffer.edit(edits, None, cx);
6233 })
6234 }
6235
6236 editor.refresh_edit_prediction(true, false, window, cx);
6237 });
6238 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6239
6240 let show_new_completions_on_confirm = completion
6241 .confirm
6242 .as_ref()
6243 .is_some_and(|confirm| confirm(intent, window, cx));
6244 if show_new_completions_on_confirm {
6245 self.open_or_update_completions_menu(None, None, false, window, cx);
6246 }
6247
6248 let provider = self.completion_provider.as_ref()?;
6249
6250 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6251 let command = lsp_store.as_ref().and_then(|lsp_store| {
6252 let CompletionSource::Lsp {
6253 lsp_completion,
6254 server_id,
6255 ..
6256 } = &completion.source
6257 else {
6258 return None;
6259 };
6260 let lsp_command = lsp_completion.command.as_ref()?;
6261 let available_commands = lsp_store
6262 .read(cx)
6263 .lsp_server_capabilities
6264 .get(server_id)
6265 .and_then(|server_capabilities| {
6266 server_capabilities
6267 .execute_command_provider
6268 .as_ref()
6269 .map(|options| options.commands.as_slice())
6270 })?;
6271 if available_commands.contains(&lsp_command.command) {
6272 Some(CodeAction {
6273 server_id: *server_id,
6274 range: language::Anchor::MIN..language::Anchor::MIN,
6275 lsp_action: LspAction::Command(lsp_command.clone()),
6276 resolved: false,
6277 })
6278 } else {
6279 None
6280 }
6281 });
6282
6283 drop(completion);
6284 let apply_edits = provider.apply_additional_edits_for_completion(
6285 buffer_handle.clone(),
6286 completions_menu.completions.clone(),
6287 candidate_id,
6288 true,
6289 cx,
6290 );
6291
6292 let editor_settings = EditorSettings::get_global(cx);
6293 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6294 // After the code completion is finished, users often want to know what signatures are needed.
6295 // so we should automatically call signature_help
6296 self.show_signature_help(&ShowSignatureHelp, window, cx);
6297 }
6298
6299 Some(cx.spawn_in(window, async move |editor, cx| {
6300 apply_edits.await?;
6301
6302 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6303 let title = command.lsp_action.title().to_owned();
6304 let project_transaction = lsp_store
6305 .update(cx, |lsp_store, cx| {
6306 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6307 })
6308 .await
6309 .context("applying post-completion command")?;
6310 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6311 Self::open_project_transaction(
6312 &editor,
6313 workspace.downgrade(),
6314 project_transaction,
6315 title,
6316 cx,
6317 )
6318 .await?;
6319 }
6320 }
6321
6322 Ok(())
6323 }))
6324 }
6325
6326 pub fn toggle_code_actions(
6327 &mut self,
6328 action: &ToggleCodeActions,
6329 window: &mut Window,
6330 cx: &mut Context<Self>,
6331 ) {
6332 let quick_launch = action.quick_launch;
6333 let mut context_menu = self.context_menu.borrow_mut();
6334 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6335 if code_actions.deployed_from == action.deployed_from {
6336 // Toggle if we're selecting the same one
6337 *context_menu = None;
6338 cx.notify();
6339 return;
6340 } else {
6341 // Otherwise, clear it and start a new one
6342 *context_menu = None;
6343 cx.notify();
6344 }
6345 }
6346 drop(context_menu);
6347 let snapshot = self.snapshot(window, cx);
6348 let deployed_from = action.deployed_from.clone();
6349 let action = action.clone();
6350 self.completion_tasks.clear();
6351 self.discard_edit_prediction(false, cx);
6352
6353 let multibuffer_point = match &action.deployed_from {
6354 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6355 DisplayPoint::new(*row, 0).to_point(&snapshot)
6356 }
6357 _ => self
6358 .selections
6359 .newest::<Point>(&snapshot.display_snapshot)
6360 .head(),
6361 };
6362 let Some((buffer, buffer_row)) = snapshot
6363 .buffer_snapshot()
6364 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6365 .and_then(|(buffer_snapshot, range)| {
6366 self.buffer()
6367 .read(cx)
6368 .buffer(buffer_snapshot.remote_id())
6369 .map(|buffer| (buffer, range.start.row))
6370 })
6371 else {
6372 return;
6373 };
6374 let buffer_id = buffer.read(cx).remote_id();
6375 let tasks = self
6376 .tasks
6377 .get(&(buffer_id, buffer_row))
6378 .map(|t| Arc::new(t.to_owned()));
6379
6380 if !self.focus_handle.is_focused(window) {
6381 return;
6382 }
6383 let project = self.project.clone();
6384
6385 let code_actions_task = match deployed_from {
6386 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6387 _ => self.code_actions(buffer_row, window, cx),
6388 };
6389
6390 let runnable_task = match deployed_from {
6391 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6392 _ => {
6393 let mut task_context_task = Task::ready(None);
6394 if let Some(tasks) = &tasks
6395 && let Some(project) = project
6396 {
6397 task_context_task =
6398 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6399 }
6400
6401 cx.spawn_in(window, {
6402 let buffer = buffer.clone();
6403 async move |editor, cx| {
6404 let task_context = task_context_task.await;
6405
6406 let resolved_tasks =
6407 tasks
6408 .zip(task_context.clone())
6409 .map(|(tasks, task_context)| ResolvedTasks {
6410 templates: tasks.resolve(&task_context).collect(),
6411 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6412 multibuffer_point.row,
6413 tasks.column,
6414 )),
6415 });
6416 let debug_scenarios = editor
6417 .update(cx, |editor, cx| {
6418 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6419 })?
6420 .await;
6421 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6422 }
6423 })
6424 }
6425 };
6426
6427 cx.spawn_in(window, async move |editor, cx| {
6428 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6429 let code_actions = code_actions_task.await;
6430 let spawn_straight_away = quick_launch
6431 && resolved_tasks
6432 .as_ref()
6433 .is_some_and(|tasks| tasks.templates.len() == 1)
6434 && code_actions
6435 .as_ref()
6436 .is_none_or(|actions| actions.is_empty())
6437 && debug_scenarios.is_empty();
6438
6439 editor.update_in(cx, |editor, window, cx| {
6440 crate::hover_popover::hide_hover(editor, cx);
6441 let actions = CodeActionContents::new(
6442 resolved_tasks,
6443 code_actions,
6444 debug_scenarios,
6445 task_context.unwrap_or_default(),
6446 );
6447
6448 // Don't show the menu if there are no actions available
6449 if actions.is_empty() {
6450 cx.notify();
6451 return Task::ready(Ok(()));
6452 }
6453
6454 *editor.context_menu.borrow_mut() =
6455 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6456 buffer,
6457 actions,
6458 selected_item: Default::default(),
6459 scroll_handle: UniformListScrollHandle::default(),
6460 deployed_from,
6461 }));
6462 cx.notify();
6463 if spawn_straight_away
6464 && let Some(task) = editor.confirm_code_action(
6465 &ConfirmCodeAction { item_ix: Some(0) },
6466 window,
6467 cx,
6468 )
6469 {
6470 return task;
6471 }
6472
6473 Task::ready(Ok(()))
6474 })
6475 })
6476 .detach_and_log_err(cx);
6477 }
6478
6479 fn debug_scenarios(
6480 &mut self,
6481 resolved_tasks: &Option<ResolvedTasks>,
6482 buffer: &Entity<Buffer>,
6483 cx: &mut App,
6484 ) -> Task<Vec<task::DebugScenario>> {
6485 maybe!({
6486 let project = self.project()?;
6487 let dap_store = project.read(cx).dap_store();
6488 let mut scenarios = vec![];
6489 let resolved_tasks = resolved_tasks.as_ref()?;
6490 let buffer = buffer.read(cx);
6491 let language = buffer.language()?;
6492 let file = buffer.file();
6493 let debug_adapter = language_settings(language.name().into(), file, cx)
6494 .debuggers
6495 .first()
6496 .map(SharedString::from)
6497 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6498
6499 dap_store.update(cx, |dap_store, cx| {
6500 for (_, task) in &resolved_tasks.templates {
6501 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6502 task.original_task().clone(),
6503 debug_adapter.clone().into(),
6504 task.display_label().to_owned().into(),
6505 cx,
6506 );
6507 scenarios.push(maybe_scenario);
6508 }
6509 });
6510 Some(cx.background_spawn(async move {
6511 futures::future::join_all(scenarios)
6512 .await
6513 .into_iter()
6514 .flatten()
6515 .collect::<Vec<_>>()
6516 }))
6517 })
6518 .unwrap_or_else(|| Task::ready(vec![]))
6519 }
6520
6521 fn code_actions(
6522 &mut self,
6523 buffer_row: u32,
6524 window: &mut Window,
6525 cx: &mut Context<Self>,
6526 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6527 let mut task = self.code_actions_task.take();
6528 cx.spawn_in(window, async move |editor, cx| {
6529 while let Some(prev_task) = task {
6530 prev_task.await.log_err();
6531 task = editor
6532 .update(cx, |this, _| this.code_actions_task.take())
6533 .ok()?;
6534 }
6535
6536 editor
6537 .update(cx, |editor, cx| {
6538 editor
6539 .available_code_actions
6540 .clone()
6541 .and_then(|(location, code_actions)| {
6542 let snapshot = location.buffer.read(cx).snapshot();
6543 let point_range = location.range.to_point(&snapshot);
6544 let point_range = point_range.start.row..=point_range.end.row;
6545 if point_range.contains(&buffer_row) {
6546 Some(code_actions)
6547 } else {
6548 None
6549 }
6550 })
6551 })
6552 .ok()
6553 .flatten()
6554 })
6555 }
6556
6557 pub fn confirm_code_action(
6558 &mut self,
6559 action: &ConfirmCodeAction,
6560 window: &mut Window,
6561 cx: &mut Context<Self>,
6562 ) -> Option<Task<Result<()>>> {
6563 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6564
6565 let actions_menu =
6566 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6567 menu
6568 } else {
6569 return None;
6570 };
6571
6572 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6573 let action = actions_menu.actions.get(action_ix)?;
6574 let title = action.label();
6575 let buffer = actions_menu.buffer;
6576 let workspace = self.workspace()?;
6577
6578 match action {
6579 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6580 workspace.update(cx, |workspace, cx| {
6581 workspace.schedule_resolved_task(
6582 task_source_kind,
6583 resolved_task,
6584 false,
6585 window,
6586 cx,
6587 );
6588
6589 Some(Task::ready(Ok(())))
6590 })
6591 }
6592 CodeActionsItem::CodeAction {
6593 excerpt_id,
6594 action,
6595 provider,
6596 } => {
6597 let apply_code_action =
6598 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6599 let workspace = workspace.downgrade();
6600 Some(cx.spawn_in(window, async move |editor, cx| {
6601 let project_transaction = apply_code_action.await?;
6602 Self::open_project_transaction(
6603 &editor,
6604 workspace,
6605 project_transaction,
6606 title,
6607 cx,
6608 )
6609 .await
6610 }))
6611 }
6612 CodeActionsItem::DebugScenario(scenario) => {
6613 let context = actions_menu.actions.context;
6614
6615 workspace.update(cx, |workspace, cx| {
6616 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6617 workspace.start_debug_session(
6618 scenario,
6619 context,
6620 Some(buffer),
6621 None,
6622 window,
6623 cx,
6624 );
6625 });
6626 Some(Task::ready(Ok(())))
6627 }
6628 }
6629 }
6630
6631 fn open_transaction_for_hidden_buffers(
6632 workspace: Entity<Workspace>,
6633 transaction: ProjectTransaction,
6634 title: String,
6635 window: &mut Window,
6636 cx: &mut Context<Self>,
6637 ) {
6638 if transaction.0.is_empty() {
6639 return;
6640 }
6641
6642 let edited_buffers_already_open = {
6643 let other_editors: Vec<Entity<Editor>> = workspace
6644 .read(cx)
6645 .panes()
6646 .iter()
6647 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6648 .filter(|editor| editor.entity_id() != cx.entity_id())
6649 .collect();
6650
6651 transaction.0.keys().all(|buffer| {
6652 other_editors.iter().any(|editor| {
6653 let multi_buffer = editor.read(cx).buffer();
6654 multi_buffer.read(cx).is_singleton()
6655 && multi_buffer
6656 .read(cx)
6657 .as_singleton()
6658 .map_or(false, |singleton| {
6659 singleton.entity_id() == buffer.entity_id()
6660 })
6661 })
6662 })
6663 };
6664 if !edited_buffers_already_open {
6665 let workspace = workspace.downgrade();
6666 cx.defer_in(window, move |_, window, cx| {
6667 cx.spawn_in(window, async move |editor, cx| {
6668 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6669 .await
6670 .ok()
6671 })
6672 .detach();
6673 });
6674 }
6675 }
6676
6677 pub async fn open_project_transaction(
6678 editor: &WeakEntity<Editor>,
6679 workspace: WeakEntity<Workspace>,
6680 transaction: ProjectTransaction,
6681 title: String,
6682 cx: &mut AsyncWindowContext,
6683 ) -> Result<()> {
6684 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6685 cx.update(|_, cx| {
6686 entries.sort_unstable_by_key(|(buffer, _)| {
6687 buffer.read(cx).file().map(|f| f.path().clone())
6688 });
6689 })?;
6690 if entries.is_empty() {
6691 return Ok(());
6692 }
6693
6694 // If the project transaction's edits are all contained within this editor, then
6695 // avoid opening a new editor to display them.
6696
6697 if let [(buffer, transaction)] = &*entries {
6698 let excerpt = editor.update(cx, |editor, cx| {
6699 editor
6700 .buffer()
6701 .read(cx)
6702 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6703 })?;
6704 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6705 && excerpted_buffer == *buffer
6706 {
6707 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6708 let excerpt_range = excerpt_range.to_offset(buffer);
6709 buffer
6710 .edited_ranges_for_transaction::<usize>(transaction)
6711 .all(|range| {
6712 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6713 })
6714 });
6715
6716 if all_edits_within_excerpt {
6717 return Ok(());
6718 }
6719 }
6720 }
6721
6722 let mut ranges_to_highlight = Vec::new();
6723 let excerpt_buffer = cx.new(|cx| {
6724 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6725 for (buffer_handle, transaction) in &entries {
6726 let edited_ranges = buffer_handle
6727 .read(cx)
6728 .edited_ranges_for_transaction::<Point>(transaction)
6729 .collect::<Vec<_>>();
6730 let (ranges, _) = multibuffer.set_excerpts_for_path(
6731 PathKey::for_buffer(buffer_handle, cx),
6732 buffer_handle.clone(),
6733 edited_ranges,
6734 multibuffer_context_lines(cx),
6735 cx,
6736 );
6737
6738 ranges_to_highlight.extend(ranges);
6739 }
6740 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6741 multibuffer
6742 });
6743
6744 workspace.update_in(cx, |workspace, window, cx| {
6745 let project = workspace.project().clone();
6746 let editor =
6747 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6748 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6749 editor.update(cx, |editor, cx| {
6750 editor.highlight_background::<Self>(
6751 &ranges_to_highlight,
6752 |_, theme| theme.colors().editor_highlighted_line_background,
6753 cx,
6754 );
6755 });
6756 })?;
6757
6758 Ok(())
6759 }
6760
6761 pub fn clear_code_action_providers(&mut self) {
6762 self.code_action_providers.clear();
6763 self.available_code_actions.take();
6764 }
6765
6766 pub fn add_code_action_provider(
6767 &mut self,
6768 provider: Rc<dyn CodeActionProvider>,
6769 window: &mut Window,
6770 cx: &mut Context<Self>,
6771 ) {
6772 if self
6773 .code_action_providers
6774 .iter()
6775 .any(|existing_provider| existing_provider.id() == provider.id())
6776 {
6777 return;
6778 }
6779
6780 self.code_action_providers.push(provider);
6781 self.refresh_code_actions(window, cx);
6782 }
6783
6784 pub fn remove_code_action_provider(
6785 &mut self,
6786 id: Arc<str>,
6787 window: &mut Window,
6788 cx: &mut Context<Self>,
6789 ) {
6790 self.code_action_providers
6791 .retain(|provider| provider.id() != id);
6792 self.refresh_code_actions(window, cx);
6793 }
6794
6795 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6796 !self.code_action_providers.is_empty()
6797 && EditorSettings::get_global(cx).toolbar.code_actions
6798 }
6799
6800 pub fn has_available_code_actions(&self) -> bool {
6801 self.available_code_actions
6802 .as_ref()
6803 .is_some_and(|(_, actions)| !actions.is_empty())
6804 }
6805
6806 fn render_inline_code_actions(
6807 &self,
6808 icon_size: ui::IconSize,
6809 display_row: DisplayRow,
6810 is_active: bool,
6811 cx: &mut Context<Self>,
6812 ) -> AnyElement {
6813 let show_tooltip = !self.context_menu_visible();
6814 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6815 .icon_size(icon_size)
6816 .shape(ui::IconButtonShape::Square)
6817 .icon_color(ui::Color::Hidden)
6818 .toggle_state(is_active)
6819 .when(show_tooltip, |this| {
6820 this.tooltip({
6821 let focus_handle = self.focus_handle.clone();
6822 move |_window, cx| {
6823 Tooltip::for_action_in(
6824 "Toggle Code Actions",
6825 &ToggleCodeActions {
6826 deployed_from: None,
6827 quick_launch: false,
6828 },
6829 &focus_handle,
6830 cx,
6831 )
6832 }
6833 })
6834 })
6835 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6836 window.focus(&editor.focus_handle(cx), cx);
6837 editor.toggle_code_actions(
6838 &crate::actions::ToggleCodeActions {
6839 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6840 display_row,
6841 )),
6842 quick_launch: false,
6843 },
6844 window,
6845 cx,
6846 );
6847 }))
6848 .into_any_element()
6849 }
6850
6851 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6852 &self.context_menu
6853 }
6854
6855 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6856 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6857 cx.background_executor()
6858 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6859 .await;
6860
6861 let (start_buffer, start, _, end, newest_selection) = this
6862 .update(cx, |this, cx| {
6863 let newest_selection = this.selections.newest_anchor().clone();
6864 if newest_selection.head().diff_base_anchor.is_some() {
6865 return None;
6866 }
6867 let display_snapshot = this.display_snapshot(cx);
6868 let newest_selection_adjusted =
6869 this.selections.newest_adjusted(&display_snapshot);
6870 let buffer = this.buffer.read(cx);
6871
6872 let (start_buffer, start) =
6873 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6874 let (end_buffer, end) =
6875 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6876
6877 Some((start_buffer, start, end_buffer, end, newest_selection))
6878 })?
6879 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6880 .context(
6881 "Expected selection to lie in a single buffer when refreshing code actions",
6882 )?;
6883 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6884 let providers = this.code_action_providers.clone();
6885 let tasks = this
6886 .code_action_providers
6887 .iter()
6888 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6889 .collect::<Vec<_>>();
6890 (providers, tasks)
6891 })?;
6892
6893 let mut actions = Vec::new();
6894 for (provider, provider_actions) in
6895 providers.into_iter().zip(future::join_all(tasks).await)
6896 {
6897 if let Some(provider_actions) = provider_actions.log_err() {
6898 actions.extend(provider_actions.into_iter().map(|action| {
6899 AvailableCodeAction {
6900 excerpt_id: newest_selection.start.excerpt_id,
6901 action,
6902 provider: provider.clone(),
6903 }
6904 }));
6905 }
6906 }
6907
6908 this.update(cx, |this, cx| {
6909 this.available_code_actions = if actions.is_empty() {
6910 None
6911 } else {
6912 Some((
6913 Location {
6914 buffer: start_buffer,
6915 range: start..end,
6916 },
6917 actions.into(),
6918 ))
6919 };
6920 cx.notify();
6921 })
6922 }));
6923 }
6924
6925 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6926 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6927 self.show_git_blame_inline = false;
6928
6929 self.show_git_blame_inline_delay_task =
6930 Some(cx.spawn_in(window, async move |this, cx| {
6931 cx.background_executor().timer(delay).await;
6932
6933 this.update(cx, |this, cx| {
6934 this.show_git_blame_inline = true;
6935 cx.notify();
6936 })
6937 .log_err();
6938 }));
6939 }
6940 }
6941
6942 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6943 let snapshot = self.snapshot(window, cx);
6944 let cursor = self
6945 .selections
6946 .newest::<Point>(&snapshot.display_snapshot)
6947 .head();
6948 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6949 else {
6950 return;
6951 };
6952
6953 if self.blame.is_none() {
6954 self.start_git_blame(true, window, cx);
6955 }
6956 let Some(blame) = self.blame.as_ref() else {
6957 return;
6958 };
6959
6960 let row_info = RowInfo {
6961 buffer_id: Some(buffer.remote_id()),
6962 buffer_row: Some(point.row),
6963 ..Default::default()
6964 };
6965 let Some((buffer, blame_entry)) = blame
6966 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6967 .flatten()
6968 else {
6969 return;
6970 };
6971
6972 let anchor = self.selections.newest_anchor().head();
6973 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6974 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6975 self.show_blame_popover(
6976 buffer,
6977 &blame_entry,
6978 position + last_bounds.origin,
6979 true,
6980 cx,
6981 );
6982 };
6983 }
6984
6985 fn show_blame_popover(
6986 &mut self,
6987 buffer: BufferId,
6988 blame_entry: &BlameEntry,
6989 position: gpui::Point<Pixels>,
6990 ignore_timeout: bool,
6991 cx: &mut Context<Self>,
6992 ) {
6993 if let Some(state) = &mut self.inline_blame_popover {
6994 state.hide_task.take();
6995 } else {
6996 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6997 let blame_entry = blame_entry.clone();
6998 let show_task = cx.spawn(async move |editor, cx| {
6999 if !ignore_timeout {
7000 cx.background_executor()
7001 .timer(std::time::Duration::from_millis(blame_popover_delay))
7002 .await;
7003 }
7004 editor
7005 .update(cx, |editor, cx| {
7006 editor.inline_blame_popover_show_task.take();
7007 let Some(blame) = editor.blame.as_ref() else {
7008 return;
7009 };
7010 let blame = blame.read(cx);
7011 let details = blame.details_for_entry(buffer, &blame_entry);
7012 let markdown = cx.new(|cx| {
7013 Markdown::new(
7014 details
7015 .as_ref()
7016 .map(|message| message.message.clone())
7017 .unwrap_or_default(),
7018 None,
7019 None,
7020 cx,
7021 )
7022 });
7023 editor.inline_blame_popover = Some(InlineBlamePopover {
7024 position,
7025 hide_task: None,
7026 popover_bounds: None,
7027 popover_state: InlineBlamePopoverState {
7028 scroll_handle: ScrollHandle::new(),
7029 commit_message: details,
7030 markdown,
7031 },
7032 keyboard_grace: ignore_timeout,
7033 });
7034 cx.notify();
7035 })
7036 .ok();
7037 });
7038 self.inline_blame_popover_show_task = Some(show_task);
7039 }
7040 }
7041
7042 pub fn has_mouse_context_menu(&self) -> bool {
7043 self.mouse_context_menu.is_some()
7044 }
7045
7046 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7047 self.inline_blame_popover_show_task.take();
7048 if let Some(state) = &mut self.inline_blame_popover {
7049 let hide_task = cx.spawn(async move |editor, cx| {
7050 if !ignore_timeout {
7051 cx.background_executor()
7052 .timer(std::time::Duration::from_millis(100))
7053 .await;
7054 }
7055 editor
7056 .update(cx, |editor, cx| {
7057 editor.inline_blame_popover.take();
7058 cx.notify();
7059 })
7060 .ok();
7061 });
7062 state.hide_task = Some(hide_task);
7063 true
7064 } else {
7065 false
7066 }
7067 }
7068
7069 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7070 if self.pending_rename.is_some() {
7071 return None;
7072 }
7073
7074 let provider = self.semantics_provider.clone()?;
7075 let buffer = self.buffer.read(cx);
7076 let newest_selection = self.selections.newest_anchor().clone();
7077 let cursor_position = newest_selection.head();
7078 let (cursor_buffer, cursor_buffer_position) =
7079 buffer.text_anchor_for_position(cursor_position, cx)?;
7080 let (tail_buffer, tail_buffer_position) =
7081 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7082 if cursor_buffer != tail_buffer {
7083 return None;
7084 }
7085
7086 let snapshot = cursor_buffer.read(cx).snapshot();
7087 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7088 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7089 if start_word_range != end_word_range {
7090 self.document_highlights_task.take();
7091 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7092 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7093 return None;
7094 }
7095
7096 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7097 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7098 cx.background_executor()
7099 .timer(Duration::from_millis(debounce))
7100 .await;
7101
7102 let highlights = if let Some(highlights) = cx.update(|cx| {
7103 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7104 }) {
7105 highlights.await.log_err()
7106 } else {
7107 None
7108 };
7109
7110 if let Some(highlights) = highlights {
7111 this.update(cx, |this, cx| {
7112 if this.pending_rename.is_some() {
7113 return;
7114 }
7115
7116 let buffer = this.buffer.read(cx);
7117 if buffer
7118 .text_anchor_for_position(cursor_position, cx)
7119 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7120 {
7121 return;
7122 }
7123
7124 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7125 let mut write_ranges = Vec::new();
7126 let mut read_ranges = Vec::new();
7127 for highlight in highlights {
7128 let buffer_id = cursor_buffer.read(cx).remote_id();
7129 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7130 {
7131 let start = highlight
7132 .range
7133 .start
7134 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7135 let end = highlight
7136 .range
7137 .end
7138 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7139 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7140 continue;
7141 }
7142
7143 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7144 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7145 write_ranges.push(range);
7146 } else {
7147 read_ranges.push(range);
7148 }
7149 }
7150 }
7151
7152 this.highlight_background::<DocumentHighlightRead>(
7153 &read_ranges,
7154 |_, theme| theme.colors().editor_document_highlight_read_background,
7155 cx,
7156 );
7157 this.highlight_background::<DocumentHighlightWrite>(
7158 &write_ranges,
7159 |_, theme| theme.colors().editor_document_highlight_write_background,
7160 cx,
7161 );
7162 cx.notify();
7163 })
7164 .log_err();
7165 }
7166 }));
7167 None
7168 }
7169
7170 fn prepare_highlight_query_from_selection(
7171 &mut self,
7172 window: &Window,
7173 cx: &mut Context<Editor>,
7174 ) -> Option<(String, Range<Anchor>)> {
7175 if matches!(self.mode, EditorMode::SingleLine) {
7176 return None;
7177 }
7178 if !EditorSettings::get_global(cx).selection_highlight {
7179 return None;
7180 }
7181 if self.selections.count() != 1 || self.selections.line_mode() {
7182 return None;
7183 }
7184 let snapshot = self.snapshot(window, cx);
7185 let selection = self.selections.newest::<Point>(&snapshot);
7186 // If the selection spans multiple rows OR it is empty
7187 if selection.start.row != selection.end.row
7188 || selection.start.column == selection.end.column
7189 {
7190 return None;
7191 }
7192 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7193 let query = snapshot
7194 .buffer_snapshot()
7195 .text_for_range(selection_anchor_range.clone())
7196 .collect::<String>();
7197 if query.trim().is_empty() {
7198 return None;
7199 }
7200 Some((query, selection_anchor_range))
7201 }
7202
7203 #[ztracing::instrument(skip_all)]
7204 fn update_selection_occurrence_highlights(
7205 &mut self,
7206 query_text: String,
7207 query_range: Range<Anchor>,
7208 multi_buffer_range_to_query: Range<Point>,
7209 use_debounce: bool,
7210 window: &mut Window,
7211 cx: &mut Context<Editor>,
7212 ) -> Task<()> {
7213 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7214 cx.spawn_in(window, async move |editor, cx| {
7215 if use_debounce {
7216 cx.background_executor()
7217 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7218 .await;
7219 }
7220 let match_task = cx.background_spawn(async move {
7221 let buffer_ranges = multi_buffer_snapshot
7222 .range_to_buffer_ranges(multi_buffer_range_to_query)
7223 .into_iter()
7224 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7225 let mut match_ranges = Vec::new();
7226 let Ok(regex) = project::search::SearchQuery::text(
7227 query_text.clone(),
7228 false,
7229 false,
7230 false,
7231 Default::default(),
7232 Default::default(),
7233 false,
7234 None,
7235 ) else {
7236 return Vec::default();
7237 };
7238 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7239 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7240 match_ranges.extend(
7241 regex
7242 .search(
7243 buffer_snapshot,
7244 Some(search_range.start.0..search_range.end.0),
7245 )
7246 .await
7247 .into_iter()
7248 .filter_map(|match_range| {
7249 let match_start = buffer_snapshot
7250 .anchor_after(search_range.start + match_range.start);
7251 let match_end = buffer_snapshot
7252 .anchor_before(search_range.start + match_range.end);
7253 let match_anchor_range =
7254 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7255 (match_anchor_range != query_range).then_some(match_anchor_range)
7256 }),
7257 );
7258 }
7259 match_ranges
7260 });
7261 let match_ranges = match_task.await;
7262 editor
7263 .update_in(cx, |editor, _, cx| {
7264 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7265 if !match_ranges.is_empty() {
7266 editor.highlight_background::<SelectedTextHighlight>(
7267 &match_ranges,
7268 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7269 cx,
7270 )
7271 }
7272 })
7273 .log_err();
7274 })
7275 }
7276
7277 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7278 struct NewlineFold;
7279 let type_id = std::any::TypeId::of::<NewlineFold>();
7280 if !self.mode.is_single_line() {
7281 return;
7282 }
7283 let snapshot = self.snapshot(window, cx);
7284 if snapshot.buffer_snapshot().max_point().row == 0 {
7285 return;
7286 }
7287 let task = cx.background_spawn(async move {
7288 let new_newlines = snapshot
7289 .buffer_chars_at(MultiBufferOffset(0))
7290 .filter_map(|(c, i)| {
7291 if c == '\n' {
7292 Some(
7293 snapshot.buffer_snapshot().anchor_after(i)
7294 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7295 )
7296 } else {
7297 None
7298 }
7299 })
7300 .collect::<Vec<_>>();
7301 let existing_newlines = snapshot
7302 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7303 .filter_map(|fold| {
7304 if fold.placeholder.type_tag == Some(type_id) {
7305 Some(fold.range.start..fold.range.end)
7306 } else {
7307 None
7308 }
7309 })
7310 .collect::<Vec<_>>();
7311
7312 (new_newlines, existing_newlines)
7313 });
7314 self.folding_newlines = cx.spawn(async move |this, cx| {
7315 let (new_newlines, existing_newlines) = task.await;
7316 if new_newlines == existing_newlines {
7317 return;
7318 }
7319 let placeholder = FoldPlaceholder {
7320 render: Arc::new(move |_, _, cx| {
7321 div()
7322 .bg(cx.theme().status().hint_background)
7323 .border_b_1()
7324 .size_full()
7325 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7326 .border_color(cx.theme().status().hint)
7327 .child("\\n")
7328 .into_any()
7329 }),
7330 constrain_width: false,
7331 merge_adjacent: false,
7332 type_tag: Some(type_id),
7333 };
7334 let creases = new_newlines
7335 .into_iter()
7336 .map(|range| Crease::simple(range, placeholder.clone()))
7337 .collect();
7338 this.update(cx, |this, cx| {
7339 this.display_map.update(cx, |display_map, cx| {
7340 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7341 display_map.fold(creases, cx);
7342 });
7343 })
7344 .ok();
7345 });
7346 }
7347
7348 #[ztracing::instrument(skip_all)]
7349 fn refresh_selected_text_highlights(
7350 &mut self,
7351 on_buffer_edit: bool,
7352 window: &mut Window,
7353 cx: &mut Context<Editor>,
7354 ) {
7355 let Some((query_text, query_range)) =
7356 self.prepare_highlight_query_from_selection(window, cx)
7357 else {
7358 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7359 self.quick_selection_highlight_task.take();
7360 self.debounced_selection_highlight_task.take();
7361 return;
7362 };
7363 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7364 if on_buffer_edit
7365 || self
7366 .quick_selection_highlight_task
7367 .as_ref()
7368 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7369 {
7370 let multi_buffer_visible_start = self
7371 .scroll_manager
7372 .anchor()
7373 .anchor
7374 .to_point(&multi_buffer_snapshot);
7375 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7376 multi_buffer_visible_start
7377 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7378 Bias::Left,
7379 );
7380 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7381 self.quick_selection_highlight_task = Some((
7382 query_range.clone(),
7383 self.update_selection_occurrence_highlights(
7384 query_text.clone(),
7385 query_range.clone(),
7386 multi_buffer_visible_range,
7387 false,
7388 window,
7389 cx,
7390 ),
7391 ));
7392 }
7393 if on_buffer_edit
7394 || self
7395 .debounced_selection_highlight_task
7396 .as_ref()
7397 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7398 {
7399 let multi_buffer_start = multi_buffer_snapshot
7400 .anchor_before(MultiBufferOffset(0))
7401 .to_point(&multi_buffer_snapshot);
7402 let multi_buffer_end = multi_buffer_snapshot
7403 .anchor_after(multi_buffer_snapshot.len())
7404 .to_point(&multi_buffer_snapshot);
7405 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7406 self.debounced_selection_highlight_task = Some((
7407 query_range.clone(),
7408 self.update_selection_occurrence_highlights(
7409 query_text,
7410 query_range,
7411 multi_buffer_full_range,
7412 true,
7413 window,
7414 cx,
7415 ),
7416 ));
7417 }
7418 }
7419
7420 pub fn refresh_edit_prediction(
7421 &mut self,
7422 debounce: bool,
7423 user_requested: bool,
7424 window: &mut Window,
7425 cx: &mut Context<Self>,
7426 ) -> Option<()> {
7427 if DisableAiSettings::get_global(cx).disable_ai {
7428 return None;
7429 }
7430
7431 let provider = self.edit_prediction_provider()?;
7432 let cursor = self.selections.newest_anchor().head();
7433 let (buffer, cursor_buffer_position) =
7434 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7435
7436 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7437 self.discard_edit_prediction(false, cx);
7438 return None;
7439 }
7440
7441 self.update_visible_edit_prediction(window, cx);
7442
7443 if !user_requested
7444 && (!self.should_show_edit_predictions()
7445 || !self.is_focused(window)
7446 || buffer.read(cx).is_empty())
7447 {
7448 self.discard_edit_prediction(false, cx);
7449 return None;
7450 }
7451
7452 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7453 Some(())
7454 }
7455
7456 fn show_edit_predictions_in_menu(&self) -> bool {
7457 match self.edit_prediction_settings {
7458 EditPredictionSettings::Disabled => false,
7459 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7460 }
7461 }
7462
7463 pub fn edit_predictions_enabled(&self) -> bool {
7464 match self.edit_prediction_settings {
7465 EditPredictionSettings::Disabled => false,
7466 EditPredictionSettings::Enabled { .. } => true,
7467 }
7468 }
7469
7470 fn edit_prediction_requires_modifier(&self) -> bool {
7471 match self.edit_prediction_settings {
7472 EditPredictionSettings::Disabled => false,
7473 EditPredictionSettings::Enabled {
7474 preview_requires_modifier,
7475 ..
7476 } => preview_requires_modifier,
7477 }
7478 }
7479
7480 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7481 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7482 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7483 self.discard_edit_prediction(false, cx);
7484 } else {
7485 let selection = self.selections.newest_anchor();
7486 let cursor = selection.head();
7487
7488 if let Some((buffer, cursor_buffer_position)) =
7489 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7490 {
7491 self.edit_prediction_settings =
7492 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7493 }
7494 }
7495 }
7496
7497 fn edit_prediction_settings_at_position(
7498 &self,
7499 buffer: &Entity<Buffer>,
7500 buffer_position: language::Anchor,
7501 cx: &App,
7502 ) -> EditPredictionSettings {
7503 if !self.mode.is_full()
7504 || !self.show_edit_predictions_override.unwrap_or(true)
7505 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7506 {
7507 return EditPredictionSettings::Disabled;
7508 }
7509
7510 let buffer = buffer.read(cx);
7511
7512 let file = buffer.file();
7513
7514 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7515 return EditPredictionSettings::Disabled;
7516 };
7517
7518 let by_provider = matches!(
7519 self.menu_edit_predictions_policy,
7520 MenuEditPredictionsPolicy::ByProvider
7521 );
7522
7523 let show_in_menu = by_provider
7524 && self
7525 .edit_prediction_provider
7526 .as_ref()
7527 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7528
7529 let preview_requires_modifier =
7530 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7531
7532 EditPredictionSettings::Enabled {
7533 show_in_menu,
7534 preview_requires_modifier,
7535 }
7536 }
7537
7538 fn should_show_edit_predictions(&self) -> bool {
7539 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7540 }
7541
7542 pub fn edit_prediction_preview_is_active(&self) -> bool {
7543 matches!(
7544 self.edit_prediction_preview,
7545 EditPredictionPreview::Active { .. }
7546 )
7547 }
7548
7549 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7550 let cursor = self.selections.newest_anchor().head();
7551 if let Some((buffer, cursor_position)) =
7552 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7553 {
7554 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7555 } else {
7556 false
7557 }
7558 }
7559
7560 pub fn supports_minimap(&self, cx: &App) -> bool {
7561 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7562 }
7563
7564 fn edit_predictions_enabled_in_buffer(
7565 &self,
7566 buffer: &Entity<Buffer>,
7567 buffer_position: language::Anchor,
7568 cx: &App,
7569 ) -> bool {
7570 maybe!({
7571 if self.read_only(cx) {
7572 return Some(false);
7573 }
7574 let provider = self.edit_prediction_provider()?;
7575 if !provider.is_enabled(buffer, buffer_position, cx) {
7576 return Some(false);
7577 }
7578 let buffer = buffer.read(cx);
7579 let Some(file) = buffer.file() else {
7580 return Some(true);
7581 };
7582 let settings = all_language_settings(Some(file), cx);
7583 Some(settings.edit_predictions_enabled_for_file(file, cx))
7584 })
7585 .unwrap_or(false)
7586 }
7587
7588 pub fn show_edit_prediction(
7589 &mut self,
7590 _: &ShowEditPrediction,
7591 window: &mut Window,
7592 cx: &mut Context<Self>,
7593 ) {
7594 if !self.has_active_edit_prediction() {
7595 self.refresh_edit_prediction(false, true, window, cx);
7596 return;
7597 }
7598
7599 self.update_visible_edit_prediction(window, cx);
7600 }
7601
7602 pub fn display_cursor_names(
7603 &mut self,
7604 _: &DisplayCursorNames,
7605 window: &mut Window,
7606 cx: &mut Context<Self>,
7607 ) {
7608 self.show_cursor_names(window, cx);
7609 }
7610
7611 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7612 self.show_cursor_names = true;
7613 cx.notify();
7614 cx.spawn_in(window, async move |this, cx| {
7615 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7616 this.update(cx, |this, cx| {
7617 this.show_cursor_names = false;
7618 cx.notify()
7619 })
7620 .ok()
7621 })
7622 .detach();
7623 }
7624
7625 pub fn accept_partial_edit_prediction(
7626 &mut self,
7627 granularity: EditPredictionGranularity,
7628 window: &mut Window,
7629 cx: &mut Context<Self>,
7630 ) {
7631 if self.show_edit_predictions_in_menu() {
7632 self.hide_context_menu(window, cx);
7633 }
7634
7635 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7636 return;
7637 };
7638
7639 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7640 return;
7641 }
7642
7643 match &active_edit_prediction.completion {
7644 EditPrediction::MoveWithin { target, .. } => {
7645 let target = *target;
7646
7647 if matches!(granularity, EditPredictionGranularity::Full) {
7648 if let Some(position_map) = &self.last_position_map {
7649 let target_row = target.to_display_point(&position_map.snapshot).row();
7650 let is_visible = position_map.visible_row_range.contains(&target_row);
7651
7652 if is_visible || !self.edit_prediction_requires_modifier() {
7653 self.unfold_ranges(&[target..target], true, false, cx);
7654 self.change_selections(
7655 SelectionEffects::scroll(Autoscroll::newest()),
7656 window,
7657 cx,
7658 |selections| {
7659 selections.select_anchor_ranges([target..target]);
7660 },
7661 );
7662 self.clear_row_highlights::<EditPredictionPreview>();
7663 self.edit_prediction_preview
7664 .set_previous_scroll_position(None);
7665 } else {
7666 // Highlight and request scroll
7667 self.edit_prediction_preview
7668 .set_previous_scroll_position(Some(
7669 position_map.snapshot.scroll_anchor,
7670 ));
7671 self.highlight_rows::<EditPredictionPreview>(
7672 target..target,
7673 cx.theme().colors().editor_highlighted_line_background,
7674 RowHighlightOptions {
7675 autoscroll: true,
7676 ..Default::default()
7677 },
7678 cx,
7679 );
7680 self.request_autoscroll(Autoscroll::fit(), cx);
7681 }
7682 }
7683 } else {
7684 self.change_selections(
7685 SelectionEffects::scroll(Autoscroll::newest()),
7686 window,
7687 cx,
7688 |selections| {
7689 selections.select_anchor_ranges([target..target]);
7690 },
7691 );
7692 }
7693 }
7694 EditPrediction::MoveOutside { snapshot, target } => {
7695 if let Some(workspace) = self.workspace() {
7696 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7697 .detach_and_log_err(cx);
7698 }
7699 }
7700 EditPrediction::Edit { edits, .. } => {
7701 self.report_edit_prediction_event(
7702 active_edit_prediction.completion_id.clone(),
7703 true,
7704 cx,
7705 );
7706
7707 match granularity {
7708 EditPredictionGranularity::Full => {
7709 if let Some(provider) = self.edit_prediction_provider() {
7710 provider.accept(cx);
7711 }
7712
7713 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7714 let snapshot = self.buffer.read(cx).snapshot(cx);
7715 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7716
7717 self.buffer.update(cx, |buffer, cx| {
7718 buffer.edit(edits.iter().cloned(), None, cx)
7719 });
7720
7721 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7722 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7723 });
7724
7725 let selections = self.selections.disjoint_anchors_arc();
7726 if let Some(transaction_id_now) =
7727 self.buffer.read(cx).last_transaction_id(cx)
7728 {
7729 if transaction_id_prev != Some(transaction_id_now) {
7730 self.selection_history
7731 .insert_transaction(transaction_id_now, selections);
7732 }
7733 }
7734
7735 self.update_visible_edit_prediction(window, cx);
7736 if self.active_edit_prediction.is_none() {
7737 self.refresh_edit_prediction(true, true, window, cx);
7738 }
7739 cx.notify();
7740 }
7741 _ => {
7742 let snapshot = self.buffer.read(cx).snapshot(cx);
7743 let cursor_offset = self
7744 .selections
7745 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7746 .head();
7747
7748 let insertion = edits.iter().find_map(|(range, text)| {
7749 let range = range.to_offset(&snapshot);
7750 if range.is_empty() && range.start == cursor_offset {
7751 Some(text)
7752 } else {
7753 None
7754 }
7755 });
7756
7757 if let Some(text) = insertion {
7758 let text_to_insert = match granularity {
7759 EditPredictionGranularity::Word => {
7760 let mut partial = text
7761 .chars()
7762 .by_ref()
7763 .take_while(|c| c.is_alphabetic())
7764 .collect::<String>();
7765 if partial.is_empty() {
7766 partial = text
7767 .chars()
7768 .by_ref()
7769 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7770 .collect::<String>();
7771 }
7772 partial
7773 }
7774 EditPredictionGranularity::Line => {
7775 if let Some(line) = text.split_inclusive('\n').next() {
7776 line.to_string()
7777 } else {
7778 text.to_string()
7779 }
7780 }
7781 EditPredictionGranularity::Full => unreachable!(),
7782 };
7783
7784 cx.emit(EditorEvent::InputHandled {
7785 utf16_range_to_replace: None,
7786 text: text_to_insert.clone().into(),
7787 });
7788
7789 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7790 self.refresh_edit_prediction(true, true, window, cx);
7791 cx.notify();
7792 } else {
7793 self.accept_partial_edit_prediction(
7794 EditPredictionGranularity::Full,
7795 window,
7796 cx,
7797 );
7798 }
7799 }
7800 }
7801 }
7802 }
7803
7804 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7805 }
7806
7807 pub fn accept_next_word_edit_prediction(
7808 &mut self,
7809 _: &AcceptNextWordEditPrediction,
7810 window: &mut Window,
7811 cx: &mut Context<Self>,
7812 ) {
7813 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7814 }
7815
7816 pub fn accept_next_line_edit_prediction(
7817 &mut self,
7818 _: &AcceptNextLineEditPrediction,
7819 window: &mut Window,
7820 cx: &mut Context<Self>,
7821 ) {
7822 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7823 }
7824
7825 pub fn accept_edit_prediction(
7826 &mut self,
7827 _: &AcceptEditPrediction,
7828 window: &mut Window,
7829 cx: &mut Context<Self>,
7830 ) {
7831 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7832 }
7833
7834 fn discard_edit_prediction(
7835 &mut self,
7836 should_report_edit_prediction_event: bool,
7837 cx: &mut Context<Self>,
7838 ) -> bool {
7839 if should_report_edit_prediction_event {
7840 let completion_id = self
7841 .active_edit_prediction
7842 .as_ref()
7843 .and_then(|active_completion| active_completion.completion_id.clone());
7844
7845 self.report_edit_prediction_event(completion_id, false, cx);
7846 }
7847
7848 if let Some(provider) = self.edit_prediction_provider() {
7849 provider.discard(cx);
7850 }
7851
7852 self.take_active_edit_prediction(cx)
7853 }
7854
7855 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7856 let Some(provider) = self.edit_prediction_provider() else {
7857 return;
7858 };
7859
7860 let Some((_, buffer, _)) = self
7861 .buffer
7862 .read(cx)
7863 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7864 else {
7865 return;
7866 };
7867
7868 let extension = buffer
7869 .read(cx)
7870 .file()
7871 .and_then(|file| Some(file.path().extension()?.to_string()));
7872
7873 let event_type = match accepted {
7874 true => "Edit Prediction Accepted",
7875 false => "Edit Prediction Discarded",
7876 };
7877 telemetry::event!(
7878 event_type,
7879 provider = provider.name(),
7880 prediction_id = id,
7881 suggestion_accepted = accepted,
7882 file_extension = extension,
7883 );
7884 }
7885
7886 fn open_editor_at_anchor(
7887 snapshot: &language::BufferSnapshot,
7888 target: language::Anchor,
7889 workspace: &Entity<Workspace>,
7890 window: &mut Window,
7891 cx: &mut App,
7892 ) -> Task<Result<()>> {
7893 workspace.update(cx, |workspace, cx| {
7894 let path = snapshot.file().map(|file| file.full_path(cx));
7895 let Some(path) =
7896 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7897 else {
7898 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7899 };
7900 let target = text::ToPoint::to_point(&target, snapshot);
7901 let item = workspace.open_path(path, None, true, window, cx);
7902 window.spawn(cx, async move |cx| {
7903 let Some(editor) = item.await?.downcast::<Editor>() else {
7904 return Ok(());
7905 };
7906 editor
7907 .update_in(cx, |editor, window, cx| {
7908 editor.go_to_singleton_buffer_point(target, window, cx);
7909 })
7910 .ok();
7911 anyhow::Ok(())
7912 })
7913 })
7914 }
7915
7916 pub fn has_active_edit_prediction(&self) -> bool {
7917 self.active_edit_prediction.is_some()
7918 }
7919
7920 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7921 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7922 return false;
7923 };
7924
7925 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7926 self.clear_highlights::<EditPredictionHighlight>(cx);
7927 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7928 true
7929 }
7930
7931 /// Returns true when we're displaying the edit prediction popover below the cursor
7932 /// like we are not previewing and the LSP autocomplete menu is visible
7933 /// or we are in `when_holding_modifier` mode.
7934 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7935 if self.edit_prediction_preview_is_active()
7936 || !self.show_edit_predictions_in_menu()
7937 || !self.edit_predictions_enabled()
7938 {
7939 return false;
7940 }
7941
7942 if self.has_visible_completions_menu() {
7943 return true;
7944 }
7945
7946 has_completion && self.edit_prediction_requires_modifier()
7947 }
7948
7949 fn handle_modifiers_changed(
7950 &mut self,
7951 modifiers: Modifiers,
7952 position_map: &PositionMap,
7953 window: &mut Window,
7954 cx: &mut Context<Self>,
7955 ) {
7956 // Ensure that the edit prediction preview is updated, even when not
7957 // enabled, if there's an active edit prediction preview.
7958 if self.show_edit_predictions_in_menu()
7959 || matches!(
7960 self.edit_prediction_preview,
7961 EditPredictionPreview::Active { .. }
7962 )
7963 {
7964 self.update_edit_prediction_preview(&modifiers, window, cx);
7965 }
7966
7967 self.update_selection_mode(&modifiers, position_map, window, cx);
7968
7969 let mouse_position = window.mouse_position();
7970 if !position_map.text_hitbox.is_hovered(window) {
7971 return;
7972 }
7973
7974 self.update_hovered_link(
7975 position_map.point_for_position(mouse_position),
7976 &position_map.snapshot,
7977 modifiers,
7978 window,
7979 cx,
7980 )
7981 }
7982
7983 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7984 match EditorSettings::get_global(cx).multi_cursor_modifier {
7985 MultiCursorModifier::Alt => modifiers.secondary(),
7986 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7987 }
7988 }
7989
7990 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7991 match EditorSettings::get_global(cx).multi_cursor_modifier {
7992 MultiCursorModifier::Alt => modifiers.alt,
7993 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7994 }
7995 }
7996
7997 fn columnar_selection_mode(
7998 modifiers: &Modifiers,
7999 cx: &mut Context<Self>,
8000 ) -> Option<ColumnarMode> {
8001 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8002 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8003 Some(ColumnarMode::FromMouse)
8004 } else if Self::is_alt_pressed(modifiers, cx) {
8005 Some(ColumnarMode::FromSelection)
8006 } else {
8007 None
8008 }
8009 } else {
8010 None
8011 }
8012 }
8013
8014 fn update_selection_mode(
8015 &mut self,
8016 modifiers: &Modifiers,
8017 position_map: &PositionMap,
8018 window: &mut Window,
8019 cx: &mut Context<Self>,
8020 ) {
8021 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8022 return;
8023 };
8024 if self.selections.pending_anchor().is_none() {
8025 return;
8026 }
8027
8028 let mouse_position = window.mouse_position();
8029 let point_for_position = position_map.point_for_position(mouse_position);
8030 let position = point_for_position.previous_valid;
8031
8032 self.select(
8033 SelectPhase::BeginColumnar {
8034 position,
8035 reset: false,
8036 mode,
8037 goal_column: point_for_position.exact_unclipped.column(),
8038 },
8039 window,
8040 cx,
8041 );
8042 }
8043
8044 fn update_edit_prediction_preview(
8045 &mut self,
8046 modifiers: &Modifiers,
8047 window: &mut Window,
8048 cx: &mut Context<Self>,
8049 ) {
8050 let mut modifiers_held = false;
8051
8052 // Check bindings for all granularities.
8053 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8054 let granularities = [
8055 EditPredictionGranularity::Full,
8056 EditPredictionGranularity::Line,
8057 EditPredictionGranularity::Word,
8058 ];
8059
8060 for granularity in granularities {
8061 if let Some(keystroke) = self
8062 .accept_edit_prediction_keybind(granularity, window, cx)
8063 .keystroke()
8064 {
8065 modifiers_held = modifiers_held
8066 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8067 }
8068 }
8069
8070 if modifiers_held {
8071 if matches!(
8072 self.edit_prediction_preview,
8073 EditPredictionPreview::Inactive { .. }
8074 ) {
8075 self.edit_prediction_preview = EditPredictionPreview::Active {
8076 previous_scroll_position: None,
8077 since: Instant::now(),
8078 };
8079
8080 self.update_visible_edit_prediction(window, cx);
8081 cx.notify();
8082 }
8083 } else if let EditPredictionPreview::Active {
8084 previous_scroll_position,
8085 since,
8086 } = self.edit_prediction_preview
8087 {
8088 if let (Some(previous_scroll_position), Some(position_map)) =
8089 (previous_scroll_position, self.last_position_map.as_ref())
8090 {
8091 self.set_scroll_position(
8092 previous_scroll_position
8093 .scroll_position(&position_map.snapshot.display_snapshot),
8094 window,
8095 cx,
8096 );
8097 }
8098
8099 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8100 released_too_fast: since.elapsed() < Duration::from_millis(200),
8101 };
8102 self.clear_row_highlights::<EditPredictionPreview>();
8103 self.update_visible_edit_prediction(window, cx);
8104 cx.notify();
8105 }
8106 }
8107
8108 fn update_visible_edit_prediction(
8109 &mut self,
8110 _window: &mut Window,
8111 cx: &mut Context<Self>,
8112 ) -> Option<()> {
8113 if DisableAiSettings::get_global(cx).disable_ai {
8114 return None;
8115 }
8116
8117 if self.ime_transaction.is_some() {
8118 self.discard_edit_prediction(false, cx);
8119 return None;
8120 }
8121
8122 let selection = self.selections.newest_anchor();
8123 let cursor = selection.head();
8124 let multibuffer = self.buffer.read(cx).snapshot(cx);
8125 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8126 let excerpt_id = cursor.excerpt_id;
8127
8128 let show_in_menu = self.show_edit_predictions_in_menu();
8129 let completions_menu_has_precedence = !show_in_menu
8130 && (self.context_menu.borrow().is_some()
8131 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8132
8133 if completions_menu_has_precedence
8134 || !offset_selection.is_empty()
8135 || self
8136 .active_edit_prediction
8137 .as_ref()
8138 .is_some_and(|completion| {
8139 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8140 return false;
8141 };
8142 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8143 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8144 !invalidation_range.contains(&offset_selection.head())
8145 })
8146 {
8147 self.discard_edit_prediction(false, cx);
8148 return None;
8149 }
8150
8151 self.take_active_edit_prediction(cx);
8152 let Some(provider) = self.edit_prediction_provider() else {
8153 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8154 return None;
8155 };
8156
8157 let (buffer, cursor_buffer_position) =
8158 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8159
8160 self.edit_prediction_settings =
8161 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8162
8163 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8164
8165 if self.edit_prediction_indent_conflict {
8166 let cursor_point = cursor.to_point(&multibuffer);
8167 let mut suggested_indent = None;
8168 multibuffer.suggested_indents_callback(
8169 cursor_point.row..cursor_point.row + 1,
8170 |_, indent| {
8171 suggested_indent = Some(indent);
8172 ControlFlow::Break(())
8173 },
8174 cx,
8175 );
8176
8177 if let Some(indent) = suggested_indent
8178 && indent.len == cursor_point.column
8179 {
8180 self.edit_prediction_indent_conflict = false;
8181 }
8182 }
8183
8184 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8185
8186 let (completion_id, edits, edit_preview) = match edit_prediction {
8187 edit_prediction_types::EditPrediction::Local {
8188 id,
8189 edits,
8190 edit_preview,
8191 } => (id, edits, edit_preview),
8192 edit_prediction_types::EditPrediction::Jump {
8193 id,
8194 snapshot,
8195 target,
8196 } => {
8197 if let Some(provider) = &self.edit_prediction_provider {
8198 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8199 }
8200 self.stale_edit_prediction_in_menu = None;
8201 self.active_edit_prediction = Some(EditPredictionState {
8202 inlay_ids: vec![],
8203 completion: EditPrediction::MoveOutside { snapshot, target },
8204 completion_id: id,
8205 invalidation_range: None,
8206 });
8207 cx.notify();
8208 return Some(());
8209 }
8210 };
8211
8212 let edits = edits
8213 .into_iter()
8214 .flat_map(|(range, new_text)| {
8215 Some((
8216 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8217 new_text,
8218 ))
8219 })
8220 .collect::<Vec<_>>();
8221 if edits.is_empty() {
8222 return None;
8223 }
8224
8225 let first_edit_start = edits.first().unwrap().0.start;
8226 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8227 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8228
8229 let last_edit_end = edits.last().unwrap().0.end;
8230 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8231 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8232
8233 let cursor_row = cursor.to_point(&multibuffer).row;
8234
8235 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8236
8237 let mut inlay_ids = Vec::new();
8238 let invalidation_row_range;
8239 let move_invalidation_row_range = if cursor_row < edit_start_row {
8240 Some(cursor_row..edit_end_row)
8241 } else if cursor_row > edit_end_row {
8242 Some(edit_start_row..cursor_row)
8243 } else {
8244 None
8245 };
8246 let supports_jump = self
8247 .edit_prediction_provider
8248 .as_ref()
8249 .map(|provider| provider.provider.supports_jump_to_edit())
8250 .unwrap_or(true);
8251
8252 let is_move = supports_jump
8253 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8254 let completion = if is_move {
8255 if let Some(provider) = &self.edit_prediction_provider {
8256 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8257 }
8258 invalidation_row_range =
8259 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8260 let target = first_edit_start;
8261 EditPrediction::MoveWithin { target, snapshot }
8262 } else {
8263 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8264 && !self.edit_predictions_hidden_for_vim_mode;
8265
8266 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8267 if provider.show_tab_accept_marker() {
8268 EditDisplayMode::TabAccept
8269 } else {
8270 EditDisplayMode::Inline
8271 }
8272 } else {
8273 EditDisplayMode::DiffPopover
8274 };
8275
8276 if show_completions_in_buffer {
8277 if let Some(provider) = &self.edit_prediction_provider {
8278 let suggestion_display_type = match display_mode {
8279 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8280 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8281 SuggestionDisplayType::GhostText
8282 }
8283 };
8284 provider.provider.did_show(suggestion_display_type, cx);
8285 }
8286 if edits
8287 .iter()
8288 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8289 {
8290 let mut inlays = Vec::new();
8291 for (range, new_text) in &edits {
8292 let inlay = Inlay::edit_prediction(
8293 post_inc(&mut self.next_inlay_id),
8294 range.start,
8295 new_text.as_ref(),
8296 );
8297 inlay_ids.push(inlay.id);
8298 inlays.push(inlay);
8299 }
8300
8301 self.splice_inlays(&[], inlays, cx);
8302 } else {
8303 let background_color = cx.theme().status().deleted_background;
8304 self.highlight_text::<EditPredictionHighlight>(
8305 edits.iter().map(|(range, _)| range.clone()).collect(),
8306 HighlightStyle {
8307 background_color: Some(background_color),
8308 ..Default::default()
8309 },
8310 cx,
8311 );
8312 }
8313 }
8314
8315 invalidation_row_range = edit_start_row..edit_end_row;
8316
8317 EditPrediction::Edit {
8318 edits,
8319 edit_preview,
8320 display_mode,
8321 snapshot,
8322 }
8323 };
8324
8325 let invalidation_range = multibuffer
8326 .anchor_before(Point::new(invalidation_row_range.start, 0))
8327 ..multibuffer.anchor_after(Point::new(
8328 invalidation_row_range.end,
8329 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8330 ));
8331
8332 self.stale_edit_prediction_in_menu = None;
8333 self.active_edit_prediction = Some(EditPredictionState {
8334 inlay_ids,
8335 completion,
8336 completion_id,
8337 invalidation_range: Some(invalidation_range),
8338 });
8339
8340 cx.notify();
8341
8342 Some(())
8343 }
8344
8345 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8346 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8347 }
8348
8349 fn clear_tasks(&mut self) {
8350 self.tasks.clear()
8351 }
8352
8353 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8354 if self.tasks.insert(key, value).is_some() {
8355 // This case should hopefully be rare, but just in case...
8356 log::error!(
8357 "multiple different run targets found on a single line, only the last target will be rendered"
8358 )
8359 }
8360 }
8361
8362 /// Get all display points of breakpoints that will be rendered within editor
8363 ///
8364 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8365 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8366 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8367 fn active_breakpoints(
8368 &self,
8369 range: Range<DisplayRow>,
8370 window: &mut Window,
8371 cx: &mut Context<Self>,
8372 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8373 let mut breakpoint_display_points = HashMap::default();
8374
8375 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8376 return breakpoint_display_points;
8377 };
8378
8379 let snapshot = self.snapshot(window, cx);
8380
8381 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8382 let Some(project) = self.project() else {
8383 return breakpoint_display_points;
8384 };
8385
8386 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8387 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8388
8389 for (buffer_snapshot, range, excerpt_id) in
8390 multi_buffer_snapshot.range_to_buffer_ranges(range)
8391 {
8392 let Some(buffer) = project
8393 .read(cx)
8394 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8395 else {
8396 continue;
8397 };
8398 let breakpoints = breakpoint_store.read(cx).breakpoints(
8399 &buffer,
8400 Some(
8401 buffer_snapshot.anchor_before(range.start)
8402 ..buffer_snapshot.anchor_after(range.end),
8403 ),
8404 buffer_snapshot,
8405 cx,
8406 );
8407 for (breakpoint, state) in breakpoints {
8408 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8409 let position = multi_buffer_anchor
8410 .to_point(&multi_buffer_snapshot)
8411 .to_display_point(&snapshot);
8412
8413 breakpoint_display_points.insert(
8414 position.row(),
8415 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8416 );
8417 }
8418 }
8419
8420 breakpoint_display_points
8421 }
8422
8423 fn breakpoint_context_menu(
8424 &self,
8425 anchor: Anchor,
8426 window: &mut Window,
8427 cx: &mut Context<Self>,
8428 ) -> Entity<ui::ContextMenu> {
8429 let weak_editor = cx.weak_entity();
8430 let focus_handle = self.focus_handle(cx);
8431
8432 let row = self
8433 .buffer
8434 .read(cx)
8435 .snapshot(cx)
8436 .summary_for_anchor::<Point>(&anchor)
8437 .row;
8438
8439 let breakpoint = self
8440 .breakpoint_at_row(row, window, cx)
8441 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8442
8443 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8444 "Edit Log Breakpoint"
8445 } else {
8446 "Set Log Breakpoint"
8447 };
8448
8449 let condition_breakpoint_msg = if breakpoint
8450 .as_ref()
8451 .is_some_and(|bp| bp.1.condition.is_some())
8452 {
8453 "Edit Condition Breakpoint"
8454 } else {
8455 "Set Condition Breakpoint"
8456 };
8457
8458 let hit_condition_breakpoint_msg = if breakpoint
8459 .as_ref()
8460 .is_some_and(|bp| bp.1.hit_condition.is_some())
8461 {
8462 "Edit Hit Condition Breakpoint"
8463 } else {
8464 "Set Hit Condition Breakpoint"
8465 };
8466
8467 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8468 "Unset Breakpoint"
8469 } else {
8470 "Set Breakpoint"
8471 };
8472
8473 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8474
8475 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8476 BreakpointState::Enabled => Some("Disable"),
8477 BreakpointState::Disabled => Some("Enable"),
8478 });
8479
8480 let (anchor, breakpoint) =
8481 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8482
8483 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8484 menu.on_blur_subscription(Subscription::new(|| {}))
8485 .context(focus_handle)
8486 .when(run_to_cursor, |this| {
8487 let weak_editor = weak_editor.clone();
8488 this.entry("Run to cursor", None, move |window, cx| {
8489 weak_editor
8490 .update(cx, |editor, cx| {
8491 editor.change_selections(
8492 SelectionEffects::no_scroll(),
8493 window,
8494 cx,
8495 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8496 );
8497 })
8498 .ok();
8499
8500 window.dispatch_action(Box::new(RunToCursor), cx);
8501 })
8502 .separator()
8503 })
8504 .when_some(toggle_state_msg, |this, msg| {
8505 this.entry(msg, None, {
8506 let weak_editor = weak_editor.clone();
8507 let breakpoint = breakpoint.clone();
8508 move |_window, cx| {
8509 weak_editor
8510 .update(cx, |this, cx| {
8511 this.edit_breakpoint_at_anchor(
8512 anchor,
8513 breakpoint.as_ref().clone(),
8514 BreakpointEditAction::InvertState,
8515 cx,
8516 );
8517 })
8518 .log_err();
8519 }
8520 })
8521 })
8522 .entry(set_breakpoint_msg, None, {
8523 let weak_editor = weak_editor.clone();
8524 let breakpoint = breakpoint.clone();
8525 move |_window, cx| {
8526 weak_editor
8527 .update(cx, |this, cx| {
8528 this.edit_breakpoint_at_anchor(
8529 anchor,
8530 breakpoint.as_ref().clone(),
8531 BreakpointEditAction::Toggle,
8532 cx,
8533 );
8534 })
8535 .log_err();
8536 }
8537 })
8538 .entry(log_breakpoint_msg, None, {
8539 let breakpoint = breakpoint.clone();
8540 let weak_editor = weak_editor.clone();
8541 move |window, cx| {
8542 weak_editor
8543 .update(cx, |this, cx| {
8544 this.add_edit_breakpoint_block(
8545 anchor,
8546 breakpoint.as_ref(),
8547 BreakpointPromptEditAction::Log,
8548 window,
8549 cx,
8550 );
8551 })
8552 .log_err();
8553 }
8554 })
8555 .entry(condition_breakpoint_msg, None, {
8556 let breakpoint = breakpoint.clone();
8557 let weak_editor = weak_editor.clone();
8558 move |window, cx| {
8559 weak_editor
8560 .update(cx, |this, cx| {
8561 this.add_edit_breakpoint_block(
8562 anchor,
8563 breakpoint.as_ref(),
8564 BreakpointPromptEditAction::Condition,
8565 window,
8566 cx,
8567 );
8568 })
8569 .log_err();
8570 }
8571 })
8572 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8573 weak_editor
8574 .update(cx, |this, cx| {
8575 this.add_edit_breakpoint_block(
8576 anchor,
8577 breakpoint.as_ref(),
8578 BreakpointPromptEditAction::HitCondition,
8579 window,
8580 cx,
8581 );
8582 })
8583 .log_err();
8584 })
8585 })
8586 }
8587
8588 fn render_breakpoint(
8589 &self,
8590 position: Anchor,
8591 row: DisplayRow,
8592 breakpoint: &Breakpoint,
8593 state: Option<BreakpointSessionState>,
8594 cx: &mut Context<Self>,
8595 ) -> IconButton {
8596 let is_rejected = state.is_some_and(|s| !s.verified);
8597 // Is it a breakpoint that shows up when hovering over gutter?
8598 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8599 (false, false),
8600 |PhantomBreakpointIndicator {
8601 is_active,
8602 display_row,
8603 collides_with_existing_breakpoint,
8604 }| {
8605 (
8606 is_active && display_row == row,
8607 collides_with_existing_breakpoint,
8608 )
8609 },
8610 );
8611
8612 let (color, icon) = {
8613 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8614 (false, false) => ui::IconName::DebugBreakpoint,
8615 (true, false) => ui::IconName::DebugLogBreakpoint,
8616 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8617 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8618 };
8619
8620 let color = cx.theme().colors();
8621
8622 let color = if is_phantom {
8623 if collides_with_existing {
8624 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8625 } else {
8626 Color::Hint
8627 }
8628 } else if is_rejected {
8629 Color::Disabled
8630 } else {
8631 Color::Debugger
8632 };
8633
8634 (color, icon)
8635 };
8636
8637 let breakpoint = Arc::from(breakpoint.clone());
8638
8639 let alt_as_text = gpui::Keystroke {
8640 modifiers: Modifiers::secondary_key(),
8641 ..Default::default()
8642 };
8643 let primary_action_text = if breakpoint.is_disabled() {
8644 "Enable breakpoint"
8645 } else if is_phantom && !collides_with_existing {
8646 "Set breakpoint"
8647 } else {
8648 "Unset breakpoint"
8649 };
8650 let focus_handle = self.focus_handle.clone();
8651
8652 let meta = if is_rejected {
8653 SharedString::from("No executable code is associated with this line.")
8654 } else if collides_with_existing && !breakpoint.is_disabled() {
8655 SharedString::from(format!(
8656 "{alt_as_text}-click to disable,\nright-click for more options."
8657 ))
8658 } else {
8659 SharedString::from("Right-click for more options.")
8660 };
8661 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8662 .icon_size(IconSize::XSmall)
8663 .size(ui::ButtonSize::None)
8664 .when(is_rejected, |this| {
8665 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8666 })
8667 .icon_color(color)
8668 .style(ButtonStyle::Transparent)
8669 .on_click(cx.listener({
8670 move |editor, event: &ClickEvent, window, cx| {
8671 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8672 BreakpointEditAction::InvertState
8673 } else {
8674 BreakpointEditAction::Toggle
8675 };
8676
8677 window.focus(&editor.focus_handle(cx), cx);
8678 editor.edit_breakpoint_at_anchor(
8679 position,
8680 breakpoint.as_ref().clone(),
8681 edit_action,
8682 cx,
8683 );
8684 }
8685 }))
8686 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8687 editor.set_breakpoint_context_menu(
8688 row,
8689 Some(position),
8690 event.position(),
8691 window,
8692 cx,
8693 );
8694 }))
8695 .tooltip(move |_window, cx| {
8696 Tooltip::with_meta_in(
8697 primary_action_text,
8698 Some(&ToggleBreakpoint),
8699 meta.clone(),
8700 &focus_handle,
8701 cx,
8702 )
8703 })
8704 }
8705
8706 fn build_tasks_context(
8707 project: &Entity<Project>,
8708 buffer: &Entity<Buffer>,
8709 buffer_row: u32,
8710 tasks: &Arc<RunnableTasks>,
8711 cx: &mut Context<Self>,
8712 ) -> Task<Option<task::TaskContext>> {
8713 let position = Point::new(buffer_row, tasks.column);
8714 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8715 let location = Location {
8716 buffer: buffer.clone(),
8717 range: range_start..range_start,
8718 };
8719 // Fill in the environmental variables from the tree-sitter captures
8720 let mut captured_task_variables = TaskVariables::default();
8721 for (capture_name, value) in tasks.extra_variables.clone() {
8722 captured_task_variables.insert(
8723 task::VariableName::Custom(capture_name.into()),
8724 value.clone(),
8725 );
8726 }
8727 project.update(cx, |project, cx| {
8728 project.task_store().update(cx, |task_store, cx| {
8729 task_store.task_context_for_location(captured_task_variables, location, cx)
8730 })
8731 })
8732 }
8733
8734 pub fn spawn_nearest_task(
8735 &mut self,
8736 action: &SpawnNearestTask,
8737 window: &mut Window,
8738 cx: &mut Context<Self>,
8739 ) {
8740 let Some((workspace, _)) = self.workspace.clone() else {
8741 return;
8742 };
8743 let Some(project) = self.project.clone() else {
8744 return;
8745 };
8746
8747 // Try to find a closest, enclosing node using tree-sitter that has a task
8748 let Some((buffer, buffer_row, tasks)) = self
8749 .find_enclosing_node_task(cx)
8750 // Or find the task that's closest in row-distance.
8751 .or_else(|| self.find_closest_task(cx))
8752 else {
8753 return;
8754 };
8755
8756 let reveal_strategy = action.reveal;
8757 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8758 cx.spawn_in(window, async move |_, cx| {
8759 let context = task_context.await?;
8760 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8761
8762 let resolved = &mut resolved_task.resolved;
8763 resolved.reveal = reveal_strategy;
8764
8765 workspace
8766 .update_in(cx, |workspace, window, cx| {
8767 workspace.schedule_resolved_task(
8768 task_source_kind,
8769 resolved_task,
8770 false,
8771 window,
8772 cx,
8773 );
8774 })
8775 .ok()
8776 })
8777 .detach();
8778 }
8779
8780 fn find_closest_task(
8781 &mut self,
8782 cx: &mut Context<Self>,
8783 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8784 let cursor_row = self
8785 .selections
8786 .newest_adjusted(&self.display_snapshot(cx))
8787 .head()
8788 .row;
8789
8790 let ((buffer_id, row), tasks) = self
8791 .tasks
8792 .iter()
8793 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8794
8795 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8796 let tasks = Arc::new(tasks.to_owned());
8797 Some((buffer, *row, tasks))
8798 }
8799
8800 fn find_enclosing_node_task(
8801 &mut self,
8802 cx: &mut Context<Self>,
8803 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8804 let snapshot = self.buffer.read(cx).snapshot(cx);
8805 let offset = self
8806 .selections
8807 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8808 .head();
8809 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8810 let offset = excerpt.map_offset_to_buffer(offset);
8811 let buffer_id = excerpt.buffer().remote_id();
8812
8813 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8814 let mut cursor = layer.node().walk();
8815
8816 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8817 if cursor.node().end_byte() == offset.0 {
8818 cursor.goto_next_sibling();
8819 }
8820 }
8821
8822 // Ascend to the smallest ancestor that contains the range and has a task.
8823 loop {
8824 let node = cursor.node();
8825 let node_range = node.byte_range();
8826 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8827
8828 // Check if this node contains our offset
8829 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8830 // If it contains offset, check for task
8831 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8832 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8833 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8834 }
8835 }
8836
8837 if !cursor.goto_parent() {
8838 break;
8839 }
8840 }
8841 None
8842 }
8843
8844 fn render_run_indicator(
8845 &self,
8846 _style: &EditorStyle,
8847 is_active: bool,
8848 row: DisplayRow,
8849 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8850 cx: &mut Context<Self>,
8851 ) -> IconButton {
8852 let color = Color::Muted;
8853 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8854
8855 IconButton::new(
8856 ("run_indicator", row.0 as usize),
8857 ui::IconName::PlayOutlined,
8858 )
8859 .shape(ui::IconButtonShape::Square)
8860 .icon_size(IconSize::XSmall)
8861 .icon_color(color)
8862 .toggle_state(is_active)
8863 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8864 let quick_launch = match e {
8865 ClickEvent::Keyboard(_) => true,
8866 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8867 };
8868
8869 window.focus(&editor.focus_handle(cx), cx);
8870 editor.toggle_code_actions(
8871 &ToggleCodeActions {
8872 deployed_from: Some(CodeActionSource::RunMenu(row)),
8873 quick_launch,
8874 },
8875 window,
8876 cx,
8877 );
8878 }))
8879 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8880 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8881 }))
8882 }
8883
8884 pub fn context_menu_visible(&self) -> bool {
8885 !self.edit_prediction_preview_is_active()
8886 && self
8887 .context_menu
8888 .borrow()
8889 .as_ref()
8890 .is_some_and(|menu| menu.visible())
8891 }
8892
8893 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8894 self.context_menu
8895 .borrow()
8896 .as_ref()
8897 .map(|menu| menu.origin())
8898 }
8899
8900 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8901 self.context_menu_options = Some(options);
8902 }
8903
8904 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8905 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8906
8907 fn render_edit_prediction_popover(
8908 &mut self,
8909 text_bounds: &Bounds<Pixels>,
8910 content_origin: gpui::Point<Pixels>,
8911 right_margin: Pixels,
8912 editor_snapshot: &EditorSnapshot,
8913 visible_row_range: Range<DisplayRow>,
8914 scroll_top: ScrollOffset,
8915 scroll_bottom: ScrollOffset,
8916 line_layouts: &[LineWithInvisibles],
8917 line_height: Pixels,
8918 scroll_position: gpui::Point<ScrollOffset>,
8919 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8920 newest_selection_head: Option<DisplayPoint>,
8921 editor_width: Pixels,
8922 style: &EditorStyle,
8923 window: &mut Window,
8924 cx: &mut App,
8925 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8926 if self.mode().is_minimap() {
8927 return None;
8928 }
8929 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8930
8931 if self.edit_prediction_visible_in_cursor_popover(true) {
8932 return None;
8933 }
8934
8935 match &active_edit_prediction.completion {
8936 EditPrediction::MoveWithin { target, .. } => {
8937 let target_display_point = target.to_display_point(editor_snapshot);
8938
8939 if self.edit_prediction_requires_modifier() {
8940 if !self.edit_prediction_preview_is_active() {
8941 return None;
8942 }
8943
8944 self.render_edit_prediction_modifier_jump_popover(
8945 text_bounds,
8946 content_origin,
8947 visible_row_range,
8948 line_layouts,
8949 line_height,
8950 scroll_pixel_position,
8951 newest_selection_head,
8952 target_display_point,
8953 window,
8954 cx,
8955 )
8956 } else {
8957 self.render_edit_prediction_eager_jump_popover(
8958 text_bounds,
8959 content_origin,
8960 editor_snapshot,
8961 visible_row_range,
8962 scroll_top,
8963 scroll_bottom,
8964 line_height,
8965 scroll_pixel_position,
8966 target_display_point,
8967 editor_width,
8968 window,
8969 cx,
8970 )
8971 }
8972 }
8973 EditPrediction::Edit {
8974 display_mode: EditDisplayMode::Inline,
8975 ..
8976 } => None,
8977 EditPrediction::Edit {
8978 display_mode: EditDisplayMode::TabAccept,
8979 edits,
8980 ..
8981 } => {
8982 let range = &edits.first()?.0;
8983 let target_display_point = range.end.to_display_point(editor_snapshot);
8984
8985 self.render_edit_prediction_end_of_line_popover(
8986 "Accept",
8987 editor_snapshot,
8988 visible_row_range,
8989 target_display_point,
8990 line_height,
8991 scroll_pixel_position,
8992 content_origin,
8993 editor_width,
8994 window,
8995 cx,
8996 )
8997 }
8998 EditPrediction::Edit {
8999 edits,
9000 edit_preview,
9001 display_mode: EditDisplayMode::DiffPopover,
9002 snapshot,
9003 } => self.render_edit_prediction_diff_popover(
9004 text_bounds,
9005 content_origin,
9006 right_margin,
9007 editor_snapshot,
9008 visible_row_range,
9009 line_layouts,
9010 line_height,
9011 scroll_position,
9012 scroll_pixel_position,
9013 newest_selection_head,
9014 editor_width,
9015 style,
9016 edits,
9017 edit_preview,
9018 snapshot,
9019 window,
9020 cx,
9021 ),
9022 EditPrediction::MoveOutside { snapshot, .. } => {
9023 let mut element = self
9024 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9025 .into_any();
9026
9027 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9028 let origin_x = text_bounds.size.width - size.width - px(30.);
9029 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9030 element.prepaint_at(origin, window, cx);
9031
9032 Some((element, origin))
9033 }
9034 }
9035 }
9036
9037 fn render_edit_prediction_modifier_jump_popover(
9038 &mut self,
9039 text_bounds: &Bounds<Pixels>,
9040 content_origin: gpui::Point<Pixels>,
9041 visible_row_range: Range<DisplayRow>,
9042 line_layouts: &[LineWithInvisibles],
9043 line_height: Pixels,
9044 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9045 newest_selection_head: Option<DisplayPoint>,
9046 target_display_point: DisplayPoint,
9047 window: &mut Window,
9048 cx: &mut App,
9049 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9050 let scrolled_content_origin =
9051 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9052
9053 const SCROLL_PADDING_Y: Pixels = px(12.);
9054
9055 if target_display_point.row() < visible_row_range.start {
9056 return self.render_edit_prediction_scroll_popover(
9057 |_| SCROLL_PADDING_Y,
9058 IconName::ArrowUp,
9059 visible_row_range,
9060 line_layouts,
9061 newest_selection_head,
9062 scrolled_content_origin,
9063 window,
9064 cx,
9065 );
9066 } else if target_display_point.row() >= visible_row_range.end {
9067 return self.render_edit_prediction_scroll_popover(
9068 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9069 IconName::ArrowDown,
9070 visible_row_range,
9071 line_layouts,
9072 newest_selection_head,
9073 scrolled_content_origin,
9074 window,
9075 cx,
9076 );
9077 }
9078
9079 const POLE_WIDTH: Pixels = px(2.);
9080
9081 let line_layout =
9082 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9083 let target_column = target_display_point.column() as usize;
9084
9085 let target_x = line_layout.x_for_index(target_column);
9086 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9087 - scroll_pixel_position.y;
9088
9089 let flag_on_right = target_x < text_bounds.size.width / 2.;
9090
9091 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9092 border_color.l += 0.001;
9093
9094 let mut element = v_flex()
9095 .items_end()
9096 .when(flag_on_right, |el| el.items_start())
9097 .child(if flag_on_right {
9098 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9099 .rounded_bl(px(0.))
9100 .rounded_tl(px(0.))
9101 .border_l_2()
9102 .border_color(border_color)
9103 } else {
9104 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9105 .rounded_br(px(0.))
9106 .rounded_tr(px(0.))
9107 .border_r_2()
9108 .border_color(border_color)
9109 })
9110 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9111 .into_any();
9112
9113 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9114
9115 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9116 - point(
9117 if flag_on_right {
9118 POLE_WIDTH
9119 } else {
9120 size.width - POLE_WIDTH
9121 },
9122 size.height - line_height,
9123 );
9124
9125 origin.x = origin.x.max(content_origin.x);
9126
9127 element.prepaint_at(origin, window, cx);
9128
9129 Some((element, origin))
9130 }
9131
9132 fn render_edit_prediction_scroll_popover(
9133 &mut self,
9134 to_y: impl Fn(Size<Pixels>) -> Pixels,
9135 scroll_icon: IconName,
9136 visible_row_range: Range<DisplayRow>,
9137 line_layouts: &[LineWithInvisibles],
9138 newest_selection_head: Option<DisplayPoint>,
9139 scrolled_content_origin: gpui::Point<Pixels>,
9140 window: &mut Window,
9141 cx: &mut App,
9142 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9143 let mut element = self
9144 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9145 .into_any();
9146
9147 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9148
9149 let cursor = newest_selection_head?;
9150 let cursor_row_layout =
9151 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9152 let cursor_column = cursor.column() as usize;
9153
9154 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9155
9156 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9157
9158 element.prepaint_at(origin, window, cx);
9159 Some((element, origin))
9160 }
9161
9162 fn render_edit_prediction_eager_jump_popover(
9163 &mut self,
9164 text_bounds: &Bounds<Pixels>,
9165 content_origin: gpui::Point<Pixels>,
9166 editor_snapshot: &EditorSnapshot,
9167 visible_row_range: Range<DisplayRow>,
9168 scroll_top: ScrollOffset,
9169 scroll_bottom: ScrollOffset,
9170 line_height: Pixels,
9171 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9172 target_display_point: DisplayPoint,
9173 editor_width: Pixels,
9174 window: &mut Window,
9175 cx: &mut App,
9176 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9177 if target_display_point.row().as_f64() < scroll_top {
9178 let mut element = self
9179 .render_edit_prediction_line_popover(
9180 "Jump to Edit",
9181 Some(IconName::ArrowUp),
9182 window,
9183 cx,
9184 )
9185 .into_any();
9186
9187 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9188 let offset = point(
9189 (text_bounds.size.width - size.width) / 2.,
9190 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9191 );
9192
9193 let origin = text_bounds.origin + offset;
9194 element.prepaint_at(origin, window, cx);
9195 Some((element, origin))
9196 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9197 let mut element = self
9198 .render_edit_prediction_line_popover(
9199 "Jump to Edit",
9200 Some(IconName::ArrowDown),
9201 window,
9202 cx,
9203 )
9204 .into_any();
9205
9206 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9207 let offset = point(
9208 (text_bounds.size.width - size.width) / 2.,
9209 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9210 );
9211
9212 let origin = text_bounds.origin + offset;
9213 element.prepaint_at(origin, window, cx);
9214 Some((element, origin))
9215 } else {
9216 self.render_edit_prediction_end_of_line_popover(
9217 "Jump to Edit",
9218 editor_snapshot,
9219 visible_row_range,
9220 target_display_point,
9221 line_height,
9222 scroll_pixel_position,
9223 content_origin,
9224 editor_width,
9225 window,
9226 cx,
9227 )
9228 }
9229 }
9230
9231 fn render_edit_prediction_end_of_line_popover(
9232 self: &mut Editor,
9233 label: &'static str,
9234 editor_snapshot: &EditorSnapshot,
9235 visible_row_range: Range<DisplayRow>,
9236 target_display_point: DisplayPoint,
9237 line_height: Pixels,
9238 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9239 content_origin: gpui::Point<Pixels>,
9240 editor_width: Pixels,
9241 window: &mut Window,
9242 cx: &mut App,
9243 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9244 let target_line_end = DisplayPoint::new(
9245 target_display_point.row(),
9246 editor_snapshot.line_len(target_display_point.row()),
9247 );
9248
9249 let mut element = self
9250 .render_edit_prediction_line_popover(label, None, window, cx)
9251 .into_any();
9252
9253 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9254
9255 let line_origin =
9256 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9257
9258 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9259 let mut origin = start_point
9260 + line_origin
9261 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9262 origin.x = origin.x.max(content_origin.x);
9263
9264 let max_x = content_origin.x + editor_width - size.width;
9265
9266 if origin.x > max_x {
9267 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9268
9269 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9270 origin.y += offset;
9271 IconName::ArrowUp
9272 } else {
9273 origin.y -= offset;
9274 IconName::ArrowDown
9275 };
9276
9277 element = self
9278 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9279 .into_any();
9280
9281 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9282
9283 origin.x = content_origin.x + editor_width - size.width - px(2.);
9284 }
9285
9286 element.prepaint_at(origin, window, cx);
9287 Some((element, origin))
9288 }
9289
9290 fn render_edit_prediction_diff_popover(
9291 self: &Editor,
9292 text_bounds: &Bounds<Pixels>,
9293 content_origin: gpui::Point<Pixels>,
9294 right_margin: Pixels,
9295 editor_snapshot: &EditorSnapshot,
9296 visible_row_range: Range<DisplayRow>,
9297 line_layouts: &[LineWithInvisibles],
9298 line_height: Pixels,
9299 scroll_position: gpui::Point<ScrollOffset>,
9300 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9301 newest_selection_head: Option<DisplayPoint>,
9302 editor_width: Pixels,
9303 style: &EditorStyle,
9304 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9305 edit_preview: &Option<language::EditPreview>,
9306 snapshot: &language::BufferSnapshot,
9307 window: &mut Window,
9308 cx: &mut App,
9309 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9310 let edit_start = edits
9311 .first()
9312 .unwrap()
9313 .0
9314 .start
9315 .to_display_point(editor_snapshot);
9316 let edit_end = edits
9317 .last()
9318 .unwrap()
9319 .0
9320 .end
9321 .to_display_point(editor_snapshot);
9322
9323 let is_visible = visible_row_range.contains(&edit_start.row())
9324 || visible_row_range.contains(&edit_end.row());
9325 if !is_visible {
9326 return None;
9327 }
9328
9329 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9330 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9331 } else {
9332 // Fallback for providers without edit_preview
9333 crate::edit_prediction_fallback_text(edits, cx)
9334 };
9335
9336 let styled_text = highlighted_edits.to_styled_text(&style.text);
9337 let line_count = highlighted_edits.text.lines().count();
9338
9339 const BORDER_WIDTH: Pixels = px(1.);
9340
9341 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9342 let has_keybind = keybind.is_some();
9343
9344 let mut element = h_flex()
9345 .items_start()
9346 .child(
9347 h_flex()
9348 .bg(cx.theme().colors().editor_background)
9349 .border(BORDER_WIDTH)
9350 .shadow_xs()
9351 .border_color(cx.theme().colors().border)
9352 .rounded_l_lg()
9353 .when(line_count > 1, |el| el.rounded_br_lg())
9354 .pr_1()
9355 .child(styled_text),
9356 )
9357 .child(
9358 h_flex()
9359 .h(line_height + BORDER_WIDTH * 2.)
9360 .px_1p5()
9361 .gap_1()
9362 // Workaround: For some reason, there's a gap if we don't do this
9363 .ml(-BORDER_WIDTH)
9364 .shadow(vec![gpui::BoxShadow {
9365 color: gpui::black().opacity(0.05),
9366 offset: point(px(1.), px(1.)),
9367 blur_radius: px(2.),
9368 spread_radius: px(0.),
9369 }])
9370 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9371 .border(BORDER_WIDTH)
9372 .border_color(cx.theme().colors().border)
9373 .rounded_r_lg()
9374 .id("edit_prediction_diff_popover_keybind")
9375 .when(!has_keybind, |el| {
9376 let status_colors = cx.theme().status();
9377
9378 el.bg(status_colors.error_background)
9379 .border_color(status_colors.error.opacity(0.6))
9380 .child(Icon::new(IconName::Info).color(Color::Error))
9381 .cursor_default()
9382 .hoverable_tooltip(move |_window, cx| {
9383 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9384 })
9385 })
9386 .children(keybind),
9387 )
9388 .into_any();
9389
9390 let longest_row =
9391 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9392 let longest_line_width = if visible_row_range.contains(&longest_row) {
9393 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9394 } else {
9395 layout_line(
9396 longest_row,
9397 editor_snapshot,
9398 style,
9399 editor_width,
9400 |_| false,
9401 window,
9402 cx,
9403 )
9404 .width
9405 };
9406
9407 let viewport_bounds =
9408 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9409 right: -right_margin,
9410 ..Default::default()
9411 });
9412
9413 let x_after_longest = Pixels::from(
9414 ScrollPixelOffset::from(
9415 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9416 ) - scroll_pixel_position.x,
9417 );
9418
9419 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9420
9421 // Fully visible if it can be displayed within the window (allow overlapping other
9422 // panes). However, this is only allowed if the popover starts within text_bounds.
9423 let can_position_to_the_right = x_after_longest < text_bounds.right()
9424 && x_after_longest + element_bounds.width < viewport_bounds.right();
9425
9426 let mut origin = if can_position_to_the_right {
9427 point(
9428 x_after_longest,
9429 text_bounds.origin.y
9430 + Pixels::from(
9431 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9432 - scroll_pixel_position.y,
9433 ),
9434 )
9435 } else {
9436 let cursor_row = newest_selection_head.map(|head| head.row());
9437 let above_edit = edit_start
9438 .row()
9439 .0
9440 .checked_sub(line_count as u32)
9441 .map(DisplayRow);
9442 let below_edit = Some(edit_end.row() + 1);
9443 let above_cursor =
9444 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9445 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9446
9447 // Place the edit popover adjacent to the edit if there is a location
9448 // available that is onscreen and does not obscure the cursor. Otherwise,
9449 // place it adjacent to the cursor.
9450 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9451 .into_iter()
9452 .flatten()
9453 .find(|&start_row| {
9454 let end_row = start_row + line_count as u32;
9455 visible_row_range.contains(&start_row)
9456 && visible_row_range.contains(&end_row)
9457 && cursor_row
9458 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9459 })?;
9460
9461 content_origin
9462 + point(
9463 Pixels::from(-scroll_pixel_position.x),
9464 Pixels::from(
9465 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9466 ),
9467 )
9468 };
9469
9470 origin.x -= BORDER_WIDTH;
9471
9472 window.defer_draw(element, origin, 1);
9473
9474 // Do not return an element, since it will already be drawn due to defer_draw.
9475 None
9476 }
9477
9478 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9479 px(30.)
9480 }
9481
9482 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9483 if self.read_only(cx) {
9484 cx.theme().players().read_only()
9485 } else {
9486 self.style.as_ref().unwrap().local_player
9487 }
9488 }
9489
9490 fn render_edit_prediction_accept_keybind(
9491 &self,
9492 window: &mut Window,
9493 cx: &mut App,
9494 ) -> Option<AnyElement> {
9495 let accept_binding =
9496 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9497 let accept_keystroke = accept_binding.keystroke()?;
9498
9499 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9500
9501 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9502 Color::Accent
9503 } else {
9504 Color::Muted
9505 };
9506
9507 h_flex()
9508 .px_0p5()
9509 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9510 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9511 .text_size(TextSize::XSmall.rems(cx))
9512 .child(h_flex().children(ui::render_modifiers(
9513 accept_keystroke.modifiers(),
9514 PlatformStyle::platform(),
9515 Some(modifiers_color),
9516 Some(IconSize::XSmall.rems().into()),
9517 true,
9518 )))
9519 .when(is_platform_style_mac, |parent| {
9520 parent.child(accept_keystroke.key().to_string())
9521 })
9522 .when(!is_platform_style_mac, |parent| {
9523 parent.child(
9524 Key::new(
9525 util::capitalize(accept_keystroke.key()),
9526 Some(Color::Default),
9527 )
9528 .size(Some(IconSize::XSmall.rems().into())),
9529 )
9530 })
9531 .into_any()
9532 .into()
9533 }
9534
9535 fn render_edit_prediction_line_popover(
9536 &self,
9537 label: impl Into<SharedString>,
9538 icon: Option<IconName>,
9539 window: &mut Window,
9540 cx: &mut App,
9541 ) -> Stateful<Div> {
9542 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9543
9544 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9545 let has_keybind = keybind.is_some();
9546
9547 h_flex()
9548 .id("ep-line-popover")
9549 .py_0p5()
9550 .pl_1()
9551 .pr(padding_right)
9552 .gap_1()
9553 .rounded_md()
9554 .border_1()
9555 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9556 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9557 .shadow_xs()
9558 .when(!has_keybind, |el| {
9559 let status_colors = cx.theme().status();
9560
9561 el.bg(status_colors.error_background)
9562 .border_color(status_colors.error.opacity(0.6))
9563 .pl_2()
9564 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9565 .cursor_default()
9566 .hoverable_tooltip(move |_window, cx| {
9567 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9568 })
9569 })
9570 .children(keybind)
9571 .child(
9572 Label::new(label)
9573 .size(LabelSize::Small)
9574 .when(!has_keybind, |el| {
9575 el.color(cx.theme().status().error.into()).strikethrough()
9576 }),
9577 )
9578 .when(!has_keybind, |el| {
9579 el.child(
9580 h_flex().ml_1().child(
9581 Icon::new(IconName::Info)
9582 .size(IconSize::Small)
9583 .color(cx.theme().status().error.into()),
9584 ),
9585 )
9586 })
9587 .when_some(icon, |element, icon| {
9588 element.child(
9589 div()
9590 .mt(px(1.5))
9591 .child(Icon::new(icon).size(IconSize::Small)),
9592 )
9593 })
9594 }
9595
9596 fn render_edit_prediction_jump_outside_popover(
9597 &self,
9598 snapshot: &BufferSnapshot,
9599 window: &mut Window,
9600 cx: &mut App,
9601 ) -> Stateful<Div> {
9602 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9603 let has_keybind = keybind.is_some();
9604
9605 let file_name = snapshot
9606 .file()
9607 .map(|file| SharedString::new(file.file_name(cx)))
9608 .unwrap_or(SharedString::new_static("untitled"));
9609
9610 h_flex()
9611 .id("ep-jump-outside-popover")
9612 .py_1()
9613 .px_2()
9614 .gap_1()
9615 .rounded_md()
9616 .border_1()
9617 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9618 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9619 .shadow_xs()
9620 .when(!has_keybind, |el| {
9621 let status_colors = cx.theme().status();
9622
9623 el.bg(status_colors.error_background)
9624 .border_color(status_colors.error.opacity(0.6))
9625 .pl_2()
9626 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9627 .cursor_default()
9628 .hoverable_tooltip(move |_window, cx| {
9629 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9630 })
9631 })
9632 .children(keybind)
9633 .child(
9634 Label::new(file_name)
9635 .size(LabelSize::Small)
9636 .buffer_font(cx)
9637 .when(!has_keybind, |el| {
9638 el.color(cx.theme().status().error.into()).strikethrough()
9639 }),
9640 )
9641 .when(!has_keybind, |el| {
9642 el.child(
9643 h_flex().ml_1().child(
9644 Icon::new(IconName::Info)
9645 .size(IconSize::Small)
9646 .color(cx.theme().status().error.into()),
9647 ),
9648 )
9649 })
9650 .child(
9651 div()
9652 .mt(px(1.5))
9653 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9654 )
9655 }
9656
9657 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9658 let accent_color = cx.theme().colors().text_accent;
9659 let editor_bg_color = cx.theme().colors().editor_background;
9660 editor_bg_color.blend(accent_color.opacity(0.1))
9661 }
9662
9663 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9664 let accent_color = cx.theme().colors().text_accent;
9665 let editor_bg_color = cx.theme().colors().editor_background;
9666 editor_bg_color.blend(accent_color.opacity(0.6))
9667 }
9668 fn get_prediction_provider_icon_name(
9669 provider: &Option<RegisteredEditPredictionDelegate>,
9670 ) -> IconName {
9671 match provider {
9672 Some(provider) => match provider.provider.name() {
9673 "copilot" => IconName::Copilot,
9674 "supermaven" => IconName::Supermaven,
9675 _ => IconName::ZedPredict,
9676 },
9677 None => IconName::ZedPredict,
9678 }
9679 }
9680
9681 fn render_edit_prediction_cursor_popover(
9682 &self,
9683 min_width: Pixels,
9684 max_width: Pixels,
9685 cursor_point: Point,
9686 style: &EditorStyle,
9687 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9688 _window: &Window,
9689 cx: &mut Context<Editor>,
9690 ) -> Option<AnyElement> {
9691 let provider = self.edit_prediction_provider.as_ref()?;
9692 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9693
9694 let is_refreshing = provider.provider.is_refreshing(cx);
9695
9696 fn pending_completion_container(icon: IconName) -> Div {
9697 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9698 }
9699
9700 let completion = match &self.active_edit_prediction {
9701 Some(prediction) => {
9702 if !self.has_visible_completions_menu() {
9703 const RADIUS: Pixels = px(6.);
9704 const BORDER_WIDTH: Pixels = px(1.);
9705
9706 return Some(
9707 h_flex()
9708 .elevation_2(cx)
9709 .border(BORDER_WIDTH)
9710 .border_color(cx.theme().colors().border)
9711 .when(accept_keystroke.is_none(), |el| {
9712 el.border_color(cx.theme().status().error)
9713 })
9714 .rounded(RADIUS)
9715 .rounded_tl(px(0.))
9716 .overflow_hidden()
9717 .child(div().px_1p5().child(match &prediction.completion {
9718 EditPrediction::MoveWithin { target, snapshot } => {
9719 use text::ToPoint as _;
9720 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9721 {
9722 Icon::new(IconName::ZedPredictDown)
9723 } else {
9724 Icon::new(IconName::ZedPredictUp)
9725 }
9726 }
9727 EditPrediction::MoveOutside { .. } => {
9728 // TODO [zeta2] custom icon for external jump?
9729 Icon::new(provider_icon)
9730 }
9731 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9732 }))
9733 .child(
9734 h_flex()
9735 .gap_1()
9736 .py_1()
9737 .px_2()
9738 .rounded_r(RADIUS - BORDER_WIDTH)
9739 .border_l_1()
9740 .border_color(cx.theme().colors().border)
9741 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9742 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9743 el.child(
9744 Label::new("Hold")
9745 .size(LabelSize::Small)
9746 .when(accept_keystroke.is_none(), |el| {
9747 el.strikethrough()
9748 })
9749 .line_height_style(LineHeightStyle::UiLabel),
9750 )
9751 })
9752 .id("edit_prediction_cursor_popover_keybind")
9753 .when(accept_keystroke.is_none(), |el| {
9754 let status_colors = cx.theme().status();
9755
9756 el.bg(status_colors.error_background)
9757 .border_color(status_colors.error.opacity(0.6))
9758 .child(Icon::new(IconName::Info).color(Color::Error))
9759 .cursor_default()
9760 .hoverable_tooltip(move |_window, cx| {
9761 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9762 .into()
9763 })
9764 })
9765 .when_some(
9766 accept_keystroke.as_ref(),
9767 |el, accept_keystroke| {
9768 el.child(h_flex().children(ui::render_modifiers(
9769 accept_keystroke.modifiers(),
9770 PlatformStyle::platform(),
9771 Some(Color::Default),
9772 Some(IconSize::XSmall.rems().into()),
9773 false,
9774 )))
9775 },
9776 ),
9777 )
9778 .into_any(),
9779 );
9780 }
9781
9782 self.render_edit_prediction_cursor_popover_preview(
9783 prediction,
9784 cursor_point,
9785 style,
9786 cx,
9787 )?
9788 }
9789
9790 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9791 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9792 stale_completion,
9793 cursor_point,
9794 style,
9795 cx,
9796 )?,
9797
9798 None => pending_completion_container(provider_icon)
9799 .child(Label::new("...").size(LabelSize::Small)),
9800 },
9801
9802 None => pending_completion_container(provider_icon)
9803 .child(Label::new("...").size(LabelSize::Small)),
9804 };
9805
9806 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9807 completion
9808 .with_animation(
9809 "loading-completion",
9810 Animation::new(Duration::from_secs(2))
9811 .repeat()
9812 .with_easing(pulsating_between(0.4, 0.8)),
9813 |label, delta| label.opacity(delta),
9814 )
9815 .into_any_element()
9816 } else {
9817 completion.into_any_element()
9818 };
9819
9820 let has_completion = self.active_edit_prediction.is_some();
9821
9822 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9823 Some(
9824 h_flex()
9825 .min_w(min_width)
9826 .max_w(max_width)
9827 .flex_1()
9828 .elevation_2(cx)
9829 .border_color(cx.theme().colors().border)
9830 .child(
9831 div()
9832 .flex_1()
9833 .py_1()
9834 .px_2()
9835 .overflow_hidden()
9836 .child(completion),
9837 )
9838 .when_some(accept_keystroke, |el, accept_keystroke| {
9839 if !accept_keystroke.modifiers().modified() {
9840 return el;
9841 }
9842
9843 el.child(
9844 h_flex()
9845 .h_full()
9846 .border_l_1()
9847 .rounded_r_lg()
9848 .border_color(cx.theme().colors().border)
9849 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9850 .gap_1()
9851 .py_1()
9852 .px_2()
9853 .child(
9854 h_flex()
9855 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9856 .when(is_platform_style_mac, |parent| parent.gap_1())
9857 .child(h_flex().children(ui::render_modifiers(
9858 accept_keystroke.modifiers(),
9859 PlatformStyle::platform(),
9860 Some(if !has_completion {
9861 Color::Muted
9862 } else {
9863 Color::Default
9864 }),
9865 None,
9866 false,
9867 ))),
9868 )
9869 .child(Label::new("Preview").into_any_element())
9870 .opacity(if has_completion { 1.0 } else { 0.4 }),
9871 )
9872 })
9873 .into_any(),
9874 )
9875 }
9876
9877 fn render_edit_prediction_cursor_popover_preview(
9878 &self,
9879 completion: &EditPredictionState,
9880 cursor_point: Point,
9881 style: &EditorStyle,
9882 cx: &mut Context<Editor>,
9883 ) -> Option<Div> {
9884 use text::ToPoint as _;
9885
9886 fn render_relative_row_jump(
9887 prefix: impl Into<String>,
9888 current_row: u32,
9889 target_row: u32,
9890 ) -> Div {
9891 let (row_diff, arrow) = if target_row < current_row {
9892 (current_row - target_row, IconName::ArrowUp)
9893 } else {
9894 (target_row - current_row, IconName::ArrowDown)
9895 };
9896
9897 h_flex()
9898 .child(
9899 Label::new(format!("{}{}", prefix.into(), row_diff))
9900 .color(Color::Muted)
9901 .size(LabelSize::Small),
9902 )
9903 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9904 }
9905
9906 let supports_jump = self
9907 .edit_prediction_provider
9908 .as_ref()
9909 .map(|provider| provider.provider.supports_jump_to_edit())
9910 .unwrap_or(true);
9911
9912 match &completion.completion {
9913 EditPrediction::MoveWithin {
9914 target, snapshot, ..
9915 } => {
9916 if !supports_jump {
9917 return None;
9918 }
9919
9920 Some(
9921 h_flex()
9922 .px_2()
9923 .gap_2()
9924 .flex_1()
9925 .child(
9926 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9927 Icon::new(IconName::ZedPredictDown)
9928 } else {
9929 Icon::new(IconName::ZedPredictUp)
9930 },
9931 )
9932 .child(Label::new("Jump to Edit")),
9933 )
9934 }
9935 EditPrediction::MoveOutside { snapshot, .. } => {
9936 let file_name = snapshot
9937 .file()
9938 .map(|file| file.file_name(cx))
9939 .unwrap_or("untitled");
9940 Some(
9941 h_flex()
9942 .px_2()
9943 .gap_2()
9944 .flex_1()
9945 .child(Icon::new(IconName::ZedPredict))
9946 .child(Label::new(format!("Jump to {file_name}"))),
9947 )
9948 }
9949 EditPrediction::Edit {
9950 edits,
9951 edit_preview,
9952 snapshot,
9953 display_mode: _,
9954 } => {
9955 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9956
9957 let (highlighted_edits, has_more_lines) =
9958 if let Some(edit_preview) = edit_preview.as_ref() {
9959 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9960 .first_line_preview()
9961 } else {
9962 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9963 };
9964
9965 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9966 .with_default_highlights(&style.text, highlighted_edits.highlights);
9967
9968 let preview = h_flex()
9969 .gap_1()
9970 .min_w_16()
9971 .child(styled_text)
9972 .when(has_more_lines, |parent| parent.child("…"));
9973
9974 let left = if supports_jump && first_edit_row != cursor_point.row {
9975 render_relative_row_jump("", cursor_point.row, first_edit_row)
9976 .into_any_element()
9977 } else {
9978 let icon_name =
9979 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9980 Icon::new(icon_name).into_any_element()
9981 };
9982
9983 Some(
9984 h_flex()
9985 .h_full()
9986 .flex_1()
9987 .gap_2()
9988 .pr_1()
9989 .overflow_x_hidden()
9990 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9991 .child(left)
9992 .child(preview),
9993 )
9994 }
9995 }
9996 }
9997
9998 pub fn render_context_menu(
9999 &mut self,
10000 max_height_in_lines: u32,
10001 window: &mut Window,
10002 cx: &mut Context<Editor>,
10003 ) -> Option<AnyElement> {
10004 let menu = self.context_menu.borrow();
10005 let menu = menu.as_ref()?;
10006 if !menu.visible() {
10007 return None;
10008 };
10009 self.style
10010 .as_ref()
10011 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10012 }
10013
10014 fn render_context_menu_aside(
10015 &mut self,
10016 max_size: Size<Pixels>,
10017 window: &mut Window,
10018 cx: &mut Context<Editor>,
10019 ) -> Option<AnyElement> {
10020 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10021 if menu.visible() {
10022 menu.render_aside(max_size, window, cx)
10023 } else {
10024 None
10025 }
10026 })
10027 }
10028
10029 fn hide_context_menu(
10030 &mut self,
10031 window: &mut Window,
10032 cx: &mut Context<Self>,
10033 ) -> Option<CodeContextMenu> {
10034 cx.notify();
10035 self.completion_tasks.clear();
10036 let context_menu = self.context_menu.borrow_mut().take();
10037 self.stale_edit_prediction_in_menu.take();
10038 self.update_visible_edit_prediction(window, cx);
10039 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10040 && let Some(completion_provider) = &self.completion_provider
10041 {
10042 completion_provider.selection_changed(None, window, cx);
10043 }
10044 context_menu
10045 }
10046
10047 fn show_snippet_choices(
10048 &mut self,
10049 choices: &Vec<String>,
10050 selection: Range<Anchor>,
10051 cx: &mut Context<Self>,
10052 ) {
10053 let Some((_, buffer, _)) = self
10054 .buffer()
10055 .read(cx)
10056 .excerpt_containing(selection.start, cx)
10057 else {
10058 return;
10059 };
10060 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10061 else {
10062 return;
10063 };
10064 if buffer != end_buffer {
10065 log::error!("expected anchor range to have matching buffer IDs");
10066 return;
10067 }
10068
10069 let id = post_inc(&mut self.next_completion_id);
10070 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10071 let mut context_menu = self.context_menu.borrow_mut();
10072 let old_menu = context_menu.take();
10073 *context_menu = Some(CodeContextMenu::Completions(
10074 CompletionsMenu::new_snippet_choices(
10075 id,
10076 true,
10077 choices,
10078 selection,
10079 buffer,
10080 old_menu.map(|menu| menu.primary_scroll_handle()),
10081 snippet_sort_order,
10082 ),
10083 ));
10084 }
10085
10086 pub fn insert_snippet(
10087 &mut self,
10088 insertion_ranges: &[Range<MultiBufferOffset>],
10089 snippet: Snippet,
10090 window: &mut Window,
10091 cx: &mut Context<Self>,
10092 ) -> Result<()> {
10093 struct Tabstop<T> {
10094 is_end_tabstop: bool,
10095 ranges: Vec<Range<T>>,
10096 choices: Option<Vec<String>>,
10097 }
10098
10099 let tabstops = self.buffer.update(cx, |buffer, cx| {
10100 let snippet_text: Arc<str> = snippet.text.clone().into();
10101 let edits = insertion_ranges
10102 .iter()
10103 .cloned()
10104 .map(|range| (range, snippet_text.clone()));
10105 let autoindent_mode = AutoindentMode::Block {
10106 original_indent_columns: Vec::new(),
10107 };
10108 buffer.edit(edits, Some(autoindent_mode), cx);
10109
10110 let snapshot = &*buffer.read(cx);
10111 let snippet = &snippet;
10112 snippet
10113 .tabstops
10114 .iter()
10115 .map(|tabstop| {
10116 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10117 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10118 });
10119 let mut tabstop_ranges = tabstop
10120 .ranges
10121 .iter()
10122 .flat_map(|tabstop_range| {
10123 let mut delta = 0_isize;
10124 insertion_ranges.iter().map(move |insertion_range| {
10125 let insertion_start = insertion_range.start + delta;
10126 delta += snippet.text.len() as isize
10127 - (insertion_range.end - insertion_range.start) as isize;
10128
10129 let start =
10130 (insertion_start + tabstop_range.start).min(snapshot.len());
10131 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10132 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10133 })
10134 })
10135 .collect::<Vec<_>>();
10136 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10137
10138 Tabstop {
10139 is_end_tabstop,
10140 ranges: tabstop_ranges,
10141 choices: tabstop.choices.clone(),
10142 }
10143 })
10144 .collect::<Vec<_>>()
10145 });
10146 if let Some(tabstop) = tabstops.first() {
10147 self.change_selections(Default::default(), window, cx, |s| {
10148 // Reverse order so that the first range is the newest created selection.
10149 // Completions will use it and autoscroll will prioritize it.
10150 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10151 });
10152
10153 if let Some(choices) = &tabstop.choices
10154 && let Some(selection) = tabstop.ranges.first()
10155 {
10156 self.show_snippet_choices(choices, selection.clone(), cx)
10157 }
10158
10159 // If we're already at the last tabstop and it's at the end of the snippet,
10160 // we're done, we don't need to keep the state around.
10161 if !tabstop.is_end_tabstop {
10162 let choices = tabstops
10163 .iter()
10164 .map(|tabstop| tabstop.choices.clone())
10165 .collect();
10166
10167 let ranges = tabstops
10168 .into_iter()
10169 .map(|tabstop| tabstop.ranges)
10170 .collect::<Vec<_>>();
10171
10172 self.snippet_stack.push(SnippetState {
10173 active_index: 0,
10174 ranges,
10175 choices,
10176 });
10177 }
10178
10179 // Check whether the just-entered snippet ends with an auto-closable bracket.
10180 if self.autoclose_regions.is_empty() {
10181 let snapshot = self.buffer.read(cx).snapshot(cx);
10182 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10183 let selection_head = selection.head();
10184 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10185 continue;
10186 };
10187
10188 let mut bracket_pair = None;
10189 let max_lookup_length = scope
10190 .brackets()
10191 .map(|(pair, _)| {
10192 pair.start
10193 .as_str()
10194 .chars()
10195 .count()
10196 .max(pair.end.as_str().chars().count())
10197 })
10198 .max();
10199 if let Some(max_lookup_length) = max_lookup_length {
10200 let next_text = snapshot
10201 .chars_at(selection_head)
10202 .take(max_lookup_length)
10203 .collect::<String>();
10204 let prev_text = snapshot
10205 .reversed_chars_at(selection_head)
10206 .take(max_lookup_length)
10207 .collect::<String>();
10208
10209 for (pair, enabled) in scope.brackets() {
10210 if enabled
10211 && pair.close
10212 && prev_text.starts_with(pair.start.as_str())
10213 && next_text.starts_with(pair.end.as_str())
10214 {
10215 bracket_pair = Some(pair.clone());
10216 break;
10217 }
10218 }
10219 }
10220
10221 if let Some(pair) = bracket_pair {
10222 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10223 let autoclose_enabled =
10224 self.use_autoclose && snapshot_settings.use_autoclose;
10225 if autoclose_enabled {
10226 let start = snapshot.anchor_after(selection_head);
10227 let end = snapshot.anchor_after(selection_head);
10228 self.autoclose_regions.push(AutocloseRegion {
10229 selection_id: selection.id,
10230 range: start..end,
10231 pair,
10232 });
10233 }
10234 }
10235 }
10236 }
10237 }
10238 Ok(())
10239 }
10240
10241 pub fn move_to_next_snippet_tabstop(
10242 &mut self,
10243 window: &mut Window,
10244 cx: &mut Context<Self>,
10245 ) -> bool {
10246 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10247 }
10248
10249 pub fn move_to_prev_snippet_tabstop(
10250 &mut self,
10251 window: &mut Window,
10252 cx: &mut Context<Self>,
10253 ) -> bool {
10254 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10255 }
10256
10257 pub fn move_to_snippet_tabstop(
10258 &mut self,
10259 bias: Bias,
10260 window: &mut Window,
10261 cx: &mut Context<Self>,
10262 ) -> bool {
10263 if let Some(mut snippet) = self.snippet_stack.pop() {
10264 match bias {
10265 Bias::Left => {
10266 if snippet.active_index > 0 {
10267 snippet.active_index -= 1;
10268 } else {
10269 self.snippet_stack.push(snippet);
10270 return false;
10271 }
10272 }
10273 Bias::Right => {
10274 if snippet.active_index + 1 < snippet.ranges.len() {
10275 snippet.active_index += 1;
10276 } else {
10277 self.snippet_stack.push(snippet);
10278 return false;
10279 }
10280 }
10281 }
10282 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10283 self.change_selections(Default::default(), window, cx, |s| {
10284 // Reverse order so that the first range is the newest created selection.
10285 // Completions will use it and autoscroll will prioritize it.
10286 s.select_ranges(current_ranges.iter().rev().cloned())
10287 });
10288
10289 if let Some(choices) = &snippet.choices[snippet.active_index]
10290 && let Some(selection) = current_ranges.first()
10291 {
10292 self.show_snippet_choices(choices, selection.clone(), cx);
10293 }
10294
10295 // If snippet state is not at the last tabstop, push it back on the stack
10296 if snippet.active_index + 1 < snippet.ranges.len() {
10297 self.snippet_stack.push(snippet);
10298 }
10299 return true;
10300 }
10301 }
10302
10303 false
10304 }
10305
10306 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10307 self.transact(window, cx, |this, window, cx| {
10308 this.select_all(&SelectAll, window, cx);
10309 this.insert("", window, cx);
10310 });
10311 }
10312
10313 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10314 if self.read_only(cx) {
10315 return;
10316 }
10317 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10318 self.transact(window, cx, |this, window, cx| {
10319 this.select_autoclose_pair(window, cx);
10320
10321 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10322
10323 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10324 if !this.linked_edit_ranges.is_empty() {
10325 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10326 let snapshot = this.buffer.read(cx).snapshot(cx);
10327
10328 for selection in selections.iter() {
10329 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10330 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10331 if selection_start.buffer_id != selection_end.buffer_id {
10332 continue;
10333 }
10334 if let Some(ranges) =
10335 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10336 {
10337 for (buffer, entries) in ranges {
10338 linked_ranges.entry(buffer).or_default().extend(entries);
10339 }
10340 }
10341 }
10342 }
10343
10344 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10345 for selection in &mut selections {
10346 if selection.is_empty() {
10347 let old_head = selection.head();
10348 let mut new_head =
10349 movement::left(&display_map, old_head.to_display_point(&display_map))
10350 .to_point(&display_map);
10351 if let Some((buffer, line_buffer_range)) = display_map
10352 .buffer_snapshot()
10353 .buffer_line_for_row(MultiBufferRow(old_head.row))
10354 {
10355 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10356 let indent_len = match indent_size.kind {
10357 IndentKind::Space => {
10358 buffer.settings_at(line_buffer_range.start, cx).tab_size
10359 }
10360 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10361 };
10362 if old_head.column <= indent_size.len && old_head.column > 0 {
10363 let indent_len = indent_len.get();
10364 new_head = cmp::min(
10365 new_head,
10366 MultiBufferPoint::new(
10367 old_head.row,
10368 ((old_head.column - 1) / indent_len) * indent_len,
10369 ),
10370 );
10371 }
10372 }
10373
10374 selection.set_head(new_head, SelectionGoal::None);
10375 }
10376 }
10377
10378 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10379 this.insert("", window, cx);
10380 let empty_str: Arc<str> = Arc::from("");
10381 for (buffer, edits) in linked_ranges {
10382 let snapshot = buffer.read(cx).snapshot();
10383 use text::ToPoint as TP;
10384
10385 let edits = edits
10386 .into_iter()
10387 .map(|range| {
10388 let end_point = TP::to_point(&range.end, &snapshot);
10389 let mut start_point = TP::to_point(&range.start, &snapshot);
10390
10391 if end_point == start_point {
10392 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10393 .saturating_sub(1);
10394 start_point =
10395 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10396 };
10397
10398 (start_point..end_point, empty_str.clone())
10399 })
10400 .sorted_by_key(|(range, _)| range.start)
10401 .collect::<Vec<_>>();
10402 buffer.update(cx, |this, cx| {
10403 this.edit(edits, None, cx);
10404 })
10405 }
10406 this.refresh_edit_prediction(true, false, window, cx);
10407 refresh_linked_ranges(this, window, cx);
10408 });
10409 }
10410
10411 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10412 if self.read_only(cx) {
10413 return;
10414 }
10415 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10416 self.transact(window, cx, |this, window, cx| {
10417 this.change_selections(Default::default(), window, cx, |s| {
10418 s.move_with(|map, selection| {
10419 if selection.is_empty() {
10420 let cursor = movement::right(map, selection.head());
10421 selection.end = cursor;
10422 selection.reversed = true;
10423 selection.goal = SelectionGoal::None;
10424 }
10425 })
10426 });
10427 this.insert("", window, cx);
10428 this.refresh_edit_prediction(true, false, window, cx);
10429 });
10430 }
10431
10432 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10433 if self.mode.is_single_line() {
10434 cx.propagate();
10435 return;
10436 }
10437
10438 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10439 if self.move_to_prev_snippet_tabstop(window, cx) {
10440 return;
10441 }
10442 self.outdent(&Outdent, window, cx);
10443 }
10444
10445 pub fn next_snippet_tabstop(
10446 &mut self,
10447 _: &NextSnippetTabstop,
10448 window: &mut Window,
10449 cx: &mut Context<Self>,
10450 ) {
10451 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10452 cx.propagate();
10453 return;
10454 }
10455
10456 if self.move_to_next_snippet_tabstop(window, cx) {
10457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10458 return;
10459 }
10460 cx.propagate();
10461 }
10462
10463 pub fn previous_snippet_tabstop(
10464 &mut self,
10465 _: &PreviousSnippetTabstop,
10466 window: &mut Window,
10467 cx: &mut Context<Self>,
10468 ) {
10469 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10470 cx.propagate();
10471 return;
10472 }
10473
10474 if self.move_to_prev_snippet_tabstop(window, cx) {
10475 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10476 return;
10477 }
10478 cx.propagate();
10479 }
10480
10481 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10482 if self.mode.is_single_line() {
10483 cx.propagate();
10484 return;
10485 }
10486
10487 if self.move_to_next_snippet_tabstop(window, cx) {
10488 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10489 return;
10490 }
10491 if self.read_only(cx) {
10492 return;
10493 }
10494 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10495 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10496 let buffer = self.buffer.read(cx);
10497 let snapshot = buffer.snapshot(cx);
10498 let rows_iter = selections.iter().map(|s| s.head().row);
10499 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10500
10501 let has_some_cursor_in_whitespace = selections
10502 .iter()
10503 .filter(|selection| selection.is_empty())
10504 .any(|selection| {
10505 let cursor = selection.head();
10506 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10507 cursor.column < current_indent.len
10508 });
10509
10510 let mut edits = Vec::new();
10511 let mut prev_edited_row = 0;
10512 let mut row_delta = 0;
10513 for selection in &mut selections {
10514 if selection.start.row != prev_edited_row {
10515 row_delta = 0;
10516 }
10517 prev_edited_row = selection.end.row;
10518
10519 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10520 if selection.is_empty() {
10521 let cursor = selection.head();
10522 let settings = buffer.language_settings_at(cursor, cx);
10523 if settings.indent_list_on_tab {
10524 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10525 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10526 row_delta = Self::indent_selection(
10527 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10528 );
10529 continue;
10530 }
10531 }
10532 }
10533 }
10534
10535 // If the selection is non-empty, then increase the indentation of the selected lines.
10536 if !selection.is_empty() {
10537 row_delta =
10538 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10539 continue;
10540 }
10541
10542 let cursor = selection.head();
10543 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10544 if let Some(suggested_indent) =
10545 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10546 {
10547 // Don't do anything if already at suggested indent
10548 // and there is any other cursor which is not
10549 if has_some_cursor_in_whitespace
10550 && cursor.column == current_indent.len
10551 && current_indent.len == suggested_indent.len
10552 {
10553 continue;
10554 }
10555
10556 // Adjust line and move cursor to suggested indent
10557 // if cursor is not at suggested indent
10558 if cursor.column < suggested_indent.len
10559 && cursor.column <= current_indent.len
10560 && current_indent.len <= suggested_indent.len
10561 {
10562 selection.start = Point::new(cursor.row, suggested_indent.len);
10563 selection.end = selection.start;
10564 if row_delta == 0 {
10565 edits.extend(Buffer::edit_for_indent_size_adjustment(
10566 cursor.row,
10567 current_indent,
10568 suggested_indent,
10569 ));
10570 row_delta = suggested_indent.len - current_indent.len;
10571 }
10572 continue;
10573 }
10574
10575 // If current indent is more than suggested indent
10576 // only move cursor to current indent and skip indent
10577 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10578 selection.start = Point::new(cursor.row, current_indent.len);
10579 selection.end = selection.start;
10580 continue;
10581 }
10582 }
10583
10584 // Otherwise, insert a hard or soft tab.
10585 let settings = buffer.language_settings_at(cursor, cx);
10586 let tab_size = if settings.hard_tabs {
10587 IndentSize::tab()
10588 } else {
10589 let tab_size = settings.tab_size.get();
10590 let indent_remainder = snapshot
10591 .text_for_range(Point::new(cursor.row, 0)..cursor)
10592 .flat_map(str::chars)
10593 .fold(row_delta % tab_size, |counter: u32, c| {
10594 if c == '\t' {
10595 0
10596 } else {
10597 (counter + 1) % tab_size
10598 }
10599 });
10600
10601 let chars_to_next_tab_stop = tab_size - indent_remainder;
10602 IndentSize::spaces(chars_to_next_tab_stop)
10603 };
10604 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10605 selection.end = selection.start;
10606 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10607 row_delta += tab_size.len;
10608 }
10609
10610 self.transact(window, cx, |this, window, cx| {
10611 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10612 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10613 this.refresh_edit_prediction(true, false, window, cx);
10614 });
10615 }
10616
10617 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10618 if self.read_only(cx) {
10619 return;
10620 }
10621 if self.mode.is_single_line() {
10622 cx.propagate();
10623 return;
10624 }
10625
10626 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10627 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10628 let mut prev_edited_row = 0;
10629 let mut row_delta = 0;
10630 let mut edits = Vec::new();
10631 let buffer = self.buffer.read(cx);
10632 let snapshot = buffer.snapshot(cx);
10633 for selection in &mut selections {
10634 if selection.start.row != prev_edited_row {
10635 row_delta = 0;
10636 }
10637 prev_edited_row = selection.end.row;
10638
10639 row_delta =
10640 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
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 });
10647 }
10648
10649 fn indent_selection(
10650 buffer: &MultiBuffer,
10651 snapshot: &MultiBufferSnapshot,
10652 selection: &mut Selection<Point>,
10653 edits: &mut Vec<(Range<Point>, String)>,
10654 delta_for_start_row: u32,
10655 cx: &App,
10656 ) -> u32 {
10657 let settings = buffer.language_settings_at(selection.start, cx);
10658 let tab_size = settings.tab_size.get();
10659 let indent_kind = if settings.hard_tabs {
10660 IndentKind::Tab
10661 } else {
10662 IndentKind::Space
10663 };
10664 let mut start_row = selection.start.row;
10665 let mut end_row = selection.end.row + 1;
10666
10667 // If a selection ends at the beginning of a line, don't indent
10668 // that last line.
10669 if selection.end.column == 0 && selection.end.row > selection.start.row {
10670 end_row -= 1;
10671 }
10672
10673 // Avoid re-indenting a row that has already been indented by a
10674 // previous selection, but still update this selection's column
10675 // to reflect that indentation.
10676 if delta_for_start_row > 0 {
10677 start_row += 1;
10678 selection.start.column += delta_for_start_row;
10679 if selection.end.row == selection.start.row {
10680 selection.end.column += delta_for_start_row;
10681 }
10682 }
10683
10684 let mut delta_for_end_row = 0;
10685 let has_multiple_rows = start_row + 1 != end_row;
10686 for row in start_row..end_row {
10687 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10688 let indent_delta = match (current_indent.kind, indent_kind) {
10689 (IndentKind::Space, IndentKind::Space) => {
10690 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10691 IndentSize::spaces(columns_to_next_tab_stop)
10692 }
10693 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10694 (_, IndentKind::Tab) => IndentSize::tab(),
10695 };
10696
10697 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10698 0
10699 } else {
10700 selection.start.column
10701 };
10702 let row_start = Point::new(row, start);
10703 edits.push((
10704 row_start..row_start,
10705 indent_delta.chars().collect::<String>(),
10706 ));
10707
10708 // Update this selection's endpoints to reflect the indentation.
10709 if row == selection.start.row {
10710 selection.start.column += indent_delta.len;
10711 }
10712 if row == selection.end.row {
10713 selection.end.column += indent_delta.len;
10714 delta_for_end_row = indent_delta.len;
10715 }
10716 }
10717
10718 if selection.start.row == selection.end.row {
10719 delta_for_start_row + delta_for_end_row
10720 } else {
10721 delta_for_end_row
10722 }
10723 }
10724
10725 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10726 if self.read_only(cx) {
10727 return;
10728 }
10729 if self.mode.is_single_line() {
10730 cx.propagate();
10731 return;
10732 }
10733
10734 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10735 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10736 let selections = self.selections.all::<Point>(&display_map);
10737 let mut deletion_ranges = Vec::new();
10738 let mut last_outdent = None;
10739 {
10740 let buffer = self.buffer.read(cx);
10741 let snapshot = buffer.snapshot(cx);
10742 for selection in &selections {
10743 let settings = buffer.language_settings_at(selection.start, cx);
10744 let tab_size = settings.tab_size.get();
10745 let mut rows = selection.spanned_rows(false, &display_map);
10746
10747 // Avoid re-outdenting a row that has already been outdented by a
10748 // previous selection.
10749 if let Some(last_row) = last_outdent
10750 && last_row == rows.start
10751 {
10752 rows.start = rows.start.next_row();
10753 }
10754 let has_multiple_rows = rows.len() > 1;
10755 for row in rows.iter_rows() {
10756 let indent_size = snapshot.indent_size_for_line(row);
10757 if indent_size.len > 0 {
10758 let deletion_len = match indent_size.kind {
10759 IndentKind::Space => {
10760 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10761 if columns_to_prev_tab_stop == 0 {
10762 tab_size
10763 } else {
10764 columns_to_prev_tab_stop
10765 }
10766 }
10767 IndentKind::Tab => 1,
10768 };
10769 let start = if has_multiple_rows
10770 || deletion_len > selection.start.column
10771 || indent_size.len < selection.start.column
10772 {
10773 0
10774 } else {
10775 selection.start.column - deletion_len
10776 };
10777 deletion_ranges.push(
10778 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10779 );
10780 last_outdent = Some(row);
10781 }
10782 }
10783 }
10784 }
10785
10786 self.transact(window, cx, |this, window, cx| {
10787 this.buffer.update(cx, |buffer, cx| {
10788 let empty_str: Arc<str> = Arc::default();
10789 buffer.edit(
10790 deletion_ranges
10791 .into_iter()
10792 .map(|range| (range, empty_str.clone())),
10793 None,
10794 cx,
10795 );
10796 });
10797 let selections = this
10798 .selections
10799 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10800 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10801 });
10802 }
10803
10804 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10805 if self.read_only(cx) {
10806 return;
10807 }
10808 if self.mode.is_single_line() {
10809 cx.propagate();
10810 return;
10811 }
10812
10813 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10814 let selections = self
10815 .selections
10816 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10817 .into_iter()
10818 .map(|s| s.range());
10819
10820 self.transact(window, cx, |this, window, cx| {
10821 this.buffer.update(cx, |buffer, cx| {
10822 buffer.autoindent_ranges(selections, cx);
10823 });
10824 let selections = this
10825 .selections
10826 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10827 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10828 });
10829 }
10830
10831 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10832 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10833 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10834 let selections = self.selections.all::<Point>(&display_map);
10835
10836 let mut new_cursors = Vec::new();
10837 let mut edit_ranges = Vec::new();
10838 let mut selections = selections.iter().peekable();
10839 while let Some(selection) = selections.next() {
10840 let mut rows = selection.spanned_rows(false, &display_map);
10841
10842 // Accumulate contiguous regions of rows that we want to delete.
10843 while let Some(next_selection) = selections.peek() {
10844 let next_rows = next_selection.spanned_rows(false, &display_map);
10845 if next_rows.start <= rows.end {
10846 rows.end = next_rows.end;
10847 selections.next().unwrap();
10848 } else {
10849 break;
10850 }
10851 }
10852
10853 let buffer = display_map.buffer_snapshot();
10854 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10855 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10856 // If there's a line after the range, delete the \n from the end of the row range
10857 (
10858 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10859 rows.end,
10860 )
10861 } else {
10862 // If there isn't a line after the range, delete the \n from the line before the
10863 // start of the row range
10864 edit_start = edit_start.saturating_sub_usize(1);
10865 (buffer.len(), rows.start.previous_row())
10866 };
10867
10868 let text_layout_details = self.text_layout_details(window);
10869 let x = display_map.x_for_display_point(
10870 selection.head().to_display_point(&display_map),
10871 &text_layout_details,
10872 );
10873 let row = Point::new(target_row.0, 0)
10874 .to_display_point(&display_map)
10875 .row();
10876 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10877
10878 new_cursors.push((
10879 selection.id,
10880 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10881 SelectionGoal::None,
10882 ));
10883 edit_ranges.push(edit_start..edit_end);
10884 }
10885
10886 self.transact(window, cx, |this, window, cx| {
10887 let buffer = this.buffer.update(cx, |buffer, cx| {
10888 let empty_str: Arc<str> = Arc::default();
10889 buffer.edit(
10890 edit_ranges
10891 .into_iter()
10892 .map(|range| (range, empty_str.clone())),
10893 None,
10894 cx,
10895 );
10896 buffer.snapshot(cx)
10897 });
10898 let new_selections = new_cursors
10899 .into_iter()
10900 .map(|(id, cursor, goal)| {
10901 let cursor = cursor.to_point(&buffer);
10902 Selection {
10903 id,
10904 start: cursor,
10905 end: cursor,
10906 reversed: false,
10907 goal,
10908 }
10909 })
10910 .collect();
10911
10912 this.change_selections(Default::default(), window, cx, |s| {
10913 s.select(new_selections);
10914 });
10915 });
10916 }
10917
10918 pub fn join_lines_impl(
10919 &mut self,
10920 insert_whitespace: bool,
10921 window: &mut Window,
10922 cx: &mut Context<Self>,
10923 ) {
10924 if self.read_only(cx) {
10925 return;
10926 }
10927 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10928 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10929 let start = MultiBufferRow(selection.start.row);
10930 // Treat single line selections as if they include the next line. Otherwise this action
10931 // would do nothing for single line selections individual cursors.
10932 let end = if selection.start.row == selection.end.row {
10933 MultiBufferRow(selection.start.row + 1)
10934 } else {
10935 MultiBufferRow(selection.end.row)
10936 };
10937
10938 if let Some(last_row_range) = row_ranges.last_mut()
10939 && start <= last_row_range.end
10940 {
10941 last_row_range.end = end;
10942 continue;
10943 }
10944 row_ranges.push(start..end);
10945 }
10946
10947 let snapshot = self.buffer.read(cx).snapshot(cx);
10948 let mut cursor_positions = Vec::new();
10949 for row_range in &row_ranges {
10950 let anchor = snapshot.anchor_before(Point::new(
10951 row_range.end.previous_row().0,
10952 snapshot.line_len(row_range.end.previous_row()),
10953 ));
10954 cursor_positions.push(anchor..anchor);
10955 }
10956
10957 self.transact(window, cx, |this, window, cx| {
10958 for row_range in row_ranges.into_iter().rev() {
10959 for row in row_range.iter_rows().rev() {
10960 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10961 let next_line_row = row.next_row();
10962 let indent = snapshot.indent_size_for_line(next_line_row);
10963 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10964
10965 let replace =
10966 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10967 " "
10968 } else {
10969 ""
10970 };
10971
10972 this.buffer.update(cx, |buffer, cx| {
10973 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10974 });
10975 }
10976 }
10977
10978 this.change_selections(Default::default(), window, cx, |s| {
10979 s.select_anchor_ranges(cursor_positions)
10980 });
10981 });
10982 }
10983
10984 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10985 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10986 self.join_lines_impl(true, window, cx);
10987 }
10988
10989 pub fn sort_lines_case_sensitive(
10990 &mut self,
10991 _: &SortLinesCaseSensitive,
10992 window: &mut Window,
10993 cx: &mut Context<Self>,
10994 ) {
10995 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10996 }
10997
10998 pub fn sort_lines_by_length(
10999 &mut self,
11000 _: &SortLinesByLength,
11001 window: &mut Window,
11002 cx: &mut Context<Self>,
11003 ) {
11004 self.manipulate_immutable_lines(window, cx, |lines| {
11005 lines.sort_by_key(|&line| line.chars().count())
11006 })
11007 }
11008
11009 pub fn sort_lines_case_insensitive(
11010 &mut self,
11011 _: &SortLinesCaseInsensitive,
11012 window: &mut Window,
11013 cx: &mut Context<Self>,
11014 ) {
11015 self.manipulate_immutable_lines(window, cx, |lines| {
11016 lines.sort_by_key(|line| line.to_lowercase())
11017 })
11018 }
11019
11020 pub fn unique_lines_case_insensitive(
11021 &mut self,
11022 _: &UniqueLinesCaseInsensitive,
11023 window: &mut Window,
11024 cx: &mut Context<Self>,
11025 ) {
11026 self.manipulate_immutable_lines(window, cx, |lines| {
11027 let mut seen = HashSet::default();
11028 lines.retain(|line| seen.insert(line.to_lowercase()));
11029 })
11030 }
11031
11032 pub fn unique_lines_case_sensitive(
11033 &mut self,
11034 _: &UniqueLinesCaseSensitive,
11035 window: &mut Window,
11036 cx: &mut Context<Self>,
11037 ) {
11038 self.manipulate_immutable_lines(window, cx, |lines| {
11039 let mut seen = HashSet::default();
11040 lines.retain(|line| seen.insert(*line));
11041 })
11042 }
11043
11044 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11045 let snapshot = self.buffer.read(cx).snapshot(cx);
11046 for selection in self.selections.disjoint_anchors_arc().iter() {
11047 if snapshot
11048 .language_at(selection.start)
11049 .and_then(|lang| lang.config().wrap_characters.as_ref())
11050 .is_some()
11051 {
11052 return true;
11053 }
11054 }
11055 false
11056 }
11057
11058 fn wrap_selections_in_tag(
11059 &mut self,
11060 _: &WrapSelectionsInTag,
11061 window: &mut Window,
11062 cx: &mut Context<Self>,
11063 ) {
11064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11065
11066 let snapshot = self.buffer.read(cx).snapshot(cx);
11067
11068 let mut edits = Vec::new();
11069 let mut boundaries = Vec::new();
11070
11071 for selection in self
11072 .selections
11073 .all_adjusted(&self.display_snapshot(cx))
11074 .iter()
11075 {
11076 let Some(wrap_config) = snapshot
11077 .language_at(selection.start)
11078 .and_then(|lang| lang.config().wrap_characters.clone())
11079 else {
11080 continue;
11081 };
11082
11083 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11084 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11085
11086 let start_before = snapshot.anchor_before(selection.start);
11087 let end_after = snapshot.anchor_after(selection.end);
11088
11089 edits.push((start_before..start_before, open_tag));
11090 edits.push((end_after..end_after, close_tag));
11091
11092 boundaries.push((
11093 start_before,
11094 end_after,
11095 wrap_config.start_prefix.len(),
11096 wrap_config.end_suffix.len(),
11097 ));
11098 }
11099
11100 if edits.is_empty() {
11101 return;
11102 }
11103
11104 self.transact(window, cx, |this, window, cx| {
11105 let buffer = this.buffer.update(cx, |buffer, cx| {
11106 buffer.edit(edits, None, cx);
11107 buffer.snapshot(cx)
11108 });
11109
11110 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11111 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11112 boundaries.into_iter()
11113 {
11114 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11115 let close_offset = end_after
11116 .to_offset(&buffer)
11117 .saturating_sub_usize(end_suffix_len);
11118 new_selections.push(open_offset..open_offset);
11119 new_selections.push(close_offset..close_offset);
11120 }
11121
11122 this.change_selections(Default::default(), window, cx, |s| {
11123 s.select_ranges(new_selections);
11124 });
11125
11126 this.request_autoscroll(Autoscroll::fit(), cx);
11127 });
11128 }
11129
11130 pub fn toggle_read_only(
11131 &mut self,
11132 _: &workspace::ToggleReadOnlyFile,
11133 _: &mut Window,
11134 cx: &mut Context<Self>,
11135 ) {
11136 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11137 buffer.update(cx, |buffer, cx| {
11138 buffer.set_capability(
11139 match buffer.capability() {
11140 Capability::ReadWrite => Capability::Read,
11141 Capability::Read => Capability::ReadWrite,
11142 Capability::ReadOnly => Capability::ReadOnly,
11143 },
11144 cx,
11145 );
11146 })
11147 }
11148 }
11149
11150 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11151 let Some(project) = self.project.clone() else {
11152 return;
11153 };
11154 self.reload(project, window, cx)
11155 .detach_and_notify_err(window, cx);
11156 }
11157
11158 pub fn restore_file(
11159 &mut self,
11160 _: &::git::RestoreFile,
11161 window: &mut Window,
11162 cx: &mut Context<Self>,
11163 ) {
11164 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11165 let mut buffer_ids = HashSet::default();
11166 let snapshot = self.buffer().read(cx).snapshot(cx);
11167 for selection in self
11168 .selections
11169 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11170 {
11171 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11172 }
11173
11174 let buffer = self.buffer().read(cx);
11175 let ranges = buffer_ids
11176 .into_iter()
11177 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11178 .collect::<Vec<_>>();
11179
11180 self.restore_hunks_in_ranges(ranges, window, cx);
11181 }
11182
11183 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11184 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11185 let selections = self
11186 .selections
11187 .all(&self.display_snapshot(cx))
11188 .into_iter()
11189 .map(|s| s.range())
11190 .collect();
11191 self.restore_hunks_in_ranges(selections, window, cx);
11192 }
11193
11194 pub fn restore_hunks_in_ranges(
11195 &mut self,
11196 ranges: Vec<Range<Point>>,
11197 window: &mut Window,
11198 cx: &mut Context<Editor>,
11199 ) {
11200 let mut revert_changes = HashMap::default();
11201 let chunk_by = self
11202 .snapshot(window, cx)
11203 .hunks_for_ranges(ranges)
11204 .into_iter()
11205 .chunk_by(|hunk| hunk.buffer_id);
11206 for (buffer_id, hunks) in &chunk_by {
11207 let hunks = hunks.collect::<Vec<_>>();
11208 for hunk in &hunks {
11209 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11210 }
11211 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11212 }
11213 drop(chunk_by);
11214 if !revert_changes.is_empty() {
11215 self.transact(window, cx, |editor, window, cx| {
11216 editor.restore(revert_changes, window, cx);
11217 });
11218 }
11219 }
11220
11221 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11222 if let Some(status) = self
11223 .addons
11224 .iter()
11225 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11226 {
11227 return Some(status);
11228 }
11229 self.project
11230 .as_ref()?
11231 .read(cx)
11232 .status_for_buffer_id(buffer_id, cx)
11233 }
11234
11235 pub fn open_active_item_in_terminal(
11236 &mut self,
11237 _: &OpenInTerminal,
11238 window: &mut Window,
11239 cx: &mut Context<Self>,
11240 ) {
11241 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11242 let project_path = buffer.read(cx).project_path(cx)?;
11243 let project = self.project()?.read(cx);
11244 let entry = project.entry_for_path(&project_path, cx)?;
11245 let parent = match &entry.canonical_path {
11246 Some(canonical_path) => canonical_path.to_path_buf(),
11247 None => project.absolute_path(&project_path, cx)?,
11248 }
11249 .parent()?
11250 .to_path_buf();
11251 Some(parent)
11252 }) {
11253 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11254 }
11255 }
11256
11257 fn set_breakpoint_context_menu(
11258 &mut self,
11259 display_row: DisplayRow,
11260 position: Option<Anchor>,
11261 clicked_point: gpui::Point<Pixels>,
11262 window: &mut Window,
11263 cx: &mut Context<Self>,
11264 ) {
11265 let source = self
11266 .buffer
11267 .read(cx)
11268 .snapshot(cx)
11269 .anchor_before(Point::new(display_row.0, 0u32));
11270
11271 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11272
11273 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11274 self,
11275 source,
11276 clicked_point,
11277 context_menu,
11278 window,
11279 cx,
11280 );
11281 }
11282
11283 fn add_edit_breakpoint_block(
11284 &mut self,
11285 anchor: Anchor,
11286 breakpoint: &Breakpoint,
11287 edit_action: BreakpointPromptEditAction,
11288 window: &mut Window,
11289 cx: &mut Context<Self>,
11290 ) {
11291 let weak_editor = cx.weak_entity();
11292 let bp_prompt = cx.new(|cx| {
11293 BreakpointPromptEditor::new(
11294 weak_editor,
11295 anchor,
11296 breakpoint.clone(),
11297 edit_action,
11298 window,
11299 cx,
11300 )
11301 });
11302
11303 let height = bp_prompt.update(cx, |this, cx| {
11304 this.prompt
11305 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11306 });
11307 let cloned_prompt = bp_prompt.clone();
11308 let blocks = vec![BlockProperties {
11309 style: BlockStyle::Sticky,
11310 placement: BlockPlacement::Above(anchor),
11311 height: Some(height),
11312 render: Arc::new(move |cx| {
11313 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11314 cloned_prompt.clone().into_any_element()
11315 }),
11316 priority: 0,
11317 }];
11318
11319 let focus_handle = bp_prompt.focus_handle(cx);
11320 window.focus(&focus_handle, cx);
11321
11322 let block_ids = self.insert_blocks(blocks, None, cx);
11323 bp_prompt.update(cx, |prompt, _| {
11324 prompt.add_block_ids(block_ids);
11325 });
11326 }
11327
11328 pub(crate) fn breakpoint_at_row(
11329 &self,
11330 row: u32,
11331 window: &mut Window,
11332 cx: &mut Context<Self>,
11333 ) -> Option<(Anchor, Breakpoint)> {
11334 let snapshot = self.snapshot(window, cx);
11335 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11336
11337 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11338 }
11339
11340 pub(crate) fn breakpoint_at_anchor(
11341 &self,
11342 breakpoint_position: Anchor,
11343 snapshot: &EditorSnapshot,
11344 cx: &mut Context<Self>,
11345 ) -> Option<(Anchor, Breakpoint)> {
11346 let buffer = self
11347 .buffer
11348 .read(cx)
11349 .buffer_for_anchor(breakpoint_position, cx)?;
11350
11351 let enclosing_excerpt = breakpoint_position.excerpt_id;
11352 let buffer_snapshot = buffer.read(cx).snapshot();
11353
11354 let row = buffer_snapshot
11355 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11356 .row;
11357
11358 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11359 let anchor_end = snapshot
11360 .buffer_snapshot()
11361 .anchor_after(Point::new(row, line_len));
11362
11363 self.breakpoint_store
11364 .as_ref()?
11365 .read_with(cx, |breakpoint_store, cx| {
11366 breakpoint_store
11367 .breakpoints(
11368 &buffer,
11369 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11370 &buffer_snapshot,
11371 cx,
11372 )
11373 .next()
11374 .and_then(|(bp, _)| {
11375 let breakpoint_row = buffer_snapshot
11376 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11377 .row;
11378
11379 if breakpoint_row == row {
11380 snapshot
11381 .buffer_snapshot()
11382 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11383 .map(|position| (position, bp.bp.clone()))
11384 } else {
11385 None
11386 }
11387 })
11388 })
11389 }
11390
11391 pub fn edit_log_breakpoint(
11392 &mut self,
11393 _: &EditLogBreakpoint,
11394 window: &mut Window,
11395 cx: &mut Context<Self>,
11396 ) {
11397 if self.breakpoint_store.is_none() {
11398 return;
11399 }
11400
11401 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11402 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11403 message: None,
11404 state: BreakpointState::Enabled,
11405 condition: None,
11406 hit_condition: None,
11407 });
11408
11409 self.add_edit_breakpoint_block(
11410 anchor,
11411 &breakpoint,
11412 BreakpointPromptEditAction::Log,
11413 window,
11414 cx,
11415 );
11416 }
11417 }
11418
11419 fn breakpoints_at_cursors(
11420 &self,
11421 window: &mut Window,
11422 cx: &mut Context<Self>,
11423 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11424 let snapshot = self.snapshot(window, cx);
11425 let cursors = self
11426 .selections
11427 .disjoint_anchors_arc()
11428 .iter()
11429 .map(|selection| {
11430 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11431
11432 let breakpoint_position = self
11433 .breakpoint_at_row(cursor_position.row, window, cx)
11434 .map(|bp| bp.0)
11435 .unwrap_or_else(|| {
11436 snapshot
11437 .display_snapshot
11438 .buffer_snapshot()
11439 .anchor_after(Point::new(cursor_position.row, 0))
11440 });
11441
11442 let breakpoint = self
11443 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11444 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11445
11446 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11447 })
11448 // 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.
11449 .collect::<HashMap<Anchor, _>>();
11450
11451 cursors.into_iter().collect()
11452 }
11453
11454 pub fn enable_breakpoint(
11455 &mut self,
11456 _: &crate::actions::EnableBreakpoint,
11457 window: &mut Window,
11458 cx: &mut Context<Self>,
11459 ) {
11460 if self.breakpoint_store.is_none() {
11461 return;
11462 }
11463
11464 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11465 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11466 continue;
11467 };
11468 self.edit_breakpoint_at_anchor(
11469 anchor,
11470 breakpoint,
11471 BreakpointEditAction::InvertState,
11472 cx,
11473 );
11474 }
11475 }
11476
11477 pub fn disable_breakpoint(
11478 &mut self,
11479 _: &crate::actions::DisableBreakpoint,
11480 window: &mut Window,
11481 cx: &mut Context<Self>,
11482 ) {
11483 if self.breakpoint_store.is_none() {
11484 return;
11485 }
11486
11487 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11488 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11489 continue;
11490 };
11491 self.edit_breakpoint_at_anchor(
11492 anchor,
11493 breakpoint,
11494 BreakpointEditAction::InvertState,
11495 cx,
11496 );
11497 }
11498 }
11499
11500 pub fn toggle_breakpoint(
11501 &mut self,
11502 _: &crate::actions::ToggleBreakpoint,
11503 window: &mut Window,
11504 cx: &mut Context<Self>,
11505 ) {
11506 if self.breakpoint_store.is_none() {
11507 return;
11508 }
11509
11510 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11511 if let Some(breakpoint) = breakpoint {
11512 self.edit_breakpoint_at_anchor(
11513 anchor,
11514 breakpoint,
11515 BreakpointEditAction::Toggle,
11516 cx,
11517 );
11518 } else {
11519 self.edit_breakpoint_at_anchor(
11520 anchor,
11521 Breakpoint::new_standard(),
11522 BreakpointEditAction::Toggle,
11523 cx,
11524 );
11525 }
11526 }
11527 }
11528
11529 pub fn edit_breakpoint_at_anchor(
11530 &mut self,
11531 breakpoint_position: Anchor,
11532 breakpoint: Breakpoint,
11533 edit_action: BreakpointEditAction,
11534 cx: &mut Context<Self>,
11535 ) {
11536 let Some(breakpoint_store) = &self.breakpoint_store else {
11537 return;
11538 };
11539
11540 let Some(buffer) = self
11541 .buffer
11542 .read(cx)
11543 .buffer_for_anchor(breakpoint_position, cx)
11544 else {
11545 return;
11546 };
11547
11548 breakpoint_store.update(cx, |breakpoint_store, cx| {
11549 breakpoint_store.toggle_breakpoint(
11550 buffer,
11551 BreakpointWithPosition {
11552 position: breakpoint_position.text_anchor,
11553 bp: breakpoint,
11554 },
11555 edit_action,
11556 cx,
11557 );
11558 });
11559
11560 cx.notify();
11561 }
11562
11563 #[cfg(any(test, feature = "test-support"))]
11564 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11565 self.breakpoint_store.clone()
11566 }
11567
11568 pub fn prepare_restore_change(
11569 &self,
11570 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11571 hunk: &MultiBufferDiffHunk,
11572 cx: &mut App,
11573 ) -> Option<()> {
11574 if hunk.is_created_file() {
11575 return None;
11576 }
11577 let buffer = self.buffer.read(cx);
11578 let diff = buffer.diff_for(hunk.buffer_id)?;
11579 let buffer = buffer.buffer(hunk.buffer_id)?;
11580 let buffer = buffer.read(cx);
11581 let original_text = diff
11582 .read(cx)
11583 .base_text(cx)
11584 .as_rope()
11585 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11586 let buffer_snapshot = buffer.snapshot();
11587 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11588 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11589 probe
11590 .0
11591 .start
11592 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11593 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11594 }) {
11595 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11596 Some(())
11597 } else {
11598 None
11599 }
11600 }
11601
11602 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11603 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11604 }
11605
11606 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11607 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11608 }
11609
11610 pub fn rotate_selections_forward(
11611 &mut self,
11612 _: &RotateSelectionsForward,
11613 window: &mut Window,
11614 cx: &mut Context<Self>,
11615 ) {
11616 self.rotate_selections(window, cx, false)
11617 }
11618
11619 pub fn rotate_selections_backward(
11620 &mut self,
11621 _: &RotateSelectionsBackward,
11622 window: &mut Window,
11623 cx: &mut Context<Self>,
11624 ) {
11625 self.rotate_selections(window, cx, true)
11626 }
11627
11628 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11629 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11630 let display_snapshot = self.display_snapshot(cx);
11631 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11632
11633 if selections.len() < 2 {
11634 return;
11635 }
11636
11637 let (edits, new_selections) = {
11638 let buffer = self.buffer.read(cx).read(cx);
11639 let has_selections = selections.iter().any(|s| !s.is_empty());
11640 if has_selections {
11641 let mut selected_texts: Vec<String> = selections
11642 .iter()
11643 .map(|selection| {
11644 buffer
11645 .text_for_range(selection.start..selection.end)
11646 .collect()
11647 })
11648 .collect();
11649
11650 if reverse {
11651 selected_texts.rotate_left(1);
11652 } else {
11653 selected_texts.rotate_right(1);
11654 }
11655
11656 let mut offset_delta: i64 = 0;
11657 let mut new_selections = Vec::new();
11658 let edits: Vec<_> = selections
11659 .iter()
11660 .zip(selected_texts.iter())
11661 .map(|(selection, new_text)| {
11662 let old_len = (selection.end.0 - selection.start.0) as i64;
11663 let new_len = new_text.len() as i64;
11664 let adjusted_start =
11665 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11666 let adjusted_end =
11667 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11668
11669 new_selections.push(Selection {
11670 id: selection.id,
11671 start: adjusted_start,
11672 end: adjusted_end,
11673 reversed: selection.reversed,
11674 goal: selection.goal,
11675 });
11676
11677 offset_delta += new_len - old_len;
11678 (selection.start..selection.end, new_text.clone())
11679 })
11680 .collect();
11681 (edits, new_selections)
11682 } else {
11683 let mut all_rows: Vec<u32> = selections
11684 .iter()
11685 .map(|selection| buffer.offset_to_point(selection.start).row)
11686 .collect();
11687 all_rows.sort_unstable();
11688 all_rows.dedup();
11689
11690 if all_rows.len() < 2 {
11691 return;
11692 }
11693
11694 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11695 .iter()
11696 .map(|&row| {
11697 let start = Point::new(row, 0);
11698 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11699 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11700 })
11701 .collect();
11702
11703 let mut line_texts: Vec<String> = line_ranges
11704 .iter()
11705 .map(|range| buffer.text_for_range(range.clone()).collect())
11706 .collect();
11707
11708 if reverse {
11709 line_texts.rotate_left(1);
11710 } else {
11711 line_texts.rotate_right(1);
11712 }
11713
11714 let edits = line_ranges
11715 .iter()
11716 .zip(line_texts.iter())
11717 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11718 .collect();
11719
11720 let num_rows = all_rows.len();
11721 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11722 .iter()
11723 .enumerate()
11724 .map(|(i, &row)| (row, i))
11725 .collect();
11726
11727 // Compute new line start offsets after rotation (handles CRLF)
11728 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11729 let first_line_start = line_ranges[0].start.0;
11730 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11731 for text in line_texts.iter().take(num_rows - 1) {
11732 let prev_start = *new_line_starts.last().unwrap();
11733 new_line_starts.push(prev_start + text.len() + newline_len);
11734 }
11735
11736 let new_selections = selections
11737 .iter()
11738 .map(|selection| {
11739 let point = buffer.offset_to_point(selection.start);
11740 let old_index = row_to_index[&point.row];
11741 let new_index = if reverse {
11742 (old_index + num_rows - 1) % num_rows
11743 } else {
11744 (old_index + 1) % num_rows
11745 };
11746 let new_offset =
11747 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11748 Selection {
11749 id: selection.id,
11750 start: new_offset,
11751 end: new_offset,
11752 reversed: selection.reversed,
11753 goal: selection.goal,
11754 }
11755 })
11756 .collect();
11757
11758 (edits, new_selections)
11759 }
11760 };
11761
11762 self.transact(window, cx, |this, window, cx| {
11763 this.buffer.update(cx, |buffer, cx| {
11764 buffer.edit(edits, None, cx);
11765 });
11766 this.change_selections(Default::default(), window, cx, |s| {
11767 s.select(new_selections);
11768 });
11769 });
11770 }
11771
11772 fn manipulate_lines<M>(
11773 &mut self,
11774 window: &mut Window,
11775 cx: &mut Context<Self>,
11776 mut manipulate: M,
11777 ) where
11778 M: FnMut(&str) -> LineManipulationResult,
11779 {
11780 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11781
11782 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11783 let buffer = self.buffer.read(cx).snapshot(cx);
11784
11785 let mut edits = Vec::new();
11786
11787 let selections = self.selections.all::<Point>(&display_map);
11788 let mut selections = selections.iter().peekable();
11789 let mut contiguous_row_selections = Vec::new();
11790 let mut new_selections = Vec::new();
11791 let mut added_lines = 0;
11792 let mut removed_lines = 0;
11793
11794 while let Some(selection) = selections.next() {
11795 let (start_row, end_row) = consume_contiguous_rows(
11796 &mut contiguous_row_selections,
11797 selection,
11798 &display_map,
11799 &mut selections,
11800 );
11801
11802 let start_point = Point::new(start_row.0, 0);
11803 let end_point = Point::new(
11804 end_row.previous_row().0,
11805 buffer.line_len(end_row.previous_row()),
11806 );
11807 let text = buffer
11808 .text_for_range(start_point..end_point)
11809 .collect::<String>();
11810
11811 let LineManipulationResult {
11812 new_text,
11813 line_count_before,
11814 line_count_after,
11815 } = manipulate(&text);
11816
11817 edits.push((start_point..end_point, new_text));
11818
11819 // Selections must change based on added and removed line count
11820 let start_row =
11821 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11822 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11823 new_selections.push(Selection {
11824 id: selection.id,
11825 start: start_row,
11826 end: end_row,
11827 goal: SelectionGoal::None,
11828 reversed: selection.reversed,
11829 });
11830
11831 if line_count_after > line_count_before {
11832 added_lines += line_count_after - line_count_before;
11833 } else if line_count_before > line_count_after {
11834 removed_lines += line_count_before - line_count_after;
11835 }
11836 }
11837
11838 self.transact(window, cx, |this, window, cx| {
11839 let buffer = this.buffer.update(cx, |buffer, cx| {
11840 buffer.edit(edits, None, cx);
11841 buffer.snapshot(cx)
11842 });
11843
11844 // Recalculate offsets on newly edited buffer
11845 let new_selections = new_selections
11846 .iter()
11847 .map(|s| {
11848 let start_point = Point::new(s.start.0, 0);
11849 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11850 Selection {
11851 id: s.id,
11852 start: buffer.point_to_offset(start_point),
11853 end: buffer.point_to_offset(end_point),
11854 goal: s.goal,
11855 reversed: s.reversed,
11856 }
11857 })
11858 .collect();
11859
11860 this.change_selections(Default::default(), window, cx, |s| {
11861 s.select(new_selections);
11862 });
11863
11864 this.request_autoscroll(Autoscroll::fit(), cx);
11865 });
11866 }
11867
11868 fn manipulate_immutable_lines<Fn>(
11869 &mut self,
11870 window: &mut Window,
11871 cx: &mut Context<Self>,
11872 mut callback: Fn,
11873 ) where
11874 Fn: FnMut(&mut Vec<&str>),
11875 {
11876 self.manipulate_lines(window, cx, |text| {
11877 let mut lines: Vec<&str> = text.split('\n').collect();
11878 let line_count_before = lines.len();
11879
11880 callback(&mut lines);
11881
11882 LineManipulationResult {
11883 new_text: lines.join("\n"),
11884 line_count_before,
11885 line_count_after: lines.len(),
11886 }
11887 });
11888 }
11889
11890 fn manipulate_mutable_lines<Fn>(
11891 &mut self,
11892 window: &mut Window,
11893 cx: &mut Context<Self>,
11894 mut callback: Fn,
11895 ) where
11896 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11897 {
11898 self.manipulate_lines(window, cx, |text| {
11899 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11900 let line_count_before = lines.len();
11901
11902 callback(&mut lines);
11903
11904 LineManipulationResult {
11905 new_text: lines.join("\n"),
11906 line_count_before,
11907 line_count_after: lines.len(),
11908 }
11909 });
11910 }
11911
11912 pub fn convert_indentation_to_spaces(
11913 &mut self,
11914 _: &ConvertIndentationToSpaces,
11915 window: &mut Window,
11916 cx: &mut Context<Self>,
11917 ) {
11918 let settings = self.buffer.read(cx).language_settings(cx);
11919 let tab_size = settings.tab_size.get() as usize;
11920
11921 self.manipulate_mutable_lines(window, cx, |lines| {
11922 // Allocates a reasonably sized scratch buffer once for the whole loop
11923 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11924 // Avoids recomputing spaces that could be inserted many times
11925 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11926 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11927 .collect();
11928
11929 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11930 let mut chars = line.as_ref().chars();
11931 let mut col = 0;
11932 let mut changed = false;
11933
11934 for ch in chars.by_ref() {
11935 match ch {
11936 ' ' => {
11937 reindented_line.push(' ');
11938 col += 1;
11939 }
11940 '\t' => {
11941 // \t are converted to spaces depending on the current column
11942 let spaces_len = tab_size - (col % tab_size);
11943 reindented_line.extend(&space_cache[spaces_len - 1]);
11944 col += spaces_len;
11945 changed = true;
11946 }
11947 _ => {
11948 // If we dont append before break, the character is consumed
11949 reindented_line.push(ch);
11950 break;
11951 }
11952 }
11953 }
11954
11955 if !changed {
11956 reindented_line.clear();
11957 continue;
11958 }
11959 // Append the rest of the line and replace old reference with new one
11960 reindented_line.extend(chars);
11961 *line = Cow::Owned(reindented_line.clone());
11962 reindented_line.clear();
11963 }
11964 });
11965 }
11966
11967 pub fn convert_indentation_to_tabs(
11968 &mut self,
11969 _: &ConvertIndentationToTabs,
11970 window: &mut Window,
11971 cx: &mut Context<Self>,
11972 ) {
11973 let settings = self.buffer.read(cx).language_settings(cx);
11974 let tab_size = settings.tab_size.get() as usize;
11975
11976 self.manipulate_mutable_lines(window, cx, |lines| {
11977 // Allocates a reasonably sized buffer once for the whole loop
11978 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11979 // Avoids recomputing spaces that could be inserted many times
11980 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11981 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11982 .collect();
11983
11984 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11985 let mut chars = line.chars();
11986 let mut spaces_count = 0;
11987 let mut first_non_indent_char = None;
11988 let mut changed = false;
11989
11990 for ch in chars.by_ref() {
11991 match ch {
11992 ' ' => {
11993 // Keep track of spaces. Append \t when we reach tab_size
11994 spaces_count += 1;
11995 changed = true;
11996 if spaces_count == tab_size {
11997 reindented_line.push('\t');
11998 spaces_count = 0;
11999 }
12000 }
12001 '\t' => {
12002 reindented_line.push('\t');
12003 spaces_count = 0;
12004 }
12005 _ => {
12006 // Dont append it yet, we might have remaining spaces
12007 first_non_indent_char = Some(ch);
12008 break;
12009 }
12010 }
12011 }
12012
12013 if !changed {
12014 reindented_line.clear();
12015 continue;
12016 }
12017 // Remaining spaces that didn't make a full tab stop
12018 if spaces_count > 0 {
12019 reindented_line.extend(&space_cache[spaces_count - 1]);
12020 }
12021 // If we consume an extra character that was not indentation, add it back
12022 if let Some(extra_char) = first_non_indent_char {
12023 reindented_line.push(extra_char);
12024 }
12025 // Append the rest of the line and replace old reference with new one
12026 reindented_line.extend(chars);
12027 *line = Cow::Owned(reindented_line.clone());
12028 reindented_line.clear();
12029 }
12030 });
12031 }
12032
12033 pub fn convert_to_upper_case(
12034 &mut self,
12035 _: &ConvertToUpperCase,
12036 window: &mut Window,
12037 cx: &mut Context<Self>,
12038 ) {
12039 self.manipulate_text(window, cx, |text| text.to_uppercase())
12040 }
12041
12042 pub fn convert_to_lower_case(
12043 &mut self,
12044 _: &ConvertToLowerCase,
12045 window: &mut Window,
12046 cx: &mut Context<Self>,
12047 ) {
12048 self.manipulate_text(window, cx, |text| text.to_lowercase())
12049 }
12050
12051 pub fn convert_to_title_case(
12052 &mut self,
12053 _: &ConvertToTitleCase,
12054 window: &mut Window,
12055 cx: &mut Context<Self>,
12056 ) {
12057 self.manipulate_text(window, cx, |text| {
12058 text.split('\n')
12059 .map(|line| line.to_case(Case::Title))
12060 .join("\n")
12061 })
12062 }
12063
12064 pub fn convert_to_snake_case(
12065 &mut self,
12066 _: &ConvertToSnakeCase,
12067 window: &mut Window,
12068 cx: &mut Context<Self>,
12069 ) {
12070 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12071 }
12072
12073 pub fn convert_to_kebab_case(
12074 &mut self,
12075 _: &ConvertToKebabCase,
12076 window: &mut Window,
12077 cx: &mut Context<Self>,
12078 ) {
12079 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12080 }
12081
12082 pub fn convert_to_upper_camel_case(
12083 &mut self,
12084 _: &ConvertToUpperCamelCase,
12085 window: &mut Window,
12086 cx: &mut Context<Self>,
12087 ) {
12088 self.manipulate_text(window, cx, |text| {
12089 text.split('\n')
12090 .map(|line| line.to_case(Case::UpperCamel))
12091 .join("\n")
12092 })
12093 }
12094
12095 pub fn convert_to_lower_camel_case(
12096 &mut self,
12097 _: &ConvertToLowerCamelCase,
12098 window: &mut Window,
12099 cx: &mut Context<Self>,
12100 ) {
12101 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12102 }
12103
12104 pub fn convert_to_opposite_case(
12105 &mut self,
12106 _: &ConvertToOppositeCase,
12107 window: &mut Window,
12108 cx: &mut Context<Self>,
12109 ) {
12110 self.manipulate_text(window, cx, |text| {
12111 text.chars()
12112 .fold(String::with_capacity(text.len()), |mut t, c| {
12113 if c.is_uppercase() {
12114 t.extend(c.to_lowercase());
12115 } else {
12116 t.extend(c.to_uppercase());
12117 }
12118 t
12119 })
12120 })
12121 }
12122
12123 pub fn convert_to_sentence_case(
12124 &mut self,
12125 _: &ConvertToSentenceCase,
12126 window: &mut Window,
12127 cx: &mut Context<Self>,
12128 ) {
12129 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12130 }
12131
12132 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12133 self.manipulate_text(window, cx, |text| {
12134 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12135 if has_upper_case_characters {
12136 text.to_lowercase()
12137 } else {
12138 text.to_uppercase()
12139 }
12140 })
12141 }
12142
12143 pub fn convert_to_rot13(
12144 &mut self,
12145 _: &ConvertToRot13,
12146 window: &mut Window,
12147 cx: &mut Context<Self>,
12148 ) {
12149 self.manipulate_text(window, cx, |text| {
12150 text.chars()
12151 .map(|c| match c {
12152 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12153 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12154 _ => c,
12155 })
12156 .collect()
12157 })
12158 }
12159
12160 pub fn convert_to_rot47(
12161 &mut self,
12162 _: &ConvertToRot47,
12163 window: &mut Window,
12164 cx: &mut Context<Self>,
12165 ) {
12166 self.manipulate_text(window, cx, |text| {
12167 text.chars()
12168 .map(|c| {
12169 let code_point = c as u32;
12170 if code_point >= 33 && code_point <= 126 {
12171 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12172 }
12173 c
12174 })
12175 .collect()
12176 })
12177 }
12178
12179 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12180 where
12181 Fn: FnMut(&str) -> String,
12182 {
12183 let buffer = self.buffer.read(cx).snapshot(cx);
12184
12185 let mut new_selections = Vec::new();
12186 let mut edits = Vec::new();
12187 let mut selection_adjustment = 0isize;
12188
12189 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12190 let selection_is_empty = selection.is_empty();
12191
12192 let (start, end) = if selection_is_empty {
12193 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12194 (word_range.start, word_range.end)
12195 } else {
12196 (
12197 buffer.point_to_offset(selection.start),
12198 buffer.point_to_offset(selection.end),
12199 )
12200 };
12201
12202 let text = buffer.text_for_range(start..end).collect::<String>();
12203 let old_length = text.len() as isize;
12204 let text = callback(&text);
12205
12206 new_selections.push(Selection {
12207 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12208 end: MultiBufferOffset(
12209 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12210 ),
12211 goal: SelectionGoal::None,
12212 id: selection.id,
12213 reversed: selection.reversed,
12214 });
12215
12216 selection_adjustment += old_length - text.len() as isize;
12217
12218 edits.push((start..end, text));
12219 }
12220
12221 self.transact(window, cx, |this, window, cx| {
12222 this.buffer.update(cx, |buffer, cx| {
12223 buffer.edit(edits, None, cx);
12224 });
12225
12226 this.change_selections(Default::default(), window, cx, |s| {
12227 s.select(new_selections);
12228 });
12229
12230 this.request_autoscroll(Autoscroll::fit(), cx);
12231 });
12232 }
12233
12234 pub fn move_selection_on_drop(
12235 &mut self,
12236 selection: &Selection<Anchor>,
12237 target: DisplayPoint,
12238 is_cut: bool,
12239 window: &mut Window,
12240 cx: &mut Context<Self>,
12241 ) {
12242 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12243 let buffer = display_map.buffer_snapshot();
12244 let mut edits = Vec::new();
12245 let insert_point = display_map
12246 .clip_point(target, Bias::Left)
12247 .to_point(&display_map);
12248 let text = buffer
12249 .text_for_range(selection.start..selection.end)
12250 .collect::<String>();
12251 if is_cut {
12252 edits.push(((selection.start..selection.end), String::new()));
12253 }
12254 let insert_anchor = buffer.anchor_before(insert_point);
12255 edits.push(((insert_anchor..insert_anchor), text));
12256 let last_edit_start = insert_anchor.bias_left(buffer);
12257 let last_edit_end = insert_anchor.bias_right(buffer);
12258 self.transact(window, cx, |this, window, cx| {
12259 this.buffer.update(cx, |buffer, cx| {
12260 buffer.edit(edits, None, cx);
12261 });
12262 this.change_selections(Default::default(), window, cx, |s| {
12263 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12264 });
12265 });
12266 }
12267
12268 pub fn clear_selection_drag_state(&mut self) {
12269 self.selection_drag_state = SelectionDragState::None;
12270 }
12271
12272 pub fn duplicate(
12273 &mut self,
12274 upwards: bool,
12275 whole_lines: bool,
12276 window: &mut Window,
12277 cx: &mut Context<Self>,
12278 ) {
12279 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12280
12281 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12282 let buffer = display_map.buffer_snapshot();
12283 let selections = self.selections.all::<Point>(&display_map);
12284
12285 let mut edits = Vec::new();
12286 let mut selections_iter = selections.iter().peekable();
12287 while let Some(selection) = selections_iter.next() {
12288 let mut rows = selection.spanned_rows(false, &display_map);
12289 // duplicate line-wise
12290 if whole_lines || selection.start == selection.end {
12291 // Avoid duplicating the same lines twice.
12292 while let Some(next_selection) = selections_iter.peek() {
12293 let next_rows = next_selection.spanned_rows(false, &display_map);
12294 if next_rows.start < rows.end {
12295 rows.end = next_rows.end;
12296 selections_iter.next().unwrap();
12297 } else {
12298 break;
12299 }
12300 }
12301
12302 // Copy the text from the selected row region and splice it either at the start
12303 // or end of the region.
12304 let start = Point::new(rows.start.0, 0);
12305 let end = Point::new(
12306 rows.end.previous_row().0,
12307 buffer.line_len(rows.end.previous_row()),
12308 );
12309
12310 let mut text = buffer.text_for_range(start..end).collect::<String>();
12311
12312 let insert_location = if upwards {
12313 // When duplicating upward, we need to insert before the current line.
12314 // If we're on the last line and it doesn't end with a newline,
12315 // we need to add a newline before the duplicated content.
12316 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12317 && buffer.max_point().column > 0
12318 && !text.ends_with('\n');
12319
12320 if needs_leading_newline {
12321 text.insert(0, '\n');
12322 end
12323 } else {
12324 text.push('\n');
12325 Point::new(rows.start.0, 0)
12326 }
12327 } else {
12328 text.push('\n');
12329 start
12330 };
12331 edits.push((insert_location..insert_location, text));
12332 } else {
12333 // duplicate character-wise
12334 let start = selection.start;
12335 let end = selection.end;
12336 let text = buffer.text_for_range(start..end).collect::<String>();
12337 edits.push((selection.end..selection.end, text));
12338 }
12339 }
12340
12341 self.transact(window, cx, |this, window, cx| {
12342 this.buffer.update(cx, |buffer, cx| {
12343 buffer.edit(edits, None, cx);
12344 });
12345
12346 // When duplicating upward with whole lines, move the cursor to the duplicated line
12347 if upwards && whole_lines {
12348 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12349
12350 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12351 let mut new_ranges = Vec::new();
12352 let selections = s.all::<Point>(&display_map);
12353 let mut selections_iter = selections.iter().peekable();
12354
12355 while let Some(first_selection) = selections_iter.next() {
12356 // Group contiguous selections together to find the total row span
12357 let mut group_selections = vec![first_selection];
12358 let mut rows = first_selection.spanned_rows(false, &display_map);
12359
12360 while let Some(next_selection) = selections_iter.peek() {
12361 let next_rows = next_selection.spanned_rows(false, &display_map);
12362 if next_rows.start < rows.end {
12363 rows.end = next_rows.end;
12364 group_selections.push(selections_iter.next().unwrap());
12365 } else {
12366 break;
12367 }
12368 }
12369
12370 let row_count = rows.end.0 - rows.start.0;
12371
12372 // Move all selections in this group up by the total number of duplicated rows
12373 for selection in group_selections {
12374 let new_start = Point::new(
12375 selection.start.row.saturating_sub(row_count),
12376 selection.start.column,
12377 );
12378
12379 let new_end = Point::new(
12380 selection.end.row.saturating_sub(row_count),
12381 selection.end.column,
12382 );
12383
12384 new_ranges.push(new_start..new_end);
12385 }
12386 }
12387
12388 s.select_ranges(new_ranges);
12389 });
12390 }
12391
12392 this.request_autoscroll(Autoscroll::fit(), cx);
12393 });
12394 }
12395
12396 pub fn duplicate_line_up(
12397 &mut self,
12398 _: &DuplicateLineUp,
12399 window: &mut Window,
12400 cx: &mut Context<Self>,
12401 ) {
12402 self.duplicate(true, true, window, cx);
12403 }
12404
12405 pub fn duplicate_line_down(
12406 &mut self,
12407 _: &DuplicateLineDown,
12408 window: &mut Window,
12409 cx: &mut Context<Self>,
12410 ) {
12411 self.duplicate(false, true, window, cx);
12412 }
12413
12414 pub fn duplicate_selection(
12415 &mut self,
12416 _: &DuplicateSelection,
12417 window: &mut Window,
12418 cx: &mut Context<Self>,
12419 ) {
12420 self.duplicate(false, false, window, cx);
12421 }
12422
12423 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12424 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12425 if self.mode.is_single_line() {
12426 cx.propagate();
12427 return;
12428 }
12429
12430 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12431 let buffer = self.buffer.read(cx).snapshot(cx);
12432
12433 let mut edits = Vec::new();
12434 let mut unfold_ranges = Vec::new();
12435 let mut refold_creases = Vec::new();
12436
12437 let selections = self.selections.all::<Point>(&display_map);
12438 let mut selections = selections.iter().peekable();
12439 let mut contiguous_row_selections = Vec::new();
12440 let mut new_selections = Vec::new();
12441
12442 while let Some(selection) = selections.next() {
12443 // Find all the selections that span a contiguous row range
12444 let (start_row, end_row) = consume_contiguous_rows(
12445 &mut contiguous_row_selections,
12446 selection,
12447 &display_map,
12448 &mut selections,
12449 );
12450
12451 // Move the text spanned by the row range to be before the line preceding the row range
12452 if start_row.0 > 0 {
12453 let range_to_move = Point::new(
12454 start_row.previous_row().0,
12455 buffer.line_len(start_row.previous_row()),
12456 )
12457 ..Point::new(
12458 end_row.previous_row().0,
12459 buffer.line_len(end_row.previous_row()),
12460 );
12461 let insertion_point = display_map
12462 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12463 .0;
12464
12465 // Don't move lines across excerpts
12466 if buffer
12467 .excerpt_containing(insertion_point..range_to_move.end)
12468 .is_some()
12469 {
12470 let text = buffer
12471 .text_for_range(range_to_move.clone())
12472 .flat_map(|s| s.chars())
12473 .skip(1)
12474 .chain(['\n'])
12475 .collect::<String>();
12476
12477 edits.push((
12478 buffer.anchor_after(range_to_move.start)
12479 ..buffer.anchor_before(range_to_move.end),
12480 String::new(),
12481 ));
12482 let insertion_anchor = buffer.anchor_after(insertion_point);
12483 edits.push((insertion_anchor..insertion_anchor, text));
12484
12485 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12486
12487 // Move selections up
12488 new_selections.extend(contiguous_row_selections.drain(..).map(
12489 |mut selection| {
12490 selection.start.row -= row_delta;
12491 selection.end.row -= row_delta;
12492 selection
12493 },
12494 ));
12495
12496 // Move folds up
12497 unfold_ranges.push(range_to_move.clone());
12498 for fold in display_map.folds_in_range(
12499 buffer.anchor_before(range_to_move.start)
12500 ..buffer.anchor_after(range_to_move.end),
12501 ) {
12502 let mut start = fold.range.start.to_point(&buffer);
12503 let mut end = fold.range.end.to_point(&buffer);
12504 start.row -= row_delta;
12505 end.row -= row_delta;
12506 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12507 }
12508 }
12509 }
12510
12511 // If we didn't move line(s), preserve the existing selections
12512 new_selections.append(&mut contiguous_row_selections);
12513 }
12514
12515 self.transact(window, cx, |this, window, cx| {
12516 this.unfold_ranges(&unfold_ranges, true, true, cx);
12517 this.buffer.update(cx, |buffer, cx| {
12518 for (range, text) in edits {
12519 buffer.edit([(range, text)], None, cx);
12520 }
12521 });
12522 this.fold_creases(refold_creases, true, window, cx);
12523 this.change_selections(Default::default(), window, cx, |s| {
12524 s.select(new_selections);
12525 })
12526 });
12527 }
12528
12529 pub fn move_line_down(
12530 &mut self,
12531 _: &MoveLineDown,
12532 window: &mut Window,
12533 cx: &mut Context<Self>,
12534 ) {
12535 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12536 if self.mode.is_single_line() {
12537 cx.propagate();
12538 return;
12539 }
12540
12541 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12542 let buffer = self.buffer.read(cx).snapshot(cx);
12543
12544 let mut edits = Vec::new();
12545 let mut unfold_ranges = Vec::new();
12546 let mut refold_creases = Vec::new();
12547
12548 let selections = self.selections.all::<Point>(&display_map);
12549 let mut selections = selections.iter().peekable();
12550 let mut contiguous_row_selections = Vec::new();
12551 let mut new_selections = Vec::new();
12552
12553 while let Some(selection) = selections.next() {
12554 // Find all the selections that span a contiguous row range
12555 let (start_row, end_row) = consume_contiguous_rows(
12556 &mut contiguous_row_selections,
12557 selection,
12558 &display_map,
12559 &mut selections,
12560 );
12561
12562 // Move the text spanned by the row range to be after the last line of the row range
12563 if end_row.0 <= buffer.max_point().row {
12564 let range_to_move =
12565 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12566 let insertion_point = display_map
12567 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12568 .0;
12569
12570 // Don't move lines across excerpt boundaries
12571 if buffer
12572 .excerpt_containing(range_to_move.start..insertion_point)
12573 .is_some()
12574 {
12575 let mut text = String::from("\n");
12576 text.extend(buffer.text_for_range(range_to_move.clone()));
12577 text.pop(); // Drop trailing newline
12578 edits.push((
12579 buffer.anchor_after(range_to_move.start)
12580 ..buffer.anchor_before(range_to_move.end),
12581 String::new(),
12582 ));
12583 let insertion_anchor = buffer.anchor_after(insertion_point);
12584 edits.push((insertion_anchor..insertion_anchor, text));
12585
12586 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12587
12588 // Move selections down
12589 new_selections.extend(contiguous_row_selections.drain(..).map(
12590 |mut selection| {
12591 selection.start.row += row_delta;
12592 selection.end.row += row_delta;
12593 selection
12594 },
12595 ));
12596
12597 // Move folds down
12598 unfold_ranges.push(range_to_move.clone());
12599 for fold in display_map.folds_in_range(
12600 buffer.anchor_before(range_to_move.start)
12601 ..buffer.anchor_after(range_to_move.end),
12602 ) {
12603 let mut start = fold.range.start.to_point(&buffer);
12604 let mut end = fold.range.end.to_point(&buffer);
12605 start.row += row_delta;
12606 end.row += row_delta;
12607 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12608 }
12609 }
12610 }
12611
12612 // If we didn't move line(s), preserve the existing selections
12613 new_selections.append(&mut contiguous_row_selections);
12614 }
12615
12616 self.transact(window, cx, |this, window, cx| {
12617 this.unfold_ranges(&unfold_ranges, true, true, cx);
12618 this.buffer.update(cx, |buffer, cx| {
12619 for (range, text) in edits {
12620 buffer.edit([(range, text)], None, cx);
12621 }
12622 });
12623 this.fold_creases(refold_creases, true, window, cx);
12624 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12625 });
12626 }
12627
12628 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12629 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12630 let text_layout_details = &self.text_layout_details(window);
12631 self.transact(window, cx, |this, window, cx| {
12632 let edits = this.change_selections(Default::default(), window, cx, |s| {
12633 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12634 s.move_with(|display_map, selection| {
12635 if !selection.is_empty() {
12636 return;
12637 }
12638
12639 let mut head = selection.head();
12640 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12641 if head.column() == display_map.line_len(head.row()) {
12642 transpose_offset = display_map
12643 .buffer_snapshot()
12644 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12645 }
12646
12647 if transpose_offset == MultiBufferOffset(0) {
12648 return;
12649 }
12650
12651 *head.column_mut() += 1;
12652 head = display_map.clip_point(head, Bias::Right);
12653 let goal = SelectionGoal::HorizontalPosition(
12654 display_map
12655 .x_for_display_point(head, text_layout_details)
12656 .into(),
12657 );
12658 selection.collapse_to(head, goal);
12659
12660 let transpose_start = display_map
12661 .buffer_snapshot()
12662 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12663 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12664 let transpose_end = display_map
12665 .buffer_snapshot()
12666 .clip_offset(transpose_offset + 1usize, Bias::Right);
12667 if let Some(ch) = display_map
12668 .buffer_snapshot()
12669 .chars_at(transpose_start)
12670 .next()
12671 {
12672 edits.push((transpose_start..transpose_offset, String::new()));
12673 edits.push((transpose_end..transpose_end, ch.to_string()));
12674 }
12675 }
12676 });
12677 edits
12678 });
12679 this.buffer
12680 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12681 let selections = this
12682 .selections
12683 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12684 this.change_selections(Default::default(), window, cx, |s| {
12685 s.select(selections);
12686 });
12687 });
12688 }
12689
12690 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12691 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12692 if self.mode.is_single_line() {
12693 cx.propagate();
12694 return;
12695 }
12696
12697 self.rewrap_impl(RewrapOptions::default(), cx)
12698 }
12699
12700 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12701 let buffer = self.buffer.read(cx).snapshot(cx);
12702 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12703
12704 #[derive(Clone, Debug, PartialEq)]
12705 enum CommentFormat {
12706 /// single line comment, with prefix for line
12707 Line(String),
12708 /// single line within a block comment, with prefix for line
12709 BlockLine(String),
12710 /// a single line of a block comment that includes the initial delimiter
12711 BlockCommentWithStart(BlockCommentConfig),
12712 /// a single line of a block comment that includes the ending delimiter
12713 BlockCommentWithEnd(BlockCommentConfig),
12714 }
12715
12716 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12717 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12718 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12719 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12720 .peekable();
12721
12722 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12723 row
12724 } else {
12725 return Vec::new();
12726 };
12727
12728 let language_settings = buffer.language_settings_at(selection.head(), cx);
12729 let language_scope = buffer.language_scope_at(selection.head());
12730
12731 let indent_and_prefix_for_row =
12732 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12733 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12734 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12735 &language_scope
12736 {
12737 let indent_end = Point::new(row, indent.len);
12738 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12739 let line_text_after_indent = buffer
12740 .text_for_range(indent_end..line_end)
12741 .collect::<String>();
12742
12743 let is_within_comment_override = buffer
12744 .language_scope_at(indent_end)
12745 .is_some_and(|scope| scope.override_name() == Some("comment"));
12746 let comment_delimiters = if is_within_comment_override {
12747 // we are within a comment syntax node, but we don't
12748 // yet know what kind of comment: block, doc or line
12749 match (
12750 language_scope.documentation_comment(),
12751 language_scope.block_comment(),
12752 ) {
12753 (Some(config), _) | (_, Some(config))
12754 if buffer.contains_str_at(indent_end, &config.start) =>
12755 {
12756 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12757 }
12758 (Some(config), _) | (_, Some(config))
12759 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12760 {
12761 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12762 }
12763 (Some(config), _) | (_, Some(config))
12764 if buffer.contains_str_at(indent_end, &config.prefix) =>
12765 {
12766 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12767 }
12768 (_, _) => language_scope
12769 .line_comment_prefixes()
12770 .iter()
12771 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12772 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12773 }
12774 } else {
12775 // we not in an overridden comment node, but we may
12776 // be within a non-overridden line comment node
12777 language_scope
12778 .line_comment_prefixes()
12779 .iter()
12780 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12781 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12782 };
12783
12784 let rewrap_prefix = language_scope
12785 .rewrap_prefixes()
12786 .iter()
12787 .find_map(|prefix_regex| {
12788 prefix_regex.find(&line_text_after_indent).map(|mat| {
12789 if mat.start() == 0 {
12790 Some(mat.as_str().to_string())
12791 } else {
12792 None
12793 }
12794 })
12795 })
12796 .flatten();
12797 (comment_delimiters, rewrap_prefix)
12798 } else {
12799 (None, None)
12800 };
12801 (indent, comment_prefix, rewrap_prefix)
12802 };
12803
12804 let mut ranges = Vec::new();
12805 let from_empty_selection = selection.is_empty();
12806
12807 let mut current_range_start = first_row;
12808 let mut prev_row = first_row;
12809 let (
12810 mut current_range_indent,
12811 mut current_range_comment_delimiters,
12812 mut current_range_rewrap_prefix,
12813 ) = indent_and_prefix_for_row(first_row);
12814
12815 for row in non_blank_rows_iter.skip(1) {
12816 let has_paragraph_break = row > prev_row + 1;
12817
12818 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12819 indent_and_prefix_for_row(row);
12820
12821 let has_indent_change = row_indent != current_range_indent;
12822 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12823
12824 let has_boundary_change = has_comment_change
12825 || row_rewrap_prefix.is_some()
12826 || (has_indent_change && current_range_comment_delimiters.is_some());
12827
12828 if has_paragraph_break || has_boundary_change {
12829 ranges.push((
12830 language_settings.clone(),
12831 Point::new(current_range_start, 0)
12832 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12833 current_range_indent,
12834 current_range_comment_delimiters.clone(),
12835 current_range_rewrap_prefix.clone(),
12836 from_empty_selection,
12837 ));
12838 current_range_start = row;
12839 current_range_indent = row_indent;
12840 current_range_comment_delimiters = row_comment_delimiters;
12841 current_range_rewrap_prefix = row_rewrap_prefix;
12842 }
12843 prev_row = row;
12844 }
12845
12846 ranges.push((
12847 language_settings.clone(),
12848 Point::new(current_range_start, 0)
12849 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12850 current_range_indent,
12851 current_range_comment_delimiters,
12852 current_range_rewrap_prefix,
12853 from_empty_selection,
12854 ));
12855
12856 ranges
12857 });
12858
12859 let mut edits = Vec::new();
12860 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12861
12862 for (
12863 language_settings,
12864 wrap_range,
12865 mut indent_size,
12866 comment_prefix,
12867 rewrap_prefix,
12868 from_empty_selection,
12869 ) in wrap_ranges
12870 {
12871 let mut start_row = wrap_range.start.row;
12872 let mut end_row = wrap_range.end.row;
12873
12874 // Skip selections that overlap with a range that has already been rewrapped.
12875 let selection_range = start_row..end_row;
12876 if rewrapped_row_ranges
12877 .iter()
12878 .any(|range| range.overlaps(&selection_range))
12879 {
12880 continue;
12881 }
12882
12883 let tab_size = language_settings.tab_size;
12884
12885 let (line_prefix, inside_comment) = match &comment_prefix {
12886 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12887 (Some(prefix.as_str()), true)
12888 }
12889 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12890 (Some(prefix.as_ref()), true)
12891 }
12892 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12893 start: _,
12894 end: _,
12895 prefix,
12896 tab_size,
12897 })) => {
12898 indent_size.len += tab_size;
12899 (Some(prefix.as_ref()), true)
12900 }
12901 None => (None, false),
12902 };
12903 let indent_prefix = indent_size.chars().collect::<String>();
12904 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12905
12906 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12907 RewrapBehavior::InComments => inside_comment,
12908 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12909 RewrapBehavior::Anywhere => true,
12910 };
12911
12912 let should_rewrap = options.override_language_settings
12913 || allow_rewrap_based_on_language
12914 || self.hard_wrap.is_some();
12915 if !should_rewrap {
12916 continue;
12917 }
12918
12919 if from_empty_selection {
12920 'expand_upwards: while start_row > 0 {
12921 let prev_row = start_row - 1;
12922 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12923 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12924 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12925 {
12926 start_row = prev_row;
12927 } else {
12928 break 'expand_upwards;
12929 }
12930 }
12931
12932 'expand_downwards: while end_row < buffer.max_point().row {
12933 let next_row = end_row + 1;
12934 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12935 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12936 && !buffer.is_line_blank(MultiBufferRow(next_row))
12937 {
12938 end_row = next_row;
12939 } else {
12940 break 'expand_downwards;
12941 }
12942 }
12943 }
12944
12945 let start = Point::new(start_row, 0);
12946 let start_offset = ToOffset::to_offset(&start, &buffer);
12947 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12948 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12949 let mut first_line_delimiter = None;
12950 let mut last_line_delimiter = None;
12951 let Some(lines_without_prefixes) = selection_text
12952 .lines()
12953 .enumerate()
12954 .map(|(ix, line)| {
12955 let line_trimmed = line.trim_start();
12956 if rewrap_prefix.is_some() && ix > 0 {
12957 Ok(line_trimmed)
12958 } else if let Some(
12959 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12960 start,
12961 prefix,
12962 end,
12963 tab_size,
12964 })
12965 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12966 start,
12967 prefix,
12968 end,
12969 tab_size,
12970 }),
12971 ) = &comment_prefix
12972 {
12973 let line_trimmed = line_trimmed
12974 .strip_prefix(start.as_ref())
12975 .map(|s| {
12976 let mut indent_size = indent_size;
12977 indent_size.len -= tab_size;
12978 let indent_prefix: String = indent_size.chars().collect();
12979 first_line_delimiter = Some((indent_prefix, start));
12980 s.trim_start()
12981 })
12982 .unwrap_or(line_trimmed);
12983 let line_trimmed = line_trimmed
12984 .strip_suffix(end.as_ref())
12985 .map(|s| {
12986 last_line_delimiter = Some(end);
12987 s.trim_end()
12988 })
12989 .unwrap_or(line_trimmed);
12990 let line_trimmed = line_trimmed
12991 .strip_prefix(prefix.as_ref())
12992 .unwrap_or(line_trimmed);
12993 Ok(line_trimmed)
12994 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12995 line_trimmed.strip_prefix(prefix).with_context(|| {
12996 format!("line did not start with prefix {prefix:?}: {line:?}")
12997 })
12998 } else {
12999 line_trimmed
13000 .strip_prefix(&line_prefix.trim_start())
13001 .with_context(|| {
13002 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13003 })
13004 }
13005 })
13006 .collect::<Result<Vec<_>, _>>()
13007 .log_err()
13008 else {
13009 continue;
13010 };
13011
13012 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13013 buffer
13014 .language_settings_at(Point::new(start_row, 0), cx)
13015 .preferred_line_length as usize
13016 });
13017
13018 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13019 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13020 } else {
13021 line_prefix.clone()
13022 };
13023
13024 let wrapped_text = {
13025 let mut wrapped_text = wrap_with_prefix(
13026 line_prefix,
13027 subsequent_lines_prefix,
13028 lines_without_prefixes.join("\n"),
13029 wrap_column,
13030 tab_size,
13031 options.preserve_existing_whitespace,
13032 );
13033
13034 if let Some((indent, delimiter)) = first_line_delimiter {
13035 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13036 }
13037 if let Some(last_line) = last_line_delimiter {
13038 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13039 }
13040
13041 wrapped_text
13042 };
13043
13044 // TODO: should always use char-based diff while still supporting cursor behavior that
13045 // matches vim.
13046 let mut diff_options = DiffOptions::default();
13047 if options.override_language_settings {
13048 diff_options.max_word_diff_len = 0;
13049 diff_options.max_word_diff_line_count = 0;
13050 } else {
13051 diff_options.max_word_diff_len = usize::MAX;
13052 diff_options.max_word_diff_line_count = usize::MAX;
13053 }
13054
13055 for (old_range, new_text) in
13056 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13057 {
13058 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13059 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13060 edits.push((edit_start..edit_end, new_text));
13061 }
13062
13063 rewrapped_row_ranges.push(start_row..=end_row);
13064 }
13065
13066 self.buffer
13067 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13068 }
13069
13070 pub fn cut_common(
13071 &mut self,
13072 cut_no_selection_line: bool,
13073 window: &mut Window,
13074 cx: &mut Context<Self>,
13075 ) -> ClipboardItem {
13076 let mut text = String::new();
13077 let buffer = self.buffer.read(cx).snapshot(cx);
13078 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13079 let mut clipboard_selections = Vec::with_capacity(selections.len());
13080 {
13081 let max_point = buffer.max_point();
13082 let mut is_first = true;
13083 let mut prev_selection_was_entire_line = false;
13084 for selection in &mut selections {
13085 let is_entire_line =
13086 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13087 if is_entire_line {
13088 selection.start = Point::new(selection.start.row, 0);
13089 if !selection.is_empty() && selection.end.column == 0 {
13090 selection.end = cmp::min(max_point, selection.end);
13091 } else {
13092 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13093 }
13094 selection.goal = SelectionGoal::None;
13095 }
13096 if is_first {
13097 is_first = false;
13098 } else if !prev_selection_was_entire_line {
13099 text += "\n";
13100 }
13101 prev_selection_was_entire_line = is_entire_line;
13102 let mut len = 0;
13103 for chunk in buffer.text_for_range(selection.start..selection.end) {
13104 text.push_str(chunk);
13105 len += chunk.len();
13106 }
13107
13108 clipboard_selections.push(ClipboardSelection::for_buffer(
13109 len,
13110 is_entire_line,
13111 selection.range(),
13112 &buffer,
13113 self.project.as_ref(),
13114 cx,
13115 ));
13116 }
13117 }
13118
13119 self.transact(window, cx, |this, window, cx| {
13120 this.change_selections(Default::default(), window, cx, |s| {
13121 s.select(selections);
13122 });
13123 this.insert("", window, cx);
13124 });
13125 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13126 }
13127
13128 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13129 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13130 let item = self.cut_common(true, window, cx);
13131 cx.write_to_clipboard(item);
13132 }
13133
13134 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13135 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13136 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13137 s.move_with(|snapshot, sel| {
13138 if sel.is_empty() {
13139 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13140 }
13141 if sel.is_empty() {
13142 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13143 }
13144 });
13145 });
13146 let item = self.cut_common(false, window, cx);
13147 cx.set_global(KillRing(item))
13148 }
13149
13150 pub fn kill_ring_yank(
13151 &mut self,
13152 _: &KillRingYank,
13153 window: &mut Window,
13154 cx: &mut Context<Self>,
13155 ) {
13156 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13157 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13158 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13159 (kill_ring.text().to_string(), kill_ring.metadata_json())
13160 } else {
13161 return;
13162 }
13163 } else {
13164 return;
13165 };
13166 self.do_paste(&text, metadata, false, window, cx);
13167 }
13168
13169 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13170 self.do_copy(true, cx);
13171 }
13172
13173 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13174 self.do_copy(false, cx);
13175 }
13176
13177 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13178 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13179 let buffer = self.buffer.read(cx).read(cx);
13180 let mut text = String::new();
13181
13182 let mut clipboard_selections = Vec::with_capacity(selections.len());
13183 {
13184 let max_point = buffer.max_point();
13185 let mut is_first = true;
13186 let mut prev_selection_was_entire_line = false;
13187 for selection in &selections {
13188 let mut start = selection.start;
13189 let mut end = selection.end;
13190 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13191 let mut add_trailing_newline = false;
13192 if is_entire_line {
13193 start = Point::new(start.row, 0);
13194 let next_line_start = Point::new(end.row + 1, 0);
13195 if next_line_start <= max_point {
13196 end = next_line_start;
13197 } else {
13198 // We're on the last line without a trailing newline.
13199 // Copy to the end of the line and add a newline afterwards.
13200 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13201 add_trailing_newline = true;
13202 }
13203 }
13204
13205 let mut trimmed_selections = Vec::new();
13206 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13207 let row = MultiBufferRow(start.row);
13208 let first_indent = buffer.indent_size_for_line(row);
13209 if first_indent.len == 0 || start.column > first_indent.len {
13210 trimmed_selections.push(start..end);
13211 } else {
13212 trimmed_selections.push(
13213 Point::new(row.0, first_indent.len)
13214 ..Point::new(row.0, buffer.line_len(row)),
13215 );
13216 for row in start.row + 1..=end.row {
13217 let mut line_len = buffer.line_len(MultiBufferRow(row));
13218 if row == end.row {
13219 line_len = end.column;
13220 }
13221 if line_len == 0 {
13222 trimmed_selections
13223 .push(Point::new(row, 0)..Point::new(row, line_len));
13224 continue;
13225 }
13226 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13227 if row_indent_size.len >= first_indent.len {
13228 trimmed_selections.push(
13229 Point::new(row, first_indent.len)..Point::new(row, line_len),
13230 );
13231 } else {
13232 trimmed_selections.clear();
13233 trimmed_selections.push(start..end);
13234 break;
13235 }
13236 }
13237 }
13238 } else {
13239 trimmed_selections.push(start..end);
13240 }
13241
13242 for trimmed_range in trimmed_selections {
13243 if is_first {
13244 is_first = false;
13245 } else if !prev_selection_was_entire_line {
13246 text += "\n";
13247 }
13248 prev_selection_was_entire_line = is_entire_line;
13249 let mut len = 0;
13250 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13251 text.push_str(chunk);
13252 len += chunk.len();
13253 }
13254 if add_trailing_newline {
13255 text.push('\n');
13256 len += 1;
13257 }
13258 clipboard_selections.push(ClipboardSelection::for_buffer(
13259 len,
13260 is_entire_line,
13261 trimmed_range,
13262 &buffer,
13263 self.project.as_ref(),
13264 cx,
13265 ));
13266 }
13267 }
13268 }
13269
13270 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13271 text,
13272 clipboard_selections,
13273 ));
13274 }
13275
13276 pub fn do_paste(
13277 &mut self,
13278 text: &String,
13279 clipboard_selections: Option<Vec<ClipboardSelection>>,
13280 handle_entire_lines: bool,
13281 window: &mut Window,
13282 cx: &mut Context<Self>,
13283 ) {
13284 if self.read_only(cx) {
13285 return;
13286 }
13287
13288 let clipboard_text = Cow::Borrowed(text.as_str());
13289
13290 self.transact(window, cx, |this, window, cx| {
13291 let had_active_edit_prediction = this.has_active_edit_prediction();
13292 let display_map = this.display_snapshot(cx);
13293 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13294 let cursor_offset = this
13295 .selections
13296 .last::<MultiBufferOffset>(&display_map)
13297 .head();
13298
13299 if let Some(mut clipboard_selections) = clipboard_selections {
13300 let all_selections_were_entire_line =
13301 clipboard_selections.iter().all(|s| s.is_entire_line);
13302 let first_selection_indent_column =
13303 clipboard_selections.first().map(|s| s.first_line_indent);
13304 if clipboard_selections.len() != old_selections.len() {
13305 clipboard_selections.drain(..);
13306 }
13307 let mut auto_indent_on_paste = true;
13308
13309 this.buffer.update(cx, |buffer, cx| {
13310 let snapshot = buffer.read(cx);
13311 auto_indent_on_paste = snapshot
13312 .language_settings_at(cursor_offset, cx)
13313 .auto_indent_on_paste;
13314
13315 let mut start_offset = 0;
13316 let mut edits = Vec::new();
13317 let mut original_indent_columns = Vec::new();
13318 for (ix, selection) in old_selections.iter().enumerate() {
13319 let to_insert;
13320 let entire_line;
13321 let original_indent_column;
13322 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13323 let end_offset = start_offset + clipboard_selection.len;
13324 to_insert = &clipboard_text[start_offset..end_offset];
13325 entire_line = clipboard_selection.is_entire_line;
13326 start_offset = if entire_line {
13327 end_offset
13328 } else {
13329 end_offset + 1
13330 };
13331 original_indent_column = Some(clipboard_selection.first_line_indent);
13332 } else {
13333 to_insert = &*clipboard_text;
13334 entire_line = all_selections_were_entire_line;
13335 original_indent_column = first_selection_indent_column
13336 }
13337
13338 let (range, to_insert) =
13339 if selection.is_empty() && handle_entire_lines && entire_line {
13340 // If the corresponding selection was empty when this slice of the
13341 // clipboard text was written, then the entire line containing the
13342 // selection was copied. If this selection is also currently empty,
13343 // then paste the line before the current line of the buffer.
13344 let column = selection.start.to_point(&snapshot).column as usize;
13345 let line_start = selection.start - column;
13346 (line_start..line_start, Cow::Borrowed(to_insert))
13347 } else {
13348 let language = snapshot.language_at(selection.head());
13349 let range = selection.range();
13350 if let Some(language) = language
13351 && language.name() == "Markdown".into()
13352 {
13353 edit_for_markdown_paste(
13354 &snapshot,
13355 range,
13356 to_insert,
13357 url::Url::parse(to_insert).ok(),
13358 )
13359 } else {
13360 (range, Cow::Borrowed(to_insert))
13361 }
13362 };
13363
13364 edits.push((range, to_insert));
13365 original_indent_columns.push(original_indent_column);
13366 }
13367 drop(snapshot);
13368
13369 buffer.edit(
13370 edits,
13371 if auto_indent_on_paste {
13372 Some(AutoindentMode::Block {
13373 original_indent_columns,
13374 })
13375 } else {
13376 None
13377 },
13378 cx,
13379 );
13380 });
13381
13382 let selections = this
13383 .selections
13384 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13385 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13386 } else {
13387 let url = url::Url::parse(&clipboard_text).ok();
13388
13389 let auto_indent_mode = if !clipboard_text.is_empty() {
13390 Some(AutoindentMode::Block {
13391 original_indent_columns: Vec::new(),
13392 })
13393 } else {
13394 None
13395 };
13396
13397 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13398 let snapshot = buffer.snapshot(cx);
13399
13400 let anchors = old_selections
13401 .iter()
13402 .map(|s| {
13403 let anchor = snapshot.anchor_after(s.head());
13404 s.map(|_| anchor)
13405 })
13406 .collect::<Vec<_>>();
13407
13408 let mut edits = Vec::new();
13409
13410 for selection in old_selections.iter() {
13411 let language = snapshot.language_at(selection.head());
13412 let range = selection.range();
13413
13414 let (edit_range, edit_text) = if let Some(language) = language
13415 && language.name() == "Markdown".into()
13416 {
13417 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13418 } else {
13419 (range, clipboard_text.clone())
13420 };
13421
13422 edits.push((edit_range, edit_text));
13423 }
13424
13425 drop(snapshot);
13426 buffer.edit(edits, auto_indent_mode, cx);
13427
13428 anchors
13429 });
13430
13431 this.change_selections(Default::default(), window, cx, |s| {
13432 s.select_anchors(selection_anchors);
13433 });
13434 }
13435
13436 // 🤔 | .. | show_in_menu |
13437 // | .. | true true
13438 // | had_edit_prediction | false true
13439
13440 let trigger_in_words =
13441 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13442
13443 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13444 });
13445 }
13446
13447 pub fn diff_clipboard_with_selection(
13448 &mut self,
13449 _: &DiffClipboardWithSelection,
13450 window: &mut Window,
13451 cx: &mut Context<Self>,
13452 ) {
13453 let selections = self
13454 .selections
13455 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13456
13457 if selections.is_empty() {
13458 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13459 return;
13460 };
13461
13462 let clipboard_text = match cx.read_from_clipboard() {
13463 Some(item) => match item.entries().first() {
13464 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13465 _ => None,
13466 },
13467 None => None,
13468 };
13469
13470 let Some(clipboard_text) = clipboard_text else {
13471 log::warn!("Clipboard doesn't contain text.");
13472 return;
13473 };
13474
13475 window.dispatch_action(
13476 Box::new(DiffClipboardWithSelectionData {
13477 clipboard_text,
13478 editor: cx.entity(),
13479 }),
13480 cx,
13481 );
13482 }
13483
13484 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13485 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13486 if let Some(item) = cx.read_from_clipboard() {
13487 let entries = item.entries();
13488
13489 match entries.first() {
13490 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13491 // of all the pasted entries.
13492 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13493 .do_paste(
13494 clipboard_string.text(),
13495 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13496 true,
13497 window,
13498 cx,
13499 ),
13500 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13501 }
13502 }
13503 }
13504
13505 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13506 if self.read_only(cx) {
13507 return;
13508 }
13509
13510 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13511
13512 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13513 if let Some((selections, _)) =
13514 self.selection_history.transaction(transaction_id).cloned()
13515 {
13516 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13517 s.select_anchors(selections.to_vec());
13518 });
13519 } else {
13520 log::error!(
13521 "No entry in selection_history found for undo. \
13522 This may correspond to a bug where undo does not update the selection. \
13523 If this is occurring, please add details to \
13524 https://github.com/zed-industries/zed/issues/22692"
13525 );
13526 }
13527 self.request_autoscroll(Autoscroll::fit(), cx);
13528 self.unmark_text(window, cx);
13529 self.refresh_edit_prediction(true, false, window, cx);
13530 cx.emit(EditorEvent::Edited { transaction_id });
13531 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13532 }
13533 }
13534
13535 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13536 if self.read_only(cx) {
13537 return;
13538 }
13539
13540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13541
13542 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13543 if let Some((_, Some(selections))) =
13544 self.selection_history.transaction(transaction_id).cloned()
13545 {
13546 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13547 s.select_anchors(selections.to_vec());
13548 });
13549 } else {
13550 log::error!(
13551 "No entry in selection_history found for redo. \
13552 This may correspond to a bug where undo does not update the selection. \
13553 If this is occurring, please add details to \
13554 https://github.com/zed-industries/zed/issues/22692"
13555 );
13556 }
13557 self.request_autoscroll(Autoscroll::fit(), cx);
13558 self.unmark_text(window, cx);
13559 self.refresh_edit_prediction(true, false, window, cx);
13560 cx.emit(EditorEvent::Edited { transaction_id });
13561 }
13562 }
13563
13564 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13565 self.buffer
13566 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13567 }
13568
13569 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13570 self.buffer
13571 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13572 }
13573
13574 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13575 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13576 self.change_selections(Default::default(), window, cx, |s| {
13577 s.move_with(|map, selection| {
13578 let cursor = if selection.is_empty() {
13579 movement::left(map, selection.start)
13580 } else {
13581 selection.start
13582 };
13583 selection.collapse_to(cursor, SelectionGoal::None);
13584 });
13585 })
13586 }
13587
13588 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13589 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13590 self.change_selections(Default::default(), window, cx, |s| {
13591 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13592 })
13593 }
13594
13595 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13596 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13597 self.change_selections(Default::default(), window, cx, |s| {
13598 s.move_with(|map, selection| {
13599 let cursor = if selection.is_empty() {
13600 movement::right(map, selection.end)
13601 } else {
13602 selection.end
13603 };
13604 selection.collapse_to(cursor, SelectionGoal::None)
13605 });
13606 })
13607 }
13608
13609 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13611 self.change_selections(Default::default(), window, cx, |s| {
13612 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13613 });
13614 }
13615
13616 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13617 if self.take_rename(true, window, cx).is_some() {
13618 return;
13619 }
13620
13621 if self.mode.is_single_line() {
13622 cx.propagate();
13623 return;
13624 }
13625
13626 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13627
13628 let text_layout_details = &self.text_layout_details(window);
13629 let selection_count = self.selections.count();
13630 let first_selection = self.selections.first_anchor();
13631
13632 self.change_selections(Default::default(), window, cx, |s| {
13633 s.move_with(|map, selection| {
13634 if !selection.is_empty() {
13635 selection.goal = SelectionGoal::None;
13636 }
13637 let (cursor, goal) = movement::up(
13638 map,
13639 selection.start,
13640 selection.goal,
13641 false,
13642 text_layout_details,
13643 );
13644 selection.collapse_to(cursor, goal);
13645 });
13646 });
13647
13648 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13649 {
13650 cx.propagate();
13651 }
13652 }
13653
13654 pub fn move_up_by_lines(
13655 &mut self,
13656 action: &MoveUpByLines,
13657 window: &mut Window,
13658 cx: &mut Context<Self>,
13659 ) {
13660 if self.take_rename(true, window, cx).is_some() {
13661 return;
13662 }
13663
13664 if self.mode.is_single_line() {
13665 cx.propagate();
13666 return;
13667 }
13668
13669 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13670
13671 let text_layout_details = &self.text_layout_details(window);
13672
13673 self.change_selections(Default::default(), window, cx, |s| {
13674 s.move_with(|map, selection| {
13675 if !selection.is_empty() {
13676 selection.goal = SelectionGoal::None;
13677 }
13678 let (cursor, goal) = movement::up_by_rows(
13679 map,
13680 selection.start,
13681 action.lines,
13682 selection.goal,
13683 false,
13684 text_layout_details,
13685 );
13686 selection.collapse_to(cursor, goal);
13687 });
13688 })
13689 }
13690
13691 pub fn move_down_by_lines(
13692 &mut self,
13693 action: &MoveDownByLines,
13694 window: &mut Window,
13695 cx: &mut Context<Self>,
13696 ) {
13697 if self.take_rename(true, window, cx).is_some() {
13698 return;
13699 }
13700
13701 if self.mode.is_single_line() {
13702 cx.propagate();
13703 return;
13704 }
13705
13706 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13707
13708 let text_layout_details = &self.text_layout_details(window);
13709
13710 self.change_selections(Default::default(), window, cx, |s| {
13711 s.move_with(|map, selection| {
13712 if !selection.is_empty() {
13713 selection.goal = SelectionGoal::None;
13714 }
13715 let (cursor, goal) = movement::down_by_rows(
13716 map,
13717 selection.start,
13718 action.lines,
13719 selection.goal,
13720 false,
13721 text_layout_details,
13722 );
13723 selection.collapse_to(cursor, goal);
13724 });
13725 })
13726 }
13727
13728 pub fn select_down_by_lines(
13729 &mut self,
13730 action: &SelectDownByLines,
13731 window: &mut Window,
13732 cx: &mut Context<Self>,
13733 ) {
13734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13735 let text_layout_details = &self.text_layout_details(window);
13736 self.change_selections(Default::default(), window, cx, |s| {
13737 s.move_heads_with(|map, head, goal| {
13738 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13739 })
13740 })
13741 }
13742
13743 pub fn select_up_by_lines(
13744 &mut self,
13745 action: &SelectUpByLines,
13746 window: &mut Window,
13747 cx: &mut Context<Self>,
13748 ) {
13749 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13750 let text_layout_details = &self.text_layout_details(window);
13751 self.change_selections(Default::default(), window, cx, |s| {
13752 s.move_heads_with(|map, head, goal| {
13753 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13754 })
13755 })
13756 }
13757
13758 pub fn select_page_up(
13759 &mut self,
13760 _: &SelectPageUp,
13761 window: &mut Window,
13762 cx: &mut Context<Self>,
13763 ) {
13764 let Some(row_count) = self.visible_row_count() else {
13765 return;
13766 };
13767
13768 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13769
13770 let text_layout_details = &self.text_layout_details(window);
13771
13772 self.change_selections(Default::default(), window, cx, |s| {
13773 s.move_heads_with(|map, head, goal| {
13774 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13775 })
13776 })
13777 }
13778
13779 pub fn move_page_up(
13780 &mut self,
13781 action: &MovePageUp,
13782 window: &mut Window,
13783 cx: &mut Context<Self>,
13784 ) {
13785 if self.take_rename(true, window, cx).is_some() {
13786 return;
13787 }
13788
13789 if self
13790 .context_menu
13791 .borrow_mut()
13792 .as_mut()
13793 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13794 .unwrap_or(false)
13795 {
13796 return;
13797 }
13798
13799 if matches!(self.mode, EditorMode::SingleLine) {
13800 cx.propagate();
13801 return;
13802 }
13803
13804 let Some(row_count) = self.visible_row_count() else {
13805 return;
13806 };
13807
13808 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13809
13810 let effects = if action.center_cursor {
13811 SelectionEffects::scroll(Autoscroll::center())
13812 } else {
13813 SelectionEffects::default()
13814 };
13815
13816 let text_layout_details = &self.text_layout_details(window);
13817
13818 self.change_selections(effects, window, cx, |s| {
13819 s.move_with(|map, selection| {
13820 if !selection.is_empty() {
13821 selection.goal = SelectionGoal::None;
13822 }
13823 let (cursor, goal) = movement::up_by_rows(
13824 map,
13825 selection.end,
13826 row_count,
13827 selection.goal,
13828 false,
13829 text_layout_details,
13830 );
13831 selection.collapse_to(cursor, goal);
13832 });
13833 });
13834 }
13835
13836 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13837 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13838 let text_layout_details = &self.text_layout_details(window);
13839 self.change_selections(Default::default(), window, cx, |s| {
13840 s.move_heads_with(|map, head, goal| {
13841 movement::up(map, head, goal, false, text_layout_details)
13842 })
13843 })
13844 }
13845
13846 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13847 self.take_rename(true, window, cx);
13848
13849 if self.mode.is_single_line() {
13850 cx.propagate();
13851 return;
13852 }
13853
13854 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13855
13856 let text_layout_details = &self.text_layout_details(window);
13857 let selection_count = self.selections.count();
13858 let first_selection = self.selections.first_anchor();
13859
13860 self.change_selections(Default::default(), window, cx, |s| {
13861 s.move_with(|map, selection| {
13862 if !selection.is_empty() {
13863 selection.goal = SelectionGoal::None;
13864 }
13865 let (cursor, goal) = movement::down(
13866 map,
13867 selection.end,
13868 selection.goal,
13869 false,
13870 text_layout_details,
13871 );
13872 selection.collapse_to(cursor, goal);
13873 });
13874 });
13875
13876 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13877 {
13878 cx.propagate();
13879 }
13880 }
13881
13882 pub fn select_page_down(
13883 &mut self,
13884 _: &SelectPageDown,
13885 window: &mut Window,
13886 cx: &mut Context<Self>,
13887 ) {
13888 let Some(row_count) = self.visible_row_count() else {
13889 return;
13890 };
13891
13892 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13893
13894 let text_layout_details = &self.text_layout_details(window);
13895
13896 self.change_selections(Default::default(), window, cx, |s| {
13897 s.move_heads_with(|map, head, goal| {
13898 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13899 })
13900 })
13901 }
13902
13903 pub fn move_page_down(
13904 &mut self,
13905 action: &MovePageDown,
13906 window: &mut Window,
13907 cx: &mut Context<Self>,
13908 ) {
13909 if self.take_rename(true, window, cx).is_some() {
13910 return;
13911 }
13912
13913 if self
13914 .context_menu
13915 .borrow_mut()
13916 .as_mut()
13917 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13918 .unwrap_or(false)
13919 {
13920 return;
13921 }
13922
13923 if matches!(self.mode, EditorMode::SingleLine) {
13924 cx.propagate();
13925 return;
13926 }
13927
13928 let Some(row_count) = self.visible_row_count() else {
13929 return;
13930 };
13931
13932 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13933
13934 let effects = if action.center_cursor {
13935 SelectionEffects::scroll(Autoscroll::center())
13936 } else {
13937 SelectionEffects::default()
13938 };
13939
13940 let text_layout_details = &self.text_layout_details(window);
13941 self.change_selections(effects, window, cx, |s| {
13942 s.move_with(|map, selection| {
13943 if !selection.is_empty() {
13944 selection.goal = SelectionGoal::None;
13945 }
13946 let (cursor, goal) = movement::down_by_rows(
13947 map,
13948 selection.end,
13949 row_count,
13950 selection.goal,
13951 false,
13952 text_layout_details,
13953 );
13954 selection.collapse_to(cursor, goal);
13955 });
13956 });
13957 }
13958
13959 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13960 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13961 let text_layout_details = &self.text_layout_details(window);
13962 self.change_selections(Default::default(), window, cx, |s| {
13963 s.move_heads_with(|map, head, goal| {
13964 movement::down(map, head, goal, false, text_layout_details)
13965 })
13966 });
13967 }
13968
13969 pub fn context_menu_first(
13970 &mut self,
13971 _: &ContextMenuFirst,
13972 window: &mut Window,
13973 cx: &mut Context<Self>,
13974 ) {
13975 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13976 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13977 }
13978 }
13979
13980 pub fn context_menu_prev(
13981 &mut self,
13982 _: &ContextMenuPrevious,
13983 window: &mut Window,
13984 cx: &mut Context<Self>,
13985 ) {
13986 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13987 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13988 }
13989 }
13990
13991 pub fn context_menu_next(
13992 &mut self,
13993 _: &ContextMenuNext,
13994 window: &mut Window,
13995 cx: &mut Context<Self>,
13996 ) {
13997 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13998 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13999 }
14000 }
14001
14002 pub fn context_menu_last(
14003 &mut self,
14004 _: &ContextMenuLast,
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_last(self.completion_provider.as_deref(), window, cx);
14010 }
14011 }
14012
14013 pub fn signature_help_prev(
14014 &mut self,
14015 _: &SignatureHelpPrevious,
14016 _: &mut Window,
14017 cx: &mut Context<Self>,
14018 ) {
14019 if let Some(popover) = self.signature_help_state.popover_mut() {
14020 if popover.current_signature == 0 {
14021 popover.current_signature = popover.signatures.len() - 1;
14022 } else {
14023 popover.current_signature -= 1;
14024 }
14025 cx.notify();
14026 }
14027 }
14028
14029 pub fn signature_help_next(
14030 &mut self,
14031 _: &SignatureHelpNext,
14032 _: &mut Window,
14033 cx: &mut Context<Self>,
14034 ) {
14035 if let Some(popover) = self.signature_help_state.popover_mut() {
14036 if popover.current_signature + 1 == popover.signatures.len() {
14037 popover.current_signature = 0;
14038 } else {
14039 popover.current_signature += 1;
14040 }
14041 cx.notify();
14042 }
14043 }
14044
14045 pub fn move_to_previous_word_start(
14046 &mut self,
14047 _: &MoveToPreviousWordStart,
14048 window: &mut Window,
14049 cx: &mut Context<Self>,
14050 ) {
14051 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14052 self.change_selections(Default::default(), window, cx, |s| {
14053 s.move_cursors_with(|map, head, _| {
14054 (
14055 movement::previous_word_start(map, head),
14056 SelectionGoal::None,
14057 )
14058 });
14059 })
14060 }
14061
14062 pub fn move_to_previous_subword_start(
14063 &mut self,
14064 _: &MoveToPreviousSubwordStart,
14065 window: &mut Window,
14066 cx: &mut Context<Self>,
14067 ) {
14068 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14069 self.change_selections(Default::default(), window, cx, |s| {
14070 s.move_cursors_with(|map, head, _| {
14071 (
14072 movement::previous_subword_start(map, head),
14073 SelectionGoal::None,
14074 )
14075 });
14076 })
14077 }
14078
14079 pub fn select_to_previous_word_start(
14080 &mut self,
14081 _: &SelectToPreviousWordStart,
14082 window: &mut Window,
14083 cx: &mut Context<Self>,
14084 ) {
14085 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14086 self.change_selections(Default::default(), window, cx, |s| {
14087 s.move_heads_with(|map, head, _| {
14088 (
14089 movement::previous_word_start(map, head),
14090 SelectionGoal::None,
14091 )
14092 });
14093 })
14094 }
14095
14096 pub fn select_to_previous_subword_start(
14097 &mut self,
14098 _: &SelectToPreviousSubwordStart,
14099 window: &mut Window,
14100 cx: &mut Context<Self>,
14101 ) {
14102 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14103 self.change_selections(Default::default(), window, cx, |s| {
14104 s.move_heads_with(|map, head, _| {
14105 (
14106 movement::previous_subword_start(map, head),
14107 SelectionGoal::None,
14108 )
14109 });
14110 })
14111 }
14112
14113 pub fn delete_to_previous_word_start(
14114 &mut self,
14115 action: &DeleteToPreviousWordStart,
14116 window: &mut Window,
14117 cx: &mut Context<Self>,
14118 ) {
14119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14120 self.transact(window, cx, |this, window, cx| {
14121 this.select_autoclose_pair(window, cx);
14122 this.change_selections(Default::default(), window, cx, |s| {
14123 s.move_with(|map, selection| {
14124 if selection.is_empty() {
14125 let mut cursor = if action.ignore_newlines {
14126 movement::previous_word_start(map, selection.head())
14127 } else {
14128 movement::previous_word_start_or_newline(map, selection.head())
14129 };
14130 cursor = movement::adjust_greedy_deletion(
14131 map,
14132 selection.head(),
14133 cursor,
14134 action.ignore_brackets,
14135 );
14136 selection.set_head(cursor, SelectionGoal::None);
14137 }
14138 });
14139 });
14140 this.insert("", window, cx);
14141 });
14142 }
14143
14144 pub fn delete_to_previous_subword_start(
14145 &mut self,
14146 _: &DeleteToPreviousSubwordStart,
14147 window: &mut Window,
14148 cx: &mut Context<Self>,
14149 ) {
14150 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14151 self.transact(window, cx, |this, window, cx| {
14152 this.select_autoclose_pair(window, cx);
14153 this.change_selections(Default::default(), window, cx, |s| {
14154 s.move_with(|map, selection| {
14155 if selection.is_empty() {
14156 let mut cursor = movement::previous_subword_start(map, selection.head());
14157 cursor =
14158 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14159 selection.set_head(cursor, SelectionGoal::None);
14160 }
14161 });
14162 });
14163 this.insert("", window, cx);
14164 });
14165 }
14166
14167 pub fn move_to_next_word_end(
14168 &mut self,
14169 _: &MoveToNextWordEnd,
14170 window: &mut Window,
14171 cx: &mut Context<Self>,
14172 ) {
14173 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14174 self.change_selections(Default::default(), window, cx, |s| {
14175 s.move_cursors_with(|map, head, _| {
14176 (movement::next_word_end(map, head), SelectionGoal::None)
14177 });
14178 })
14179 }
14180
14181 pub fn move_to_next_subword_end(
14182 &mut self,
14183 _: &MoveToNextSubwordEnd,
14184 window: &mut Window,
14185 cx: &mut Context<Self>,
14186 ) {
14187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14188 self.change_selections(Default::default(), window, cx, |s| {
14189 s.move_cursors_with(|map, head, _| {
14190 (movement::next_subword_end(map, head), SelectionGoal::None)
14191 });
14192 })
14193 }
14194
14195 pub fn select_to_next_word_end(
14196 &mut self,
14197 _: &SelectToNextWordEnd,
14198 window: &mut Window,
14199 cx: &mut Context<Self>,
14200 ) {
14201 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14202 self.change_selections(Default::default(), window, cx, |s| {
14203 s.move_heads_with(|map, head, _| {
14204 (movement::next_word_end(map, head), SelectionGoal::None)
14205 });
14206 })
14207 }
14208
14209 pub fn select_to_next_subword_end(
14210 &mut self,
14211 _: &SelectToNextSubwordEnd,
14212 window: &mut Window,
14213 cx: &mut Context<Self>,
14214 ) {
14215 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14216 self.change_selections(Default::default(), window, cx, |s| {
14217 s.move_heads_with(|map, head, _| {
14218 (movement::next_subword_end(map, head), SelectionGoal::None)
14219 });
14220 })
14221 }
14222
14223 pub fn delete_to_next_word_end(
14224 &mut self,
14225 action: &DeleteToNextWordEnd,
14226 window: &mut Window,
14227 cx: &mut Context<Self>,
14228 ) {
14229 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14230 self.transact(window, cx, |this, window, cx| {
14231 this.change_selections(Default::default(), window, cx, |s| {
14232 s.move_with(|map, selection| {
14233 if selection.is_empty() {
14234 let mut cursor = if action.ignore_newlines {
14235 movement::next_word_end(map, selection.head())
14236 } else {
14237 movement::next_word_end_or_newline(map, selection.head())
14238 };
14239 cursor = movement::adjust_greedy_deletion(
14240 map,
14241 selection.head(),
14242 cursor,
14243 action.ignore_brackets,
14244 );
14245 selection.set_head(cursor, SelectionGoal::None);
14246 }
14247 });
14248 });
14249 this.insert("", window, cx);
14250 });
14251 }
14252
14253 pub fn delete_to_next_subword_end(
14254 &mut self,
14255 _: &DeleteToNextSubwordEnd,
14256 window: &mut Window,
14257 cx: &mut Context<Self>,
14258 ) {
14259 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14260 self.transact(window, cx, |this, window, cx| {
14261 this.change_selections(Default::default(), window, cx, |s| {
14262 s.move_with(|map, selection| {
14263 if selection.is_empty() {
14264 let mut cursor = movement::next_subword_end(map, selection.head());
14265 cursor =
14266 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14267 selection.set_head(cursor, SelectionGoal::None);
14268 }
14269 });
14270 });
14271 this.insert("", window, cx);
14272 });
14273 }
14274
14275 pub fn move_to_beginning_of_line(
14276 &mut self,
14277 action: &MoveToBeginningOfLine,
14278 window: &mut Window,
14279 cx: &mut Context<Self>,
14280 ) {
14281 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14282 self.change_selections(Default::default(), window, cx, |s| {
14283 s.move_cursors_with(|map, head, _| {
14284 (
14285 movement::indented_line_beginning(
14286 map,
14287 head,
14288 action.stop_at_soft_wraps,
14289 action.stop_at_indent,
14290 ),
14291 SelectionGoal::None,
14292 )
14293 });
14294 })
14295 }
14296
14297 pub fn select_to_beginning_of_line(
14298 &mut self,
14299 action: &SelectToBeginningOfLine,
14300 window: &mut Window,
14301 cx: &mut Context<Self>,
14302 ) {
14303 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14304 self.change_selections(Default::default(), window, cx, |s| {
14305 s.move_heads_with(|map, head, _| {
14306 (
14307 movement::indented_line_beginning(
14308 map,
14309 head,
14310 action.stop_at_soft_wraps,
14311 action.stop_at_indent,
14312 ),
14313 SelectionGoal::None,
14314 )
14315 });
14316 });
14317 }
14318
14319 pub fn delete_to_beginning_of_line(
14320 &mut self,
14321 action: &DeleteToBeginningOfLine,
14322 window: &mut Window,
14323 cx: &mut Context<Self>,
14324 ) {
14325 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14326 self.transact(window, cx, |this, window, cx| {
14327 this.change_selections(Default::default(), window, cx, |s| {
14328 s.move_with(|_, selection| {
14329 selection.reversed = true;
14330 });
14331 });
14332
14333 this.select_to_beginning_of_line(
14334 &SelectToBeginningOfLine {
14335 stop_at_soft_wraps: false,
14336 stop_at_indent: action.stop_at_indent,
14337 },
14338 window,
14339 cx,
14340 );
14341 this.backspace(&Backspace, window, cx);
14342 });
14343 }
14344
14345 pub fn move_to_end_of_line(
14346 &mut self,
14347 action: &MoveToEndOfLine,
14348 window: &mut Window,
14349 cx: &mut Context<Self>,
14350 ) {
14351 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14352 self.change_selections(Default::default(), window, cx, |s| {
14353 s.move_cursors_with(|map, head, _| {
14354 (
14355 movement::line_end(map, head, action.stop_at_soft_wraps),
14356 SelectionGoal::None,
14357 )
14358 });
14359 })
14360 }
14361
14362 pub fn select_to_end_of_line(
14363 &mut self,
14364 action: &SelectToEndOfLine,
14365 window: &mut Window,
14366 cx: &mut Context<Self>,
14367 ) {
14368 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14369 self.change_selections(Default::default(), window, cx, |s| {
14370 s.move_heads_with(|map, head, _| {
14371 (
14372 movement::line_end(map, head, action.stop_at_soft_wraps),
14373 SelectionGoal::None,
14374 )
14375 });
14376 })
14377 }
14378
14379 pub fn delete_to_end_of_line(
14380 &mut self,
14381 _: &DeleteToEndOfLine,
14382 window: &mut Window,
14383 cx: &mut Context<Self>,
14384 ) {
14385 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14386 self.transact(window, cx, |this, window, cx| {
14387 this.select_to_end_of_line(
14388 &SelectToEndOfLine {
14389 stop_at_soft_wraps: false,
14390 },
14391 window,
14392 cx,
14393 );
14394 this.delete(&Delete, window, cx);
14395 });
14396 }
14397
14398 pub fn cut_to_end_of_line(
14399 &mut self,
14400 action: &CutToEndOfLine,
14401 window: &mut Window,
14402 cx: &mut Context<Self>,
14403 ) {
14404 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14405 self.transact(window, cx, |this, window, cx| {
14406 this.select_to_end_of_line(
14407 &SelectToEndOfLine {
14408 stop_at_soft_wraps: false,
14409 },
14410 window,
14411 cx,
14412 );
14413 if !action.stop_at_newlines {
14414 this.change_selections(Default::default(), window, cx, |s| {
14415 s.move_with(|_, sel| {
14416 if sel.is_empty() {
14417 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14418 }
14419 });
14420 });
14421 }
14422 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14423 let item = this.cut_common(false, window, cx);
14424 cx.write_to_clipboard(item);
14425 });
14426 }
14427
14428 pub fn move_to_start_of_paragraph(
14429 &mut self,
14430 _: &MoveToStartOfParagraph,
14431 window: &mut Window,
14432 cx: &mut Context<Self>,
14433 ) {
14434 if matches!(self.mode, EditorMode::SingleLine) {
14435 cx.propagate();
14436 return;
14437 }
14438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14439 self.change_selections(Default::default(), window, cx, |s| {
14440 s.move_with(|map, selection| {
14441 selection.collapse_to(
14442 movement::start_of_paragraph(map, selection.head(), 1),
14443 SelectionGoal::None,
14444 )
14445 });
14446 })
14447 }
14448
14449 pub fn move_to_end_of_paragraph(
14450 &mut self,
14451 _: &MoveToEndOfParagraph,
14452 window: &mut Window,
14453 cx: &mut Context<Self>,
14454 ) {
14455 if matches!(self.mode, EditorMode::SingleLine) {
14456 cx.propagate();
14457 return;
14458 }
14459 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14460 self.change_selections(Default::default(), window, cx, |s| {
14461 s.move_with(|map, selection| {
14462 selection.collapse_to(
14463 movement::end_of_paragraph(map, selection.head(), 1),
14464 SelectionGoal::None,
14465 )
14466 });
14467 })
14468 }
14469
14470 pub fn select_to_start_of_paragraph(
14471 &mut self,
14472 _: &SelectToStartOfParagraph,
14473 window: &mut Window,
14474 cx: &mut Context<Self>,
14475 ) {
14476 if matches!(self.mode, EditorMode::SingleLine) {
14477 cx.propagate();
14478 return;
14479 }
14480 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14481 self.change_selections(Default::default(), window, cx, |s| {
14482 s.move_heads_with(|map, head, _| {
14483 (
14484 movement::start_of_paragraph(map, head, 1),
14485 SelectionGoal::None,
14486 )
14487 });
14488 })
14489 }
14490
14491 pub fn select_to_end_of_paragraph(
14492 &mut self,
14493 _: &SelectToEndOfParagraph,
14494 window: &mut Window,
14495 cx: &mut Context<Self>,
14496 ) {
14497 if matches!(self.mode, EditorMode::SingleLine) {
14498 cx.propagate();
14499 return;
14500 }
14501 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14502 self.change_selections(Default::default(), window, cx, |s| {
14503 s.move_heads_with(|map, head, _| {
14504 (
14505 movement::end_of_paragraph(map, head, 1),
14506 SelectionGoal::None,
14507 )
14508 });
14509 })
14510 }
14511
14512 pub fn move_to_start_of_excerpt(
14513 &mut self,
14514 _: &MoveToStartOfExcerpt,
14515 window: &mut Window,
14516 cx: &mut Context<Self>,
14517 ) {
14518 if matches!(self.mode, EditorMode::SingleLine) {
14519 cx.propagate();
14520 return;
14521 }
14522 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14523 self.change_selections(Default::default(), window, cx, |s| {
14524 s.move_with(|map, selection| {
14525 selection.collapse_to(
14526 movement::start_of_excerpt(
14527 map,
14528 selection.head(),
14529 workspace::searchable::Direction::Prev,
14530 ),
14531 SelectionGoal::None,
14532 )
14533 });
14534 })
14535 }
14536
14537 pub fn move_to_start_of_next_excerpt(
14538 &mut self,
14539 _: &MoveToStartOfNextExcerpt,
14540 window: &mut Window,
14541 cx: &mut Context<Self>,
14542 ) {
14543 if matches!(self.mode, EditorMode::SingleLine) {
14544 cx.propagate();
14545 return;
14546 }
14547
14548 self.change_selections(Default::default(), window, cx, |s| {
14549 s.move_with(|map, selection| {
14550 selection.collapse_to(
14551 movement::start_of_excerpt(
14552 map,
14553 selection.head(),
14554 workspace::searchable::Direction::Next,
14555 ),
14556 SelectionGoal::None,
14557 )
14558 });
14559 })
14560 }
14561
14562 pub fn move_to_end_of_excerpt(
14563 &mut self,
14564 _: &MoveToEndOfExcerpt,
14565 window: &mut Window,
14566 cx: &mut Context<Self>,
14567 ) {
14568 if matches!(self.mode, EditorMode::SingleLine) {
14569 cx.propagate();
14570 return;
14571 }
14572 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14573 self.change_selections(Default::default(), window, cx, |s| {
14574 s.move_with(|map, selection| {
14575 selection.collapse_to(
14576 movement::end_of_excerpt(
14577 map,
14578 selection.head(),
14579 workspace::searchable::Direction::Next,
14580 ),
14581 SelectionGoal::None,
14582 )
14583 });
14584 })
14585 }
14586
14587 pub fn move_to_end_of_previous_excerpt(
14588 &mut self,
14589 _: &MoveToEndOfPreviousExcerpt,
14590 window: &mut Window,
14591 cx: &mut Context<Self>,
14592 ) {
14593 if matches!(self.mode, EditorMode::SingleLine) {
14594 cx.propagate();
14595 return;
14596 }
14597 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14598 self.change_selections(Default::default(), window, cx, |s| {
14599 s.move_with(|map, selection| {
14600 selection.collapse_to(
14601 movement::end_of_excerpt(
14602 map,
14603 selection.head(),
14604 workspace::searchable::Direction::Prev,
14605 ),
14606 SelectionGoal::None,
14607 )
14608 });
14609 })
14610 }
14611
14612 pub fn select_to_start_of_excerpt(
14613 &mut self,
14614 _: &SelectToStartOfExcerpt,
14615 window: &mut Window,
14616 cx: &mut Context<Self>,
14617 ) {
14618 if matches!(self.mode, EditorMode::SingleLine) {
14619 cx.propagate();
14620 return;
14621 }
14622 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14623 self.change_selections(Default::default(), window, cx, |s| {
14624 s.move_heads_with(|map, head, _| {
14625 (
14626 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14627 SelectionGoal::None,
14628 )
14629 });
14630 })
14631 }
14632
14633 pub fn select_to_start_of_next_excerpt(
14634 &mut self,
14635 _: &SelectToStartOfNextExcerpt,
14636 window: &mut Window,
14637 cx: &mut Context<Self>,
14638 ) {
14639 if matches!(self.mode, EditorMode::SingleLine) {
14640 cx.propagate();
14641 return;
14642 }
14643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14644 self.change_selections(Default::default(), window, cx, |s| {
14645 s.move_heads_with(|map, head, _| {
14646 (
14647 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14648 SelectionGoal::None,
14649 )
14650 });
14651 })
14652 }
14653
14654 pub fn select_to_end_of_excerpt(
14655 &mut self,
14656 _: &SelectToEndOfExcerpt,
14657 window: &mut Window,
14658 cx: &mut Context<Self>,
14659 ) {
14660 if matches!(self.mode, EditorMode::SingleLine) {
14661 cx.propagate();
14662 return;
14663 }
14664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14665 self.change_selections(Default::default(), window, cx, |s| {
14666 s.move_heads_with(|map, head, _| {
14667 (
14668 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14669 SelectionGoal::None,
14670 )
14671 });
14672 })
14673 }
14674
14675 pub fn select_to_end_of_previous_excerpt(
14676 &mut self,
14677 _: &SelectToEndOfPreviousExcerpt,
14678 window: &mut Window,
14679 cx: &mut Context<Self>,
14680 ) {
14681 if matches!(self.mode, EditorMode::SingleLine) {
14682 cx.propagate();
14683 return;
14684 }
14685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14686 self.change_selections(Default::default(), window, cx, |s| {
14687 s.move_heads_with(|map, head, _| {
14688 (
14689 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14690 SelectionGoal::None,
14691 )
14692 });
14693 })
14694 }
14695
14696 pub fn move_to_beginning(
14697 &mut self,
14698 _: &MoveToBeginning,
14699 window: &mut Window,
14700 cx: &mut Context<Self>,
14701 ) {
14702 if matches!(self.mode, EditorMode::SingleLine) {
14703 cx.propagate();
14704 return;
14705 }
14706 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14707 self.change_selections(Default::default(), window, cx, |s| {
14708 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14709 });
14710 }
14711
14712 pub fn select_to_beginning(
14713 &mut self,
14714 _: &SelectToBeginning,
14715 window: &mut Window,
14716 cx: &mut Context<Self>,
14717 ) {
14718 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14719 selection.set_head(Point::zero(), SelectionGoal::None);
14720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14721 self.change_selections(Default::default(), window, cx, |s| {
14722 s.select(vec![selection]);
14723 });
14724 }
14725
14726 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14727 if matches!(self.mode, EditorMode::SingleLine) {
14728 cx.propagate();
14729 return;
14730 }
14731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14732 let cursor = self.buffer.read(cx).read(cx).len();
14733 self.change_selections(Default::default(), window, cx, |s| {
14734 s.select_ranges(vec![cursor..cursor])
14735 });
14736 }
14737
14738 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14739 self.nav_history = nav_history;
14740 }
14741
14742 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14743 self.nav_history.as_ref()
14744 }
14745
14746 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14747 self.push_to_nav_history(
14748 self.selections.newest_anchor().head(),
14749 None,
14750 false,
14751 true,
14752 cx,
14753 );
14754 }
14755
14756 fn push_to_nav_history(
14757 &mut self,
14758 cursor_anchor: Anchor,
14759 new_position: Option<Point>,
14760 is_deactivate: bool,
14761 always: bool,
14762 cx: &mut Context<Self>,
14763 ) {
14764 if let Some(nav_history) = self.nav_history.as_mut() {
14765 let buffer = self.buffer.read(cx).read(cx);
14766 let cursor_position = cursor_anchor.to_point(&buffer);
14767 let scroll_state = self.scroll_manager.anchor();
14768 let scroll_top_row = scroll_state.top_row(&buffer);
14769 drop(buffer);
14770
14771 if let Some(new_position) = new_position {
14772 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14773 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14774 return;
14775 }
14776 }
14777
14778 nav_history.push(
14779 Some(NavigationData {
14780 cursor_anchor,
14781 cursor_position,
14782 scroll_anchor: scroll_state,
14783 scroll_top_row,
14784 }),
14785 cx,
14786 );
14787 cx.emit(EditorEvent::PushedToNavHistory {
14788 anchor: cursor_anchor,
14789 is_deactivate,
14790 })
14791 }
14792 }
14793
14794 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14795 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14796 let buffer = self.buffer.read(cx).snapshot(cx);
14797 let mut selection = self
14798 .selections
14799 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14800 selection.set_head(buffer.len(), SelectionGoal::None);
14801 self.change_selections(Default::default(), window, cx, |s| {
14802 s.select(vec![selection]);
14803 });
14804 }
14805
14806 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14807 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14808 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14809 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14810 });
14811 }
14812
14813 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14814 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14815 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14816 let mut selections = self.selections.all::<Point>(&display_map);
14817 let max_point = display_map.buffer_snapshot().max_point();
14818 for selection in &mut selections {
14819 let rows = selection.spanned_rows(true, &display_map);
14820 selection.start = Point::new(rows.start.0, 0);
14821 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14822 selection.reversed = false;
14823 }
14824 self.change_selections(Default::default(), window, cx, |s| {
14825 s.select(selections);
14826 });
14827 }
14828
14829 pub fn split_selection_into_lines(
14830 &mut self,
14831 action: &SplitSelectionIntoLines,
14832 window: &mut Window,
14833 cx: &mut Context<Self>,
14834 ) {
14835 let selections = self
14836 .selections
14837 .all::<Point>(&self.display_snapshot(cx))
14838 .into_iter()
14839 .map(|selection| selection.start..selection.end)
14840 .collect::<Vec<_>>();
14841 self.unfold_ranges(&selections, true, true, cx);
14842
14843 let mut new_selection_ranges = Vec::new();
14844 {
14845 let buffer = self.buffer.read(cx).read(cx);
14846 for selection in selections {
14847 for row in selection.start.row..selection.end.row {
14848 let line_start = Point::new(row, 0);
14849 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14850
14851 if action.keep_selections {
14852 // Keep the selection range for each line
14853 let selection_start = if row == selection.start.row {
14854 selection.start
14855 } else {
14856 line_start
14857 };
14858 new_selection_ranges.push(selection_start..line_end);
14859 } else {
14860 // Collapse to cursor at end of line
14861 new_selection_ranges.push(line_end..line_end);
14862 }
14863 }
14864
14865 let is_multiline_selection = selection.start.row != selection.end.row;
14866 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14867 // so this action feels more ergonomic when paired with other selection operations
14868 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14869 if !should_skip_last {
14870 if action.keep_selections {
14871 if is_multiline_selection {
14872 let line_start = Point::new(selection.end.row, 0);
14873 new_selection_ranges.push(line_start..selection.end);
14874 } else {
14875 new_selection_ranges.push(selection.start..selection.end);
14876 }
14877 } else {
14878 new_selection_ranges.push(selection.end..selection.end);
14879 }
14880 }
14881 }
14882 }
14883 self.change_selections(Default::default(), window, cx, |s| {
14884 s.select_ranges(new_selection_ranges);
14885 });
14886 }
14887
14888 pub fn add_selection_above(
14889 &mut self,
14890 action: &AddSelectionAbove,
14891 window: &mut Window,
14892 cx: &mut Context<Self>,
14893 ) {
14894 self.add_selection(true, action.skip_soft_wrap, window, cx);
14895 }
14896
14897 pub fn add_selection_below(
14898 &mut self,
14899 action: &AddSelectionBelow,
14900 window: &mut Window,
14901 cx: &mut Context<Self>,
14902 ) {
14903 self.add_selection(false, action.skip_soft_wrap, window, cx);
14904 }
14905
14906 fn add_selection(
14907 &mut self,
14908 above: bool,
14909 skip_soft_wrap: bool,
14910 window: &mut Window,
14911 cx: &mut Context<Self>,
14912 ) {
14913 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14914
14915 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14916 let all_selections = self.selections.all::<Point>(&display_map);
14917 let text_layout_details = self.text_layout_details(window);
14918
14919 let (mut columnar_selections, new_selections_to_columnarize) = {
14920 if let Some(state) = self.add_selections_state.as_ref() {
14921 let columnar_selection_ids: HashSet<_> = state
14922 .groups
14923 .iter()
14924 .flat_map(|group| group.stack.iter())
14925 .copied()
14926 .collect();
14927
14928 all_selections
14929 .into_iter()
14930 .partition(|s| columnar_selection_ids.contains(&s.id))
14931 } else {
14932 (Vec::new(), all_selections)
14933 }
14934 };
14935
14936 let mut state = self
14937 .add_selections_state
14938 .take()
14939 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14940
14941 for selection in new_selections_to_columnarize {
14942 let range = selection.display_range(&display_map).sorted();
14943 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14944 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14945 let positions = start_x.min(end_x)..start_x.max(end_x);
14946 let mut stack = Vec::new();
14947 for row in range.start.row().0..=range.end.row().0 {
14948 if let Some(selection) = self.selections.build_columnar_selection(
14949 &display_map,
14950 DisplayRow(row),
14951 &positions,
14952 selection.reversed,
14953 &text_layout_details,
14954 ) {
14955 stack.push(selection.id);
14956 columnar_selections.push(selection);
14957 }
14958 }
14959 if !stack.is_empty() {
14960 if above {
14961 stack.reverse();
14962 }
14963 state.groups.push(AddSelectionsGroup { above, stack });
14964 }
14965 }
14966
14967 let mut final_selections = Vec::new();
14968 let end_row = if above {
14969 DisplayRow(0)
14970 } else {
14971 display_map.max_point().row()
14972 };
14973
14974 let mut last_added_item_per_group = HashMap::default();
14975 for group in state.groups.iter_mut() {
14976 if let Some(last_id) = group.stack.last() {
14977 last_added_item_per_group.insert(*last_id, group);
14978 }
14979 }
14980
14981 for selection in columnar_selections {
14982 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14983 if above == group.above {
14984 let range = selection.display_range(&display_map).sorted();
14985 debug_assert_eq!(range.start.row(), range.end.row());
14986 let mut row = range.start.row();
14987 let positions =
14988 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14989 Pixels::from(start)..Pixels::from(end)
14990 } else {
14991 let start_x =
14992 display_map.x_for_display_point(range.start, &text_layout_details);
14993 let end_x =
14994 display_map.x_for_display_point(range.end, &text_layout_details);
14995 start_x.min(end_x)..start_x.max(end_x)
14996 };
14997
14998 let mut maybe_new_selection = None;
14999 let direction = if above { -1 } else { 1 };
15000
15001 while row != end_row {
15002 let new_buffer_row = if skip_soft_wrap {
15003 let new_row = display_map
15004 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction);
15005 row = new_row.row();
15006 Some(new_row.to_point(&display_map).row)
15007 } else {
15008 if above {
15009 row.0 -= 1;
15010 } else {
15011 row.0 += 1;
15012 }
15013 None
15014 };
15015
15016 let new_selection = if let Some(buffer_row) = new_buffer_row {
15017 let start_col = selection.start.column;
15018 let end_col = selection.end.column;
15019 let buffer_columns = start_col.min(end_col)..start_col.max(end_col);
15020
15021 self.selections
15022 .build_columnar_selection_from_buffer_columns(
15023 &display_map,
15024 buffer_row,
15025 &buffer_columns,
15026 selection.reversed,
15027 &text_layout_details,
15028 )
15029 } else {
15030 self.selections.build_columnar_selection(
15031 &display_map,
15032 row,
15033 &positions,
15034 selection.reversed,
15035 &text_layout_details,
15036 )
15037 };
15038
15039 if let Some(new_selection) = new_selection {
15040 maybe_new_selection = Some(new_selection);
15041 break;
15042 }
15043 }
15044
15045 if let Some(new_selection) = maybe_new_selection {
15046 group.stack.push(new_selection.id);
15047 if above {
15048 final_selections.push(new_selection);
15049 final_selections.push(selection);
15050 } else {
15051 final_selections.push(selection);
15052 final_selections.push(new_selection);
15053 }
15054 } else {
15055 final_selections.push(selection);
15056 }
15057 } else {
15058 group.stack.pop();
15059 }
15060 } else {
15061 final_selections.push(selection);
15062 }
15063 }
15064
15065 self.change_selections(Default::default(), window, cx, |s| {
15066 s.select(final_selections);
15067 });
15068
15069 let final_selection_ids: HashSet<_> = self
15070 .selections
15071 .all::<Point>(&display_map)
15072 .iter()
15073 .map(|s| s.id)
15074 .collect();
15075 state.groups.retain_mut(|group| {
15076 // selections might get merged above so we remove invalid items from stacks
15077 group.stack.retain(|id| final_selection_ids.contains(id));
15078
15079 // single selection in stack can be treated as initial state
15080 group.stack.len() > 1
15081 });
15082
15083 if !state.groups.is_empty() {
15084 self.add_selections_state = Some(state);
15085 }
15086 }
15087
15088 pub fn insert_snippet_at_selections(
15089 &mut self,
15090 action: &InsertSnippet,
15091 window: &mut Window,
15092 cx: &mut Context<Self>,
15093 ) {
15094 self.try_insert_snippet_at_selections(action, window, cx)
15095 .log_err();
15096 }
15097
15098 fn try_insert_snippet_at_selections(
15099 &mut self,
15100 action: &InsertSnippet,
15101 window: &mut Window,
15102 cx: &mut Context<Self>,
15103 ) -> Result<()> {
15104 let insertion_ranges = self
15105 .selections
15106 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15107 .into_iter()
15108 .map(|selection| selection.range())
15109 .collect_vec();
15110
15111 let snippet = if let Some(snippet_body) = &action.snippet {
15112 if action.language.is_none() && action.name.is_none() {
15113 Snippet::parse(snippet_body)?
15114 } else {
15115 bail!("`snippet` is mutually exclusive with `language` and `name`")
15116 }
15117 } else if let Some(name) = &action.name {
15118 let project = self.project().context("no project")?;
15119 let snippet_store = project.read(cx).snippets().read(cx);
15120 let snippet = snippet_store
15121 .snippets_for(action.language.clone(), cx)
15122 .into_iter()
15123 .find(|snippet| snippet.name == *name)
15124 .context("snippet not found")?;
15125 Snippet::parse(&snippet.body)?
15126 } else {
15127 // todo(andrew): open modal to select snippet
15128 bail!("`name` or `snippet` is required")
15129 };
15130
15131 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15132 }
15133
15134 fn select_match_ranges(
15135 &mut self,
15136 range: Range<MultiBufferOffset>,
15137 reversed: bool,
15138 replace_newest: bool,
15139 auto_scroll: Option<Autoscroll>,
15140 window: &mut Window,
15141 cx: &mut Context<Editor>,
15142 ) {
15143 self.unfold_ranges(
15144 std::slice::from_ref(&range),
15145 false,
15146 auto_scroll.is_some(),
15147 cx,
15148 );
15149 let effects = if let Some(scroll) = auto_scroll {
15150 SelectionEffects::scroll(scroll)
15151 } else {
15152 SelectionEffects::no_scroll()
15153 };
15154 self.change_selections(effects, window, cx, |s| {
15155 if replace_newest {
15156 s.delete(s.newest_anchor().id);
15157 }
15158 if reversed {
15159 s.insert_range(range.end..range.start);
15160 } else {
15161 s.insert_range(range);
15162 }
15163 });
15164 }
15165
15166 pub fn select_next_match_internal(
15167 &mut self,
15168 display_map: &DisplaySnapshot,
15169 replace_newest: bool,
15170 autoscroll: Option<Autoscroll>,
15171 window: &mut Window,
15172 cx: &mut Context<Self>,
15173 ) -> Result<()> {
15174 let buffer = display_map.buffer_snapshot();
15175 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15176 if let Some(mut select_next_state) = self.select_next_state.take() {
15177 let query = &select_next_state.query;
15178 if !select_next_state.done {
15179 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15180 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15181 let mut next_selected_range = None;
15182
15183 let bytes_after_last_selection =
15184 buffer.bytes_in_range(last_selection.end..buffer.len());
15185 let bytes_before_first_selection =
15186 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15187 let query_matches = query
15188 .stream_find_iter(bytes_after_last_selection)
15189 .map(|result| (last_selection.end, result))
15190 .chain(
15191 query
15192 .stream_find_iter(bytes_before_first_selection)
15193 .map(|result| (MultiBufferOffset(0), result)),
15194 );
15195
15196 for (start_offset, query_match) in query_matches {
15197 let query_match = query_match.unwrap(); // can only fail due to I/O
15198 let offset_range =
15199 start_offset + query_match.start()..start_offset + query_match.end();
15200
15201 if !select_next_state.wordwise
15202 || (!buffer.is_inside_word(offset_range.start, None)
15203 && !buffer.is_inside_word(offset_range.end, None))
15204 {
15205 let idx = selections
15206 .partition_point(|selection| selection.end <= offset_range.start);
15207 let overlaps = selections
15208 .get(idx)
15209 .map_or(false, |selection| selection.start < offset_range.end);
15210
15211 if !overlaps {
15212 next_selected_range = Some(offset_range);
15213 break;
15214 }
15215 }
15216 }
15217
15218 if let Some(next_selected_range) = next_selected_range {
15219 self.select_match_ranges(
15220 next_selected_range,
15221 last_selection.reversed,
15222 replace_newest,
15223 autoscroll,
15224 window,
15225 cx,
15226 );
15227 } else {
15228 select_next_state.done = true;
15229 }
15230 }
15231
15232 self.select_next_state = Some(select_next_state);
15233 } else {
15234 let mut only_carets = true;
15235 let mut same_text_selected = true;
15236 let mut selected_text = None;
15237
15238 let mut selections_iter = selections.iter().peekable();
15239 while let Some(selection) = selections_iter.next() {
15240 if selection.start != selection.end {
15241 only_carets = false;
15242 }
15243
15244 if same_text_selected {
15245 if selected_text.is_none() {
15246 selected_text =
15247 Some(buffer.text_for_range(selection.range()).collect::<String>());
15248 }
15249
15250 if let Some(next_selection) = selections_iter.peek() {
15251 if next_selection.len() == selection.len() {
15252 let next_selected_text = buffer
15253 .text_for_range(next_selection.range())
15254 .collect::<String>();
15255 if Some(next_selected_text) != selected_text {
15256 same_text_selected = false;
15257 selected_text = None;
15258 }
15259 } else {
15260 same_text_selected = false;
15261 selected_text = None;
15262 }
15263 }
15264 }
15265 }
15266
15267 if only_carets {
15268 for selection in &mut selections {
15269 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15270 selection.start = word_range.start;
15271 selection.end = word_range.end;
15272 selection.goal = SelectionGoal::None;
15273 selection.reversed = false;
15274 self.select_match_ranges(
15275 selection.start..selection.end,
15276 selection.reversed,
15277 replace_newest,
15278 autoscroll,
15279 window,
15280 cx,
15281 );
15282 }
15283
15284 if selections.len() == 1 {
15285 let selection = selections
15286 .last()
15287 .expect("ensured that there's only one selection");
15288 let query = buffer
15289 .text_for_range(selection.start..selection.end)
15290 .collect::<String>();
15291 let is_empty = query.is_empty();
15292 let select_state = SelectNextState {
15293 query: self.build_query(&[query], cx)?,
15294 wordwise: true,
15295 done: is_empty,
15296 };
15297 self.select_next_state = Some(select_state);
15298 } else {
15299 self.select_next_state = None;
15300 }
15301 } else if let Some(selected_text) = selected_text {
15302 self.select_next_state = Some(SelectNextState {
15303 query: self.build_query(&[selected_text], cx)?,
15304 wordwise: false,
15305 done: false,
15306 });
15307 self.select_next_match_internal(
15308 display_map,
15309 replace_newest,
15310 autoscroll,
15311 window,
15312 cx,
15313 )?;
15314 }
15315 }
15316 Ok(())
15317 }
15318
15319 pub fn select_all_matches(
15320 &mut self,
15321 _action: &SelectAllMatches,
15322 window: &mut Window,
15323 cx: &mut Context<Self>,
15324 ) -> Result<()> {
15325 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15326
15327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15328
15329 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15330 let Some(select_next_state) = self.select_next_state.as_mut() else {
15331 return Ok(());
15332 };
15333 if select_next_state.done {
15334 return Ok(());
15335 }
15336
15337 let mut new_selections = Vec::new();
15338
15339 let reversed = self
15340 .selections
15341 .oldest::<MultiBufferOffset>(&display_map)
15342 .reversed;
15343 let buffer = display_map.buffer_snapshot();
15344 let query_matches = select_next_state
15345 .query
15346 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15347
15348 for query_match in query_matches.into_iter() {
15349 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15350 let offset_range = if reversed {
15351 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15352 } else {
15353 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15354 };
15355
15356 if !select_next_state.wordwise
15357 || (!buffer.is_inside_word(offset_range.start, None)
15358 && !buffer.is_inside_word(offset_range.end, None))
15359 {
15360 new_selections.push(offset_range.start..offset_range.end);
15361 }
15362 }
15363
15364 select_next_state.done = true;
15365
15366 if new_selections.is_empty() {
15367 log::error!("bug: new_selections is empty in select_all_matches");
15368 return Ok(());
15369 }
15370
15371 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15372 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15373 selections.select_ranges(new_selections)
15374 });
15375
15376 Ok(())
15377 }
15378
15379 pub fn select_next(
15380 &mut self,
15381 action: &SelectNext,
15382 window: &mut Window,
15383 cx: &mut Context<Self>,
15384 ) -> Result<()> {
15385 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15386 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15387 self.select_next_match_internal(
15388 &display_map,
15389 action.replace_newest,
15390 Some(Autoscroll::newest()),
15391 window,
15392 cx,
15393 )?;
15394 Ok(())
15395 }
15396
15397 pub fn select_previous(
15398 &mut self,
15399 action: &SelectPrevious,
15400 window: &mut Window,
15401 cx: &mut Context<Self>,
15402 ) -> Result<()> {
15403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15404 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15405 let buffer = display_map.buffer_snapshot();
15406 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15407 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15408 let query = &select_prev_state.query;
15409 if !select_prev_state.done {
15410 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15411 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15412 let mut next_selected_range = None;
15413 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15414 let bytes_before_last_selection =
15415 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15416 let bytes_after_first_selection =
15417 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15418 let query_matches = query
15419 .stream_find_iter(bytes_before_last_selection)
15420 .map(|result| (last_selection.start, result))
15421 .chain(
15422 query
15423 .stream_find_iter(bytes_after_first_selection)
15424 .map(|result| (buffer.len(), result)),
15425 );
15426 for (end_offset, query_match) in query_matches {
15427 let query_match = query_match.unwrap(); // can only fail due to I/O
15428 let offset_range =
15429 end_offset - query_match.end()..end_offset - query_match.start();
15430
15431 if !select_prev_state.wordwise
15432 || (!buffer.is_inside_word(offset_range.start, None)
15433 && !buffer.is_inside_word(offset_range.end, None))
15434 {
15435 next_selected_range = Some(offset_range);
15436 break;
15437 }
15438 }
15439
15440 if let Some(next_selected_range) = next_selected_range {
15441 self.select_match_ranges(
15442 next_selected_range,
15443 last_selection.reversed,
15444 action.replace_newest,
15445 Some(Autoscroll::newest()),
15446 window,
15447 cx,
15448 );
15449 } else {
15450 select_prev_state.done = true;
15451 }
15452 }
15453
15454 self.select_prev_state = Some(select_prev_state);
15455 } else {
15456 let mut only_carets = true;
15457 let mut same_text_selected = true;
15458 let mut selected_text = None;
15459
15460 let mut selections_iter = selections.iter().peekable();
15461 while let Some(selection) = selections_iter.next() {
15462 if selection.start != selection.end {
15463 only_carets = false;
15464 }
15465
15466 if same_text_selected {
15467 if selected_text.is_none() {
15468 selected_text =
15469 Some(buffer.text_for_range(selection.range()).collect::<String>());
15470 }
15471
15472 if let Some(next_selection) = selections_iter.peek() {
15473 if next_selection.len() == selection.len() {
15474 let next_selected_text = buffer
15475 .text_for_range(next_selection.range())
15476 .collect::<String>();
15477 if Some(next_selected_text) != selected_text {
15478 same_text_selected = false;
15479 selected_text = None;
15480 }
15481 } else {
15482 same_text_selected = false;
15483 selected_text = None;
15484 }
15485 }
15486 }
15487 }
15488
15489 if only_carets {
15490 for selection in &mut selections {
15491 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15492 selection.start = word_range.start;
15493 selection.end = word_range.end;
15494 selection.goal = SelectionGoal::None;
15495 selection.reversed = false;
15496 self.select_match_ranges(
15497 selection.start..selection.end,
15498 selection.reversed,
15499 action.replace_newest,
15500 Some(Autoscroll::newest()),
15501 window,
15502 cx,
15503 );
15504 }
15505 if selections.len() == 1 {
15506 let selection = selections
15507 .last()
15508 .expect("ensured that there's only one selection");
15509 let query = buffer
15510 .text_for_range(selection.start..selection.end)
15511 .collect::<String>();
15512 let is_empty = query.is_empty();
15513 let select_state = SelectNextState {
15514 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15515 wordwise: true,
15516 done: is_empty,
15517 };
15518 self.select_prev_state = Some(select_state);
15519 } else {
15520 self.select_prev_state = None;
15521 }
15522 } else if let Some(selected_text) = selected_text {
15523 self.select_prev_state = Some(SelectNextState {
15524 query: self
15525 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15526 wordwise: false,
15527 done: false,
15528 });
15529 self.select_previous(action, window, cx)?;
15530 }
15531 }
15532 Ok(())
15533 }
15534
15535 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15536 /// setting the case sensitivity based on the global
15537 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15538 /// editor's settings.
15539 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15540 where
15541 I: IntoIterator<Item = P>,
15542 P: AsRef<[u8]>,
15543 {
15544 let case_sensitive = self
15545 .select_next_is_case_sensitive
15546 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15547
15548 let mut builder = AhoCorasickBuilder::new();
15549 builder.ascii_case_insensitive(!case_sensitive);
15550 builder.build(patterns)
15551 }
15552
15553 pub fn find_next_match(
15554 &mut self,
15555 _: &FindNextMatch,
15556 window: &mut Window,
15557 cx: &mut Context<Self>,
15558 ) -> Result<()> {
15559 let selections = self.selections.disjoint_anchors_arc();
15560 match selections.first() {
15561 Some(first) if selections.len() >= 2 => {
15562 self.change_selections(Default::default(), window, cx, |s| {
15563 s.select_ranges([first.range()]);
15564 });
15565 }
15566 _ => self.select_next(
15567 &SelectNext {
15568 replace_newest: true,
15569 },
15570 window,
15571 cx,
15572 )?,
15573 }
15574 Ok(())
15575 }
15576
15577 pub fn find_previous_match(
15578 &mut self,
15579 _: &FindPreviousMatch,
15580 window: &mut Window,
15581 cx: &mut Context<Self>,
15582 ) -> Result<()> {
15583 let selections = self.selections.disjoint_anchors_arc();
15584 match selections.last() {
15585 Some(last) if selections.len() >= 2 => {
15586 self.change_selections(Default::default(), window, cx, |s| {
15587 s.select_ranges([last.range()]);
15588 });
15589 }
15590 _ => self.select_previous(
15591 &SelectPrevious {
15592 replace_newest: true,
15593 },
15594 window,
15595 cx,
15596 )?,
15597 }
15598 Ok(())
15599 }
15600
15601 pub fn toggle_comments(
15602 &mut self,
15603 action: &ToggleComments,
15604 window: &mut Window,
15605 cx: &mut Context<Self>,
15606 ) {
15607 if self.read_only(cx) {
15608 return;
15609 }
15610 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15611 let text_layout_details = &self.text_layout_details(window);
15612 self.transact(window, cx, |this, window, cx| {
15613 let mut selections = this
15614 .selections
15615 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15616 let mut edits = Vec::new();
15617 let mut selection_edit_ranges = Vec::new();
15618 let mut last_toggled_row = None;
15619 let snapshot = this.buffer.read(cx).read(cx);
15620 let empty_str: Arc<str> = Arc::default();
15621 let mut suffixes_inserted = Vec::new();
15622 let ignore_indent = action.ignore_indent;
15623
15624 fn comment_prefix_range(
15625 snapshot: &MultiBufferSnapshot,
15626 row: MultiBufferRow,
15627 comment_prefix: &str,
15628 comment_prefix_whitespace: &str,
15629 ignore_indent: bool,
15630 ) -> Range<Point> {
15631 let indent_size = if ignore_indent {
15632 0
15633 } else {
15634 snapshot.indent_size_for_line(row).len
15635 };
15636
15637 let start = Point::new(row.0, indent_size);
15638
15639 let mut line_bytes = snapshot
15640 .bytes_in_range(start..snapshot.max_point())
15641 .flatten()
15642 .copied();
15643
15644 // If this line currently begins with the line comment prefix, then record
15645 // the range containing the prefix.
15646 if line_bytes
15647 .by_ref()
15648 .take(comment_prefix.len())
15649 .eq(comment_prefix.bytes())
15650 {
15651 // Include any whitespace that matches the comment prefix.
15652 let matching_whitespace_len = line_bytes
15653 .zip(comment_prefix_whitespace.bytes())
15654 .take_while(|(a, b)| a == b)
15655 .count() as u32;
15656 let end = Point::new(
15657 start.row,
15658 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15659 );
15660 start..end
15661 } else {
15662 start..start
15663 }
15664 }
15665
15666 fn comment_suffix_range(
15667 snapshot: &MultiBufferSnapshot,
15668 row: MultiBufferRow,
15669 comment_suffix: &str,
15670 comment_suffix_has_leading_space: bool,
15671 ) -> Range<Point> {
15672 let end = Point::new(row.0, snapshot.line_len(row));
15673 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15674
15675 let mut line_end_bytes = snapshot
15676 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15677 .flatten()
15678 .copied();
15679
15680 let leading_space_len = if suffix_start_column > 0
15681 && line_end_bytes.next() == Some(b' ')
15682 && comment_suffix_has_leading_space
15683 {
15684 1
15685 } else {
15686 0
15687 };
15688
15689 // If this line currently begins with the line comment prefix, then record
15690 // the range containing the prefix.
15691 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15692 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15693 start..end
15694 } else {
15695 end..end
15696 }
15697 }
15698
15699 // TODO: Handle selections that cross excerpts
15700 for selection in &mut selections {
15701 let start_column = snapshot
15702 .indent_size_for_line(MultiBufferRow(selection.start.row))
15703 .len;
15704 let language = if let Some(language) =
15705 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15706 {
15707 language
15708 } else {
15709 continue;
15710 };
15711
15712 selection_edit_ranges.clear();
15713
15714 // If multiple selections contain a given row, avoid processing that
15715 // row more than once.
15716 let mut start_row = MultiBufferRow(selection.start.row);
15717 if last_toggled_row == Some(start_row) {
15718 start_row = start_row.next_row();
15719 }
15720 let end_row =
15721 if selection.end.row > selection.start.row && selection.end.column == 0 {
15722 MultiBufferRow(selection.end.row - 1)
15723 } else {
15724 MultiBufferRow(selection.end.row)
15725 };
15726 last_toggled_row = Some(end_row);
15727
15728 if start_row > end_row {
15729 continue;
15730 }
15731
15732 // If the language has line comments, toggle those.
15733 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15734
15735 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15736 if ignore_indent {
15737 full_comment_prefixes = full_comment_prefixes
15738 .into_iter()
15739 .map(|s| Arc::from(s.trim_end()))
15740 .collect();
15741 }
15742
15743 if !full_comment_prefixes.is_empty() {
15744 let first_prefix = full_comment_prefixes
15745 .first()
15746 .expect("prefixes is non-empty");
15747 let prefix_trimmed_lengths = full_comment_prefixes
15748 .iter()
15749 .map(|p| p.trim_end_matches(' ').len())
15750 .collect::<SmallVec<[usize; 4]>>();
15751
15752 let mut all_selection_lines_are_comments = true;
15753
15754 for row in start_row.0..=end_row.0 {
15755 let row = MultiBufferRow(row);
15756 if start_row < end_row && snapshot.is_line_blank(row) {
15757 continue;
15758 }
15759
15760 let prefix_range = full_comment_prefixes
15761 .iter()
15762 .zip(prefix_trimmed_lengths.iter().copied())
15763 .map(|(prefix, trimmed_prefix_len)| {
15764 comment_prefix_range(
15765 snapshot.deref(),
15766 row,
15767 &prefix[..trimmed_prefix_len],
15768 &prefix[trimmed_prefix_len..],
15769 ignore_indent,
15770 )
15771 })
15772 .max_by_key(|range| range.end.column - range.start.column)
15773 .expect("prefixes is non-empty");
15774
15775 if prefix_range.is_empty() {
15776 all_selection_lines_are_comments = false;
15777 }
15778
15779 selection_edit_ranges.push(prefix_range);
15780 }
15781
15782 if all_selection_lines_are_comments {
15783 edits.extend(
15784 selection_edit_ranges
15785 .iter()
15786 .cloned()
15787 .map(|range| (range, empty_str.clone())),
15788 );
15789 } else {
15790 let min_column = selection_edit_ranges
15791 .iter()
15792 .map(|range| range.start.column)
15793 .min()
15794 .unwrap_or(0);
15795 edits.extend(selection_edit_ranges.iter().map(|range| {
15796 let position = Point::new(range.start.row, min_column);
15797 (position..position, first_prefix.clone())
15798 }));
15799 }
15800 } else if let Some(BlockCommentConfig {
15801 start: full_comment_prefix,
15802 end: comment_suffix,
15803 ..
15804 }) = language.block_comment()
15805 {
15806 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15807 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15808 let prefix_range = comment_prefix_range(
15809 snapshot.deref(),
15810 start_row,
15811 comment_prefix,
15812 comment_prefix_whitespace,
15813 ignore_indent,
15814 );
15815 let suffix_range = comment_suffix_range(
15816 snapshot.deref(),
15817 end_row,
15818 comment_suffix.trim_start_matches(' '),
15819 comment_suffix.starts_with(' '),
15820 );
15821
15822 if prefix_range.is_empty() || suffix_range.is_empty() {
15823 edits.push((
15824 prefix_range.start..prefix_range.start,
15825 full_comment_prefix.clone(),
15826 ));
15827 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15828 suffixes_inserted.push((end_row, comment_suffix.len()));
15829 } else {
15830 edits.push((prefix_range, empty_str.clone()));
15831 edits.push((suffix_range, empty_str.clone()));
15832 }
15833 } else {
15834 continue;
15835 }
15836 }
15837
15838 drop(snapshot);
15839 this.buffer.update(cx, |buffer, cx| {
15840 buffer.edit(edits, None, cx);
15841 });
15842
15843 // Adjust selections so that they end before any comment suffixes that
15844 // were inserted.
15845 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15846 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15847 let snapshot = this.buffer.read(cx).read(cx);
15848 for selection in &mut selections {
15849 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15850 match row.cmp(&MultiBufferRow(selection.end.row)) {
15851 Ordering::Less => {
15852 suffixes_inserted.next();
15853 continue;
15854 }
15855 Ordering::Greater => break,
15856 Ordering::Equal => {
15857 if selection.end.column == snapshot.line_len(row) {
15858 if selection.is_empty() {
15859 selection.start.column -= suffix_len as u32;
15860 }
15861 selection.end.column -= suffix_len as u32;
15862 }
15863 break;
15864 }
15865 }
15866 }
15867 }
15868
15869 drop(snapshot);
15870 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15871
15872 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15873 let selections_on_single_row = selections.windows(2).all(|selections| {
15874 selections[0].start.row == selections[1].start.row
15875 && selections[0].end.row == selections[1].end.row
15876 && selections[0].start.row == selections[0].end.row
15877 });
15878 let selections_selecting = selections
15879 .iter()
15880 .any(|selection| selection.start != selection.end);
15881 let advance_downwards = action.advance_downwards
15882 && selections_on_single_row
15883 && !selections_selecting
15884 && !matches!(this.mode, EditorMode::SingleLine);
15885
15886 if advance_downwards {
15887 let snapshot = this.buffer.read(cx).snapshot(cx);
15888
15889 this.change_selections(Default::default(), window, cx, |s| {
15890 s.move_cursors_with(|display_snapshot, display_point, _| {
15891 let mut point = display_point.to_point(display_snapshot);
15892 point.row += 1;
15893 point = snapshot.clip_point(point, Bias::Left);
15894 let display_point = point.to_display_point(display_snapshot);
15895 let goal = SelectionGoal::HorizontalPosition(
15896 display_snapshot
15897 .x_for_display_point(display_point, text_layout_details)
15898 .into(),
15899 );
15900 (display_point, goal)
15901 })
15902 });
15903 }
15904 });
15905 }
15906
15907 pub fn select_enclosing_symbol(
15908 &mut self,
15909 _: &SelectEnclosingSymbol,
15910 window: &mut Window,
15911 cx: &mut Context<Self>,
15912 ) {
15913 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15914
15915 let buffer = self.buffer.read(cx).snapshot(cx);
15916 let old_selections = self
15917 .selections
15918 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15919 .into_boxed_slice();
15920
15921 fn update_selection(
15922 selection: &Selection<MultiBufferOffset>,
15923 buffer_snap: &MultiBufferSnapshot,
15924 ) -> Option<Selection<MultiBufferOffset>> {
15925 let cursor = selection.head();
15926 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15927 for symbol in symbols.iter().rev() {
15928 let start = symbol.range.start.to_offset(buffer_snap);
15929 let end = symbol.range.end.to_offset(buffer_snap);
15930 let new_range = start..end;
15931 if start < selection.start || end > selection.end {
15932 return Some(Selection {
15933 id: selection.id,
15934 start: new_range.start,
15935 end: new_range.end,
15936 goal: SelectionGoal::None,
15937 reversed: selection.reversed,
15938 });
15939 }
15940 }
15941 None
15942 }
15943
15944 let mut selected_larger_symbol = false;
15945 let new_selections = old_selections
15946 .iter()
15947 .map(|selection| match update_selection(selection, &buffer) {
15948 Some(new_selection) => {
15949 if new_selection.range() != selection.range() {
15950 selected_larger_symbol = true;
15951 }
15952 new_selection
15953 }
15954 None => selection.clone(),
15955 })
15956 .collect::<Vec<_>>();
15957
15958 if selected_larger_symbol {
15959 self.change_selections(Default::default(), window, cx, |s| {
15960 s.select(new_selections);
15961 });
15962 }
15963 }
15964
15965 pub fn select_larger_syntax_node(
15966 &mut self,
15967 _: &SelectLargerSyntaxNode,
15968 window: &mut Window,
15969 cx: &mut Context<Self>,
15970 ) {
15971 let Some(visible_row_count) = self.visible_row_count() else {
15972 return;
15973 };
15974 let old_selections: Box<[_]> = self
15975 .selections
15976 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15977 .into();
15978 if old_selections.is_empty() {
15979 return;
15980 }
15981
15982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15983
15984 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15985 let buffer = self.buffer.read(cx).snapshot(cx);
15986
15987 let mut selected_larger_node = false;
15988 let mut new_selections = old_selections
15989 .iter()
15990 .map(|selection| {
15991 let old_range = selection.start..selection.end;
15992
15993 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15994 // manually select word at selection
15995 if ["string_content", "inline"].contains(&node.kind()) {
15996 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15997 // ignore if word is already selected
15998 if !word_range.is_empty() && old_range != word_range {
15999 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16000 // only select word if start and end point belongs to same word
16001 if word_range == last_word_range {
16002 selected_larger_node = true;
16003 return Selection {
16004 id: selection.id,
16005 start: word_range.start,
16006 end: word_range.end,
16007 goal: SelectionGoal::None,
16008 reversed: selection.reversed,
16009 };
16010 }
16011 }
16012 }
16013 }
16014
16015 let mut new_range = old_range.clone();
16016 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16017 new_range = range;
16018 if !node.is_named() {
16019 continue;
16020 }
16021 if !display_map.intersects_fold(new_range.start)
16022 && !display_map.intersects_fold(new_range.end)
16023 {
16024 break;
16025 }
16026 }
16027
16028 selected_larger_node |= new_range != old_range;
16029 Selection {
16030 id: selection.id,
16031 start: new_range.start,
16032 end: new_range.end,
16033 goal: SelectionGoal::None,
16034 reversed: selection.reversed,
16035 }
16036 })
16037 .collect::<Vec<_>>();
16038
16039 if !selected_larger_node {
16040 return; // don't put this call in the history
16041 }
16042
16043 // scroll based on transformation done to the last selection created by the user
16044 let (last_old, last_new) = old_selections
16045 .last()
16046 .zip(new_selections.last().cloned())
16047 .expect("old_selections isn't empty");
16048
16049 // revert selection
16050 let is_selection_reversed = {
16051 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16052 new_selections.last_mut().expect("checked above").reversed =
16053 should_newest_selection_be_reversed;
16054 should_newest_selection_be_reversed
16055 };
16056
16057 if selected_larger_node {
16058 self.select_syntax_node_history.disable_clearing = true;
16059 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16060 s.select(new_selections.clone());
16061 });
16062 self.select_syntax_node_history.disable_clearing = false;
16063 }
16064
16065 let start_row = last_new.start.to_display_point(&display_map).row().0;
16066 let end_row = last_new.end.to_display_point(&display_map).row().0;
16067 let selection_height = end_row - start_row + 1;
16068 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16069
16070 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16071 let scroll_behavior = if fits_on_the_screen {
16072 self.request_autoscroll(Autoscroll::fit(), cx);
16073 SelectSyntaxNodeScrollBehavior::FitSelection
16074 } else if is_selection_reversed {
16075 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16076 SelectSyntaxNodeScrollBehavior::CursorTop
16077 } else {
16078 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16079 SelectSyntaxNodeScrollBehavior::CursorBottom
16080 };
16081
16082 self.select_syntax_node_history.push((
16083 old_selections,
16084 scroll_behavior,
16085 is_selection_reversed,
16086 ));
16087 }
16088
16089 pub fn select_smaller_syntax_node(
16090 &mut self,
16091 _: &SelectSmallerSyntaxNode,
16092 window: &mut Window,
16093 cx: &mut Context<Self>,
16094 ) {
16095 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16096
16097 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16098 self.select_syntax_node_history.pop()
16099 {
16100 if let Some(selection) = selections.last_mut() {
16101 selection.reversed = is_selection_reversed;
16102 }
16103
16104 self.select_syntax_node_history.disable_clearing = true;
16105 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16106 s.select(selections.to_vec());
16107 });
16108 self.select_syntax_node_history.disable_clearing = false;
16109
16110 match scroll_behavior {
16111 SelectSyntaxNodeScrollBehavior::CursorTop => {
16112 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16113 }
16114 SelectSyntaxNodeScrollBehavior::FitSelection => {
16115 self.request_autoscroll(Autoscroll::fit(), cx);
16116 }
16117 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16118 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16119 }
16120 }
16121 }
16122 }
16123
16124 pub fn unwrap_syntax_node(
16125 &mut self,
16126 _: &UnwrapSyntaxNode,
16127 window: &mut Window,
16128 cx: &mut Context<Self>,
16129 ) {
16130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16131
16132 let buffer = self.buffer.read(cx).snapshot(cx);
16133 let selections = self
16134 .selections
16135 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16136 .into_iter()
16137 // subtracting the offset requires sorting
16138 .sorted_by_key(|i| i.start);
16139
16140 let full_edits = selections
16141 .into_iter()
16142 .filter_map(|selection| {
16143 let child = if selection.is_empty()
16144 && let Some((_, ancestor_range)) =
16145 buffer.syntax_ancestor(selection.start..selection.end)
16146 {
16147 ancestor_range
16148 } else {
16149 selection.range()
16150 };
16151
16152 let mut parent = child.clone();
16153 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16154 parent = ancestor_range;
16155 if parent.start < child.start || parent.end > child.end {
16156 break;
16157 }
16158 }
16159
16160 if parent == child {
16161 return None;
16162 }
16163 let text = buffer.text_for_range(child).collect::<String>();
16164 Some((selection.id, parent, text))
16165 })
16166 .collect::<Vec<_>>();
16167 if full_edits.is_empty() {
16168 return;
16169 }
16170
16171 self.transact(window, cx, |this, window, cx| {
16172 this.buffer.update(cx, |buffer, cx| {
16173 buffer.edit(
16174 full_edits
16175 .iter()
16176 .map(|(_, p, t)| (p.clone(), t.clone()))
16177 .collect::<Vec<_>>(),
16178 None,
16179 cx,
16180 );
16181 });
16182 this.change_selections(Default::default(), window, cx, |s| {
16183 let mut offset = 0;
16184 let mut selections = vec![];
16185 for (id, parent, text) in full_edits {
16186 let start = parent.start - offset;
16187 offset += (parent.end - parent.start) - text.len();
16188 selections.push(Selection {
16189 id,
16190 start,
16191 end: start + text.len(),
16192 reversed: false,
16193 goal: Default::default(),
16194 });
16195 }
16196 s.select(selections);
16197 });
16198 });
16199 }
16200
16201 pub fn select_next_syntax_node(
16202 &mut self,
16203 _: &SelectNextSyntaxNode,
16204 window: &mut Window,
16205 cx: &mut Context<Self>,
16206 ) {
16207 let old_selections: Box<[_]> = self
16208 .selections
16209 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16210 .into();
16211 if old_selections.is_empty() {
16212 return;
16213 }
16214
16215 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16216
16217 let buffer = self.buffer.read(cx).snapshot(cx);
16218 let mut selected_sibling = false;
16219
16220 let new_selections = old_selections
16221 .iter()
16222 .map(|selection| {
16223 let old_range = selection.start..selection.end;
16224
16225 let old_range =
16226 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16227 let excerpt = buffer.excerpt_containing(old_range.clone());
16228
16229 if let Some(mut excerpt) = excerpt
16230 && let Some(node) = excerpt
16231 .buffer()
16232 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16233 {
16234 let new_range = excerpt.map_range_from_buffer(
16235 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16236 );
16237 selected_sibling = true;
16238 Selection {
16239 id: selection.id,
16240 start: new_range.start,
16241 end: new_range.end,
16242 goal: SelectionGoal::None,
16243 reversed: selection.reversed,
16244 }
16245 } else {
16246 selection.clone()
16247 }
16248 })
16249 .collect::<Vec<_>>();
16250
16251 if selected_sibling {
16252 self.change_selections(
16253 SelectionEffects::scroll(Autoscroll::fit()),
16254 window,
16255 cx,
16256 |s| {
16257 s.select(new_selections);
16258 },
16259 );
16260 }
16261 }
16262
16263 pub fn select_prev_syntax_node(
16264 &mut self,
16265 _: &SelectPreviousSyntaxNode,
16266 window: &mut Window,
16267 cx: &mut Context<Self>,
16268 ) {
16269 let old_selections: Box<[_]> = self
16270 .selections
16271 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16272 .into();
16273 if old_selections.is_empty() {
16274 return;
16275 }
16276
16277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16278
16279 let buffer = self.buffer.read(cx).snapshot(cx);
16280 let mut selected_sibling = false;
16281
16282 let new_selections = old_selections
16283 .iter()
16284 .map(|selection| {
16285 let old_range = selection.start..selection.end;
16286 let old_range =
16287 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16288 let excerpt = buffer.excerpt_containing(old_range.clone());
16289
16290 if let Some(mut excerpt) = excerpt
16291 && let Some(node) = excerpt
16292 .buffer()
16293 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16294 {
16295 let new_range = excerpt.map_range_from_buffer(
16296 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16297 );
16298 selected_sibling = true;
16299 Selection {
16300 id: selection.id,
16301 start: new_range.start,
16302 end: new_range.end,
16303 goal: SelectionGoal::None,
16304 reversed: selection.reversed,
16305 }
16306 } else {
16307 selection.clone()
16308 }
16309 })
16310 .collect::<Vec<_>>();
16311
16312 if selected_sibling {
16313 self.change_selections(
16314 SelectionEffects::scroll(Autoscroll::fit()),
16315 window,
16316 cx,
16317 |s| {
16318 s.select(new_selections);
16319 },
16320 );
16321 }
16322 }
16323
16324 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16325 if !EditorSettings::get_global(cx).gutter.runnables {
16326 self.clear_tasks();
16327 return Task::ready(());
16328 }
16329 let project = self.project().map(Entity::downgrade);
16330 let task_sources = self.lsp_task_sources(cx);
16331 let multi_buffer = self.buffer.downgrade();
16332 cx.spawn_in(window, async move |editor, cx| {
16333 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16334 let Some(project) = project.and_then(|p| p.upgrade()) else {
16335 return;
16336 };
16337 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16338 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16339 }) else {
16340 return;
16341 };
16342
16343 let hide_runnables = project.update(cx, |project, _| project.is_via_collab());
16344 if hide_runnables {
16345 return;
16346 }
16347 let new_rows =
16348 cx.background_spawn({
16349 let snapshot = display_snapshot.clone();
16350 async move {
16351 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16352 }
16353 })
16354 .await;
16355 let Ok(lsp_tasks) =
16356 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16357 else {
16358 return;
16359 };
16360 let lsp_tasks = lsp_tasks.await;
16361
16362 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16363 lsp_tasks
16364 .into_iter()
16365 .flat_map(|(kind, tasks)| {
16366 tasks.into_iter().filter_map(move |(location, task)| {
16367 Some((kind.clone(), location?, task))
16368 })
16369 })
16370 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16371 let buffer = location.target.buffer;
16372 let buffer_snapshot = buffer.read(cx).snapshot();
16373 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16374 |(excerpt_id, snapshot, _)| {
16375 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16376 display_snapshot
16377 .buffer_snapshot()
16378 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16379 } else {
16380 None
16381 }
16382 },
16383 );
16384 if let Some(offset) = offset {
16385 let task_buffer_range =
16386 location.target.range.to_point(&buffer_snapshot);
16387 let context_buffer_range =
16388 task_buffer_range.to_offset(&buffer_snapshot);
16389 let context_range = BufferOffset(context_buffer_range.start)
16390 ..BufferOffset(context_buffer_range.end);
16391
16392 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16393 .or_insert_with(|| RunnableTasks {
16394 templates: Vec::new(),
16395 offset,
16396 column: task_buffer_range.start.column,
16397 extra_variables: HashMap::default(),
16398 context_range,
16399 })
16400 .templates
16401 .push((kind, task.original_task().clone()));
16402 }
16403
16404 acc
16405 })
16406 }) else {
16407 return;
16408 };
16409
16410 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16411 buffer.language_settings(cx).tasks.prefer_lsp
16412 }) else {
16413 return;
16414 };
16415
16416 let rows = Self::runnable_rows(
16417 project,
16418 display_snapshot,
16419 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16420 new_rows,
16421 cx.clone(),
16422 )
16423 .await;
16424 editor
16425 .update(cx, |editor, _| {
16426 editor.clear_tasks();
16427 for (key, mut value) in rows {
16428 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16429 value.templates.extend(lsp_tasks.templates);
16430 }
16431
16432 editor.insert_tasks(key, value);
16433 }
16434 for (key, value) in lsp_tasks_by_rows {
16435 editor.insert_tasks(key, value);
16436 }
16437 })
16438 .ok();
16439 })
16440 }
16441 fn fetch_runnable_ranges(
16442 snapshot: &DisplaySnapshot,
16443 range: Range<Anchor>,
16444 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16445 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16446 }
16447
16448 fn runnable_rows(
16449 project: Entity<Project>,
16450 snapshot: DisplaySnapshot,
16451 prefer_lsp: bool,
16452 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16453 cx: AsyncWindowContext,
16454 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16455 cx.spawn(async move |cx| {
16456 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16457 for (run_range, mut runnable) in runnable_ranges {
16458 let Some(tasks) = cx
16459 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16460 .ok()
16461 else {
16462 continue;
16463 };
16464 let mut tasks = tasks.await;
16465
16466 if prefer_lsp {
16467 tasks.retain(|(task_kind, _)| {
16468 !matches!(task_kind, TaskSourceKind::Language { .. })
16469 });
16470 }
16471 if tasks.is_empty() {
16472 continue;
16473 }
16474
16475 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16476 let Some(row) = snapshot
16477 .buffer_snapshot()
16478 .buffer_line_for_row(MultiBufferRow(point.row))
16479 .map(|(_, range)| range.start.row)
16480 else {
16481 continue;
16482 };
16483
16484 let context_range =
16485 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16486 runnable_rows.push((
16487 (runnable.buffer_id, row),
16488 RunnableTasks {
16489 templates: tasks,
16490 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16491 context_range,
16492 column: point.column,
16493 extra_variables: runnable.extra_captures,
16494 },
16495 ));
16496 }
16497 runnable_rows
16498 })
16499 }
16500
16501 fn templates_with_tags(
16502 project: &Entity<Project>,
16503 runnable: &mut Runnable,
16504 cx: &mut App,
16505 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16506 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16507 let (worktree_id, file) = project
16508 .buffer_for_id(runnable.buffer, cx)
16509 .and_then(|buffer| buffer.read(cx).file())
16510 .map(|file| (file.worktree_id(cx), file.clone()))
16511 .unzip();
16512
16513 (
16514 project.task_store().read(cx).task_inventory().cloned(),
16515 worktree_id,
16516 file,
16517 )
16518 });
16519
16520 let tags = mem::take(&mut runnable.tags);
16521 let language = runnable.language.clone();
16522 cx.spawn(async move |cx| {
16523 let mut templates_with_tags = Vec::new();
16524 if let Some(inventory) = inventory {
16525 for RunnableTag(tag) in tags {
16526 let new_tasks = inventory.update(cx, |inventory, cx| {
16527 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16528 });
16529 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16530 move |(_, template)| {
16531 template.tags.iter().any(|source_tag| source_tag == &tag)
16532 },
16533 ));
16534 }
16535 }
16536 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16537
16538 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16539 // Strongest source wins; if we have worktree tag binding, prefer that to
16540 // global and language bindings;
16541 // if we have a global binding, prefer that to language binding.
16542 let first_mismatch = templates_with_tags
16543 .iter()
16544 .position(|(tag_source, _)| tag_source != leading_tag_source);
16545 if let Some(index) = first_mismatch {
16546 templates_with_tags.truncate(index);
16547 }
16548 }
16549
16550 templates_with_tags
16551 })
16552 }
16553
16554 pub fn move_to_enclosing_bracket(
16555 &mut self,
16556 _: &MoveToEnclosingBracket,
16557 window: &mut Window,
16558 cx: &mut Context<Self>,
16559 ) {
16560 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16561 self.change_selections(Default::default(), window, cx, |s| {
16562 s.move_offsets_with(|snapshot, selection| {
16563 let Some(enclosing_bracket_ranges) =
16564 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16565 else {
16566 return;
16567 };
16568
16569 let mut best_length = usize::MAX;
16570 let mut best_inside = false;
16571 let mut best_in_bracket_range = false;
16572 let mut best_destination = None;
16573 for (open, close) in enclosing_bracket_ranges {
16574 let close = close.to_inclusive();
16575 let length = *close.end() - open.start;
16576 let inside = selection.start >= open.end && selection.end <= *close.start();
16577 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16578 || close.contains(&selection.head());
16579
16580 // If best is next to a bracket and current isn't, skip
16581 if !in_bracket_range && best_in_bracket_range {
16582 continue;
16583 }
16584
16585 // Prefer smaller lengths unless best is inside and current isn't
16586 if length > best_length && (best_inside || !inside) {
16587 continue;
16588 }
16589
16590 best_length = length;
16591 best_inside = inside;
16592 best_in_bracket_range = in_bracket_range;
16593 best_destination = Some(
16594 if close.contains(&selection.start) && close.contains(&selection.end) {
16595 if inside { open.end } else { open.start }
16596 } else if inside {
16597 *close.start()
16598 } else {
16599 *close.end()
16600 },
16601 );
16602 }
16603
16604 if let Some(destination) = best_destination {
16605 selection.collapse_to(destination, SelectionGoal::None);
16606 }
16607 })
16608 });
16609 }
16610
16611 pub fn undo_selection(
16612 &mut self,
16613 _: &UndoSelection,
16614 window: &mut Window,
16615 cx: &mut Context<Self>,
16616 ) {
16617 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16618 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16619 self.selection_history.mode = SelectionHistoryMode::Undoing;
16620 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16621 this.end_selection(window, cx);
16622 this.change_selections(
16623 SelectionEffects::scroll(Autoscroll::newest()),
16624 window,
16625 cx,
16626 |s| s.select_anchors(entry.selections.to_vec()),
16627 );
16628 });
16629 self.selection_history.mode = SelectionHistoryMode::Normal;
16630
16631 self.select_next_state = entry.select_next_state;
16632 self.select_prev_state = entry.select_prev_state;
16633 self.add_selections_state = entry.add_selections_state;
16634 }
16635 }
16636
16637 pub fn redo_selection(
16638 &mut self,
16639 _: &RedoSelection,
16640 window: &mut Window,
16641 cx: &mut Context<Self>,
16642 ) {
16643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16644 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16645 self.selection_history.mode = SelectionHistoryMode::Redoing;
16646 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16647 this.end_selection(window, cx);
16648 this.change_selections(
16649 SelectionEffects::scroll(Autoscroll::newest()),
16650 window,
16651 cx,
16652 |s| s.select_anchors(entry.selections.to_vec()),
16653 );
16654 });
16655 self.selection_history.mode = SelectionHistoryMode::Normal;
16656
16657 self.select_next_state = entry.select_next_state;
16658 self.select_prev_state = entry.select_prev_state;
16659 self.add_selections_state = entry.add_selections_state;
16660 }
16661 }
16662
16663 pub fn expand_excerpts(
16664 &mut self,
16665 action: &ExpandExcerpts,
16666 _: &mut Window,
16667 cx: &mut Context<Self>,
16668 ) {
16669 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16670 }
16671
16672 pub fn expand_excerpts_down(
16673 &mut self,
16674 action: &ExpandExcerptsDown,
16675 _: &mut Window,
16676 cx: &mut Context<Self>,
16677 ) {
16678 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16679 }
16680
16681 pub fn expand_excerpts_up(
16682 &mut self,
16683 action: &ExpandExcerptsUp,
16684 _: &mut Window,
16685 cx: &mut Context<Self>,
16686 ) {
16687 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16688 }
16689
16690 pub fn expand_excerpts_for_direction(
16691 &mut self,
16692 lines: u32,
16693 direction: ExpandExcerptDirection,
16694 cx: &mut Context<Self>,
16695 ) {
16696 let selections = self.selections.disjoint_anchors_arc();
16697
16698 let lines = if lines == 0 {
16699 EditorSettings::get_global(cx).expand_excerpt_lines
16700 } else {
16701 lines
16702 };
16703
16704 let snapshot = self.buffer.read(cx).snapshot(cx);
16705 let mut excerpt_ids = selections
16706 .iter()
16707 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16708 .collect::<Vec<_>>();
16709 excerpt_ids.sort();
16710 excerpt_ids.dedup();
16711
16712 if self.delegate_expand_excerpts {
16713 cx.emit(EditorEvent::ExpandExcerptsRequested {
16714 excerpt_ids,
16715 lines,
16716 direction,
16717 });
16718 return;
16719 }
16720
16721 self.buffer.update(cx, |buffer, cx| {
16722 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16723 })
16724 }
16725
16726 pub fn expand_excerpt(
16727 &mut self,
16728 excerpt: ExcerptId,
16729 direction: ExpandExcerptDirection,
16730 window: &mut Window,
16731 cx: &mut Context<Self>,
16732 ) {
16733 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16734
16735 if self.delegate_expand_excerpts {
16736 cx.emit(EditorEvent::ExpandExcerptsRequested {
16737 excerpt_ids: vec![excerpt],
16738 lines: lines_to_expand,
16739 direction,
16740 });
16741 return;
16742 }
16743
16744 let current_scroll_position = self.scroll_position(cx);
16745 let mut scroll = None;
16746
16747 if direction == ExpandExcerptDirection::Down {
16748 let multi_buffer = self.buffer.read(cx);
16749 let snapshot = multi_buffer.snapshot(cx);
16750 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16751 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16752 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16753 {
16754 let buffer_snapshot = buffer.read(cx).snapshot();
16755 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16756 let last_row = buffer_snapshot.max_point().row;
16757 let lines_below = last_row.saturating_sub(excerpt_end_row);
16758 if lines_below >= lines_to_expand {
16759 scroll = Some(
16760 current_scroll_position
16761 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16762 );
16763 }
16764 }
16765 }
16766 if direction == ExpandExcerptDirection::Up
16767 && self
16768 .buffer
16769 .read(cx)
16770 .snapshot(cx)
16771 .excerpt_before(excerpt)
16772 .is_none()
16773 {
16774 scroll = Some(current_scroll_position);
16775 }
16776
16777 self.buffer.update(cx, |buffer, cx| {
16778 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16779 });
16780
16781 if let Some(new_scroll_position) = scroll {
16782 self.set_scroll_position(new_scroll_position, window, cx);
16783 }
16784 }
16785
16786 pub fn go_to_singleton_buffer_point(
16787 &mut self,
16788 point: Point,
16789 window: &mut Window,
16790 cx: &mut Context<Self>,
16791 ) {
16792 self.go_to_singleton_buffer_range(point..point, window, cx);
16793 }
16794
16795 pub fn go_to_singleton_buffer_range(
16796 &mut self,
16797 range: Range<Point>,
16798 window: &mut Window,
16799 cx: &mut Context<Self>,
16800 ) {
16801 let multibuffer = self.buffer().read(cx);
16802 let Some(buffer) = multibuffer.as_singleton() else {
16803 return;
16804 };
16805 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16806 return;
16807 };
16808 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16809 return;
16810 };
16811 self.change_selections(
16812 SelectionEffects::default().nav_history(true),
16813 window,
16814 cx,
16815 |s| s.select_anchor_ranges([start..end]),
16816 );
16817 }
16818
16819 pub fn go_to_diagnostic(
16820 &mut self,
16821 action: &GoToDiagnostic,
16822 window: &mut Window,
16823 cx: &mut Context<Self>,
16824 ) {
16825 if !self.diagnostics_enabled() {
16826 return;
16827 }
16828 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16829 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16830 }
16831
16832 pub fn go_to_prev_diagnostic(
16833 &mut self,
16834 action: &GoToPreviousDiagnostic,
16835 window: &mut Window,
16836 cx: &mut Context<Self>,
16837 ) {
16838 if !self.diagnostics_enabled() {
16839 return;
16840 }
16841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16842 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16843 }
16844
16845 pub fn go_to_diagnostic_impl(
16846 &mut self,
16847 direction: Direction,
16848 severity: GoToDiagnosticSeverityFilter,
16849 window: &mut Window,
16850 cx: &mut Context<Self>,
16851 ) {
16852 let buffer = self.buffer.read(cx).snapshot(cx);
16853 let selection = self
16854 .selections
16855 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16856
16857 let mut active_group_id = None;
16858 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16859 && active_group.active_range.start.to_offset(&buffer) == selection.start
16860 {
16861 active_group_id = Some(active_group.group_id);
16862 }
16863
16864 fn filtered<'a>(
16865 severity: GoToDiagnosticSeverityFilter,
16866 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16867 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16868 diagnostics
16869 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16870 .filter(|entry| entry.range.start != entry.range.end)
16871 .filter(|entry| !entry.diagnostic.is_unnecessary)
16872 }
16873
16874 let before = filtered(
16875 severity,
16876 buffer
16877 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16878 .filter(|entry| entry.range.start <= selection.start),
16879 );
16880 let after = filtered(
16881 severity,
16882 buffer
16883 .diagnostics_in_range(selection.start..buffer.len())
16884 .filter(|entry| entry.range.start >= selection.start),
16885 );
16886
16887 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16888 if direction == Direction::Prev {
16889 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16890 {
16891 for diagnostic in prev_diagnostics.into_iter().rev() {
16892 if diagnostic.range.start != selection.start
16893 || active_group_id
16894 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16895 {
16896 found = Some(diagnostic);
16897 break 'outer;
16898 }
16899 }
16900 }
16901 } else {
16902 for diagnostic in after.chain(before) {
16903 if diagnostic.range.start != selection.start
16904 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16905 {
16906 found = Some(diagnostic);
16907 break;
16908 }
16909 }
16910 }
16911 let Some(next_diagnostic) = found else {
16912 return;
16913 };
16914
16915 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16916 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16917 return;
16918 };
16919 let snapshot = self.snapshot(window, cx);
16920 if snapshot.intersects_fold(next_diagnostic.range.start) {
16921 self.unfold_ranges(
16922 std::slice::from_ref(&next_diagnostic.range),
16923 true,
16924 false,
16925 cx,
16926 );
16927 }
16928 self.change_selections(Default::default(), window, cx, |s| {
16929 s.select_ranges(vec![
16930 next_diagnostic.range.start..next_diagnostic.range.start,
16931 ])
16932 });
16933 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16934 self.refresh_edit_prediction(false, true, window, cx);
16935 }
16936
16937 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16938 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16939 let snapshot = self.snapshot(window, cx);
16940 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16941 self.go_to_hunk_before_or_after_position(
16942 &snapshot,
16943 selection.head(),
16944 Direction::Next,
16945 window,
16946 cx,
16947 );
16948 }
16949
16950 pub fn go_to_hunk_before_or_after_position(
16951 &mut self,
16952 snapshot: &EditorSnapshot,
16953 position: Point,
16954 direction: Direction,
16955 window: &mut Window,
16956 cx: &mut Context<Editor>,
16957 ) {
16958 let row = if direction == Direction::Next {
16959 self.hunk_after_position(snapshot, position)
16960 .map(|hunk| hunk.row_range.start)
16961 } else {
16962 self.hunk_before_position(snapshot, position)
16963 };
16964
16965 if let Some(row) = row {
16966 let destination = Point::new(row.0, 0);
16967 let autoscroll = Autoscroll::center();
16968
16969 self.unfold_ranges(&[destination..destination], false, false, cx);
16970 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16971 s.select_ranges([destination..destination]);
16972 });
16973 }
16974 }
16975
16976 fn hunk_after_position(
16977 &mut self,
16978 snapshot: &EditorSnapshot,
16979 position: Point,
16980 ) -> Option<MultiBufferDiffHunk> {
16981 snapshot
16982 .buffer_snapshot()
16983 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16984 .find(|hunk| hunk.row_range.start.0 > position.row)
16985 .or_else(|| {
16986 snapshot
16987 .buffer_snapshot()
16988 .diff_hunks_in_range(Point::zero()..position)
16989 .find(|hunk| hunk.row_range.end.0 < position.row)
16990 })
16991 }
16992
16993 fn go_to_prev_hunk(
16994 &mut self,
16995 _: &GoToPreviousHunk,
16996 window: &mut Window,
16997 cx: &mut Context<Self>,
16998 ) {
16999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17000 let snapshot = self.snapshot(window, cx);
17001 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17002 self.go_to_hunk_before_or_after_position(
17003 &snapshot,
17004 selection.head(),
17005 Direction::Prev,
17006 window,
17007 cx,
17008 );
17009 }
17010
17011 fn hunk_before_position(
17012 &mut self,
17013 snapshot: &EditorSnapshot,
17014 position: Point,
17015 ) -> Option<MultiBufferRow> {
17016 snapshot
17017 .buffer_snapshot()
17018 .diff_hunk_before(position)
17019 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17020 }
17021
17022 fn go_to_next_change(
17023 &mut self,
17024 _: &GoToNextChange,
17025 window: &mut Window,
17026 cx: &mut Context<Self>,
17027 ) {
17028 if let Some(selections) = self
17029 .change_list
17030 .next_change(1, Direction::Next)
17031 .map(|s| s.to_vec())
17032 {
17033 self.change_selections(Default::default(), window, cx, |s| {
17034 let map = s.display_snapshot();
17035 s.select_display_ranges(selections.iter().map(|a| {
17036 let point = a.to_display_point(&map);
17037 point..point
17038 }))
17039 })
17040 }
17041 }
17042
17043 fn go_to_previous_change(
17044 &mut self,
17045 _: &GoToPreviousChange,
17046 window: &mut Window,
17047 cx: &mut Context<Self>,
17048 ) {
17049 if let Some(selections) = self
17050 .change_list
17051 .next_change(1, Direction::Prev)
17052 .map(|s| s.to_vec())
17053 {
17054 self.change_selections(Default::default(), window, cx, |s| {
17055 let map = s.display_snapshot();
17056 s.select_display_ranges(selections.iter().map(|a| {
17057 let point = a.to_display_point(&map);
17058 point..point
17059 }))
17060 })
17061 }
17062 }
17063
17064 pub fn go_to_next_document_highlight(
17065 &mut self,
17066 _: &GoToNextDocumentHighlight,
17067 window: &mut Window,
17068 cx: &mut Context<Self>,
17069 ) {
17070 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17071 }
17072
17073 pub fn go_to_prev_document_highlight(
17074 &mut self,
17075 _: &GoToPreviousDocumentHighlight,
17076 window: &mut Window,
17077 cx: &mut Context<Self>,
17078 ) {
17079 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17080 }
17081
17082 pub fn go_to_document_highlight_before_or_after_position(
17083 &mut self,
17084 direction: Direction,
17085 window: &mut Window,
17086 cx: &mut Context<Editor>,
17087 ) {
17088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17089 let snapshot = self.snapshot(window, cx);
17090 let buffer = &snapshot.buffer_snapshot();
17091 let position = self
17092 .selections
17093 .newest::<Point>(&snapshot.display_snapshot)
17094 .head();
17095 let anchor_position = buffer.anchor_after(position);
17096
17097 // Get all document highlights (both read and write)
17098 let mut all_highlights = Vec::new();
17099
17100 if let Some((_, read_highlights)) = self
17101 .background_highlights
17102 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17103 {
17104 all_highlights.extend(read_highlights.iter());
17105 }
17106
17107 if let Some((_, write_highlights)) = self
17108 .background_highlights
17109 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17110 {
17111 all_highlights.extend(write_highlights.iter());
17112 }
17113
17114 if all_highlights.is_empty() {
17115 return;
17116 }
17117
17118 // Sort highlights by position
17119 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17120
17121 let target_highlight = match direction {
17122 Direction::Next => {
17123 // Find the first highlight after the current position
17124 all_highlights
17125 .iter()
17126 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17127 }
17128 Direction::Prev => {
17129 // Find the last highlight before the current position
17130 all_highlights
17131 .iter()
17132 .rev()
17133 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17134 }
17135 };
17136
17137 if let Some(highlight) = target_highlight {
17138 let destination = highlight.start.to_point(buffer);
17139 let autoscroll = Autoscroll::center();
17140
17141 self.unfold_ranges(&[destination..destination], false, false, cx);
17142 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17143 s.select_ranges([destination..destination]);
17144 });
17145 }
17146 }
17147
17148 fn go_to_line<T: 'static>(
17149 &mut self,
17150 position: Anchor,
17151 highlight_color: Option<Hsla>,
17152 window: &mut Window,
17153 cx: &mut Context<Self>,
17154 ) {
17155 let snapshot = self.snapshot(window, cx).display_snapshot;
17156 let position = position.to_point(&snapshot.buffer_snapshot());
17157 let start = snapshot
17158 .buffer_snapshot()
17159 .clip_point(Point::new(position.row, 0), Bias::Left);
17160 let end = start + Point::new(1, 0);
17161 let start = snapshot.buffer_snapshot().anchor_before(start);
17162 let end = snapshot.buffer_snapshot().anchor_before(end);
17163
17164 self.highlight_rows::<T>(
17165 start..end,
17166 highlight_color
17167 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17168 Default::default(),
17169 cx,
17170 );
17171
17172 if self.buffer.read(cx).is_singleton() {
17173 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17174 }
17175 }
17176
17177 pub fn go_to_definition(
17178 &mut self,
17179 _: &GoToDefinition,
17180 window: &mut Window,
17181 cx: &mut Context<Self>,
17182 ) -> Task<Result<Navigated>> {
17183 let definition =
17184 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17185 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17186 cx.spawn_in(window, async move |editor, cx| {
17187 if definition.await? == Navigated::Yes {
17188 return Ok(Navigated::Yes);
17189 }
17190 match fallback_strategy {
17191 GoToDefinitionFallback::None => Ok(Navigated::No),
17192 GoToDefinitionFallback::FindAllReferences => {
17193 match editor.update_in(cx, |editor, window, cx| {
17194 editor.find_all_references(&FindAllReferences::default(), window, cx)
17195 })? {
17196 Some(references) => references.await,
17197 None => Ok(Navigated::No),
17198 }
17199 }
17200 }
17201 })
17202 }
17203
17204 pub fn go_to_declaration(
17205 &mut self,
17206 _: &GoToDeclaration,
17207 window: &mut Window,
17208 cx: &mut Context<Self>,
17209 ) -> Task<Result<Navigated>> {
17210 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17211 }
17212
17213 pub fn go_to_declaration_split(
17214 &mut self,
17215 _: &GoToDeclaration,
17216 window: &mut Window,
17217 cx: &mut Context<Self>,
17218 ) -> Task<Result<Navigated>> {
17219 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17220 }
17221
17222 pub fn go_to_implementation(
17223 &mut self,
17224 _: &GoToImplementation,
17225 window: &mut Window,
17226 cx: &mut Context<Self>,
17227 ) -> Task<Result<Navigated>> {
17228 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17229 }
17230
17231 pub fn go_to_implementation_split(
17232 &mut self,
17233 _: &GoToImplementationSplit,
17234 window: &mut Window,
17235 cx: &mut Context<Self>,
17236 ) -> Task<Result<Navigated>> {
17237 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17238 }
17239
17240 pub fn go_to_type_definition(
17241 &mut self,
17242 _: &GoToTypeDefinition,
17243 window: &mut Window,
17244 cx: &mut Context<Self>,
17245 ) -> Task<Result<Navigated>> {
17246 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17247 }
17248
17249 pub fn go_to_definition_split(
17250 &mut self,
17251 _: &GoToDefinitionSplit,
17252 window: &mut Window,
17253 cx: &mut Context<Self>,
17254 ) -> Task<Result<Navigated>> {
17255 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17256 }
17257
17258 pub fn go_to_type_definition_split(
17259 &mut self,
17260 _: &GoToTypeDefinitionSplit,
17261 window: &mut Window,
17262 cx: &mut Context<Self>,
17263 ) -> Task<Result<Navigated>> {
17264 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17265 }
17266
17267 fn go_to_definition_of_kind(
17268 &mut self,
17269 kind: GotoDefinitionKind,
17270 split: bool,
17271 window: &mut Window,
17272 cx: &mut Context<Self>,
17273 ) -> Task<Result<Navigated>> {
17274 let Some(provider) = self.semantics_provider.clone() else {
17275 return Task::ready(Ok(Navigated::No));
17276 };
17277 let head = self
17278 .selections
17279 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17280 .head();
17281 let buffer = self.buffer.read(cx);
17282 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17283 return Task::ready(Ok(Navigated::No));
17284 };
17285 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17286 return Task::ready(Ok(Navigated::No));
17287 };
17288
17289 cx.spawn_in(window, async move |editor, cx| {
17290 let Some(definitions) = definitions.await? else {
17291 return Ok(Navigated::No);
17292 };
17293 let navigated = editor
17294 .update_in(cx, |editor, window, cx| {
17295 editor.navigate_to_hover_links(
17296 Some(kind),
17297 definitions
17298 .into_iter()
17299 .filter(|location| {
17300 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17301 })
17302 .map(HoverLink::Text)
17303 .collect::<Vec<_>>(),
17304 split,
17305 window,
17306 cx,
17307 )
17308 })?
17309 .await?;
17310 anyhow::Ok(navigated)
17311 })
17312 }
17313
17314 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17315 let selection = self.selections.newest_anchor();
17316 let head = selection.head();
17317 let tail = selection.tail();
17318
17319 let Some((buffer, start_position)) =
17320 self.buffer.read(cx).text_anchor_for_position(head, cx)
17321 else {
17322 return;
17323 };
17324
17325 let end_position = if head != tail {
17326 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17327 return;
17328 };
17329 Some(pos)
17330 } else {
17331 None
17332 };
17333
17334 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17335 let url = if let Some(end_pos) = end_position {
17336 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17337 } else {
17338 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17339 };
17340
17341 if let Some(url) = url {
17342 cx.update(|window, cx| {
17343 if parse_zed_link(&url, cx).is_some() {
17344 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17345 } else {
17346 cx.open_url(&url);
17347 }
17348 })?;
17349 }
17350
17351 anyhow::Ok(())
17352 });
17353
17354 url_finder.detach();
17355 }
17356
17357 pub fn open_selected_filename(
17358 &mut self,
17359 _: &OpenSelectedFilename,
17360 window: &mut Window,
17361 cx: &mut Context<Self>,
17362 ) {
17363 let Some(workspace) = self.workspace() else {
17364 return;
17365 };
17366
17367 let position = self.selections.newest_anchor().head();
17368
17369 let Some((buffer, buffer_position)) =
17370 self.buffer.read(cx).text_anchor_for_position(position, cx)
17371 else {
17372 return;
17373 };
17374
17375 let project = self.project.clone();
17376
17377 cx.spawn_in(window, async move |_, cx| {
17378 let result = find_file(&buffer, project, buffer_position, cx).await;
17379
17380 if let Some((_, path)) = result {
17381 workspace
17382 .update_in(cx, |workspace, window, cx| {
17383 workspace.open_resolved_path(path, window, cx)
17384 })?
17385 .await?;
17386 }
17387 anyhow::Ok(())
17388 })
17389 .detach();
17390 }
17391
17392 pub(crate) fn navigate_to_hover_links(
17393 &mut self,
17394 kind: Option<GotoDefinitionKind>,
17395 definitions: Vec<HoverLink>,
17396 split: bool,
17397 window: &mut Window,
17398 cx: &mut Context<Editor>,
17399 ) -> Task<Result<Navigated>> {
17400 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17401 let mut first_url_or_file = None;
17402 let definitions: Vec<_> = definitions
17403 .into_iter()
17404 .filter_map(|def| match def {
17405 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17406 HoverLink::InlayHint(lsp_location, server_id) => {
17407 let computation =
17408 self.compute_target_location(lsp_location, server_id, window, cx);
17409 Some(cx.background_spawn(computation))
17410 }
17411 HoverLink::Url(url) => {
17412 first_url_or_file = Some(Either::Left(url));
17413 None
17414 }
17415 HoverLink::File(path) => {
17416 first_url_or_file = Some(Either::Right(path));
17417 None
17418 }
17419 })
17420 .collect();
17421
17422 let workspace = self.workspace();
17423
17424 cx.spawn_in(window, async move |editor, cx| {
17425 let locations: Vec<Location> = future::join_all(definitions)
17426 .await
17427 .into_iter()
17428 .filter_map(|location| location.transpose())
17429 .collect::<Result<_>>()
17430 .context("location tasks")?;
17431 let mut locations = cx.update(|_, cx| {
17432 locations
17433 .into_iter()
17434 .map(|location| {
17435 let buffer = location.buffer.read(cx);
17436 (location.buffer, location.range.to_point(buffer))
17437 })
17438 .into_group_map()
17439 })?;
17440 let mut num_locations = 0;
17441 for ranges in locations.values_mut() {
17442 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17443 ranges.dedup();
17444 num_locations += ranges.len();
17445 }
17446
17447 if num_locations > 1 {
17448 let tab_kind = match kind {
17449 Some(GotoDefinitionKind::Implementation) => "Implementations",
17450 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17451 Some(GotoDefinitionKind::Declaration) => "Declarations",
17452 Some(GotoDefinitionKind::Type) => "Types",
17453 };
17454 let title = editor
17455 .update_in(cx, |_, _, cx| {
17456 let target = locations
17457 .iter()
17458 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17459 .map(|(buffer, location)| {
17460 buffer
17461 .read(cx)
17462 .text_for_range(location.clone())
17463 .collect::<String>()
17464 })
17465 .filter(|text| !text.contains('\n'))
17466 .unique()
17467 .take(3)
17468 .join(", ");
17469 if target.is_empty() {
17470 tab_kind.to_owned()
17471 } else {
17472 format!("{tab_kind} for {target}")
17473 }
17474 })
17475 .context("buffer title")?;
17476
17477 let Some(workspace) = workspace else {
17478 return Ok(Navigated::No);
17479 };
17480
17481 let opened = workspace
17482 .update_in(cx, |workspace, window, cx| {
17483 let allow_preview = PreviewTabsSettings::get_global(cx)
17484 .enable_preview_multibuffer_from_code_navigation;
17485 Self::open_locations_in_multibuffer(
17486 workspace,
17487 locations,
17488 title,
17489 split,
17490 allow_preview,
17491 MultibufferSelectionMode::First,
17492 window,
17493 cx,
17494 )
17495 })
17496 .is_ok();
17497
17498 anyhow::Ok(Navigated::from_bool(opened))
17499 } else if num_locations == 0 {
17500 // If there is one url or file, open it directly
17501 match first_url_or_file {
17502 Some(Either::Left(url)) => {
17503 cx.update(|window, cx| {
17504 if parse_zed_link(&url, cx).is_some() {
17505 window
17506 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17507 } else {
17508 cx.open_url(&url);
17509 }
17510 })?;
17511 Ok(Navigated::Yes)
17512 }
17513 Some(Either::Right(path)) => {
17514 // TODO(andrew): respect preview tab settings
17515 // `enable_keep_preview_on_code_navigation` and
17516 // `enable_preview_file_from_code_navigation`
17517 let Some(workspace) = workspace else {
17518 return Ok(Navigated::No);
17519 };
17520 workspace
17521 .update_in(cx, |workspace, window, cx| {
17522 workspace.open_resolved_path(path, window, cx)
17523 })?
17524 .await?;
17525 Ok(Navigated::Yes)
17526 }
17527 None => Ok(Navigated::No),
17528 }
17529 } else {
17530 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17531 let target_range = target_ranges.first().unwrap().clone();
17532
17533 editor.update_in(cx, |editor, window, cx| {
17534 let range = target_range.to_point(target_buffer.read(cx));
17535 let range = editor.range_for_match(&range);
17536 let range = collapse_multiline_range(range);
17537
17538 if !split
17539 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17540 {
17541 editor.go_to_singleton_buffer_range(range, window, cx);
17542 } else {
17543 let Some(workspace) = workspace else {
17544 return Navigated::No;
17545 };
17546 let pane = workspace.read(cx).active_pane().clone();
17547 window.defer(cx, move |window, cx| {
17548 let target_editor: Entity<Self> =
17549 workspace.update(cx, |workspace, cx| {
17550 let pane = if split {
17551 workspace.adjacent_pane(window, cx)
17552 } else {
17553 workspace.active_pane().clone()
17554 };
17555
17556 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17557 let keep_old_preview = preview_tabs_settings
17558 .enable_keep_preview_on_code_navigation;
17559 let allow_new_preview = preview_tabs_settings
17560 .enable_preview_file_from_code_navigation;
17561
17562 workspace.open_project_item(
17563 pane,
17564 target_buffer.clone(),
17565 true,
17566 true,
17567 keep_old_preview,
17568 allow_new_preview,
17569 window,
17570 cx,
17571 )
17572 });
17573 target_editor.update(cx, |target_editor, cx| {
17574 // When selecting a definition in a different buffer, disable the nav history
17575 // to avoid creating a history entry at the previous cursor location.
17576 pane.update(cx, |pane, _| pane.disable_history());
17577 target_editor.go_to_singleton_buffer_range(range, window, cx);
17578 pane.update(cx, |pane, _| pane.enable_history());
17579 });
17580 });
17581 }
17582 Navigated::Yes
17583 })
17584 }
17585 })
17586 }
17587
17588 fn compute_target_location(
17589 &self,
17590 lsp_location: lsp::Location,
17591 server_id: LanguageServerId,
17592 window: &mut Window,
17593 cx: &mut Context<Self>,
17594 ) -> Task<anyhow::Result<Option<Location>>> {
17595 let Some(project) = self.project.clone() else {
17596 return Task::ready(Ok(None));
17597 };
17598
17599 cx.spawn_in(window, async move |editor, cx| {
17600 let location_task = editor.update(cx, |_, cx| {
17601 project.update(cx, |project, cx| {
17602 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17603 })
17604 })?;
17605 let location = Some({
17606 let target_buffer_handle = location_task.await.context("open local buffer")?;
17607 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17608 let target_start = target_buffer
17609 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17610 let target_end = target_buffer
17611 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17612 target_buffer.anchor_after(target_start)
17613 ..target_buffer.anchor_before(target_end)
17614 });
17615 Location {
17616 buffer: target_buffer_handle,
17617 range,
17618 }
17619 });
17620 Ok(location)
17621 })
17622 }
17623
17624 fn go_to_next_reference(
17625 &mut self,
17626 _: &GoToNextReference,
17627 window: &mut Window,
17628 cx: &mut Context<Self>,
17629 ) {
17630 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17631 if let Some(task) = task {
17632 task.detach();
17633 };
17634 }
17635
17636 fn go_to_prev_reference(
17637 &mut self,
17638 _: &GoToPreviousReference,
17639 window: &mut Window,
17640 cx: &mut Context<Self>,
17641 ) {
17642 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17643 if let Some(task) = task {
17644 task.detach();
17645 };
17646 }
17647
17648 pub fn go_to_reference_before_or_after_position(
17649 &mut self,
17650 direction: Direction,
17651 count: usize,
17652 window: &mut Window,
17653 cx: &mut Context<Self>,
17654 ) -> Option<Task<Result<()>>> {
17655 let selection = self.selections.newest_anchor();
17656 let head = selection.head();
17657
17658 let multi_buffer = self.buffer.read(cx);
17659
17660 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17661 let workspace = self.workspace()?;
17662 let project = workspace.read(cx).project().clone();
17663 let references =
17664 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17665 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17666 let Some(locations) = references.await? else {
17667 return Ok(());
17668 };
17669
17670 if locations.is_empty() {
17671 // totally normal - the cursor may be on something which is not
17672 // a symbol (e.g. a keyword)
17673 log::info!("no references found under cursor");
17674 return Ok(());
17675 }
17676
17677 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17678
17679 let (locations, current_location_index) =
17680 multi_buffer.update(cx, |multi_buffer, cx| {
17681 let mut locations = locations
17682 .into_iter()
17683 .filter_map(|loc| {
17684 let start = multi_buffer.buffer_anchor_to_anchor(
17685 &loc.buffer,
17686 loc.range.start,
17687 cx,
17688 )?;
17689 let end = multi_buffer.buffer_anchor_to_anchor(
17690 &loc.buffer,
17691 loc.range.end,
17692 cx,
17693 )?;
17694 Some(start..end)
17695 })
17696 .collect::<Vec<_>>();
17697
17698 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17699 // There is an O(n) implementation, but given this list will be
17700 // small (usually <100 items), the extra O(log(n)) factor isn't
17701 // worth the (surprisingly large amount of) extra complexity.
17702 locations
17703 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17704
17705 let head_offset = head.to_offset(&multi_buffer_snapshot);
17706
17707 let current_location_index = locations.iter().position(|loc| {
17708 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17709 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17710 });
17711
17712 (locations, current_location_index)
17713 });
17714
17715 let Some(current_location_index) = current_location_index else {
17716 // This indicates something has gone wrong, because we already
17717 // handle the "no references" case above
17718 log::error!(
17719 "failed to find current reference under cursor. Total references: {}",
17720 locations.len()
17721 );
17722 return Ok(());
17723 };
17724
17725 let destination_location_index = match direction {
17726 Direction::Next => (current_location_index + count) % locations.len(),
17727 Direction::Prev => {
17728 (current_location_index + locations.len() - count % locations.len())
17729 % locations.len()
17730 }
17731 };
17732
17733 // TODO(cameron): is this needed?
17734 // the thinking is to avoid "jumping to the current location" (avoid
17735 // polluting "jumplist" in vim terms)
17736 if current_location_index == destination_location_index {
17737 return Ok(());
17738 }
17739
17740 let Range { start, end } = locations[destination_location_index];
17741
17742 editor.update_in(cx, |editor, window, cx| {
17743 let effects = SelectionEffects::default();
17744
17745 editor.unfold_ranges(&[start..end], false, false, cx);
17746 editor.change_selections(effects, window, cx, |s| {
17747 s.select_ranges([start..start]);
17748 });
17749 })?;
17750
17751 Ok(())
17752 }))
17753 }
17754
17755 pub fn find_all_references(
17756 &mut self,
17757 action: &FindAllReferences,
17758 window: &mut Window,
17759 cx: &mut Context<Self>,
17760 ) -> Option<Task<Result<Navigated>>> {
17761 let always_open_multibuffer = action.always_open_multibuffer;
17762 let selection = self.selections.newest_anchor();
17763 let multi_buffer = self.buffer.read(cx);
17764 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17765 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17766 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17767 let head = selection_offset.head();
17768
17769 let head_anchor = multi_buffer_snapshot.anchor_at(
17770 head,
17771 if head < selection_offset.tail() {
17772 Bias::Right
17773 } else {
17774 Bias::Left
17775 },
17776 );
17777
17778 match self
17779 .find_all_references_task_sources
17780 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17781 {
17782 Ok(_) => {
17783 log::info!(
17784 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17785 );
17786 return None;
17787 }
17788 Err(i) => {
17789 self.find_all_references_task_sources.insert(i, head_anchor);
17790 }
17791 }
17792
17793 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17794 let workspace = self.workspace()?;
17795 let project = workspace.read(cx).project().clone();
17796 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17797 Some(cx.spawn_in(window, async move |editor, cx| {
17798 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17799 if let Ok(i) = editor
17800 .find_all_references_task_sources
17801 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17802 {
17803 editor.find_all_references_task_sources.remove(i);
17804 }
17805 });
17806
17807 let Some(locations) = references.await? else {
17808 return anyhow::Ok(Navigated::No);
17809 };
17810 let mut locations = cx.update(|_, cx| {
17811 locations
17812 .into_iter()
17813 .map(|location| {
17814 let buffer = location.buffer.read(cx);
17815 (location.buffer, location.range.to_point(buffer))
17816 })
17817 // if special-casing the single-match case, remove ranges
17818 // that intersect current selection
17819 .filter(|(location_buffer, location)| {
17820 if always_open_multibuffer || &buffer != location_buffer {
17821 return true;
17822 }
17823
17824 !location.contains_inclusive(&selection_point.range())
17825 })
17826 .into_group_map()
17827 })?;
17828 if locations.is_empty() {
17829 return anyhow::Ok(Navigated::No);
17830 }
17831 for ranges in locations.values_mut() {
17832 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17833 ranges.dedup();
17834 }
17835 let mut num_locations = 0;
17836 for ranges in locations.values_mut() {
17837 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17838 ranges.dedup();
17839 num_locations += ranges.len();
17840 }
17841
17842 if num_locations == 1 && !always_open_multibuffer {
17843 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17844 let target_range = target_ranges.first().unwrap().clone();
17845
17846 return editor.update_in(cx, |editor, window, cx| {
17847 let range = target_range.to_point(target_buffer.read(cx));
17848 let range = editor.range_for_match(&range);
17849 let range = range.start..range.start;
17850
17851 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17852 editor.go_to_singleton_buffer_range(range, window, cx);
17853 } else {
17854 let pane = workspace.read(cx).active_pane().clone();
17855 window.defer(cx, move |window, cx| {
17856 let target_editor: Entity<Self> =
17857 workspace.update(cx, |workspace, cx| {
17858 let pane = workspace.active_pane().clone();
17859
17860 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17861 let keep_old_preview = preview_tabs_settings
17862 .enable_keep_preview_on_code_navigation;
17863 let allow_new_preview = preview_tabs_settings
17864 .enable_preview_file_from_code_navigation;
17865
17866 workspace.open_project_item(
17867 pane,
17868 target_buffer.clone(),
17869 true,
17870 true,
17871 keep_old_preview,
17872 allow_new_preview,
17873 window,
17874 cx,
17875 )
17876 });
17877 target_editor.update(cx, |target_editor, cx| {
17878 // When selecting a definition in a different buffer, disable the nav history
17879 // to avoid creating a history entry at the previous cursor location.
17880 pane.update(cx, |pane, _| pane.disable_history());
17881 target_editor.go_to_singleton_buffer_range(range, window, cx);
17882 pane.update(cx, |pane, _| pane.enable_history());
17883 });
17884 });
17885 }
17886 Navigated::No
17887 });
17888 }
17889
17890 workspace.update_in(cx, |workspace, window, cx| {
17891 let target = locations
17892 .iter()
17893 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17894 .map(|(buffer, location)| {
17895 buffer
17896 .read(cx)
17897 .text_for_range(location.clone())
17898 .collect::<String>()
17899 })
17900 .filter(|text| !text.contains('\n'))
17901 .unique()
17902 .take(3)
17903 .join(", ");
17904 let title = if target.is_empty() {
17905 "References".to_owned()
17906 } else {
17907 format!("References to {target}")
17908 };
17909 let allow_preview = PreviewTabsSettings::get_global(cx)
17910 .enable_preview_multibuffer_from_code_navigation;
17911 Self::open_locations_in_multibuffer(
17912 workspace,
17913 locations,
17914 title,
17915 false,
17916 allow_preview,
17917 MultibufferSelectionMode::First,
17918 window,
17919 cx,
17920 );
17921 Navigated::Yes
17922 })
17923 }))
17924 }
17925
17926 /// Opens a multibuffer with the given project locations in it.
17927 pub fn open_locations_in_multibuffer(
17928 workspace: &mut Workspace,
17929 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17930 title: String,
17931 split: bool,
17932 allow_preview: bool,
17933 multibuffer_selection_mode: MultibufferSelectionMode,
17934 window: &mut Window,
17935 cx: &mut Context<Workspace>,
17936 ) {
17937 if locations.is_empty() {
17938 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17939 return;
17940 }
17941
17942 let capability = workspace.project().read(cx).capability();
17943 let mut ranges = <Vec<Range<Anchor>>>::new();
17944
17945 // a key to find existing multibuffer editors with the same set of locations
17946 // to prevent us from opening more and more multibuffer tabs for searches and the like
17947 let mut key = (title.clone(), vec![]);
17948 let excerpt_buffer = cx.new(|cx| {
17949 let key = &mut key.1;
17950 let mut multibuffer = MultiBuffer::new(capability);
17951 for (buffer, mut ranges_for_buffer) in locations {
17952 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17953 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17954 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17955 PathKey::for_buffer(&buffer, cx),
17956 buffer.clone(),
17957 ranges_for_buffer,
17958 multibuffer_context_lines(cx),
17959 cx,
17960 );
17961 ranges.extend(new_ranges)
17962 }
17963
17964 multibuffer.with_title(title)
17965 });
17966 let existing = workspace.active_pane().update(cx, |pane, cx| {
17967 pane.items()
17968 .filter_map(|item| item.downcast::<Editor>())
17969 .find(|editor| {
17970 editor
17971 .read(cx)
17972 .lookup_key
17973 .as_ref()
17974 .and_then(|it| {
17975 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17976 })
17977 .is_some_and(|it| *it == key)
17978 })
17979 });
17980 let was_existing = existing.is_some();
17981 let editor = existing.unwrap_or_else(|| {
17982 cx.new(|cx| {
17983 let mut editor = Editor::for_multibuffer(
17984 excerpt_buffer,
17985 Some(workspace.project().clone()),
17986 window,
17987 cx,
17988 );
17989 editor.lookup_key = Some(Box::new(key));
17990 editor
17991 })
17992 });
17993 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17994 MultibufferSelectionMode::First => {
17995 if let Some(first_range) = ranges.first() {
17996 editor.change_selections(
17997 SelectionEffects::no_scroll(),
17998 window,
17999 cx,
18000 |selections| {
18001 selections.clear_disjoint();
18002 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18003 },
18004 );
18005 }
18006 editor.highlight_background::<Self>(
18007 &ranges,
18008 |_, theme| theme.colors().editor_highlighted_line_background,
18009 cx,
18010 );
18011 }
18012 MultibufferSelectionMode::All => {
18013 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18014 selections.clear_disjoint();
18015 selections.select_anchor_ranges(ranges);
18016 });
18017 }
18018 });
18019
18020 let item = Box::new(editor);
18021
18022 let pane = if split {
18023 workspace.adjacent_pane(window, cx)
18024 } else {
18025 workspace.active_pane().clone()
18026 };
18027 let activate_pane = split;
18028
18029 let mut destination_index = None;
18030 pane.update(cx, |pane, cx| {
18031 if allow_preview && !was_existing {
18032 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18033 }
18034 if was_existing && !allow_preview {
18035 pane.unpreview_item_if_preview(item.item_id());
18036 }
18037 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18038 });
18039 }
18040
18041 pub fn rename(
18042 &mut self,
18043 _: &Rename,
18044 window: &mut Window,
18045 cx: &mut Context<Self>,
18046 ) -> Option<Task<Result<()>>> {
18047 use language::ToOffset as _;
18048
18049 let provider = self.semantics_provider.clone()?;
18050 let selection = self.selections.newest_anchor().clone();
18051 let (cursor_buffer, cursor_buffer_position) = self
18052 .buffer
18053 .read(cx)
18054 .text_anchor_for_position(selection.head(), cx)?;
18055 let (tail_buffer, cursor_buffer_position_end) = self
18056 .buffer
18057 .read(cx)
18058 .text_anchor_for_position(selection.tail(), cx)?;
18059 if tail_buffer != cursor_buffer {
18060 return None;
18061 }
18062
18063 let snapshot = cursor_buffer.read(cx).snapshot();
18064 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18065 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18066 let prepare_rename = provider
18067 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18068 .unwrap_or_else(|| Task::ready(Ok(None)));
18069 drop(snapshot);
18070
18071 Some(cx.spawn_in(window, async move |this, cx| {
18072 let rename_range = if let Some(range) = prepare_rename.await? {
18073 Some(range)
18074 } else {
18075 this.update(cx, |this, cx| {
18076 let buffer = this.buffer.read(cx).snapshot(cx);
18077 let mut buffer_highlights = this
18078 .document_highlights_for_position(selection.head(), &buffer)
18079 .filter(|highlight| {
18080 highlight.start.excerpt_id == selection.head().excerpt_id
18081 && highlight.end.excerpt_id == selection.head().excerpt_id
18082 });
18083 buffer_highlights
18084 .next()
18085 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18086 })?
18087 };
18088 if let Some(rename_range) = rename_range {
18089 this.update_in(cx, |this, window, cx| {
18090 let snapshot = cursor_buffer.read(cx).snapshot();
18091 let rename_buffer_range = rename_range.to_offset(&snapshot);
18092 let cursor_offset_in_rename_range =
18093 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18094 let cursor_offset_in_rename_range_end =
18095 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18096
18097 this.take_rename(false, window, cx);
18098 let buffer = this.buffer.read(cx).read(cx);
18099 let cursor_offset = selection.head().to_offset(&buffer);
18100 let rename_start =
18101 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18102 let rename_end = rename_start + rename_buffer_range.len();
18103 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18104 let mut old_highlight_id = None;
18105 let old_name: Arc<str> = buffer
18106 .chunks(rename_start..rename_end, true)
18107 .map(|chunk| {
18108 if old_highlight_id.is_none() {
18109 old_highlight_id = chunk.syntax_highlight_id;
18110 }
18111 chunk.text
18112 })
18113 .collect::<String>()
18114 .into();
18115
18116 drop(buffer);
18117
18118 // Position the selection in the rename editor so that it matches the current selection.
18119 this.show_local_selections = false;
18120 let rename_editor = cx.new(|cx| {
18121 let mut editor = Editor::single_line(window, cx);
18122 editor.buffer.update(cx, |buffer, cx| {
18123 buffer.edit(
18124 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18125 None,
18126 cx,
18127 )
18128 });
18129 let cursor_offset_in_rename_range =
18130 MultiBufferOffset(cursor_offset_in_rename_range);
18131 let cursor_offset_in_rename_range_end =
18132 MultiBufferOffset(cursor_offset_in_rename_range_end);
18133 let rename_selection_range = match cursor_offset_in_rename_range
18134 .cmp(&cursor_offset_in_rename_range_end)
18135 {
18136 Ordering::Equal => {
18137 editor.select_all(&SelectAll, window, cx);
18138 return editor;
18139 }
18140 Ordering::Less => {
18141 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18142 }
18143 Ordering::Greater => {
18144 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18145 }
18146 };
18147 if rename_selection_range.end.0 > old_name.len() {
18148 editor.select_all(&SelectAll, window, cx);
18149 } else {
18150 editor.change_selections(Default::default(), window, cx, |s| {
18151 s.select_ranges([rename_selection_range]);
18152 });
18153 }
18154 editor
18155 });
18156 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18157 if e == &EditorEvent::Focused {
18158 cx.emit(EditorEvent::FocusedIn)
18159 }
18160 })
18161 .detach();
18162
18163 let write_highlights =
18164 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18165 let read_highlights =
18166 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18167 let ranges = write_highlights
18168 .iter()
18169 .flat_map(|(_, ranges)| ranges.iter())
18170 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18171 .cloned()
18172 .collect();
18173
18174 this.highlight_text::<Rename>(
18175 ranges,
18176 HighlightStyle {
18177 fade_out: Some(0.6),
18178 ..Default::default()
18179 },
18180 cx,
18181 );
18182 let rename_focus_handle = rename_editor.focus_handle(cx);
18183 window.focus(&rename_focus_handle, cx);
18184 let block_id = this.insert_blocks(
18185 [BlockProperties {
18186 style: BlockStyle::Flex,
18187 placement: BlockPlacement::Below(range.start),
18188 height: Some(1),
18189 render: Arc::new({
18190 let rename_editor = rename_editor.clone();
18191 move |cx: &mut BlockContext| {
18192 let mut text_style = cx.editor_style.text.clone();
18193 if let Some(highlight_style) = old_highlight_id
18194 .and_then(|h| h.style(&cx.editor_style.syntax))
18195 {
18196 text_style = text_style.highlight(highlight_style);
18197 }
18198 div()
18199 .block_mouse_except_scroll()
18200 .pl(cx.anchor_x)
18201 .child(EditorElement::new(
18202 &rename_editor,
18203 EditorStyle {
18204 background: cx.theme().system().transparent,
18205 local_player: cx.editor_style.local_player,
18206 text: text_style,
18207 scrollbar_width: cx.editor_style.scrollbar_width,
18208 syntax: cx.editor_style.syntax.clone(),
18209 status: cx.editor_style.status.clone(),
18210 inlay_hints_style: HighlightStyle {
18211 font_weight: Some(FontWeight::BOLD),
18212 ..make_inlay_hints_style(cx.app)
18213 },
18214 edit_prediction_styles: make_suggestion_styles(
18215 cx.app,
18216 ),
18217 ..EditorStyle::default()
18218 },
18219 ))
18220 .into_any_element()
18221 }
18222 }),
18223 priority: 0,
18224 }],
18225 Some(Autoscroll::fit()),
18226 cx,
18227 )[0];
18228 this.pending_rename = Some(RenameState {
18229 range,
18230 old_name,
18231 editor: rename_editor,
18232 block_id,
18233 });
18234 })?;
18235 }
18236
18237 Ok(())
18238 }))
18239 }
18240
18241 pub fn confirm_rename(
18242 &mut self,
18243 _: &ConfirmRename,
18244 window: &mut Window,
18245 cx: &mut Context<Self>,
18246 ) -> Option<Task<Result<()>>> {
18247 let rename = self.take_rename(false, window, cx)?;
18248 let workspace = self.workspace()?.downgrade();
18249 let (buffer, start) = self
18250 .buffer
18251 .read(cx)
18252 .text_anchor_for_position(rename.range.start, cx)?;
18253 let (end_buffer, _) = self
18254 .buffer
18255 .read(cx)
18256 .text_anchor_for_position(rename.range.end, cx)?;
18257 if buffer != end_buffer {
18258 return None;
18259 }
18260
18261 let old_name = rename.old_name;
18262 let new_name = rename.editor.read(cx).text(cx);
18263
18264 let rename = self.semantics_provider.as_ref()?.perform_rename(
18265 &buffer,
18266 start,
18267 new_name.clone(),
18268 cx,
18269 )?;
18270
18271 Some(cx.spawn_in(window, async move |editor, cx| {
18272 let project_transaction = rename.await?;
18273 Self::open_project_transaction(
18274 &editor,
18275 workspace,
18276 project_transaction,
18277 format!("Rename: {} → {}", old_name, new_name),
18278 cx,
18279 )
18280 .await?;
18281
18282 editor.update(cx, |editor, cx| {
18283 editor.refresh_document_highlights(cx);
18284 })?;
18285 Ok(())
18286 }))
18287 }
18288
18289 fn take_rename(
18290 &mut self,
18291 moving_cursor: bool,
18292 window: &mut Window,
18293 cx: &mut Context<Self>,
18294 ) -> Option<RenameState> {
18295 let rename = self.pending_rename.take()?;
18296 if rename.editor.focus_handle(cx).is_focused(window) {
18297 window.focus(&self.focus_handle, cx);
18298 }
18299
18300 self.remove_blocks(
18301 [rename.block_id].into_iter().collect(),
18302 Some(Autoscroll::fit()),
18303 cx,
18304 );
18305 self.clear_highlights::<Rename>(cx);
18306 self.show_local_selections = true;
18307
18308 if moving_cursor {
18309 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18310 editor
18311 .selections
18312 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18313 .head()
18314 });
18315
18316 // Update the selection to match the position of the selection inside
18317 // the rename editor.
18318 let snapshot = self.buffer.read(cx).read(cx);
18319 let rename_range = rename.range.to_offset(&snapshot);
18320 let cursor_in_editor = snapshot
18321 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18322 .min(rename_range.end);
18323 drop(snapshot);
18324
18325 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18326 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18327 });
18328 } else {
18329 self.refresh_document_highlights(cx);
18330 }
18331
18332 Some(rename)
18333 }
18334
18335 pub fn pending_rename(&self) -> Option<&RenameState> {
18336 self.pending_rename.as_ref()
18337 }
18338
18339 fn format(
18340 &mut self,
18341 _: &Format,
18342 window: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) -> Option<Task<Result<()>>> {
18345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18346
18347 let project = match &self.project {
18348 Some(project) => project.clone(),
18349 None => return None,
18350 };
18351
18352 Some(self.perform_format(
18353 project,
18354 FormatTrigger::Manual,
18355 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18356 window,
18357 cx,
18358 ))
18359 }
18360
18361 fn format_selections(
18362 &mut self,
18363 _: &FormatSelections,
18364 window: &mut Window,
18365 cx: &mut Context<Self>,
18366 ) -> Option<Task<Result<()>>> {
18367 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18368
18369 let project = match &self.project {
18370 Some(project) => project.clone(),
18371 None => return None,
18372 };
18373
18374 let ranges = self
18375 .selections
18376 .all_adjusted(&self.display_snapshot(cx))
18377 .into_iter()
18378 .map(|selection| selection.range())
18379 .collect_vec();
18380
18381 Some(self.perform_format(
18382 project,
18383 FormatTrigger::Manual,
18384 FormatTarget::Ranges(ranges),
18385 window,
18386 cx,
18387 ))
18388 }
18389
18390 fn perform_format(
18391 &mut self,
18392 project: Entity<Project>,
18393 trigger: FormatTrigger,
18394 target: FormatTarget,
18395 window: &mut Window,
18396 cx: &mut Context<Self>,
18397 ) -> Task<Result<()>> {
18398 let buffer = self.buffer.clone();
18399 let (buffers, target) = match target {
18400 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18401 FormatTarget::Ranges(selection_ranges) => {
18402 let multi_buffer = buffer.read(cx);
18403 let snapshot = multi_buffer.read(cx);
18404 let mut buffers = HashSet::default();
18405 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18406 BTreeMap::new();
18407 for selection_range in selection_ranges {
18408 for (buffer, buffer_range, _) in
18409 snapshot.range_to_buffer_ranges(selection_range)
18410 {
18411 let buffer_id = buffer.remote_id();
18412 let start = buffer.anchor_before(buffer_range.start);
18413 let end = buffer.anchor_after(buffer_range.end);
18414 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18415 buffer_id_to_ranges
18416 .entry(buffer_id)
18417 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18418 .or_insert_with(|| vec![start..end]);
18419 }
18420 }
18421 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18422 }
18423 };
18424
18425 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18426 let selections_prev = transaction_id_prev
18427 .and_then(|transaction_id_prev| {
18428 // default to selections as they were after the last edit, if we have them,
18429 // instead of how they are now.
18430 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18431 // will take you back to where you made the last edit, instead of staying where you scrolled
18432 self.selection_history
18433 .transaction(transaction_id_prev)
18434 .map(|t| t.0.clone())
18435 })
18436 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18437
18438 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18439 let format = project.update(cx, |project, cx| {
18440 project.format(buffers, target, true, trigger, cx)
18441 });
18442
18443 cx.spawn_in(window, async move |editor, cx| {
18444 let transaction = futures::select_biased! {
18445 transaction = format.log_err().fuse() => transaction,
18446 () = timeout => {
18447 log::warn!("timed out waiting for formatting");
18448 None
18449 }
18450 };
18451
18452 buffer.update(cx, |buffer, cx| {
18453 if let Some(transaction) = transaction
18454 && !buffer.is_singleton()
18455 {
18456 buffer.push_transaction(&transaction.0, cx);
18457 }
18458 cx.notify();
18459 });
18460
18461 if let Some(transaction_id_now) =
18462 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))
18463 {
18464 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18465 if has_new_transaction {
18466 editor
18467 .update(cx, |editor, _| {
18468 editor
18469 .selection_history
18470 .insert_transaction(transaction_id_now, selections_prev);
18471 })
18472 .ok();
18473 }
18474 }
18475
18476 Ok(())
18477 })
18478 }
18479
18480 fn organize_imports(
18481 &mut self,
18482 _: &OrganizeImports,
18483 window: &mut Window,
18484 cx: &mut Context<Self>,
18485 ) -> Option<Task<Result<()>>> {
18486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18487 let project = match &self.project {
18488 Some(project) => project.clone(),
18489 None => return None,
18490 };
18491 Some(self.perform_code_action_kind(
18492 project,
18493 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18494 window,
18495 cx,
18496 ))
18497 }
18498
18499 fn perform_code_action_kind(
18500 &mut self,
18501 project: Entity<Project>,
18502 kind: CodeActionKind,
18503 window: &mut Window,
18504 cx: &mut Context<Self>,
18505 ) -> Task<Result<()>> {
18506 let buffer = self.buffer.clone();
18507 let buffers = buffer.read(cx).all_buffers();
18508 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18509 let apply_action = project.update(cx, |project, cx| {
18510 project.apply_code_action_kind(buffers, kind, true, cx)
18511 });
18512 cx.spawn_in(window, async move |_, cx| {
18513 let transaction = futures::select_biased! {
18514 () = timeout => {
18515 log::warn!("timed out waiting for executing code action");
18516 None
18517 }
18518 transaction = apply_action.log_err().fuse() => transaction,
18519 };
18520 buffer.update(cx, |buffer, cx| {
18521 // check if we need this
18522 if let Some(transaction) = transaction
18523 && !buffer.is_singleton()
18524 {
18525 buffer.push_transaction(&transaction.0, cx);
18526 }
18527 cx.notify();
18528 });
18529 Ok(())
18530 })
18531 }
18532
18533 pub fn restart_language_server(
18534 &mut self,
18535 _: &RestartLanguageServer,
18536 _: &mut Window,
18537 cx: &mut Context<Self>,
18538 ) {
18539 if let Some(project) = self.project.clone() {
18540 self.buffer.update(cx, |multi_buffer, cx| {
18541 project.update(cx, |project, cx| {
18542 project.restart_language_servers_for_buffers(
18543 multi_buffer.all_buffers().into_iter().collect(),
18544 HashSet::default(),
18545 cx,
18546 );
18547 });
18548 })
18549 }
18550 }
18551
18552 pub fn stop_language_server(
18553 &mut self,
18554 _: &StopLanguageServer,
18555 _: &mut Window,
18556 cx: &mut Context<Self>,
18557 ) {
18558 if let Some(project) = self.project.clone() {
18559 self.buffer.update(cx, |multi_buffer, cx| {
18560 project.update(cx, |project, cx| {
18561 project.stop_language_servers_for_buffers(
18562 multi_buffer.all_buffers().into_iter().collect(),
18563 HashSet::default(),
18564 cx,
18565 );
18566 });
18567 });
18568 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18569 }
18570 }
18571
18572 fn cancel_language_server_work(
18573 workspace: &mut Workspace,
18574 _: &actions::CancelLanguageServerWork,
18575 _: &mut Window,
18576 cx: &mut Context<Workspace>,
18577 ) {
18578 let project = workspace.project();
18579 let buffers = workspace
18580 .active_item(cx)
18581 .and_then(|item| item.act_as::<Editor>(cx))
18582 .map_or(HashSet::default(), |editor| {
18583 editor.read(cx).buffer.read(cx).all_buffers()
18584 });
18585 project.update(cx, |project, cx| {
18586 project.cancel_language_server_work_for_buffers(buffers, cx);
18587 });
18588 }
18589
18590 fn show_character_palette(
18591 &mut self,
18592 _: &ShowCharacterPalette,
18593 window: &mut Window,
18594 _: &mut Context<Self>,
18595 ) {
18596 window.show_character_palette();
18597 }
18598
18599 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18600 if !self.diagnostics_enabled() {
18601 return;
18602 }
18603
18604 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18605 let buffer = self.buffer.read(cx).snapshot(cx);
18606 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18607 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18608 let is_valid = buffer
18609 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18610 .any(|entry| {
18611 entry.diagnostic.is_primary
18612 && !entry.range.is_empty()
18613 && entry.range.start == primary_range_start
18614 && entry.diagnostic.message == active_diagnostics.active_message
18615 });
18616
18617 if !is_valid {
18618 self.dismiss_diagnostics(cx);
18619 }
18620 }
18621 }
18622
18623 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18624 match &self.active_diagnostics {
18625 ActiveDiagnostic::Group(group) => Some(group),
18626 _ => None,
18627 }
18628 }
18629
18630 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18631 if !self.diagnostics_enabled() {
18632 return;
18633 }
18634 self.dismiss_diagnostics(cx);
18635 self.active_diagnostics = ActiveDiagnostic::All;
18636 }
18637
18638 fn activate_diagnostics(
18639 &mut self,
18640 buffer_id: BufferId,
18641 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18642 window: &mut Window,
18643 cx: &mut Context<Self>,
18644 ) {
18645 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18646 return;
18647 }
18648 self.dismiss_diagnostics(cx);
18649 let snapshot = self.snapshot(window, cx);
18650 let buffer = self.buffer.read(cx).snapshot(cx);
18651 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18652 return;
18653 };
18654
18655 let diagnostic_group = buffer
18656 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18657 .collect::<Vec<_>>();
18658
18659 let language_registry = self
18660 .project()
18661 .map(|project| project.read(cx).languages().clone());
18662
18663 let blocks = renderer.render_group(
18664 diagnostic_group,
18665 buffer_id,
18666 snapshot,
18667 cx.weak_entity(),
18668 language_registry,
18669 cx,
18670 );
18671
18672 let blocks = self.display_map.update(cx, |display_map, cx| {
18673 display_map.insert_blocks(blocks, cx).into_iter().collect()
18674 });
18675 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18676 active_range: buffer.anchor_before(diagnostic.range.start)
18677 ..buffer.anchor_after(diagnostic.range.end),
18678 active_message: diagnostic.diagnostic.message.clone(),
18679 group_id: diagnostic.diagnostic.group_id,
18680 blocks,
18681 });
18682 cx.notify();
18683 }
18684
18685 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18686 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18687 return;
18688 };
18689
18690 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18691 if let ActiveDiagnostic::Group(group) = prev {
18692 self.display_map.update(cx, |display_map, cx| {
18693 display_map.remove_blocks(group.blocks, cx);
18694 });
18695 cx.notify();
18696 }
18697 }
18698
18699 /// Disable inline diagnostics rendering for this editor.
18700 pub fn disable_inline_diagnostics(&mut self) {
18701 self.inline_diagnostics_enabled = false;
18702 self.inline_diagnostics_update = Task::ready(());
18703 self.inline_diagnostics.clear();
18704 }
18705
18706 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18707 self.diagnostics_enabled = false;
18708 self.dismiss_diagnostics(cx);
18709 self.inline_diagnostics_update = Task::ready(());
18710 self.inline_diagnostics.clear();
18711 }
18712
18713 pub fn disable_word_completions(&mut self) {
18714 self.word_completions_enabled = false;
18715 }
18716
18717 pub fn diagnostics_enabled(&self) -> bool {
18718 self.diagnostics_enabled && self.mode.is_full()
18719 }
18720
18721 pub fn inline_diagnostics_enabled(&self) -> bool {
18722 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18723 }
18724
18725 pub fn show_inline_diagnostics(&self) -> bool {
18726 self.show_inline_diagnostics
18727 }
18728
18729 pub fn toggle_inline_diagnostics(
18730 &mut self,
18731 _: &ToggleInlineDiagnostics,
18732 window: &mut Window,
18733 cx: &mut Context<Editor>,
18734 ) {
18735 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18736 self.refresh_inline_diagnostics(false, window, cx);
18737 }
18738
18739 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18740 self.diagnostics_max_severity = severity;
18741 self.display_map.update(cx, |display_map, _| {
18742 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18743 });
18744 }
18745
18746 pub fn toggle_diagnostics(
18747 &mut self,
18748 _: &ToggleDiagnostics,
18749 window: &mut Window,
18750 cx: &mut Context<Editor>,
18751 ) {
18752 if !self.diagnostics_enabled() {
18753 return;
18754 }
18755
18756 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18757 EditorSettings::get_global(cx)
18758 .diagnostics_max_severity
18759 .filter(|severity| severity != &DiagnosticSeverity::Off)
18760 .unwrap_or(DiagnosticSeverity::Hint)
18761 } else {
18762 DiagnosticSeverity::Off
18763 };
18764 self.set_max_diagnostics_severity(new_severity, cx);
18765 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18766 self.active_diagnostics = ActiveDiagnostic::None;
18767 self.inline_diagnostics_update = Task::ready(());
18768 self.inline_diagnostics.clear();
18769 } else {
18770 self.refresh_inline_diagnostics(false, window, cx);
18771 }
18772
18773 cx.notify();
18774 }
18775
18776 pub fn toggle_minimap(
18777 &mut self,
18778 _: &ToggleMinimap,
18779 window: &mut Window,
18780 cx: &mut Context<Editor>,
18781 ) {
18782 if self.supports_minimap(cx) {
18783 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18784 }
18785 }
18786
18787 fn refresh_inline_diagnostics(
18788 &mut self,
18789 debounce: bool,
18790 window: &mut Window,
18791 cx: &mut Context<Self>,
18792 ) {
18793 let max_severity = ProjectSettings::get_global(cx)
18794 .diagnostics
18795 .inline
18796 .max_severity
18797 .unwrap_or(self.diagnostics_max_severity);
18798
18799 if !self.inline_diagnostics_enabled()
18800 || !self.diagnostics_enabled()
18801 || !self.show_inline_diagnostics
18802 || max_severity == DiagnosticSeverity::Off
18803 {
18804 self.inline_diagnostics_update = Task::ready(());
18805 self.inline_diagnostics.clear();
18806 return;
18807 }
18808
18809 let debounce_ms = ProjectSettings::get_global(cx)
18810 .diagnostics
18811 .inline
18812 .update_debounce_ms;
18813 let debounce = if debounce && debounce_ms > 0 {
18814 Some(Duration::from_millis(debounce_ms))
18815 } else {
18816 None
18817 };
18818 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18819 if let Some(debounce) = debounce {
18820 cx.background_executor().timer(debounce).await;
18821 }
18822 let Some(snapshot) = editor.upgrade().map(|editor| {
18823 editor.update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18824 }) else {
18825 return;
18826 };
18827
18828 let new_inline_diagnostics = cx
18829 .background_spawn(async move {
18830 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18831 for diagnostic_entry in
18832 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18833 {
18834 let message = diagnostic_entry
18835 .diagnostic
18836 .message
18837 .split_once('\n')
18838 .map(|(line, _)| line)
18839 .map(SharedString::new)
18840 .unwrap_or_else(|| {
18841 SharedString::new(&*diagnostic_entry.diagnostic.message)
18842 });
18843 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18844 let (Ok(i) | Err(i)) = inline_diagnostics
18845 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18846 inline_diagnostics.insert(
18847 i,
18848 (
18849 start_anchor,
18850 InlineDiagnostic {
18851 message,
18852 group_id: diagnostic_entry.diagnostic.group_id,
18853 start: diagnostic_entry.range.start.to_point(&snapshot),
18854 is_primary: diagnostic_entry.diagnostic.is_primary,
18855 severity: diagnostic_entry.diagnostic.severity,
18856 },
18857 ),
18858 );
18859 }
18860 inline_diagnostics
18861 })
18862 .await;
18863
18864 editor
18865 .update(cx, |editor, cx| {
18866 editor.inline_diagnostics = new_inline_diagnostics;
18867 cx.notify();
18868 })
18869 .ok();
18870 });
18871 }
18872
18873 fn pull_diagnostics(
18874 &mut self,
18875 buffer_id: Option<BufferId>,
18876 window: &Window,
18877 cx: &mut Context<Self>,
18878 ) -> Option<()> {
18879 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18880 return None;
18881 }
18882 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18883 .diagnostics
18884 .lsp_pull_diagnostics;
18885 if !pull_diagnostics_settings.enabled {
18886 return None;
18887 }
18888 let project = self.project()?.downgrade();
18889
18890 let mut edited_buffer_ids = HashSet::default();
18891 let mut edited_worktree_ids = HashSet::default();
18892 let edited_buffers = match buffer_id {
18893 Some(buffer_id) => {
18894 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18895 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18896 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18897 edited_worktree_ids.insert(worktree_id);
18898 vec![buffer]
18899 }
18900 None => self
18901 .buffer()
18902 .read(cx)
18903 .all_buffers()
18904 .into_iter()
18905 .filter(|buffer| {
18906 let buffer = buffer.read(cx);
18907 match buffer.file().map(|f| f.worktree_id(cx)) {
18908 Some(worktree_id) => {
18909 edited_buffer_ids.insert(buffer.remote_id());
18910 edited_worktree_ids.insert(worktree_id);
18911 true
18912 }
18913 None => false,
18914 }
18915 })
18916 .collect::<Vec<_>>(),
18917 };
18918
18919 if edited_buffers.is_empty() {
18920 self.pull_diagnostics_task = Task::ready(());
18921 self.pull_diagnostics_background_task = Task::ready(());
18922 return None;
18923 }
18924
18925 let mut already_used_buffers = HashSet::default();
18926 let related_open_buffers = self
18927 .workspace
18928 .as_ref()
18929 .and_then(|(workspace, _)| workspace.upgrade())
18930 .into_iter()
18931 .flat_map(|workspace| workspace.read(cx).panes())
18932 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18933 .filter(|editor| editor != &cx.entity())
18934 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18935 .filter(|buffer| {
18936 let buffer = buffer.read(cx);
18937 let buffer_id = buffer.remote_id();
18938 if already_used_buffers.insert(buffer_id) {
18939 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18940 return !edited_buffer_ids.contains(&buffer_id)
18941 && edited_worktree_ids.contains(&worktree_id);
18942 }
18943 }
18944 false
18945 })
18946 .collect::<Vec<_>>();
18947
18948 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18949 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18950 if buffers.is_empty() {
18951 return Task::ready(());
18952 }
18953 let project_weak = project.clone();
18954 cx.spawn_in(window, async move |_, cx| {
18955 cx.background_executor().timer(delay).await;
18956
18957 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18958 buffers
18959 .into_iter()
18960 .filter_map(|buffer| {
18961 project_weak
18962 .update(cx, |project, cx| {
18963 project.lsp_store().update(cx, |lsp_store, cx| {
18964 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18965 })
18966 })
18967 .ok()
18968 })
18969 .collect::<FuturesUnordered<_>>()
18970 }) else {
18971 return;
18972 };
18973
18974 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18975 if let Err(e) = pull_task {
18976 log::error!("Failed to update project diagnostics: {e:#}");
18977 }
18978 }
18979 })
18980 };
18981
18982 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18983 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18984
18985 Some(())
18986 }
18987
18988 pub fn set_selections_from_remote(
18989 &mut self,
18990 selections: Vec<Selection<Anchor>>,
18991 pending_selection: Option<Selection<Anchor>>,
18992 window: &mut Window,
18993 cx: &mut Context<Self>,
18994 ) {
18995 let old_cursor_position = self.selections.newest_anchor().head();
18996 self.selections
18997 .change_with(&self.display_snapshot(cx), |s| {
18998 s.select_anchors(selections);
18999 if let Some(pending_selection) = pending_selection {
19000 s.set_pending(pending_selection, SelectMode::Character);
19001 } else {
19002 s.clear_pending();
19003 }
19004 });
19005 self.selections_did_change(
19006 false,
19007 &old_cursor_position,
19008 SelectionEffects::default(),
19009 window,
19010 cx,
19011 );
19012 }
19013
19014 pub fn transact(
19015 &mut self,
19016 window: &mut Window,
19017 cx: &mut Context<Self>,
19018 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19019 ) -> Option<TransactionId> {
19020 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19021 this.start_transaction_at(Instant::now(), window, cx);
19022 update(this, window, cx);
19023 this.end_transaction_at(Instant::now(), cx)
19024 })
19025 }
19026
19027 pub fn start_transaction_at(
19028 &mut self,
19029 now: Instant,
19030 window: &mut Window,
19031 cx: &mut Context<Self>,
19032 ) -> Option<TransactionId> {
19033 self.end_selection(window, cx);
19034 if let Some(tx_id) = self
19035 .buffer
19036 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19037 {
19038 self.selection_history
19039 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19040 cx.emit(EditorEvent::TransactionBegun {
19041 transaction_id: tx_id,
19042 });
19043 Some(tx_id)
19044 } else {
19045 None
19046 }
19047 }
19048
19049 pub fn end_transaction_at(
19050 &mut self,
19051 now: Instant,
19052 cx: &mut Context<Self>,
19053 ) -> Option<TransactionId> {
19054 if let Some(transaction_id) = self
19055 .buffer
19056 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19057 {
19058 if let Some((_, end_selections)) =
19059 self.selection_history.transaction_mut(transaction_id)
19060 {
19061 *end_selections = Some(self.selections.disjoint_anchors_arc());
19062 } else {
19063 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19064 }
19065
19066 cx.emit(EditorEvent::Edited { transaction_id });
19067 Some(transaction_id)
19068 } else {
19069 None
19070 }
19071 }
19072
19073 pub fn modify_transaction_selection_history(
19074 &mut self,
19075 transaction_id: TransactionId,
19076 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19077 ) -> bool {
19078 self.selection_history
19079 .transaction_mut(transaction_id)
19080 .map(modify)
19081 .is_some()
19082 }
19083
19084 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19085 if self.selection_mark_mode {
19086 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19087 s.move_with(|_, sel| {
19088 sel.collapse_to(sel.head(), SelectionGoal::None);
19089 });
19090 })
19091 }
19092 self.selection_mark_mode = true;
19093 cx.notify();
19094 }
19095
19096 pub fn swap_selection_ends(
19097 &mut self,
19098 _: &actions::SwapSelectionEnds,
19099 window: &mut Window,
19100 cx: &mut Context<Self>,
19101 ) {
19102 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19103 s.move_with(|_, sel| {
19104 if sel.start != sel.end {
19105 sel.reversed = !sel.reversed
19106 }
19107 });
19108 });
19109 self.request_autoscroll(Autoscroll::newest(), cx);
19110 cx.notify();
19111 }
19112
19113 pub fn toggle_focus(
19114 workspace: &mut Workspace,
19115 _: &actions::ToggleFocus,
19116 window: &mut Window,
19117 cx: &mut Context<Workspace>,
19118 ) {
19119 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19120 return;
19121 };
19122 workspace.activate_item(&item, true, true, window, cx);
19123 }
19124
19125 pub fn toggle_fold(
19126 &mut self,
19127 _: &actions::ToggleFold,
19128 window: &mut Window,
19129 cx: &mut Context<Self>,
19130 ) {
19131 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19132 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19133 let selection = self.selections.newest::<Point>(&display_map);
19134
19135 let range = if selection.is_empty() {
19136 let point = selection.head().to_display_point(&display_map);
19137 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19138 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19139 .to_point(&display_map);
19140 start..end
19141 } else {
19142 selection.range()
19143 };
19144 if display_map.folds_in_range(range).next().is_some() {
19145 self.unfold_lines(&Default::default(), window, cx)
19146 } else {
19147 self.fold(&Default::default(), window, cx)
19148 }
19149 } else {
19150 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19151 let buffer_ids: HashSet<_> = self
19152 .selections
19153 .disjoint_anchor_ranges()
19154 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19155 .collect();
19156
19157 let should_unfold = buffer_ids
19158 .iter()
19159 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19160
19161 for buffer_id in buffer_ids {
19162 if should_unfold {
19163 self.unfold_buffer(buffer_id, cx);
19164 } else {
19165 self.fold_buffer(buffer_id, cx);
19166 }
19167 }
19168 }
19169 }
19170
19171 pub fn toggle_fold_recursive(
19172 &mut self,
19173 _: &actions::ToggleFoldRecursive,
19174 window: &mut Window,
19175 cx: &mut Context<Self>,
19176 ) {
19177 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19178
19179 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19180 let range = if selection.is_empty() {
19181 let point = selection.head().to_display_point(&display_map);
19182 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19183 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19184 .to_point(&display_map);
19185 start..end
19186 } else {
19187 selection.range()
19188 };
19189 if display_map.folds_in_range(range).next().is_some() {
19190 self.unfold_recursive(&Default::default(), window, cx)
19191 } else {
19192 self.fold_recursive(&Default::default(), window, cx)
19193 }
19194 }
19195
19196 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19197 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19198 let mut to_fold = Vec::new();
19199 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19200 let selections = self.selections.all_adjusted(&display_map);
19201
19202 for selection in selections {
19203 let range = selection.range().sorted();
19204 let buffer_start_row = range.start.row;
19205
19206 if range.start.row != range.end.row {
19207 let mut found = false;
19208 let mut row = range.start.row;
19209 while row <= range.end.row {
19210 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19211 {
19212 found = true;
19213 row = crease.range().end.row + 1;
19214 to_fold.push(crease);
19215 } else {
19216 row += 1
19217 }
19218 }
19219 if found {
19220 continue;
19221 }
19222 }
19223
19224 for row in (0..=range.start.row).rev() {
19225 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19226 && crease.range().end.row >= buffer_start_row
19227 {
19228 to_fold.push(crease);
19229 if row <= range.start.row {
19230 break;
19231 }
19232 }
19233 }
19234 }
19235
19236 self.fold_creases(to_fold, true, window, cx);
19237 } else {
19238 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19239 let buffer_ids = self
19240 .selections
19241 .disjoint_anchor_ranges()
19242 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19243 .collect::<HashSet<_>>();
19244 for buffer_id in buffer_ids {
19245 self.fold_buffer(buffer_id, cx);
19246 }
19247 }
19248 }
19249
19250 pub fn toggle_fold_all(
19251 &mut self,
19252 _: &actions::ToggleFoldAll,
19253 window: &mut Window,
19254 cx: &mut Context<Self>,
19255 ) {
19256 let has_folds = if self.buffer.read(cx).is_singleton() {
19257 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19258 let has_folds = display_map
19259 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19260 .next()
19261 .is_some();
19262 has_folds
19263 } else {
19264 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19265 let has_folds = buffer_ids
19266 .iter()
19267 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19268 has_folds
19269 };
19270
19271 if has_folds {
19272 self.unfold_all(&actions::UnfoldAll, window, cx);
19273 } else {
19274 self.fold_all(&actions::FoldAll, window, cx);
19275 }
19276 }
19277
19278 fn fold_at_level(
19279 &mut self,
19280 fold_at: &FoldAtLevel,
19281 window: &mut Window,
19282 cx: &mut Context<Self>,
19283 ) {
19284 if !self.buffer.read(cx).is_singleton() {
19285 return;
19286 }
19287
19288 let fold_at_level = fold_at.0;
19289 let snapshot = self.buffer.read(cx).snapshot(cx);
19290 let mut to_fold = Vec::new();
19291 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19292
19293 let row_ranges_to_keep: Vec<Range<u32>> = self
19294 .selections
19295 .all::<Point>(&self.display_snapshot(cx))
19296 .into_iter()
19297 .map(|sel| sel.start.row..sel.end.row)
19298 .collect();
19299
19300 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19301 while start_row < end_row {
19302 match self
19303 .snapshot(window, cx)
19304 .crease_for_buffer_row(MultiBufferRow(start_row))
19305 {
19306 Some(crease) => {
19307 let nested_start_row = crease.range().start.row + 1;
19308 let nested_end_row = crease.range().end.row;
19309
19310 if current_level < fold_at_level {
19311 stack.push((nested_start_row, nested_end_row, current_level + 1));
19312 } else if current_level == fold_at_level {
19313 // Fold iff there is no selection completely contained within the fold region
19314 if !row_ranges_to_keep.iter().any(|selection| {
19315 selection.end >= nested_start_row
19316 && selection.start <= nested_end_row
19317 }) {
19318 to_fold.push(crease);
19319 }
19320 }
19321
19322 start_row = nested_end_row + 1;
19323 }
19324 None => start_row += 1,
19325 }
19326 }
19327 }
19328
19329 self.fold_creases(to_fold, true, window, cx);
19330 }
19331
19332 pub fn fold_at_level_1(
19333 &mut self,
19334 _: &actions::FoldAtLevel1,
19335 window: &mut Window,
19336 cx: &mut Context<Self>,
19337 ) {
19338 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19339 }
19340
19341 pub fn fold_at_level_2(
19342 &mut self,
19343 _: &actions::FoldAtLevel2,
19344 window: &mut Window,
19345 cx: &mut Context<Self>,
19346 ) {
19347 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19348 }
19349
19350 pub fn fold_at_level_3(
19351 &mut self,
19352 _: &actions::FoldAtLevel3,
19353 window: &mut Window,
19354 cx: &mut Context<Self>,
19355 ) {
19356 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19357 }
19358
19359 pub fn fold_at_level_4(
19360 &mut self,
19361 _: &actions::FoldAtLevel4,
19362 window: &mut Window,
19363 cx: &mut Context<Self>,
19364 ) {
19365 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19366 }
19367
19368 pub fn fold_at_level_5(
19369 &mut self,
19370 _: &actions::FoldAtLevel5,
19371 window: &mut Window,
19372 cx: &mut Context<Self>,
19373 ) {
19374 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19375 }
19376
19377 pub fn fold_at_level_6(
19378 &mut self,
19379 _: &actions::FoldAtLevel6,
19380 window: &mut Window,
19381 cx: &mut Context<Self>,
19382 ) {
19383 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19384 }
19385
19386 pub fn fold_at_level_7(
19387 &mut self,
19388 _: &actions::FoldAtLevel7,
19389 window: &mut Window,
19390 cx: &mut Context<Self>,
19391 ) {
19392 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19393 }
19394
19395 pub fn fold_at_level_8(
19396 &mut self,
19397 _: &actions::FoldAtLevel8,
19398 window: &mut Window,
19399 cx: &mut Context<Self>,
19400 ) {
19401 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19402 }
19403
19404 pub fn fold_at_level_9(
19405 &mut self,
19406 _: &actions::FoldAtLevel9,
19407 window: &mut Window,
19408 cx: &mut Context<Self>,
19409 ) {
19410 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19411 }
19412
19413 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19414 if self.buffer.read(cx).is_singleton() {
19415 let mut fold_ranges = Vec::new();
19416 let snapshot = self.buffer.read(cx).snapshot(cx);
19417
19418 for row in 0..snapshot.max_row().0 {
19419 if let Some(foldable_range) = self
19420 .snapshot(window, cx)
19421 .crease_for_buffer_row(MultiBufferRow(row))
19422 {
19423 fold_ranges.push(foldable_range);
19424 }
19425 }
19426
19427 self.fold_creases(fold_ranges, true, window, cx);
19428 } else {
19429 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19430 editor
19431 .update_in(cx, |editor, _, cx| {
19432 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19433 editor.fold_buffer(buffer_id, cx);
19434 }
19435 })
19436 .ok();
19437 });
19438 }
19439 cx.emit(SearchEvent::ResultsCollapsedChanged(
19440 CollapseDirection::Collapsed,
19441 ));
19442 }
19443
19444 pub fn fold_function_bodies(
19445 &mut self,
19446 _: &actions::FoldFunctionBodies,
19447 window: &mut Window,
19448 cx: &mut Context<Self>,
19449 ) {
19450 let snapshot = self.buffer.read(cx).snapshot(cx);
19451
19452 let ranges = snapshot
19453 .text_object_ranges(
19454 MultiBufferOffset(0)..snapshot.len(),
19455 TreeSitterOptions::default(),
19456 )
19457 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19458 .collect::<Vec<_>>();
19459
19460 let creases = ranges
19461 .into_iter()
19462 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19463 .collect();
19464
19465 self.fold_creases(creases, true, window, cx);
19466 }
19467
19468 pub fn fold_recursive(
19469 &mut self,
19470 _: &actions::FoldRecursive,
19471 window: &mut Window,
19472 cx: &mut Context<Self>,
19473 ) {
19474 let mut to_fold = Vec::new();
19475 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19476 let selections = self.selections.all_adjusted(&display_map);
19477
19478 for selection in selections {
19479 let range = selection.range().sorted();
19480 let buffer_start_row = range.start.row;
19481
19482 if range.start.row != range.end.row {
19483 let mut found = false;
19484 for row in range.start.row..=range.end.row {
19485 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19486 found = true;
19487 to_fold.push(crease);
19488 }
19489 }
19490 if found {
19491 continue;
19492 }
19493 }
19494
19495 for row in (0..=range.start.row).rev() {
19496 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19497 if crease.range().end.row >= buffer_start_row {
19498 to_fold.push(crease);
19499 } else {
19500 break;
19501 }
19502 }
19503 }
19504 }
19505
19506 self.fold_creases(to_fold, true, window, cx);
19507 }
19508
19509 pub fn fold_at(
19510 &mut self,
19511 buffer_row: MultiBufferRow,
19512 window: &mut Window,
19513 cx: &mut Context<Self>,
19514 ) {
19515 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19516
19517 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19518 let autoscroll = self
19519 .selections
19520 .all::<Point>(&display_map)
19521 .iter()
19522 .any(|selection| crease.range().overlaps(&selection.range()));
19523
19524 self.fold_creases(vec![crease], autoscroll, window, cx);
19525 }
19526 }
19527
19528 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19529 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19530 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19531 let buffer = display_map.buffer_snapshot();
19532 let selections = self.selections.all::<Point>(&display_map);
19533 let ranges = selections
19534 .iter()
19535 .map(|s| {
19536 let range = s.display_range(&display_map).sorted();
19537 let mut start = range.start.to_point(&display_map);
19538 let mut end = range.end.to_point(&display_map);
19539 start.column = 0;
19540 end.column = buffer.line_len(MultiBufferRow(end.row));
19541 start..end
19542 })
19543 .collect::<Vec<_>>();
19544
19545 self.unfold_ranges(&ranges, true, true, cx);
19546 } else {
19547 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19548 let buffer_ids = self
19549 .selections
19550 .disjoint_anchor_ranges()
19551 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19552 .collect::<HashSet<_>>();
19553 for buffer_id in buffer_ids {
19554 self.unfold_buffer(buffer_id, cx);
19555 }
19556 }
19557 }
19558
19559 pub fn unfold_recursive(
19560 &mut self,
19561 _: &UnfoldRecursive,
19562 _window: &mut Window,
19563 cx: &mut Context<Self>,
19564 ) {
19565 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19566 let selections = self.selections.all::<Point>(&display_map);
19567 let ranges = selections
19568 .iter()
19569 .map(|s| {
19570 let mut range = s.display_range(&display_map).sorted();
19571 *range.start.column_mut() = 0;
19572 *range.end.column_mut() = display_map.line_len(range.end.row());
19573 let start = range.start.to_point(&display_map);
19574 let end = range.end.to_point(&display_map);
19575 start..end
19576 })
19577 .collect::<Vec<_>>();
19578
19579 self.unfold_ranges(&ranges, true, true, cx);
19580 }
19581
19582 pub fn unfold_at(
19583 &mut self,
19584 buffer_row: MultiBufferRow,
19585 _window: &mut Window,
19586 cx: &mut Context<Self>,
19587 ) {
19588 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19589
19590 let intersection_range = Point::new(buffer_row.0, 0)
19591 ..Point::new(
19592 buffer_row.0,
19593 display_map.buffer_snapshot().line_len(buffer_row),
19594 );
19595
19596 let autoscroll = self
19597 .selections
19598 .all::<Point>(&display_map)
19599 .iter()
19600 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19601
19602 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19603 }
19604
19605 pub fn unfold_all(
19606 &mut self,
19607 _: &actions::UnfoldAll,
19608 _window: &mut Window,
19609 cx: &mut Context<Self>,
19610 ) {
19611 if self.buffer.read(cx).is_singleton() {
19612 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19613 self.unfold_ranges(
19614 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19615 true,
19616 true,
19617 cx,
19618 );
19619 } else {
19620 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19621 editor
19622 .update(cx, |editor, cx| {
19623 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19624 editor.unfold_buffer(buffer_id, cx);
19625 }
19626 })
19627 .ok();
19628 });
19629 }
19630 cx.emit(SearchEvent::ResultsCollapsedChanged(
19631 CollapseDirection::Expanded,
19632 ));
19633 }
19634
19635 pub fn fold_selected_ranges(
19636 &mut self,
19637 _: &FoldSelectedRanges,
19638 window: &mut Window,
19639 cx: &mut Context<Self>,
19640 ) {
19641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19642 let selections = self.selections.all_adjusted(&display_map);
19643 let ranges = selections
19644 .into_iter()
19645 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19646 .collect::<Vec<_>>();
19647 self.fold_creases(ranges, true, window, cx);
19648 }
19649
19650 pub fn fold_ranges<T: ToOffset + Clone>(
19651 &mut self,
19652 ranges: Vec<Range<T>>,
19653 auto_scroll: bool,
19654 window: &mut Window,
19655 cx: &mut Context<Self>,
19656 ) {
19657 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19658 let ranges = ranges
19659 .into_iter()
19660 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19661 .collect::<Vec<_>>();
19662 self.fold_creases(ranges, auto_scroll, window, cx);
19663 }
19664
19665 pub fn fold_creases<T: ToOffset + Clone>(
19666 &mut self,
19667 creases: Vec<Crease<T>>,
19668 auto_scroll: bool,
19669 _window: &mut Window,
19670 cx: &mut Context<Self>,
19671 ) {
19672 if creases.is_empty() {
19673 return;
19674 }
19675
19676 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19677
19678 if auto_scroll {
19679 self.request_autoscroll(Autoscroll::fit(), cx);
19680 }
19681
19682 cx.notify();
19683
19684 self.scrollbar_marker_state.dirty = true;
19685 self.folds_did_change(cx);
19686 }
19687
19688 /// Removes any folds whose ranges intersect any of the given ranges.
19689 pub fn unfold_ranges<T: ToOffset + Clone>(
19690 &mut self,
19691 ranges: &[Range<T>],
19692 inclusive: bool,
19693 auto_scroll: bool,
19694 cx: &mut Context<Self>,
19695 ) {
19696 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19697 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19698 });
19699 self.folds_did_change(cx);
19700 }
19701
19702 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19703 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19704 return;
19705 }
19706
19707 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19708 self.display_map.update(cx, |display_map, cx| {
19709 display_map.fold_buffers([buffer_id], cx)
19710 });
19711
19712 let snapshot = self.display_snapshot(cx);
19713 self.selections.change_with(&snapshot, |selections| {
19714 selections.remove_selections_from_buffer(buffer_id);
19715 });
19716
19717 cx.emit(EditorEvent::BufferFoldToggled {
19718 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19719 folded: true,
19720 });
19721 cx.notify();
19722 }
19723
19724 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19725 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19726 return;
19727 }
19728 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19729 self.display_map.update(cx, |display_map, cx| {
19730 display_map.unfold_buffers([buffer_id], cx);
19731 });
19732 cx.emit(EditorEvent::BufferFoldToggled {
19733 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19734 folded: false,
19735 });
19736 cx.notify();
19737 }
19738
19739 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19740 self.display_map.read(cx).is_buffer_folded(buffer)
19741 }
19742
19743 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19744 self.display_map.read(cx).folded_buffers()
19745 }
19746
19747 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19748 self.display_map.update(cx, |display_map, cx| {
19749 display_map.disable_header_for_buffer(buffer_id, cx);
19750 });
19751 cx.notify();
19752 }
19753
19754 /// Removes any folds with the given ranges.
19755 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19756 &mut self,
19757 ranges: &[Range<T>],
19758 type_id: TypeId,
19759 auto_scroll: bool,
19760 cx: &mut Context<Self>,
19761 ) {
19762 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19763 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19764 });
19765 self.folds_did_change(cx);
19766 }
19767
19768 fn remove_folds_with<T: ToOffset + Clone>(
19769 &mut self,
19770 ranges: &[Range<T>],
19771 auto_scroll: bool,
19772 cx: &mut Context<Self>,
19773 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19774 ) {
19775 if ranges.is_empty() {
19776 return;
19777 }
19778
19779 let mut buffers_affected = HashSet::default();
19780 let multi_buffer = self.buffer().read(cx);
19781 for range in ranges {
19782 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19783 buffers_affected.insert(buffer.read(cx).remote_id());
19784 };
19785 }
19786
19787 self.display_map.update(cx, update);
19788
19789 if auto_scroll {
19790 self.request_autoscroll(Autoscroll::fit(), cx);
19791 }
19792
19793 cx.notify();
19794 self.scrollbar_marker_state.dirty = true;
19795 self.active_indent_guides_state.dirty = true;
19796 }
19797
19798 pub fn update_renderer_widths(
19799 &mut self,
19800 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19801 cx: &mut Context<Self>,
19802 ) -> bool {
19803 self.display_map
19804 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19805 }
19806
19807 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19808 self.display_map.read(cx).fold_placeholder.clone()
19809 }
19810
19811 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19812 self.buffer.update(cx, |buffer, cx| {
19813 buffer.set_all_diff_hunks_expanded(cx);
19814 });
19815 }
19816
19817 pub fn expand_all_diff_hunks(
19818 &mut self,
19819 _: &ExpandAllDiffHunks,
19820 _window: &mut Window,
19821 cx: &mut Context<Self>,
19822 ) {
19823 self.buffer.update(cx, |buffer, cx| {
19824 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19825 });
19826 }
19827
19828 pub fn collapse_all_diff_hunks(
19829 &mut self,
19830 _: &CollapseAllDiffHunks,
19831 _window: &mut Window,
19832 cx: &mut Context<Self>,
19833 ) {
19834 self.buffer.update(cx, |buffer, cx| {
19835 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19836 });
19837 }
19838
19839 pub fn toggle_selected_diff_hunks(
19840 &mut self,
19841 _: &ToggleSelectedDiffHunks,
19842 _window: &mut Window,
19843 cx: &mut Context<Self>,
19844 ) {
19845 let ranges: Vec<_> = self
19846 .selections
19847 .disjoint_anchors()
19848 .iter()
19849 .map(|s| s.range())
19850 .collect();
19851 self.toggle_diff_hunks_in_ranges(ranges, cx);
19852 }
19853
19854 pub fn diff_hunks_in_ranges<'a>(
19855 &'a self,
19856 ranges: &'a [Range<Anchor>],
19857 buffer: &'a MultiBufferSnapshot,
19858 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19859 ranges.iter().flat_map(move |range| {
19860 let end_excerpt_id = range.end.excerpt_id;
19861 let range = range.to_point(buffer);
19862 let mut peek_end = range.end;
19863 if range.end.row < buffer.max_row().0 {
19864 peek_end = Point::new(range.end.row + 1, 0);
19865 }
19866 buffer
19867 .diff_hunks_in_range(range.start..peek_end)
19868 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19869 })
19870 }
19871
19872 pub fn has_stageable_diff_hunks_in_ranges(
19873 &self,
19874 ranges: &[Range<Anchor>],
19875 snapshot: &MultiBufferSnapshot,
19876 ) -> bool {
19877 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19878 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19879 }
19880
19881 pub fn toggle_staged_selected_diff_hunks(
19882 &mut self,
19883 _: &::git::ToggleStaged,
19884 _: &mut Window,
19885 cx: &mut Context<Self>,
19886 ) {
19887 let snapshot = self.buffer.read(cx).snapshot(cx);
19888 let ranges: Vec<_> = self
19889 .selections
19890 .disjoint_anchors()
19891 .iter()
19892 .map(|s| s.range())
19893 .collect();
19894 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19895 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19896 }
19897
19898 pub fn set_render_diff_hunk_controls(
19899 &mut self,
19900 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19901 cx: &mut Context<Self>,
19902 ) {
19903 self.render_diff_hunk_controls = render_diff_hunk_controls;
19904 cx.notify();
19905 }
19906
19907 pub fn stage_and_next(
19908 &mut self,
19909 _: &::git::StageAndNext,
19910 window: &mut Window,
19911 cx: &mut Context<Self>,
19912 ) {
19913 self.do_stage_or_unstage_and_next(true, window, cx);
19914 }
19915
19916 pub fn unstage_and_next(
19917 &mut self,
19918 _: &::git::UnstageAndNext,
19919 window: &mut Window,
19920 cx: &mut Context<Self>,
19921 ) {
19922 self.do_stage_or_unstage_and_next(false, window, cx);
19923 }
19924
19925 pub fn stage_or_unstage_diff_hunks(
19926 &mut self,
19927 stage: bool,
19928 ranges: Vec<Range<Anchor>>,
19929 cx: &mut Context<Self>,
19930 ) {
19931 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19932 cx.spawn(async move |this, cx| {
19933 task.await?;
19934 this.update(cx, |this, cx| {
19935 let snapshot = this.buffer.read(cx).snapshot(cx);
19936 let chunk_by = this
19937 .diff_hunks_in_ranges(&ranges, &snapshot)
19938 .chunk_by(|hunk| hunk.buffer_id);
19939 for (buffer_id, hunks) in &chunk_by {
19940 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19941 }
19942 })
19943 })
19944 .detach_and_log_err(cx);
19945 }
19946
19947 fn save_buffers_for_ranges_if_needed(
19948 &mut self,
19949 ranges: &[Range<Anchor>],
19950 cx: &mut Context<Editor>,
19951 ) -> Task<Result<()>> {
19952 let multibuffer = self.buffer.read(cx);
19953 let snapshot = multibuffer.read(cx);
19954 let buffer_ids: HashSet<_> = ranges
19955 .iter()
19956 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19957 .collect();
19958 drop(snapshot);
19959
19960 let mut buffers = HashSet::default();
19961 for buffer_id in buffer_ids {
19962 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19963 let buffer = buffer_entity.read(cx);
19964 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19965 {
19966 buffers.insert(buffer_entity);
19967 }
19968 }
19969 }
19970
19971 if let Some(project) = &self.project {
19972 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19973 } else {
19974 Task::ready(Ok(()))
19975 }
19976 }
19977
19978 fn do_stage_or_unstage_and_next(
19979 &mut self,
19980 stage: bool,
19981 window: &mut Window,
19982 cx: &mut Context<Self>,
19983 ) {
19984 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19985
19986 if ranges.iter().any(|range| range.start != range.end) {
19987 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19988 return;
19989 }
19990
19991 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19992 let snapshot = self.snapshot(window, cx);
19993 let position = self
19994 .selections
19995 .newest::<Point>(&snapshot.display_snapshot)
19996 .head();
19997 let mut row = snapshot
19998 .buffer_snapshot()
19999 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20000 .find(|hunk| hunk.row_range.start.0 > position.row)
20001 .map(|hunk| hunk.row_range.start);
20002
20003 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20004 // Outside of the project diff editor, wrap around to the beginning.
20005 if !all_diff_hunks_expanded {
20006 row = row.or_else(|| {
20007 snapshot
20008 .buffer_snapshot()
20009 .diff_hunks_in_range(Point::zero()..position)
20010 .find(|hunk| hunk.row_range.end.0 < position.row)
20011 .map(|hunk| hunk.row_range.start)
20012 });
20013 }
20014
20015 if let Some(row) = row {
20016 let destination = Point::new(row.0, 0);
20017 let autoscroll = Autoscroll::center();
20018
20019 self.unfold_ranges(&[destination..destination], false, false, cx);
20020 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20021 s.select_ranges([destination..destination]);
20022 });
20023 }
20024 }
20025
20026 fn do_stage_or_unstage(
20027 &self,
20028 stage: bool,
20029 buffer_id: BufferId,
20030 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20031 cx: &mut App,
20032 ) -> Option<()> {
20033 let project = self.project()?;
20034 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20035 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20036 let buffer_snapshot = buffer.read(cx).snapshot();
20037 let file_exists = buffer_snapshot
20038 .file()
20039 .is_some_and(|file| file.disk_state().exists());
20040 diff.update(cx, |diff, cx| {
20041 diff.stage_or_unstage_hunks(
20042 stage,
20043 &hunks
20044 .map(|hunk| buffer_diff::DiffHunk {
20045 buffer_range: hunk.buffer_range,
20046 // We don't need to pass in word diffs here because they're only used for rendering and
20047 // this function changes internal state
20048 base_word_diffs: Vec::default(),
20049 buffer_word_diffs: Vec::default(),
20050 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20051 ..hunk.diff_base_byte_range.end.0,
20052 secondary_status: hunk.status.secondary,
20053 range: Point::zero()..Point::zero(), // unused
20054 })
20055 .collect::<Vec<_>>(),
20056 &buffer_snapshot,
20057 file_exists,
20058 cx,
20059 )
20060 });
20061 None
20062 }
20063
20064 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20065 let ranges: Vec<_> = self
20066 .selections
20067 .disjoint_anchors()
20068 .iter()
20069 .map(|s| s.range())
20070 .collect();
20071 self.buffer
20072 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20073 }
20074
20075 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20076 self.buffer.update(cx, |buffer, cx| {
20077 let ranges = vec![Anchor::min()..Anchor::max()];
20078 if !buffer.all_diff_hunks_expanded()
20079 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20080 {
20081 buffer.collapse_diff_hunks(ranges, cx);
20082 true
20083 } else {
20084 false
20085 }
20086 })
20087 }
20088
20089 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20090 if self.buffer.read(cx).all_diff_hunks_expanded() {
20091 return true;
20092 }
20093 let ranges = vec![Anchor::min()..Anchor::max()];
20094 self.buffer
20095 .read(cx)
20096 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20097 }
20098
20099 fn toggle_diff_hunks_in_ranges(
20100 &mut self,
20101 ranges: Vec<Range<Anchor>>,
20102 cx: &mut Context<Editor>,
20103 ) {
20104 self.buffer.update(cx, |buffer, cx| {
20105 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20106 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20107 })
20108 }
20109
20110 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20111 self.buffer.update(cx, |buffer, cx| {
20112 let snapshot = buffer.snapshot(cx);
20113 let excerpt_id = range.end.excerpt_id;
20114 let point_range = range.to_point(&snapshot);
20115 let expand = !buffer.single_hunk_is_expanded(range, cx);
20116 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20117 })
20118 }
20119
20120 pub(crate) fn apply_all_diff_hunks(
20121 &mut self,
20122 _: &ApplyAllDiffHunks,
20123 window: &mut Window,
20124 cx: &mut Context<Self>,
20125 ) {
20126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20127
20128 let buffers = self.buffer.read(cx).all_buffers();
20129 for branch_buffer in buffers {
20130 branch_buffer.update(cx, |branch_buffer, cx| {
20131 branch_buffer.merge_into_base(Vec::new(), cx);
20132 });
20133 }
20134
20135 if let Some(project) = self.project.clone() {
20136 self.save(
20137 SaveOptions {
20138 format: true,
20139 autosave: false,
20140 },
20141 project,
20142 window,
20143 cx,
20144 )
20145 .detach_and_log_err(cx);
20146 }
20147 }
20148
20149 pub(crate) fn apply_selected_diff_hunks(
20150 &mut self,
20151 _: &ApplyDiffHunk,
20152 window: &mut Window,
20153 cx: &mut Context<Self>,
20154 ) {
20155 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20156 let snapshot = self.snapshot(window, cx);
20157 let hunks = snapshot.hunks_for_ranges(
20158 self.selections
20159 .all(&snapshot.display_snapshot)
20160 .into_iter()
20161 .map(|selection| selection.range()),
20162 );
20163 let mut ranges_by_buffer = HashMap::default();
20164 self.transact(window, cx, |editor, _window, cx| {
20165 for hunk in hunks {
20166 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20167 ranges_by_buffer
20168 .entry(buffer.clone())
20169 .or_insert_with(Vec::new)
20170 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20171 }
20172 }
20173
20174 for (buffer, ranges) in ranges_by_buffer {
20175 buffer.update(cx, |buffer, cx| {
20176 buffer.merge_into_base(ranges, cx);
20177 });
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 fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20196 if hovered != self.gutter_hovered {
20197 self.gutter_hovered = hovered;
20198 cx.notify();
20199 }
20200 }
20201
20202 pub fn insert_blocks(
20203 &mut self,
20204 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20205 autoscroll: Option<Autoscroll>,
20206 cx: &mut Context<Self>,
20207 ) -> Vec<CustomBlockId> {
20208 let blocks = self
20209 .display_map
20210 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20211 if let Some(autoscroll) = autoscroll {
20212 self.request_autoscroll(autoscroll, cx);
20213 }
20214 cx.notify();
20215 blocks
20216 }
20217
20218 pub fn resize_blocks(
20219 &mut self,
20220 heights: HashMap<CustomBlockId, u32>,
20221 autoscroll: Option<Autoscroll>,
20222 cx: &mut Context<Self>,
20223 ) {
20224 self.display_map
20225 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20226 if let Some(autoscroll) = autoscroll {
20227 self.request_autoscroll(autoscroll, cx);
20228 }
20229 cx.notify();
20230 }
20231
20232 pub fn replace_blocks(
20233 &mut self,
20234 renderers: HashMap<CustomBlockId, RenderBlock>,
20235 autoscroll: Option<Autoscroll>,
20236 cx: &mut Context<Self>,
20237 ) {
20238 self.display_map
20239 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20240 if let Some(autoscroll) = autoscroll {
20241 self.request_autoscroll(autoscroll, cx);
20242 }
20243 cx.notify();
20244 }
20245
20246 pub fn remove_blocks(
20247 &mut self,
20248 block_ids: HashSet<CustomBlockId>,
20249 autoscroll: Option<Autoscroll>,
20250 cx: &mut Context<Self>,
20251 ) {
20252 self.display_map.update(cx, |display_map, cx| {
20253 display_map.remove_blocks(block_ids, cx)
20254 });
20255 if let Some(autoscroll) = autoscroll {
20256 self.request_autoscroll(autoscroll, cx);
20257 }
20258 cx.notify();
20259 }
20260
20261 pub fn row_for_block(
20262 &self,
20263 block_id: CustomBlockId,
20264 cx: &mut Context<Self>,
20265 ) -> Option<DisplayRow> {
20266 self.display_map
20267 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20268 }
20269
20270 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20271 self.focused_block = Some(focused_block);
20272 }
20273
20274 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20275 self.focused_block.take()
20276 }
20277
20278 pub fn insert_creases(
20279 &mut self,
20280 creases: impl IntoIterator<Item = Crease<Anchor>>,
20281 cx: &mut Context<Self>,
20282 ) -> Vec<CreaseId> {
20283 self.display_map
20284 .update(cx, |map, cx| map.insert_creases(creases, cx))
20285 }
20286
20287 pub fn remove_creases(
20288 &mut self,
20289 ids: impl IntoIterator<Item = CreaseId>,
20290 cx: &mut Context<Self>,
20291 ) -> Vec<(CreaseId, Range<Anchor>)> {
20292 self.display_map
20293 .update(cx, |map, cx| map.remove_creases(ids, cx))
20294 }
20295
20296 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20297 self.display_map
20298 .update(cx, |map, cx| map.snapshot(cx))
20299 .longest_row()
20300 }
20301
20302 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20303 self.display_map
20304 .update(cx, |map, cx| map.snapshot(cx))
20305 .max_point()
20306 }
20307
20308 pub fn text(&self, cx: &App) -> String {
20309 self.buffer.read(cx).read(cx).text()
20310 }
20311
20312 pub fn is_empty(&self, cx: &App) -> bool {
20313 self.buffer.read(cx).read(cx).is_empty()
20314 }
20315
20316 pub fn text_option(&self, cx: &App) -> Option<String> {
20317 let text = self.text(cx);
20318 let text = text.trim();
20319
20320 if text.is_empty() {
20321 return None;
20322 }
20323
20324 Some(text.to_string())
20325 }
20326
20327 pub fn set_text(
20328 &mut self,
20329 text: impl Into<Arc<str>>,
20330 window: &mut Window,
20331 cx: &mut Context<Self>,
20332 ) {
20333 self.transact(window, cx, |this, _, cx| {
20334 this.buffer
20335 .read(cx)
20336 .as_singleton()
20337 .expect("you can only call set_text on editors for singleton buffers")
20338 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20339 });
20340 }
20341
20342 pub fn display_text(&self, cx: &mut App) -> String {
20343 self.display_map
20344 .update(cx, |map, cx| map.snapshot(cx))
20345 .text()
20346 }
20347
20348 fn create_minimap(
20349 &self,
20350 minimap_settings: MinimapSettings,
20351 window: &mut Window,
20352 cx: &mut Context<Self>,
20353 ) -> Option<Entity<Self>> {
20354 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20355 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20356 }
20357
20358 fn initialize_new_minimap(
20359 &self,
20360 minimap_settings: MinimapSettings,
20361 window: &mut Window,
20362 cx: &mut Context<Self>,
20363 ) -> Entity<Self> {
20364 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20365 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20366
20367 let mut minimap = Editor::new_internal(
20368 EditorMode::Minimap {
20369 parent: cx.weak_entity(),
20370 },
20371 self.buffer.clone(),
20372 None,
20373 Some(self.display_map.clone()),
20374 window,
20375 cx,
20376 );
20377 minimap.scroll_manager.clone_state(&self.scroll_manager);
20378 minimap.set_text_style_refinement(TextStyleRefinement {
20379 font_size: Some(MINIMAP_FONT_SIZE),
20380 font_weight: Some(MINIMAP_FONT_WEIGHT),
20381 font_family: Some(MINIMAP_FONT_FAMILY),
20382 ..Default::default()
20383 });
20384 minimap.update_minimap_configuration(minimap_settings, cx);
20385 cx.new(|_| minimap)
20386 }
20387
20388 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20389 let current_line_highlight = minimap_settings
20390 .current_line_highlight
20391 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20392 self.set_current_line_highlight(Some(current_line_highlight));
20393 }
20394
20395 pub fn minimap(&self) -> Option<&Entity<Self>> {
20396 self.minimap
20397 .as_ref()
20398 .filter(|_| self.minimap_visibility.visible())
20399 }
20400
20401 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20402 let mut wrap_guides = smallvec![];
20403
20404 if self.show_wrap_guides == Some(false) {
20405 return wrap_guides;
20406 }
20407
20408 let settings = self.buffer.read(cx).language_settings(cx);
20409 if settings.show_wrap_guides {
20410 match self.soft_wrap_mode(cx) {
20411 SoftWrap::Column(soft_wrap) => {
20412 wrap_guides.push((soft_wrap as usize, true));
20413 }
20414 SoftWrap::Bounded(soft_wrap) => {
20415 wrap_guides.push((soft_wrap as usize, true));
20416 }
20417 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20418 }
20419 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20420 }
20421
20422 wrap_guides
20423 }
20424
20425 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20426 let settings = self.buffer.read(cx).language_settings(cx);
20427 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20428 match mode {
20429 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20430 SoftWrap::None
20431 }
20432 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20433 language_settings::SoftWrap::PreferredLineLength => {
20434 SoftWrap::Column(settings.preferred_line_length)
20435 }
20436 language_settings::SoftWrap::Bounded => {
20437 SoftWrap::Bounded(settings.preferred_line_length)
20438 }
20439 }
20440 }
20441
20442 pub fn set_soft_wrap_mode(
20443 &mut self,
20444 mode: language_settings::SoftWrap,
20445
20446 cx: &mut Context<Self>,
20447 ) {
20448 self.soft_wrap_mode_override = Some(mode);
20449 cx.notify();
20450 }
20451
20452 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20453 self.hard_wrap = hard_wrap;
20454 cx.notify();
20455 }
20456
20457 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20458 self.text_style_refinement = Some(style);
20459 }
20460
20461 /// called by the Element so we know what style we were most recently rendered with.
20462 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20463 // We intentionally do not inform the display map about the minimap style
20464 // so that wrapping is not recalculated and stays consistent for the editor
20465 // and its linked minimap.
20466 if !self.mode.is_minimap() {
20467 let font = style.text.font();
20468 let font_size = style.text.font_size.to_pixels(window.rem_size());
20469 let display_map = self
20470 .placeholder_display_map
20471 .as_ref()
20472 .filter(|_| self.is_empty(cx))
20473 .unwrap_or(&self.display_map);
20474
20475 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20476 }
20477 self.style = Some(style);
20478 }
20479
20480 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20481 if self.style.is_none() {
20482 self.style = Some(self.create_style(cx));
20483 }
20484 self.style.as_ref().unwrap()
20485 }
20486
20487 // Called by the element. This method is not designed to be called outside of the editor
20488 // element's layout code because it does not notify when rewrapping is computed synchronously.
20489 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20490 if self.is_empty(cx) {
20491 self.placeholder_display_map
20492 .as_ref()
20493 .map_or(false, |display_map| {
20494 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20495 })
20496 } else {
20497 self.display_map
20498 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20499 }
20500 }
20501
20502 pub fn set_soft_wrap(&mut self) {
20503 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20504 }
20505
20506 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20507 if self.soft_wrap_mode_override.is_some() {
20508 self.soft_wrap_mode_override.take();
20509 } else {
20510 let soft_wrap = match self.soft_wrap_mode(cx) {
20511 SoftWrap::GitDiff => return,
20512 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20513 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20514 language_settings::SoftWrap::None
20515 }
20516 };
20517 self.soft_wrap_mode_override = Some(soft_wrap);
20518 }
20519 cx.notify();
20520 }
20521
20522 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20523 let Some(workspace) = self.workspace() else {
20524 return;
20525 };
20526 let fs = workspace.read(cx).app_state().fs.clone();
20527 let current_show = TabBarSettings::get_global(cx).show;
20528 update_settings_file(fs, cx, move |setting, _| {
20529 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20530 });
20531 }
20532
20533 pub fn toggle_indent_guides(
20534 &mut self,
20535 _: &ToggleIndentGuides,
20536 _: &mut Window,
20537 cx: &mut Context<Self>,
20538 ) {
20539 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20540 self.buffer
20541 .read(cx)
20542 .language_settings(cx)
20543 .indent_guides
20544 .enabled
20545 });
20546 self.show_indent_guides = Some(!currently_enabled);
20547 cx.notify();
20548 }
20549
20550 fn should_show_indent_guides(&self) -> Option<bool> {
20551 self.show_indent_guides
20552 }
20553
20554 pub fn disable_indent_guides_for_buffer(
20555 &mut self,
20556 buffer_id: BufferId,
20557 cx: &mut Context<Self>,
20558 ) {
20559 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20560 cx.notify();
20561 }
20562
20563 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20564 self.buffers_with_disabled_indent_guides
20565 .contains(&buffer_id)
20566 }
20567
20568 pub fn toggle_line_numbers(
20569 &mut self,
20570 _: &ToggleLineNumbers,
20571 _: &mut Window,
20572 cx: &mut Context<Self>,
20573 ) {
20574 let mut editor_settings = EditorSettings::get_global(cx).clone();
20575 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20576 EditorSettings::override_global(editor_settings, cx);
20577 }
20578
20579 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20580 if let Some(show_line_numbers) = self.show_line_numbers {
20581 return show_line_numbers;
20582 }
20583 EditorSettings::get_global(cx).gutter.line_numbers
20584 }
20585
20586 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
20587 match (
20588 self.use_relative_line_numbers,
20589 EditorSettings::get_global(cx).relative_line_numbers,
20590 ) {
20591 (None, setting) => setting,
20592 (Some(false), _) => RelativeLineNumbers::Disabled,
20593 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20594 (Some(true), _) => RelativeLineNumbers::Enabled,
20595 }
20596 }
20597
20598 pub fn toggle_relative_line_numbers(
20599 &mut self,
20600 _: &ToggleRelativeLineNumbers,
20601 _: &mut Window,
20602 cx: &mut Context<Self>,
20603 ) {
20604 let is_relative = self.relative_line_numbers(cx);
20605 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20606 }
20607
20608 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20609 self.use_relative_line_numbers = is_relative;
20610 cx.notify();
20611 }
20612
20613 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20614 self.show_gutter = show_gutter;
20615 cx.notify();
20616 }
20617
20618 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20619 self.show_scrollbars = ScrollbarAxes {
20620 horizontal: show,
20621 vertical: show,
20622 };
20623 cx.notify();
20624 }
20625
20626 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20627 self.show_scrollbars.vertical = show;
20628 cx.notify();
20629 }
20630
20631 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20632 self.show_scrollbars.horizontal = show;
20633 cx.notify();
20634 }
20635
20636 pub fn set_minimap_visibility(
20637 &mut self,
20638 minimap_visibility: MinimapVisibility,
20639 window: &mut Window,
20640 cx: &mut Context<Self>,
20641 ) {
20642 if self.minimap_visibility != minimap_visibility {
20643 if minimap_visibility.visible() && self.minimap.is_none() {
20644 let minimap_settings = EditorSettings::get_global(cx).minimap;
20645 self.minimap =
20646 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20647 }
20648 self.minimap_visibility = minimap_visibility;
20649 cx.notify();
20650 }
20651 }
20652
20653 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20654 self.set_show_scrollbars(false, cx);
20655 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20656 }
20657
20658 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20659 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20660 }
20661
20662 /// Normally the text in full mode and auto height editors is padded on the
20663 /// left side by roughly half a character width for improved hit testing.
20664 ///
20665 /// Use this method to disable this for cases where this is not wanted (e.g.
20666 /// if you want to align the editor text with some other text above or below)
20667 /// or if you want to add this padding to single-line editors.
20668 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20669 self.offset_content = offset_content;
20670 cx.notify();
20671 }
20672
20673 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20674 self.show_line_numbers = Some(show_line_numbers);
20675 cx.notify();
20676 }
20677
20678 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20679 self.disable_expand_excerpt_buttons = true;
20680 cx.notify();
20681 }
20682
20683 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
20684 self.delegate_expand_excerpts = delegate;
20685 }
20686
20687 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20688 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20689 cx.notify();
20690 }
20691
20692 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20693 self.show_code_actions = Some(show_code_actions);
20694 cx.notify();
20695 }
20696
20697 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20698 self.show_runnables = Some(show_runnables);
20699 cx.notify();
20700 }
20701
20702 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20703 self.show_breakpoints = Some(show_breakpoints);
20704 cx.notify();
20705 }
20706
20707 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20708 if self.display_map.read(cx).masked != masked {
20709 self.display_map.update(cx, |map, _| map.masked = masked);
20710 }
20711 cx.notify()
20712 }
20713
20714 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20715 self.show_wrap_guides = Some(show_wrap_guides);
20716 cx.notify();
20717 }
20718
20719 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20720 self.show_indent_guides = Some(show_indent_guides);
20721 cx.notify();
20722 }
20723
20724 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20725 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20726 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20727 && let Some(dir) = file.abs_path(cx).parent()
20728 {
20729 return Some(dir.to_owned());
20730 }
20731 }
20732
20733 None
20734 }
20735
20736 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20737 self.active_excerpt(cx)?
20738 .1
20739 .read(cx)
20740 .file()
20741 .and_then(|f| f.as_local())
20742 }
20743
20744 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20745 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20746 let buffer = buffer.read(cx);
20747 if let Some(project_path) = buffer.project_path(cx) {
20748 let project = self.project()?.read(cx);
20749 project.absolute_path(&project_path, cx)
20750 } else {
20751 buffer
20752 .file()
20753 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20754 }
20755 })
20756 }
20757
20758 pub fn reveal_in_finder(
20759 &mut self,
20760 _: &RevealInFileManager,
20761 _window: &mut Window,
20762 cx: &mut Context<Self>,
20763 ) {
20764 if let Some(target) = self.target_file(cx) {
20765 cx.reveal_path(&target.abs_path(cx));
20766 }
20767 }
20768
20769 pub fn copy_path(
20770 &mut self,
20771 _: &zed_actions::workspace::CopyPath,
20772 _window: &mut Window,
20773 cx: &mut Context<Self>,
20774 ) {
20775 if let Some(path) = self.target_file_abs_path(cx)
20776 && let Some(path) = path.to_str()
20777 {
20778 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20779 } else {
20780 cx.propagate();
20781 }
20782 }
20783
20784 pub fn copy_relative_path(
20785 &mut self,
20786 _: &zed_actions::workspace::CopyRelativePath,
20787 _window: &mut Window,
20788 cx: &mut Context<Self>,
20789 ) {
20790 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20791 let project = self.project()?.read(cx);
20792 let path = buffer.read(cx).file()?.path();
20793 let path = path.display(project.path_style(cx));
20794 Some(path)
20795 }) {
20796 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20797 } else {
20798 cx.propagate();
20799 }
20800 }
20801
20802 /// Returns the project path for the editor's buffer, if any buffer is
20803 /// opened in the editor.
20804 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20805 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20806 buffer.read(cx).project_path(cx)
20807 } else {
20808 None
20809 }
20810 }
20811
20812 // Returns true if the editor handled a go-to-line request
20813 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20814 maybe!({
20815 let breakpoint_store = self.breakpoint_store.as_ref()?;
20816
20817 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20818 else {
20819 self.clear_row_highlights::<ActiveDebugLine>();
20820 return None;
20821 };
20822
20823 let position = active_stack_frame.position;
20824 let buffer_id = position.buffer_id?;
20825 let snapshot = self
20826 .project
20827 .as_ref()?
20828 .read(cx)
20829 .buffer_for_id(buffer_id, cx)?
20830 .read(cx)
20831 .snapshot();
20832
20833 let mut handled = false;
20834 for (id, ExcerptRange { context, .. }) in
20835 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20836 {
20837 if context.start.cmp(&position, &snapshot).is_ge()
20838 || context.end.cmp(&position, &snapshot).is_lt()
20839 {
20840 continue;
20841 }
20842 let snapshot = self.buffer.read(cx).snapshot(cx);
20843 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20844
20845 handled = true;
20846 self.clear_row_highlights::<ActiveDebugLine>();
20847
20848 self.go_to_line::<ActiveDebugLine>(
20849 multibuffer_anchor,
20850 Some(cx.theme().colors().editor_debugger_active_line_background),
20851 window,
20852 cx,
20853 );
20854
20855 cx.notify();
20856 }
20857
20858 handled.then_some(())
20859 })
20860 .is_some()
20861 }
20862
20863 pub fn copy_file_name_without_extension(
20864 &mut self,
20865 _: &CopyFileNameWithoutExtension,
20866 _: &mut Window,
20867 cx: &mut Context<Self>,
20868 ) {
20869 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20870 let file = buffer.read(cx).file()?;
20871 file.path().file_stem()
20872 }) {
20873 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20874 }
20875 }
20876
20877 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20878 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20879 let file = buffer.read(cx).file()?;
20880 Some(file.file_name(cx))
20881 }) {
20882 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20883 }
20884 }
20885
20886 pub fn toggle_git_blame(
20887 &mut self,
20888 _: &::git::Blame,
20889 window: &mut Window,
20890 cx: &mut Context<Self>,
20891 ) {
20892 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20893
20894 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20895 self.start_git_blame(true, window, cx);
20896 }
20897
20898 cx.notify();
20899 }
20900
20901 pub fn toggle_git_blame_inline(
20902 &mut self,
20903 _: &ToggleGitBlameInline,
20904 window: &mut Window,
20905 cx: &mut Context<Self>,
20906 ) {
20907 self.toggle_git_blame_inline_internal(true, window, cx);
20908 cx.notify();
20909 }
20910
20911 pub fn open_git_blame_commit(
20912 &mut self,
20913 _: &OpenGitBlameCommit,
20914 window: &mut Window,
20915 cx: &mut Context<Self>,
20916 ) {
20917 self.open_git_blame_commit_internal(window, cx);
20918 }
20919
20920 fn open_git_blame_commit_internal(
20921 &mut self,
20922 window: &mut Window,
20923 cx: &mut Context<Self>,
20924 ) -> Option<()> {
20925 let blame = self.blame.as_ref()?;
20926 let snapshot = self.snapshot(window, cx);
20927 let cursor = self
20928 .selections
20929 .newest::<Point>(&snapshot.display_snapshot)
20930 .head();
20931 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20932 let (_, blame_entry) = blame
20933 .update(cx, |blame, cx| {
20934 blame
20935 .blame_for_rows(
20936 &[RowInfo {
20937 buffer_id: Some(buffer.remote_id()),
20938 buffer_row: Some(point.row),
20939 ..Default::default()
20940 }],
20941 cx,
20942 )
20943 .next()
20944 })
20945 .flatten()?;
20946 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20947 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20948 let workspace = self.workspace()?.downgrade();
20949 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20950 None
20951 }
20952
20953 pub fn git_blame_inline_enabled(&self) -> bool {
20954 self.git_blame_inline_enabled
20955 }
20956
20957 pub fn toggle_selection_menu(
20958 &mut self,
20959 _: &ToggleSelectionMenu,
20960 _: &mut Window,
20961 cx: &mut Context<Self>,
20962 ) {
20963 self.show_selection_menu = self
20964 .show_selection_menu
20965 .map(|show_selections_menu| !show_selections_menu)
20966 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20967
20968 cx.notify();
20969 }
20970
20971 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20972 self.show_selection_menu
20973 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20974 }
20975
20976 fn start_git_blame(
20977 &mut self,
20978 user_triggered: bool,
20979 window: &mut Window,
20980 cx: &mut Context<Self>,
20981 ) {
20982 if let Some(project) = self.project() {
20983 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20984 && buffer.read(cx).file().is_none()
20985 {
20986 return;
20987 }
20988
20989 let focused = self.focus_handle(cx).contains_focused(window, cx);
20990
20991 let project = project.clone();
20992 let blame = cx
20993 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20994 self.blame_subscription =
20995 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20996 self.blame = Some(blame);
20997 }
20998 }
20999
21000 fn toggle_git_blame_inline_internal(
21001 &mut self,
21002 user_triggered: bool,
21003 window: &mut Window,
21004 cx: &mut Context<Self>,
21005 ) {
21006 if self.git_blame_inline_enabled {
21007 self.git_blame_inline_enabled = false;
21008 self.show_git_blame_inline = false;
21009 self.show_git_blame_inline_delay_task.take();
21010 } else {
21011 self.git_blame_inline_enabled = true;
21012 self.start_git_blame_inline(user_triggered, window, cx);
21013 }
21014
21015 cx.notify();
21016 }
21017
21018 fn start_git_blame_inline(
21019 &mut self,
21020 user_triggered: bool,
21021 window: &mut Window,
21022 cx: &mut Context<Self>,
21023 ) {
21024 self.start_git_blame(user_triggered, window, cx);
21025
21026 if ProjectSettings::get_global(cx)
21027 .git
21028 .inline_blame_delay()
21029 .is_some()
21030 {
21031 self.start_inline_blame_timer(window, cx);
21032 } else {
21033 self.show_git_blame_inline = true
21034 }
21035 }
21036
21037 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
21038 self.blame.as_ref()
21039 }
21040
21041 pub fn show_git_blame_gutter(&self) -> bool {
21042 self.show_git_blame_gutter
21043 }
21044
21045 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
21046 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
21047 }
21048
21049 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
21050 self.show_git_blame_inline
21051 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
21052 && !self.newest_selection_head_on_empty_line(cx)
21053 && self.has_blame_entries(cx)
21054 }
21055
21056 fn has_blame_entries(&self, cx: &App) -> bool {
21057 self.blame()
21058 .is_some_and(|blame| blame.read(cx).has_generated_entries())
21059 }
21060
21061 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
21062 let cursor_anchor = self.selections.newest_anchor().head();
21063
21064 let snapshot = self.buffer.read(cx).snapshot(cx);
21065 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
21066
21067 snapshot.line_len(buffer_row) == 0
21068 }
21069
21070 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
21071 let buffer_and_selection = maybe!({
21072 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
21073 let selection_range = selection.range();
21074
21075 let multi_buffer = self.buffer().read(cx);
21076 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21077 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
21078
21079 let (buffer, range, _) = if selection.reversed {
21080 buffer_ranges.first()
21081 } else {
21082 buffer_ranges.last()
21083 }?;
21084
21085 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
21086 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
21087
21088 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
21089 let selection = start_row_in_buffer..end_row_in_buffer;
21090
21091 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
21092 };
21093
21094 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
21095
21096 Some((
21097 multi_buffer.buffer(buffer.remote_id()).unwrap(),
21098 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, Bias::Left, buffer)
21099 ..buffer_diff_snapshot.row_to_base_text_row(
21100 end_row_in_buffer,
21101 Bias::Left,
21102 buffer,
21103 ),
21104 ))
21105 });
21106
21107 let Some((buffer, selection)) = buffer_and_selection else {
21108 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
21109 };
21110
21111 let Some(project) = self.project() else {
21112 return Task::ready(Err(anyhow!("editor does not have project")));
21113 };
21114
21115 project.update(cx, |project, cx| {
21116 project.get_permalink_to_line(&buffer, selection, cx)
21117 })
21118 }
21119
21120 pub fn copy_permalink_to_line(
21121 &mut self,
21122 _: &CopyPermalinkToLine,
21123 window: &mut Window,
21124 cx: &mut Context<Self>,
21125 ) {
21126 let permalink_task = self.get_permalink_to_line(cx);
21127 let workspace = self.workspace();
21128
21129 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21130 Ok(permalink) => {
21131 cx.update(|_, cx| {
21132 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21133 })
21134 .ok();
21135 }
21136 Err(err) => {
21137 let message = format!("Failed to copy permalink: {err}");
21138
21139 anyhow::Result::<()>::Err(err).log_err();
21140
21141 if let Some(workspace) = workspace {
21142 workspace
21143 .update_in(cx, |workspace, _, cx| {
21144 struct CopyPermalinkToLine;
21145
21146 workspace.show_toast(
21147 Toast::new(
21148 NotificationId::unique::<CopyPermalinkToLine>(),
21149 message,
21150 ),
21151 cx,
21152 )
21153 })
21154 .ok();
21155 }
21156 }
21157 })
21158 .detach();
21159 }
21160
21161 pub fn copy_file_location(
21162 &mut self,
21163 _: &CopyFileLocation,
21164 _: &mut Window,
21165 cx: &mut Context<Self>,
21166 ) {
21167 let selection = self
21168 .selections
21169 .newest::<Point>(&self.display_snapshot(cx))
21170 .start
21171 .row
21172 + 1;
21173 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21174 let project = self.project()?.read(cx);
21175 let file = buffer.read(cx).file()?;
21176 let path = file.path().display(project.path_style(cx));
21177
21178 Some(format!("{path}:{selection}"))
21179 }) {
21180 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21181 }
21182 }
21183
21184 pub fn open_permalink_to_line(
21185 &mut self,
21186 _: &OpenPermalinkToLine,
21187 window: &mut Window,
21188 cx: &mut Context<Self>,
21189 ) {
21190 let permalink_task = self.get_permalink_to_line(cx);
21191 let workspace = self.workspace();
21192
21193 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21194 Ok(permalink) => {
21195 cx.update(|_, cx| {
21196 cx.open_url(permalink.as_ref());
21197 })
21198 .ok();
21199 }
21200 Err(err) => {
21201 let message = format!("Failed to open permalink: {err}");
21202
21203 anyhow::Result::<()>::Err(err).log_err();
21204
21205 if let Some(workspace) = workspace {
21206 workspace.update(cx, |workspace, cx| {
21207 struct OpenPermalinkToLine;
21208
21209 workspace.show_toast(
21210 Toast::new(NotificationId::unique::<OpenPermalinkToLine>(), message),
21211 cx,
21212 )
21213 });
21214 }
21215 }
21216 })
21217 .detach();
21218 }
21219
21220 pub fn insert_uuid_v4(
21221 &mut self,
21222 _: &InsertUuidV4,
21223 window: &mut Window,
21224 cx: &mut Context<Self>,
21225 ) {
21226 self.insert_uuid(UuidVersion::V4, window, cx);
21227 }
21228
21229 pub fn insert_uuid_v7(
21230 &mut self,
21231 _: &InsertUuidV7,
21232 window: &mut Window,
21233 cx: &mut Context<Self>,
21234 ) {
21235 self.insert_uuid(UuidVersion::V7, window, cx);
21236 }
21237
21238 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21239 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21240 self.transact(window, cx, |this, window, cx| {
21241 let edits = this
21242 .selections
21243 .all::<Point>(&this.display_snapshot(cx))
21244 .into_iter()
21245 .map(|selection| {
21246 let uuid = match version {
21247 UuidVersion::V4 => uuid::Uuid::new_v4(),
21248 UuidVersion::V7 => uuid::Uuid::now_v7(),
21249 };
21250
21251 (selection.range(), uuid.to_string())
21252 });
21253 this.edit(edits, cx);
21254 this.refresh_edit_prediction(true, false, window, cx);
21255 });
21256 }
21257
21258 pub fn open_selections_in_multibuffer(
21259 &mut self,
21260 _: &OpenSelectionsInMultibuffer,
21261 window: &mut Window,
21262 cx: &mut Context<Self>,
21263 ) {
21264 let multibuffer = self.buffer.read(cx);
21265
21266 let Some(buffer) = multibuffer.as_singleton() else {
21267 return;
21268 };
21269
21270 let Some(workspace) = self.workspace() else {
21271 return;
21272 };
21273
21274 let title = multibuffer.title(cx).to_string();
21275
21276 let locations = self
21277 .selections
21278 .all_anchors(&self.display_snapshot(cx))
21279 .iter()
21280 .map(|selection| {
21281 (
21282 buffer.clone(),
21283 (selection.start.text_anchor..selection.end.text_anchor)
21284 .to_point(buffer.read(cx)),
21285 )
21286 })
21287 .into_group_map();
21288
21289 cx.spawn_in(window, async move |_, cx| {
21290 workspace.update_in(cx, |workspace, window, cx| {
21291 Self::open_locations_in_multibuffer(
21292 workspace,
21293 locations,
21294 format!("Selections for '{title}'"),
21295 false,
21296 false,
21297 MultibufferSelectionMode::All,
21298 window,
21299 cx,
21300 );
21301 })
21302 })
21303 .detach();
21304 }
21305
21306 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21307 /// last highlight added will be used.
21308 ///
21309 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21310 pub fn highlight_rows<T: 'static>(
21311 &mut self,
21312 range: Range<Anchor>,
21313 color: Hsla,
21314 options: RowHighlightOptions,
21315 cx: &mut Context<Self>,
21316 ) {
21317 let snapshot = self.buffer().read(cx).snapshot(cx);
21318 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21319 let ix = row_highlights.binary_search_by(|highlight| {
21320 Ordering::Equal
21321 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21322 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21323 });
21324
21325 if let Err(mut ix) = ix {
21326 let index = post_inc(&mut self.highlight_order);
21327
21328 // If this range intersects with the preceding highlight, then merge it with
21329 // the preceding highlight. Otherwise insert a new highlight.
21330 let mut merged = false;
21331 if ix > 0 {
21332 let prev_highlight = &mut row_highlights[ix - 1];
21333 if prev_highlight
21334 .range
21335 .end
21336 .cmp(&range.start, &snapshot)
21337 .is_ge()
21338 {
21339 ix -= 1;
21340 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21341 prev_highlight.range.end = range.end;
21342 }
21343 merged = true;
21344 prev_highlight.index = index;
21345 prev_highlight.color = color;
21346 prev_highlight.options = options;
21347 }
21348 }
21349
21350 if !merged {
21351 row_highlights.insert(
21352 ix,
21353 RowHighlight {
21354 range,
21355 index,
21356 color,
21357 options,
21358 type_id: TypeId::of::<T>(),
21359 },
21360 );
21361 }
21362
21363 // If any of the following highlights intersect with this one, merge them.
21364 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21365 let highlight = &row_highlights[ix];
21366 if next_highlight
21367 .range
21368 .start
21369 .cmp(&highlight.range.end, &snapshot)
21370 .is_le()
21371 {
21372 if next_highlight
21373 .range
21374 .end
21375 .cmp(&highlight.range.end, &snapshot)
21376 .is_gt()
21377 {
21378 row_highlights[ix].range.end = next_highlight.range.end;
21379 }
21380 row_highlights.remove(ix + 1);
21381 } else {
21382 break;
21383 }
21384 }
21385 }
21386 }
21387
21388 /// Remove any highlighted row ranges of the given type that intersect the
21389 /// given ranges.
21390 pub fn remove_highlighted_rows<T: 'static>(
21391 &mut self,
21392 ranges_to_remove: Vec<Range<Anchor>>,
21393 cx: &mut Context<Self>,
21394 ) {
21395 let snapshot = self.buffer().read(cx).snapshot(cx);
21396 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21397 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21398 row_highlights.retain(|highlight| {
21399 while let Some(range_to_remove) = ranges_to_remove.peek() {
21400 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21401 Ordering::Less | Ordering::Equal => {
21402 ranges_to_remove.next();
21403 }
21404 Ordering::Greater => {
21405 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21406 Ordering::Less | Ordering::Equal => {
21407 return false;
21408 }
21409 Ordering::Greater => break,
21410 }
21411 }
21412 }
21413 }
21414
21415 true
21416 })
21417 }
21418
21419 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21420 pub fn clear_row_highlights<T: 'static>(&mut self) {
21421 self.highlighted_rows.remove(&TypeId::of::<T>());
21422 }
21423
21424 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21425 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21426 self.highlighted_rows
21427 .get(&TypeId::of::<T>())
21428 .map_or(&[] as &[_], |vec| vec.as_slice())
21429 .iter()
21430 .map(|highlight| (highlight.range.clone(), highlight.color))
21431 }
21432
21433 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21434 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21435 /// Allows to ignore certain kinds of highlights.
21436 pub fn highlighted_display_rows(
21437 &self,
21438 window: &mut Window,
21439 cx: &mut App,
21440 ) -> BTreeMap<DisplayRow, LineHighlight> {
21441 let snapshot = self.snapshot(window, cx);
21442 let mut used_highlight_orders = HashMap::default();
21443 self.highlighted_rows
21444 .iter()
21445 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21446 .fold(
21447 BTreeMap::<DisplayRow, LineHighlight>::new(),
21448 |mut unique_rows, highlight| {
21449 let start = highlight.range.start.to_display_point(&snapshot);
21450 let end = highlight.range.end.to_display_point(&snapshot);
21451 let start_row = start.row().0;
21452 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21453 {
21454 end.row().0.saturating_sub(1)
21455 } else {
21456 end.row().0
21457 };
21458 for row in start_row..=end_row {
21459 let used_index =
21460 used_highlight_orders.entry(row).or_insert(highlight.index);
21461 if highlight.index >= *used_index {
21462 *used_index = highlight.index;
21463 unique_rows.insert(
21464 DisplayRow(row),
21465 LineHighlight {
21466 include_gutter: highlight.options.include_gutter,
21467 border: None,
21468 background: highlight.color.into(),
21469 type_id: Some(highlight.type_id),
21470 },
21471 );
21472 }
21473 }
21474 unique_rows
21475 },
21476 )
21477 }
21478
21479 pub fn highlighted_display_row_for_autoscroll(
21480 &self,
21481 snapshot: &DisplaySnapshot,
21482 ) -> Option<DisplayRow> {
21483 self.highlighted_rows
21484 .values()
21485 .flat_map(|highlighted_rows| highlighted_rows.iter())
21486 .filter_map(|highlight| {
21487 if highlight.options.autoscroll {
21488 Some(highlight.range.start.to_display_point(snapshot).row())
21489 } else {
21490 None
21491 }
21492 })
21493 .min()
21494 }
21495
21496 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21497 self.highlight_background::<SearchWithinRange>(
21498 ranges,
21499 |_, colors| colors.colors().editor_document_highlight_read_background,
21500 cx,
21501 )
21502 }
21503
21504 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21505 self.breadcrumb_header = Some(new_header);
21506 }
21507
21508 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21509 self.clear_background_highlights::<SearchWithinRange>(cx);
21510 }
21511
21512 pub fn highlight_background<T: 'static>(
21513 &mut self,
21514 ranges: &[Range<Anchor>],
21515 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21516 cx: &mut Context<Self>,
21517 ) {
21518 self.background_highlights.insert(
21519 HighlightKey::Type(TypeId::of::<T>()),
21520 (Arc::new(color_fetcher), Arc::from(ranges)),
21521 );
21522 self.scrollbar_marker_state.dirty = true;
21523 cx.notify();
21524 }
21525
21526 pub fn highlight_background_key<T: 'static>(
21527 &mut self,
21528 key: usize,
21529 ranges: &[Range<Anchor>],
21530 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21531 cx: &mut Context<Self>,
21532 ) {
21533 self.background_highlights.insert(
21534 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21535 (Arc::new(color_fetcher), Arc::from(ranges)),
21536 );
21537 self.scrollbar_marker_state.dirty = true;
21538 cx.notify();
21539 }
21540
21541 pub fn clear_background_highlights<T: 'static>(
21542 &mut self,
21543 cx: &mut Context<Self>,
21544 ) -> Option<BackgroundHighlight> {
21545 let text_highlights = self
21546 .background_highlights
21547 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21548 if !text_highlights.1.is_empty() {
21549 self.scrollbar_marker_state.dirty = true;
21550 cx.notify();
21551 }
21552 Some(text_highlights)
21553 }
21554
21555 pub fn highlight_gutter<T: 'static>(
21556 &mut self,
21557 ranges: impl Into<Vec<Range<Anchor>>>,
21558 color_fetcher: fn(&App) -> Hsla,
21559 cx: &mut Context<Self>,
21560 ) {
21561 self.gutter_highlights
21562 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21563 cx.notify();
21564 }
21565
21566 pub fn clear_gutter_highlights<T: 'static>(
21567 &mut self,
21568 cx: &mut Context<Self>,
21569 ) -> Option<GutterHighlight> {
21570 cx.notify();
21571 self.gutter_highlights.remove(&TypeId::of::<T>())
21572 }
21573
21574 pub fn insert_gutter_highlight<T: 'static>(
21575 &mut self,
21576 range: Range<Anchor>,
21577 color_fetcher: fn(&App) -> Hsla,
21578 cx: &mut Context<Self>,
21579 ) {
21580 let snapshot = self.buffer().read(cx).snapshot(cx);
21581 let mut highlights = self
21582 .gutter_highlights
21583 .remove(&TypeId::of::<T>())
21584 .map(|(_, highlights)| highlights)
21585 .unwrap_or_default();
21586 let ix = highlights.binary_search_by(|highlight| {
21587 Ordering::Equal
21588 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21589 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21590 });
21591 if let Err(ix) = ix {
21592 highlights.insert(ix, range);
21593 }
21594 self.gutter_highlights
21595 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21596 }
21597
21598 pub fn remove_gutter_highlights<T: 'static>(
21599 &mut self,
21600 ranges_to_remove: Vec<Range<Anchor>>,
21601 cx: &mut Context<Self>,
21602 ) {
21603 let snapshot = self.buffer().read(cx).snapshot(cx);
21604 let Some((color_fetcher, mut gutter_highlights)) =
21605 self.gutter_highlights.remove(&TypeId::of::<T>())
21606 else {
21607 return;
21608 };
21609 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21610 gutter_highlights.retain(|highlight| {
21611 while let Some(range_to_remove) = ranges_to_remove.peek() {
21612 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21613 Ordering::Less | Ordering::Equal => {
21614 ranges_to_remove.next();
21615 }
21616 Ordering::Greater => {
21617 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21618 Ordering::Less | Ordering::Equal => {
21619 return false;
21620 }
21621 Ordering::Greater => break,
21622 }
21623 }
21624 }
21625 }
21626
21627 true
21628 });
21629 self.gutter_highlights
21630 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21631 }
21632
21633 #[cfg(feature = "test-support")]
21634 pub fn all_text_highlights(
21635 &self,
21636 window: &mut Window,
21637 cx: &mut Context<Self>,
21638 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21639 let snapshot = self.snapshot(window, cx);
21640 self.display_map.update(cx, |display_map, _| {
21641 display_map
21642 .all_text_highlights()
21643 .map(|highlight| {
21644 let (style, ranges) = highlight.as_ref();
21645 (
21646 *style,
21647 ranges
21648 .iter()
21649 .map(|range| range.clone().to_display_points(&snapshot))
21650 .collect(),
21651 )
21652 })
21653 .collect()
21654 })
21655 }
21656
21657 #[cfg(feature = "test-support")]
21658 pub fn all_text_background_highlights(
21659 &self,
21660 window: &mut Window,
21661 cx: &mut Context<Self>,
21662 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21663 let snapshot = self.snapshot(window, cx);
21664 let buffer = &snapshot.buffer_snapshot();
21665 let start = buffer.anchor_before(MultiBufferOffset(0));
21666 let end = buffer.anchor_after(buffer.len());
21667 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21668 }
21669
21670 #[cfg(any(test, feature = "test-support"))]
21671 pub fn sorted_background_highlights_in_range(
21672 &self,
21673 search_range: Range<Anchor>,
21674 display_snapshot: &DisplaySnapshot,
21675 theme: &Theme,
21676 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21677 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21678 res.sort_by(|a, b| {
21679 a.0.start
21680 .cmp(&b.0.start)
21681 .then_with(|| a.0.end.cmp(&b.0.end))
21682 .then_with(|| a.1.cmp(&b.1))
21683 });
21684 res
21685 }
21686
21687 #[cfg(feature = "test-support")]
21688 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21689 let snapshot = self.buffer().read(cx).snapshot(cx);
21690
21691 let highlights = self
21692 .background_highlights
21693 .get(&HighlightKey::Type(TypeId::of::<
21694 items::BufferSearchHighlights,
21695 >()));
21696
21697 if let Some((_color, ranges)) = highlights {
21698 ranges
21699 .iter()
21700 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21701 .collect_vec()
21702 } else {
21703 vec![]
21704 }
21705 }
21706
21707 fn document_highlights_for_position<'a>(
21708 &'a self,
21709 position: Anchor,
21710 buffer: &'a MultiBufferSnapshot,
21711 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21712 let read_highlights = self
21713 .background_highlights
21714 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21715 .map(|h| &h.1);
21716 let write_highlights = self
21717 .background_highlights
21718 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21719 .map(|h| &h.1);
21720 let left_position = position.bias_left(buffer);
21721 let right_position = position.bias_right(buffer);
21722 read_highlights
21723 .into_iter()
21724 .chain(write_highlights)
21725 .flat_map(move |ranges| {
21726 let start_ix = match ranges.binary_search_by(|probe| {
21727 let cmp = probe.end.cmp(&left_position, buffer);
21728 if cmp.is_ge() {
21729 Ordering::Greater
21730 } else {
21731 Ordering::Less
21732 }
21733 }) {
21734 Ok(i) | Err(i) => i,
21735 };
21736
21737 ranges[start_ix..]
21738 .iter()
21739 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21740 })
21741 }
21742
21743 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21744 self.background_highlights
21745 .get(&HighlightKey::Type(TypeId::of::<T>()))
21746 .is_some_and(|(_, highlights)| !highlights.is_empty())
21747 }
21748
21749 /// Returns all background highlights for a given range.
21750 ///
21751 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21752 pub fn background_highlights_in_range(
21753 &self,
21754 search_range: Range<Anchor>,
21755 display_snapshot: &DisplaySnapshot,
21756 theme: &Theme,
21757 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21758 let mut results = Vec::new();
21759 for (color_fetcher, ranges) in self.background_highlights.values() {
21760 let start_ix = match ranges.binary_search_by(|probe| {
21761 let cmp = probe
21762 .end
21763 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21764 if cmp.is_gt() {
21765 Ordering::Greater
21766 } else {
21767 Ordering::Less
21768 }
21769 }) {
21770 Ok(i) | Err(i) => i,
21771 };
21772 for (index, range) in ranges[start_ix..].iter().enumerate() {
21773 if range
21774 .start
21775 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21776 .is_ge()
21777 {
21778 break;
21779 }
21780
21781 let color = color_fetcher(&(start_ix + index), theme);
21782 let start = range.start.to_display_point(display_snapshot);
21783 let end = range.end.to_display_point(display_snapshot);
21784 results.push((start..end, color))
21785 }
21786 }
21787 results
21788 }
21789
21790 pub fn gutter_highlights_in_range(
21791 &self,
21792 search_range: Range<Anchor>,
21793 display_snapshot: &DisplaySnapshot,
21794 cx: &App,
21795 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21796 let mut results = Vec::new();
21797 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21798 let color = color_fetcher(cx);
21799 let start_ix = match ranges.binary_search_by(|probe| {
21800 let cmp = probe
21801 .end
21802 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21803 if cmp.is_gt() {
21804 Ordering::Greater
21805 } else {
21806 Ordering::Less
21807 }
21808 }) {
21809 Ok(i) | Err(i) => i,
21810 };
21811 for range in &ranges[start_ix..] {
21812 if range
21813 .start
21814 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21815 .is_ge()
21816 {
21817 break;
21818 }
21819
21820 let start = range.start.to_display_point(display_snapshot);
21821 let end = range.end.to_display_point(display_snapshot);
21822 results.push((start..end, color))
21823 }
21824 }
21825 results
21826 }
21827
21828 /// Get the text ranges corresponding to the redaction query
21829 pub fn redacted_ranges(
21830 &self,
21831 search_range: Range<Anchor>,
21832 display_snapshot: &DisplaySnapshot,
21833 cx: &App,
21834 ) -> Vec<Range<DisplayPoint>> {
21835 display_snapshot
21836 .buffer_snapshot()
21837 .redacted_ranges(search_range, |file| {
21838 if let Some(file) = file {
21839 file.is_private()
21840 && EditorSettings::get(
21841 Some(SettingsLocation {
21842 worktree_id: file.worktree_id(cx),
21843 path: file.path().as_ref(),
21844 }),
21845 cx,
21846 )
21847 .redact_private_values
21848 } else {
21849 false
21850 }
21851 })
21852 .map(|range| {
21853 range.start.to_display_point(display_snapshot)
21854 ..range.end.to_display_point(display_snapshot)
21855 })
21856 .collect()
21857 }
21858
21859 pub fn highlight_text_key<T: 'static>(
21860 &mut self,
21861 key: usize,
21862 ranges: Vec<Range<Anchor>>,
21863 style: HighlightStyle,
21864 merge: bool,
21865 cx: &mut Context<Self>,
21866 ) {
21867 self.display_map.update(cx, |map, cx| {
21868 map.highlight_text(
21869 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21870 ranges,
21871 style,
21872 merge,
21873 cx,
21874 );
21875 });
21876 cx.notify();
21877 }
21878
21879 pub fn highlight_text<T: 'static>(
21880 &mut self,
21881 ranges: Vec<Range<Anchor>>,
21882 style: HighlightStyle,
21883 cx: &mut Context<Self>,
21884 ) {
21885 self.display_map.update(cx, |map, cx| {
21886 map.highlight_text(
21887 HighlightKey::Type(TypeId::of::<T>()),
21888 ranges,
21889 style,
21890 false,
21891 cx,
21892 )
21893 });
21894 cx.notify();
21895 }
21896
21897 pub fn text_highlights<'a, T: 'static>(
21898 &'a self,
21899 cx: &'a App,
21900 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21901 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21902 }
21903
21904 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21905 let cleared = self
21906 .display_map
21907 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21908 if cleared {
21909 cx.notify();
21910 }
21911 }
21912
21913 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21914 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21915 && self.focus_handle.is_focused(window)
21916 }
21917
21918 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21919 self.show_cursor_when_unfocused = is_enabled;
21920 cx.notify();
21921 }
21922
21923 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21924 cx.notify();
21925 }
21926
21927 fn on_debug_session_event(
21928 &mut self,
21929 _session: Entity<Session>,
21930 event: &SessionEvent,
21931 cx: &mut Context<Self>,
21932 ) {
21933 if let SessionEvent::InvalidateInlineValue = event {
21934 self.refresh_inline_values(cx);
21935 }
21936 }
21937
21938 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21939 let Some(project) = self.project.clone() else {
21940 return;
21941 };
21942
21943 if !self.inline_value_cache.enabled {
21944 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21945 self.splice_inlays(&inlays, Vec::new(), cx);
21946 return;
21947 }
21948
21949 let current_execution_position = self
21950 .highlighted_rows
21951 .get(&TypeId::of::<ActiveDebugLine>())
21952 .and_then(|lines| lines.last().map(|line| line.range.end));
21953
21954 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21955 let inline_values = editor
21956 .update(cx, |editor, cx| {
21957 let Some(current_execution_position) = current_execution_position else {
21958 return Some(Task::ready(Ok(Vec::new())));
21959 };
21960
21961 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21962 let snapshot = buffer.snapshot(cx);
21963
21964 let excerpt = snapshot.excerpt_containing(
21965 current_execution_position..current_execution_position,
21966 )?;
21967
21968 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21969 })?;
21970
21971 let range =
21972 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21973
21974 project.inline_values(buffer, range, cx)
21975 })
21976 .ok()
21977 .flatten()?
21978 .await
21979 .context("refreshing debugger inlays")
21980 .log_err()?;
21981
21982 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21983
21984 for (buffer_id, inline_value) in inline_values
21985 .into_iter()
21986 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21987 {
21988 buffer_inline_values
21989 .entry(buffer_id)
21990 .or_default()
21991 .push(inline_value);
21992 }
21993
21994 editor
21995 .update(cx, |editor, cx| {
21996 let snapshot = editor.buffer.read(cx).snapshot(cx);
21997 let mut new_inlays = Vec::default();
21998
21999 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
22000 let buffer_id = buffer_snapshot.remote_id();
22001 buffer_inline_values
22002 .get(&buffer_id)
22003 .into_iter()
22004 .flatten()
22005 .for_each(|hint| {
22006 let inlay = Inlay::debugger(
22007 post_inc(&mut editor.next_inlay_id),
22008 Anchor::in_buffer(excerpt_id, hint.position),
22009 hint.text(),
22010 );
22011 if !inlay.text().chars().contains(&'\n') {
22012 new_inlays.push(inlay);
22013 }
22014 });
22015 }
22016
22017 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
22018 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
22019
22020 editor.splice_inlays(&inlay_ids, new_inlays, cx);
22021 })
22022 .ok()?;
22023 Some(())
22024 });
22025 }
22026
22027 fn on_buffer_event(
22028 &mut self,
22029 multibuffer: &Entity<MultiBuffer>,
22030 event: &multi_buffer::Event,
22031 window: &mut Window,
22032 cx: &mut Context<Self>,
22033 ) {
22034 match event {
22035 multi_buffer::Event::Edited { edited_buffer } => {
22036 self.scrollbar_marker_state.dirty = true;
22037 self.active_indent_guides_state.dirty = true;
22038 self.refresh_active_diagnostics(cx);
22039 self.refresh_code_actions(window, cx);
22040 self.refresh_single_line_folds(window, cx);
22041 self.refresh_matching_bracket_highlights(window, cx);
22042 if self.has_active_edit_prediction() {
22043 self.update_visible_edit_prediction(window, cx);
22044 }
22045
22046 if let Some(buffer) = edited_buffer {
22047 if buffer.read(cx).file().is_none() {
22048 cx.emit(EditorEvent::TitleChanged);
22049 }
22050
22051 if self.project.is_some() {
22052 let buffer_id = buffer.read(cx).remote_id();
22053 self.register_buffer(buffer_id, cx);
22054 self.update_lsp_data(Some(buffer_id), window, cx);
22055 self.refresh_inlay_hints(
22056 InlayHintRefreshReason::BufferEdited(buffer_id),
22057 cx,
22058 );
22059 }
22060 }
22061
22062 cx.emit(EditorEvent::BufferEdited);
22063 cx.emit(SearchEvent::MatchesInvalidated);
22064
22065 let Some(project) = &self.project else { return };
22066 let (telemetry, is_via_ssh) = {
22067 let project = project.read(cx);
22068 let telemetry = project.client().telemetry().clone();
22069 let is_via_ssh = project.is_via_remote_server();
22070 (telemetry, is_via_ssh)
22071 };
22072 telemetry.log_edit_event("editor", is_via_ssh);
22073 }
22074 multi_buffer::Event::ExcerptsAdded {
22075 buffer,
22076 predecessor,
22077 excerpts,
22078 } => {
22079 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22080 let buffer_id = buffer.read(cx).remote_id();
22081 if self.buffer.read(cx).diff_for(buffer_id).is_none()
22082 && let Some(project) = &self.project
22083 {
22084 update_uncommitted_diff_for_buffer(
22085 cx.entity(),
22086 project,
22087 [buffer.clone()],
22088 self.buffer.clone(),
22089 cx,
22090 )
22091 .detach();
22092 }
22093 self.update_lsp_data(Some(buffer_id), window, cx);
22094 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22095 self.colorize_brackets(false, cx);
22096 cx.emit(EditorEvent::ExcerptsAdded {
22097 buffer: buffer.clone(),
22098 predecessor: *predecessor,
22099 excerpts: excerpts.clone(),
22100 });
22101 }
22102 multi_buffer::Event::ExcerptsRemoved {
22103 ids,
22104 removed_buffer_ids,
22105 } => {
22106 if let Some(inlay_hints) = &mut self.inlay_hints {
22107 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
22108 }
22109 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
22110 for buffer_id in removed_buffer_ids {
22111 self.registered_buffers.remove(buffer_id);
22112 }
22113 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22114 cx.emit(EditorEvent::ExcerptsRemoved {
22115 ids: ids.clone(),
22116 removed_buffer_ids: removed_buffer_ids.clone(),
22117 });
22118 }
22119 multi_buffer::Event::ExcerptsEdited {
22120 excerpt_ids,
22121 buffer_ids,
22122 } => {
22123 self.display_map.update(cx, |map, cx| {
22124 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22125 });
22126 cx.emit(EditorEvent::ExcerptsEdited {
22127 ids: excerpt_ids.clone(),
22128 });
22129 }
22130 multi_buffer::Event::ExcerptsExpanded { ids } => {
22131 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22132 self.refresh_document_highlights(cx);
22133 for id in ids {
22134 self.fetched_tree_sitter_chunks.remove(id);
22135 }
22136 self.colorize_brackets(false, cx);
22137 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22138 }
22139 multi_buffer::Event::Reparsed(buffer_id) => {
22140 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22141 self.refresh_selected_text_highlights(true, window, cx);
22142 self.colorize_brackets(true, cx);
22143 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22144
22145 cx.emit(EditorEvent::Reparsed(*buffer_id));
22146 }
22147 multi_buffer::Event::DiffHunksToggled => {
22148 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22149 }
22150 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22151 if !is_fresh_language {
22152 self.registered_buffers.remove(&buffer_id);
22153 }
22154 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22155 cx.emit(EditorEvent::Reparsed(*buffer_id));
22156 cx.notify();
22157 }
22158 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22159 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22160 multi_buffer::Event::FileHandleChanged
22161 | multi_buffer::Event::Reloaded
22162 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22163 multi_buffer::Event::DiagnosticsUpdated => {
22164 self.update_diagnostics_state(window, cx);
22165 }
22166 _ => {}
22167 };
22168 }
22169
22170 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22171 if !self.diagnostics_enabled() {
22172 return;
22173 }
22174 self.refresh_active_diagnostics(cx);
22175 self.refresh_inline_diagnostics(true, window, cx);
22176 self.scrollbar_marker_state.dirty = true;
22177 cx.notify();
22178 }
22179
22180 pub fn start_temporary_diff_override(&mut self) {
22181 self.load_diff_task.take();
22182 self.temporary_diff_override = true;
22183 }
22184
22185 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22186 self.temporary_diff_override = false;
22187 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22188 self.buffer.update(cx, |buffer, cx| {
22189 buffer.set_all_diff_hunks_collapsed(cx);
22190 });
22191
22192 if let Some(project) = self.project.clone() {
22193 self.load_diff_task = Some(
22194 update_uncommitted_diff_for_buffer(
22195 cx.entity(),
22196 &project,
22197 self.buffer.read(cx).all_buffers(),
22198 self.buffer.clone(),
22199 cx,
22200 )
22201 .shared(),
22202 );
22203 }
22204 }
22205
22206 fn on_display_map_changed(
22207 &mut self,
22208 _: Entity<DisplayMap>,
22209 _: &mut Window,
22210 cx: &mut Context<Self>,
22211 ) {
22212 cx.notify();
22213 }
22214
22215 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22216 if !self.mode.is_full() {
22217 return None;
22218 }
22219
22220 let theme_settings = theme::ThemeSettings::get_global(cx);
22221 let theme = cx.theme();
22222 let accent_colors = theme.accents().clone();
22223
22224 let accent_overrides = theme_settings
22225 .theme_overrides
22226 .get(theme.name.as_ref())
22227 .map(|theme_style| &theme_style.accents)
22228 .into_iter()
22229 .flatten()
22230 .chain(
22231 theme_settings
22232 .experimental_theme_overrides
22233 .as_ref()
22234 .map(|overrides| &overrides.accents)
22235 .into_iter()
22236 .flatten(),
22237 )
22238 .flat_map(|accent| accent.0.clone())
22239 .collect();
22240
22241 Some(AccentData {
22242 colors: accent_colors,
22243 overrides: accent_overrides,
22244 })
22245 }
22246
22247 fn fetch_applicable_language_settings(
22248 &self,
22249 cx: &App,
22250 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22251 if !self.mode.is_full() {
22252 return HashMap::default();
22253 }
22254
22255 self.buffer().read(cx).all_buffers().into_iter().fold(
22256 HashMap::default(),
22257 |mut acc, buffer| {
22258 let buffer = buffer.read(cx);
22259 let language = buffer.language().map(|language| language.name());
22260 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22261 let file = buffer.file();
22262 v.insert(language_settings(language, file, cx).into_owned());
22263 }
22264 acc
22265 },
22266 )
22267 }
22268
22269 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22270 let new_language_settings = self.fetch_applicable_language_settings(cx);
22271 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22272 self.applicable_language_settings = new_language_settings;
22273
22274 let new_accents = self.fetch_accent_data(cx);
22275 let accents_changed = new_accents != self.accent_data;
22276 self.accent_data = new_accents;
22277
22278 if self.diagnostics_enabled() {
22279 let new_severity = EditorSettings::get_global(cx)
22280 .diagnostics_max_severity
22281 .unwrap_or(DiagnosticSeverity::Hint);
22282 self.set_max_diagnostics_severity(new_severity, cx);
22283 }
22284 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22285 self.update_edit_prediction_settings(cx);
22286 self.refresh_edit_prediction(true, false, window, cx);
22287 self.refresh_inline_values(cx);
22288 self.refresh_inlay_hints(
22289 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22290 self.selections.newest_anchor().head(),
22291 &self.buffer.read(cx).snapshot(cx),
22292 cx,
22293 )),
22294 cx,
22295 );
22296
22297 let old_cursor_shape = self.cursor_shape;
22298 let old_show_breadcrumbs = self.show_breadcrumbs;
22299
22300 {
22301 let editor_settings = EditorSettings::get_global(cx);
22302 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22303 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22304 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22305 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22306 }
22307
22308 if old_cursor_shape != self.cursor_shape {
22309 cx.emit(EditorEvent::CursorShapeChanged);
22310 }
22311
22312 if old_show_breadcrumbs != self.show_breadcrumbs {
22313 cx.emit(EditorEvent::BreadcrumbsChanged);
22314 }
22315
22316 let project_settings = ProjectSettings::get_global(cx);
22317 self.buffer_serialization = self
22318 .should_serialize_buffer()
22319 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22320
22321 if self.mode.is_full() {
22322 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22323 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22324 if self.show_inline_diagnostics != show_inline_diagnostics {
22325 self.show_inline_diagnostics = show_inline_diagnostics;
22326 self.refresh_inline_diagnostics(false, window, cx);
22327 }
22328
22329 if self.git_blame_inline_enabled != inline_blame_enabled {
22330 self.toggle_git_blame_inline_internal(false, window, cx);
22331 }
22332
22333 let minimap_settings = EditorSettings::get_global(cx).minimap;
22334 if self.minimap_visibility != MinimapVisibility::Disabled {
22335 if self.minimap_visibility.settings_visibility()
22336 != minimap_settings.minimap_enabled()
22337 {
22338 self.set_minimap_visibility(
22339 MinimapVisibility::for_mode(self.mode(), cx),
22340 window,
22341 cx,
22342 );
22343 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22344 minimap_entity.update(cx, |minimap_editor, cx| {
22345 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22346 })
22347 }
22348 }
22349
22350 if language_settings_changed || accents_changed {
22351 self.colorize_brackets(true, cx);
22352 }
22353
22354 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22355 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22356 }) {
22357 if !inlay_splice.is_empty() {
22358 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22359 }
22360 self.refresh_colors_for_visible_range(None, window, cx);
22361 }
22362 }
22363
22364 cx.notify();
22365 }
22366
22367 pub fn set_searchable(&mut self, searchable: bool) {
22368 self.searchable = searchable;
22369 }
22370
22371 pub fn searchable(&self) -> bool {
22372 self.searchable
22373 }
22374
22375 pub fn open_excerpts_in_split(
22376 &mut self,
22377 _: &OpenExcerptsSplit,
22378 window: &mut Window,
22379 cx: &mut Context<Self>,
22380 ) {
22381 self.open_excerpts_common(None, true, window, cx)
22382 }
22383
22384 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22385 self.open_excerpts_common(None, false, window, cx)
22386 }
22387
22388 fn open_excerpts_common(
22389 &mut self,
22390 jump_data: Option<JumpData>,
22391 split: bool,
22392 window: &mut Window,
22393 cx: &mut Context<Self>,
22394 ) {
22395 let Some(workspace) = self.workspace() else {
22396 cx.propagate();
22397 return;
22398 };
22399
22400 if self.buffer.read(cx).is_singleton() {
22401 cx.propagate();
22402 return;
22403 }
22404
22405 let mut new_selections_by_buffer = HashMap::default();
22406 match &jump_data {
22407 Some(JumpData::MultiBufferPoint {
22408 excerpt_id,
22409 position,
22410 anchor,
22411 line_offset_from_top,
22412 }) => {
22413 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22414 if let Some(buffer) = multi_buffer_snapshot
22415 .buffer_id_for_excerpt(*excerpt_id)
22416 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22417 {
22418 let buffer_snapshot = buffer.read(cx).snapshot();
22419 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22420 language::ToPoint::to_point(anchor, &buffer_snapshot)
22421 } else {
22422 buffer_snapshot.clip_point(*position, Bias::Left)
22423 };
22424 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22425 new_selections_by_buffer.insert(
22426 buffer,
22427 (
22428 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22429 Some(*line_offset_from_top),
22430 ),
22431 );
22432 }
22433 }
22434 Some(JumpData::MultiBufferRow {
22435 row,
22436 line_offset_from_top,
22437 }) => {
22438 let point = MultiBufferPoint::new(row.0, 0);
22439 if let Some((buffer, buffer_point, _)) =
22440 self.buffer.read(cx).point_to_buffer_point(point, cx)
22441 {
22442 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22443 new_selections_by_buffer
22444 .entry(buffer)
22445 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22446 .0
22447 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22448 }
22449 }
22450 None => {
22451 let selections = self
22452 .selections
22453 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22454 let multi_buffer = self.buffer.read(cx);
22455 for selection in selections {
22456 for (snapshot, range, _, anchor) in multi_buffer
22457 .snapshot(cx)
22458 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22459 {
22460 if let Some(anchor) = anchor {
22461 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22462 else {
22463 continue;
22464 };
22465 let offset = text::ToOffset::to_offset(
22466 &anchor.text_anchor,
22467 &buffer_handle.read(cx).snapshot(),
22468 );
22469 let range = BufferOffset(offset)..BufferOffset(offset);
22470 new_selections_by_buffer
22471 .entry(buffer_handle)
22472 .or_insert((Vec::new(), None))
22473 .0
22474 .push(range)
22475 } else {
22476 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22477 else {
22478 continue;
22479 };
22480 new_selections_by_buffer
22481 .entry(buffer_handle)
22482 .or_insert((Vec::new(), None))
22483 .0
22484 .push(range)
22485 }
22486 }
22487 }
22488 }
22489 }
22490
22491 new_selections_by_buffer
22492 .retain(|buffer, _| buffer.read(cx).file().is_none_or(|file| file.can_open()));
22493
22494 if new_selections_by_buffer.is_empty() {
22495 return;
22496 }
22497
22498 // We defer the pane interaction because we ourselves are a workspace item
22499 // and activating a new item causes the pane to call a method on us reentrantly,
22500 // which panics if we're on the stack.
22501 window.defer(cx, move |window, cx| {
22502 workspace.update(cx, |workspace, cx| {
22503 let pane = if split {
22504 workspace.adjacent_pane(window, cx)
22505 } else {
22506 workspace.active_pane().clone()
22507 };
22508
22509 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22510 let buffer_read = buffer.read(cx);
22511 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22512 (true, project::File::from_dyn(Some(file)).is_some())
22513 } else {
22514 (false, false)
22515 };
22516
22517 // If project file is none workspace.open_project_item will fail to open the excerpt
22518 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22519 // so we check if there's a tab match in that case first
22520 let editor = (!has_file || !is_project_file)
22521 .then(|| {
22522 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22523 // so `workspace.open_project_item` will never find them, always opening a new editor.
22524 // Instead, we try to activate the existing editor in the pane first.
22525 let (editor, pane_item_index, pane_item_id) =
22526 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22527 let editor = item.downcast::<Editor>()?;
22528 let singleton_buffer =
22529 editor.read(cx).buffer().read(cx).as_singleton()?;
22530 if singleton_buffer == buffer {
22531 Some((editor, i, item.item_id()))
22532 } else {
22533 None
22534 }
22535 })?;
22536 pane.update(cx, |pane, cx| {
22537 pane.activate_item(pane_item_index, true, true, window, cx);
22538 if !PreviewTabsSettings::get_global(cx)
22539 .enable_preview_from_multibuffer
22540 {
22541 pane.unpreview_item_if_preview(pane_item_id);
22542 }
22543 });
22544 Some(editor)
22545 })
22546 .flatten()
22547 .unwrap_or_else(|| {
22548 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22549 .enable_keep_preview_on_code_navigation;
22550 let allow_new_preview =
22551 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22552 workspace.open_project_item::<Self>(
22553 pane.clone(),
22554 buffer,
22555 true,
22556 true,
22557 keep_old_preview,
22558 allow_new_preview,
22559 window,
22560 cx,
22561 )
22562 });
22563
22564 editor.update(cx, |editor, cx| {
22565 if has_file && !is_project_file {
22566 editor.set_read_only(true);
22567 }
22568 let autoscroll = match scroll_offset {
22569 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22570 None => Autoscroll::newest(),
22571 };
22572 let nav_history = editor.nav_history.take();
22573 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22574 let Some((&excerpt_id, _, buffer_snapshot)) =
22575 multibuffer_snapshot.as_singleton()
22576 else {
22577 return;
22578 };
22579 editor.change_selections(
22580 SelectionEffects::scroll(autoscroll),
22581 window,
22582 cx,
22583 |s| {
22584 s.select_ranges(ranges.into_iter().map(|range| {
22585 let range = buffer_snapshot.anchor_before(range.start)
22586 ..buffer_snapshot.anchor_after(range.end);
22587 multibuffer_snapshot
22588 .anchor_range_in_excerpt(excerpt_id, range)
22589 .unwrap()
22590 }));
22591 },
22592 );
22593 editor.nav_history = nav_history;
22594 });
22595 }
22596 })
22597 });
22598 }
22599
22600 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22601 let snapshot = self.buffer.read(cx).read(cx);
22602 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22603 Some(
22604 ranges
22605 .iter()
22606 .map(move |range| {
22607 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22608 })
22609 .collect(),
22610 )
22611 }
22612
22613 fn selection_replacement_ranges(
22614 &self,
22615 range: Range<MultiBufferOffsetUtf16>,
22616 cx: &mut App,
22617 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22618 let selections = self
22619 .selections
22620 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22621 let newest_selection = selections
22622 .iter()
22623 .max_by_key(|selection| selection.id)
22624 .unwrap();
22625 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22626 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22627 let snapshot = self.buffer.read(cx).read(cx);
22628 selections
22629 .into_iter()
22630 .map(|mut selection| {
22631 selection.start.0.0 =
22632 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22633 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22634 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22635 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22636 })
22637 .collect()
22638 }
22639
22640 fn report_editor_event(
22641 &self,
22642 reported_event: ReportEditorEvent,
22643 file_extension: Option<String>,
22644 cx: &App,
22645 ) {
22646 if cfg!(any(test, feature = "test-support")) {
22647 return;
22648 }
22649
22650 let Some(project) = &self.project else { return };
22651
22652 // If None, we are in a file without an extension
22653 let file = self
22654 .buffer
22655 .read(cx)
22656 .as_singleton()
22657 .and_then(|b| b.read(cx).file());
22658 let file_extension = file_extension.or(file
22659 .as_ref()
22660 .and_then(|file| Path::new(file.file_name(cx)).extension())
22661 .and_then(|e| e.to_str())
22662 .map(|a| a.to_string()));
22663
22664 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22665 .map(|vim_mode| vim_mode.0)
22666 .unwrap_or(false);
22667
22668 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22669 let copilot_enabled = edit_predictions_provider
22670 == language::language_settings::EditPredictionProvider::Copilot;
22671 let copilot_enabled_for_language = self
22672 .buffer
22673 .read(cx)
22674 .language_settings(cx)
22675 .show_edit_predictions;
22676
22677 let project = project.read(cx);
22678 let event_type = reported_event.event_type();
22679
22680 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22681 telemetry::event!(
22682 event_type,
22683 type = if auto_saved {"autosave"} else {"manual"},
22684 file_extension,
22685 vim_mode,
22686 copilot_enabled,
22687 copilot_enabled_for_language,
22688 edit_predictions_provider,
22689 is_via_ssh = project.is_via_remote_server(),
22690 );
22691 } else {
22692 telemetry::event!(
22693 event_type,
22694 file_extension,
22695 vim_mode,
22696 copilot_enabled,
22697 copilot_enabled_for_language,
22698 edit_predictions_provider,
22699 is_via_ssh = project.is_via_remote_server(),
22700 );
22701 };
22702 }
22703
22704 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22705 /// with each line being an array of {text, highlight} objects.
22706 fn copy_highlight_json(
22707 &mut self,
22708 _: &CopyHighlightJson,
22709 window: &mut Window,
22710 cx: &mut Context<Self>,
22711 ) {
22712 #[derive(Serialize)]
22713 struct Chunk<'a> {
22714 text: String,
22715 highlight: Option<&'a str>,
22716 }
22717
22718 let snapshot = self.buffer.read(cx).snapshot(cx);
22719 let range = self
22720 .selected_text_range(false, window, cx)
22721 .and_then(|selection| {
22722 if selection.range.is_empty() {
22723 None
22724 } else {
22725 Some(
22726 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22727 selection.range.start,
22728 )))
22729 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22730 selection.range.end,
22731 ))),
22732 )
22733 }
22734 })
22735 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22736
22737 let chunks = snapshot.chunks(range, true);
22738 let mut lines = Vec::new();
22739 let mut line: VecDeque<Chunk> = VecDeque::new();
22740
22741 let Some(style) = self.style.as_ref() else {
22742 return;
22743 };
22744
22745 for chunk in chunks {
22746 let highlight = chunk
22747 .syntax_highlight_id
22748 .and_then(|id| id.name(&style.syntax));
22749 let mut chunk_lines = chunk.text.split('\n').peekable();
22750 while let Some(text) = chunk_lines.next() {
22751 let mut merged_with_last_token = false;
22752 if let Some(last_token) = line.back_mut()
22753 && last_token.highlight == highlight
22754 {
22755 last_token.text.push_str(text);
22756 merged_with_last_token = true;
22757 }
22758
22759 if !merged_with_last_token {
22760 line.push_back(Chunk {
22761 text: text.into(),
22762 highlight,
22763 });
22764 }
22765
22766 if chunk_lines.peek().is_some() {
22767 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22768 line.pop_front();
22769 }
22770 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22771 line.pop_back();
22772 }
22773
22774 lines.push(mem::take(&mut line));
22775 }
22776 }
22777 }
22778
22779 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22780 return;
22781 };
22782 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22783 }
22784
22785 pub fn open_context_menu(
22786 &mut self,
22787 _: &OpenContextMenu,
22788 window: &mut Window,
22789 cx: &mut Context<Self>,
22790 ) {
22791 self.request_autoscroll(Autoscroll::newest(), cx);
22792 let position = self
22793 .selections
22794 .newest_display(&self.display_snapshot(cx))
22795 .start;
22796 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22797 }
22798
22799 pub fn replay_insert_event(
22800 &mut self,
22801 text: &str,
22802 relative_utf16_range: Option<Range<isize>>,
22803 window: &mut Window,
22804 cx: &mut Context<Self>,
22805 ) {
22806 if !self.input_enabled {
22807 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22808 return;
22809 }
22810 if let Some(relative_utf16_range) = relative_utf16_range {
22811 let selections = self
22812 .selections
22813 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22814 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22815 let new_ranges = selections.into_iter().map(|range| {
22816 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22817 range
22818 .head()
22819 .0
22820 .0
22821 .saturating_add_signed(relative_utf16_range.start),
22822 ));
22823 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22824 range
22825 .head()
22826 .0
22827 .0
22828 .saturating_add_signed(relative_utf16_range.end),
22829 ));
22830 start..end
22831 });
22832 s.select_ranges(new_ranges);
22833 });
22834 }
22835
22836 self.handle_input(text, window, cx);
22837 }
22838
22839 pub fn is_focused(&self, window: &Window) -> bool {
22840 self.focus_handle.is_focused(window)
22841 }
22842
22843 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22844 cx.emit(EditorEvent::Focused);
22845
22846 if let Some(descendant) = self
22847 .last_focused_descendant
22848 .take()
22849 .and_then(|descendant| descendant.upgrade())
22850 {
22851 window.focus(&descendant, cx);
22852 } else {
22853 if let Some(blame) = self.blame.as_ref() {
22854 blame.update(cx, GitBlame::focus)
22855 }
22856
22857 self.blink_manager.update(cx, BlinkManager::enable);
22858 self.show_cursor_names(window, cx);
22859 self.buffer.update(cx, |buffer, cx| {
22860 buffer.finalize_last_transaction(cx);
22861 if self.leader_id.is_none() {
22862 buffer.set_active_selections(
22863 &self.selections.disjoint_anchors_arc(),
22864 self.selections.line_mode(),
22865 self.cursor_shape,
22866 cx,
22867 );
22868 }
22869 });
22870
22871 if let Some(position_map) = self.last_position_map.clone() {
22872 EditorElement::mouse_moved(
22873 self,
22874 &MouseMoveEvent {
22875 position: window.mouse_position(),
22876 pressed_button: None,
22877 modifiers: window.modifiers(),
22878 },
22879 &position_map,
22880 window,
22881 cx,
22882 );
22883 }
22884 }
22885 }
22886
22887 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22888 cx.emit(EditorEvent::FocusedIn)
22889 }
22890
22891 fn handle_focus_out(
22892 &mut self,
22893 event: FocusOutEvent,
22894 _window: &mut Window,
22895 cx: &mut Context<Self>,
22896 ) {
22897 if event.blurred != self.focus_handle {
22898 self.last_focused_descendant = Some(event.blurred);
22899 }
22900 self.selection_drag_state = SelectionDragState::None;
22901 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22902 }
22903
22904 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22905 self.blink_manager.update(cx, BlinkManager::disable);
22906 self.buffer
22907 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22908
22909 if let Some(blame) = self.blame.as_ref() {
22910 blame.update(cx, GitBlame::blur)
22911 }
22912 if !self.hover_state.focused(window, cx) {
22913 hide_hover(self, cx);
22914 }
22915 if !self
22916 .context_menu
22917 .borrow()
22918 .as_ref()
22919 .is_some_and(|context_menu| context_menu.focused(window, cx))
22920 {
22921 self.hide_context_menu(window, cx);
22922 }
22923 self.take_active_edit_prediction(cx);
22924 cx.emit(EditorEvent::Blurred);
22925 cx.notify();
22926 }
22927
22928 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22929 let mut pending: String = window
22930 .pending_input_keystrokes()
22931 .into_iter()
22932 .flatten()
22933 .filter_map(|keystroke| keystroke.key_char.clone())
22934 .collect();
22935
22936 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22937 pending = "".to_string();
22938 }
22939
22940 let existing_pending = self
22941 .text_highlights::<PendingInput>(cx)
22942 .map(|(_, ranges)| ranges.to_vec());
22943 if existing_pending.is_none() && pending.is_empty() {
22944 return;
22945 }
22946 let transaction =
22947 self.transact(window, cx, |this, window, cx| {
22948 let selections = this
22949 .selections
22950 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22951 let edits = selections
22952 .iter()
22953 .map(|selection| (selection.end..selection.end, pending.clone()));
22954 this.edit(edits, cx);
22955 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22956 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22957 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22958 }));
22959 });
22960 if let Some(existing_ranges) = existing_pending {
22961 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22962 this.edit(edits, cx);
22963 }
22964 });
22965
22966 let snapshot = self.snapshot(window, cx);
22967 let ranges = self
22968 .selections
22969 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22970 .into_iter()
22971 .map(|selection| {
22972 snapshot.buffer_snapshot().anchor_after(selection.end)
22973 ..snapshot
22974 .buffer_snapshot()
22975 .anchor_before(selection.end + pending.len())
22976 })
22977 .collect();
22978
22979 if pending.is_empty() {
22980 self.clear_highlights::<PendingInput>(cx);
22981 } else {
22982 self.highlight_text::<PendingInput>(
22983 ranges,
22984 HighlightStyle {
22985 underline: Some(UnderlineStyle {
22986 thickness: px(1.),
22987 color: None,
22988 wavy: false,
22989 }),
22990 ..Default::default()
22991 },
22992 cx,
22993 );
22994 }
22995
22996 self.ime_transaction = self.ime_transaction.or(transaction);
22997 if let Some(transaction) = self.ime_transaction {
22998 self.buffer.update(cx, |buffer, cx| {
22999 buffer.group_until_transaction(transaction, cx);
23000 });
23001 }
23002
23003 if self.text_highlights::<PendingInput>(cx).is_none() {
23004 self.ime_transaction.take();
23005 }
23006 }
23007
23008 pub fn register_action_renderer(
23009 &mut self,
23010 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
23011 ) -> Subscription {
23012 let id = self.next_editor_action_id.post_inc();
23013 self.editor_actions
23014 .borrow_mut()
23015 .insert(id, Box::new(listener));
23016
23017 let editor_actions = self.editor_actions.clone();
23018 Subscription::new(move || {
23019 editor_actions.borrow_mut().remove(&id);
23020 })
23021 }
23022
23023 pub fn register_action<A: Action>(
23024 &mut self,
23025 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
23026 ) -> Subscription {
23027 let id = self.next_editor_action_id.post_inc();
23028 let listener = Arc::new(listener);
23029 self.editor_actions.borrow_mut().insert(
23030 id,
23031 Box::new(move |_, window, _| {
23032 let listener = listener.clone();
23033 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
23034 let action = action.downcast_ref().unwrap();
23035 if phase == DispatchPhase::Bubble {
23036 listener(action, window, cx)
23037 }
23038 })
23039 }),
23040 );
23041
23042 let editor_actions = self.editor_actions.clone();
23043 Subscription::new(move || {
23044 editor_actions.borrow_mut().remove(&id);
23045 })
23046 }
23047
23048 pub fn file_header_size(&self) -> u32 {
23049 FILE_HEADER_HEIGHT
23050 }
23051
23052 pub fn restore(
23053 &mut self,
23054 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
23055 window: &mut Window,
23056 cx: &mut Context<Self>,
23057 ) {
23058 self.buffer().update(cx, |multi_buffer, cx| {
23059 for (buffer_id, changes) in revert_changes {
23060 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
23061 buffer.update(cx, |buffer, cx| {
23062 buffer.edit(
23063 changes
23064 .into_iter()
23065 .map(|(range, text)| (range, text.to_string())),
23066 None,
23067 cx,
23068 );
23069 });
23070 }
23071 }
23072 });
23073 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23074 selections.refresh()
23075 });
23076 }
23077
23078 pub fn to_pixel_point(
23079 &mut self,
23080 source: multi_buffer::Anchor,
23081 editor_snapshot: &EditorSnapshot,
23082 window: &mut Window,
23083 cx: &App,
23084 ) -> Option<gpui::Point<Pixels>> {
23085 let source_point = source.to_display_point(editor_snapshot);
23086 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23087 }
23088
23089 pub fn display_to_pixel_point(
23090 &mut self,
23091 source: DisplayPoint,
23092 editor_snapshot: &EditorSnapshot,
23093 window: &mut Window,
23094 cx: &App,
23095 ) -> Option<gpui::Point<Pixels>> {
23096 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23097 let text_layout_details = self.text_layout_details(window);
23098 let scroll_top = text_layout_details
23099 .scroll_anchor
23100 .scroll_position(editor_snapshot)
23101 .y;
23102
23103 if source.row().as_f64() < scroll_top.floor() {
23104 return None;
23105 }
23106 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23107 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23108 Some(gpui::Point::new(source_x, source_y))
23109 }
23110
23111 pub fn has_visible_completions_menu(&self) -> bool {
23112 !self.edit_prediction_preview_is_active()
23113 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23114 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23115 })
23116 }
23117
23118 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23119 if self.mode.is_minimap() {
23120 return;
23121 }
23122 self.addons
23123 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23124 }
23125
23126 pub fn unregister_addon<T: Addon>(&mut self) {
23127 self.addons.remove(&std::any::TypeId::of::<T>());
23128 }
23129
23130 pub fn addon<T: Addon>(&self) -> Option<&T> {
23131 let type_id = std::any::TypeId::of::<T>();
23132 self.addons
23133 .get(&type_id)
23134 .and_then(|item| item.to_any().downcast_ref::<T>())
23135 }
23136
23137 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23138 let type_id = std::any::TypeId::of::<T>();
23139 self.addons
23140 .get_mut(&type_id)
23141 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23142 }
23143
23144 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23145 let text_layout_details = self.text_layout_details(window);
23146 let style = &text_layout_details.editor_style;
23147 let font_id = window.text_system().resolve_font(&style.text.font());
23148 let font_size = style.text.font_size.to_pixels(window.rem_size());
23149 let line_height = style.text.line_height_in_pixels(window.rem_size());
23150 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23151 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23152
23153 CharacterDimensions {
23154 em_width,
23155 em_advance,
23156 line_height,
23157 }
23158 }
23159
23160 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23161 self.load_diff_task.clone()
23162 }
23163
23164 fn read_metadata_from_db(
23165 &mut self,
23166 item_id: u64,
23167 workspace_id: WorkspaceId,
23168 window: &mut Window,
23169 cx: &mut Context<Editor>,
23170 ) {
23171 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23172 && !self.mode.is_minimap()
23173 && WorkspaceSettings::get(None, cx).restore_on_startup
23174 != RestoreOnStartupBehavior::EmptyTab
23175 {
23176 let buffer_snapshot = OnceCell::new();
23177
23178 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23179 && !folds.is_empty()
23180 {
23181 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23182 self.fold_ranges(
23183 folds
23184 .into_iter()
23185 .map(|(start, end)| {
23186 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23187 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23188 })
23189 .collect(),
23190 false,
23191 window,
23192 cx,
23193 );
23194 }
23195
23196 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23197 && !selections.is_empty()
23198 {
23199 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23200 // skip adding the initial selection to selection history
23201 self.selection_history.mode = SelectionHistoryMode::Skipping;
23202 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23203 s.select_ranges(selections.into_iter().map(|(start, end)| {
23204 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23205 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23206 }));
23207 });
23208 self.selection_history.mode = SelectionHistoryMode::Normal;
23209 };
23210 }
23211
23212 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23213 }
23214
23215 fn update_lsp_data(
23216 &mut self,
23217 for_buffer: Option<BufferId>,
23218 window: &mut Window,
23219 cx: &mut Context<'_, Self>,
23220 ) {
23221 self.pull_diagnostics(for_buffer, window, cx);
23222 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23223 }
23224
23225 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23226 if self.ignore_lsp_data() {
23227 return;
23228 }
23229 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23230 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23231 }
23232 }
23233
23234 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23235 if self.ignore_lsp_data() {
23236 return;
23237 }
23238
23239 if !self.registered_buffers.contains_key(&buffer_id)
23240 && let Some(project) = self.project.as_ref()
23241 {
23242 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23243 project.update(cx, |project, cx| {
23244 self.registered_buffers.insert(
23245 buffer_id,
23246 project.register_buffer_with_language_servers(&buffer, cx),
23247 );
23248 });
23249 } else {
23250 self.registered_buffers.remove(&buffer_id);
23251 }
23252 }
23253 }
23254
23255 fn ignore_lsp_data(&self) -> bool {
23256 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23257 // skip any LSP updates for it.
23258 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23259 }
23260
23261 fn create_style(&self, cx: &App) -> EditorStyle {
23262 let settings = ThemeSettings::get_global(cx);
23263
23264 let mut text_style = match self.mode {
23265 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23266 color: cx.theme().colors().editor_foreground,
23267 font_family: settings.ui_font.family.clone(),
23268 font_features: settings.ui_font.features.clone(),
23269 font_fallbacks: settings.ui_font.fallbacks.clone(),
23270 font_size: rems(0.875).into(),
23271 font_weight: settings.ui_font.weight,
23272 line_height: relative(settings.buffer_line_height.value()),
23273 ..Default::default()
23274 },
23275 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23276 color: cx.theme().colors().editor_foreground,
23277 font_family: settings.buffer_font.family.clone(),
23278 font_features: settings.buffer_font.features.clone(),
23279 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23280 font_size: settings.buffer_font_size(cx).into(),
23281 font_weight: settings.buffer_font.weight,
23282 line_height: relative(settings.buffer_line_height.value()),
23283 ..Default::default()
23284 },
23285 };
23286 if let Some(text_style_refinement) = &self.text_style_refinement {
23287 text_style.refine(text_style_refinement)
23288 }
23289
23290 let background = match self.mode {
23291 EditorMode::SingleLine => cx.theme().system().transparent,
23292 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23293 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23294 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23295 };
23296
23297 EditorStyle {
23298 background,
23299 border: cx.theme().colors().border,
23300 local_player: cx.theme().players().local(),
23301 text: text_style,
23302 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23303 syntax: cx.theme().syntax().clone(),
23304 status: cx.theme().status().clone(),
23305 inlay_hints_style: make_inlay_hints_style(cx),
23306 edit_prediction_styles: make_suggestion_styles(cx),
23307 unnecessary_code_fade: settings.unnecessary_code_fade,
23308 show_underlines: self.diagnostics_enabled(),
23309 }
23310 }
23311 fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
23312 let cursor = self.selections.newest_anchor().head();
23313 let multibuffer = self.buffer().read(cx);
23314 let is_singleton = multibuffer.is_singleton();
23315 let (buffer_id, symbols) = multibuffer
23316 .read(cx)
23317 .symbols_containing(cursor, Some(variant.syntax()))?;
23318 let buffer = multibuffer.buffer(buffer_id)?;
23319
23320 let buffer = buffer.read(cx);
23321 let settings = ThemeSettings::get_global(cx);
23322 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
23323 let mut breadcrumbs = if is_singleton {
23324 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
23325 buffer
23326 .snapshot()
23327 .resolve_file_path(
23328 self.project
23329 .as_ref()
23330 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
23331 .unwrap_or_default(),
23332 cx,
23333 )
23334 .unwrap_or_else(|| {
23335 if multibuffer.is_singleton() {
23336 multibuffer.title(cx).to_string()
23337 } else {
23338 "untitled".to_string()
23339 }
23340 })
23341 });
23342 vec![BreadcrumbText {
23343 text,
23344 highlights: None,
23345 font: Some(settings.buffer_font.clone()),
23346 }]
23347 } else {
23348 vec![]
23349 };
23350
23351 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
23352 text: symbol.text,
23353 highlights: Some(symbol.highlight_ranges),
23354 font: Some(settings.buffer_font.clone()),
23355 }));
23356 Some(breadcrumbs)
23357 }
23358}
23359
23360fn edit_for_markdown_paste<'a>(
23361 buffer: &MultiBufferSnapshot,
23362 range: Range<MultiBufferOffset>,
23363 to_insert: &'a str,
23364 url: Option<url::Url>,
23365) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23366 if url.is_none() {
23367 return (range, Cow::Borrowed(to_insert));
23368 };
23369
23370 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23371
23372 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23373 Cow::Borrowed(to_insert)
23374 } else {
23375 Cow::Owned(format!("[{old_text}]({to_insert})"))
23376 };
23377 (range, new_text)
23378}
23379
23380fn process_completion_for_edit(
23381 completion: &Completion,
23382 intent: CompletionIntent,
23383 buffer: &Entity<Buffer>,
23384 cursor_position: &text::Anchor,
23385 cx: &mut Context<Editor>,
23386) -> CompletionEdit {
23387 let buffer = buffer.read(cx);
23388 let buffer_snapshot = buffer.snapshot();
23389 let (snippet, new_text) = if completion.is_snippet() {
23390 let mut snippet_source = completion.new_text.clone();
23391 // Workaround for typescript language server issues so that methods don't expand within
23392 // strings and functions with type expressions. The previous point is used because the query
23393 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23394 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23395 let previous_point = if previous_point.column > 0 {
23396 cursor_position.to_previous_offset(&buffer_snapshot)
23397 } else {
23398 cursor_position.to_offset(&buffer_snapshot)
23399 };
23400 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23401 && scope.prefers_label_for_snippet_in_completion()
23402 && let Some(label) = completion.label()
23403 && matches!(
23404 completion.kind(),
23405 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23406 )
23407 {
23408 snippet_source = label;
23409 }
23410 match Snippet::parse(&snippet_source).log_err() {
23411 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23412 None => (None, completion.new_text.clone()),
23413 }
23414 } else {
23415 (None, completion.new_text.clone())
23416 };
23417
23418 let mut range_to_replace = {
23419 let replace_range = &completion.replace_range;
23420 if let CompletionSource::Lsp {
23421 insert_range: Some(insert_range),
23422 ..
23423 } = &completion.source
23424 {
23425 debug_assert_eq!(
23426 insert_range.start, replace_range.start,
23427 "insert_range and replace_range should start at the same position"
23428 );
23429 debug_assert!(
23430 insert_range
23431 .start
23432 .cmp(cursor_position, &buffer_snapshot)
23433 .is_le(),
23434 "insert_range should start before or at cursor position"
23435 );
23436 debug_assert!(
23437 replace_range
23438 .start
23439 .cmp(cursor_position, &buffer_snapshot)
23440 .is_le(),
23441 "replace_range should start before or at cursor position"
23442 );
23443
23444 let should_replace = match intent {
23445 CompletionIntent::CompleteWithInsert => false,
23446 CompletionIntent::CompleteWithReplace => true,
23447 CompletionIntent::Complete | CompletionIntent::Compose => {
23448 let insert_mode =
23449 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23450 .completions
23451 .lsp_insert_mode;
23452 match insert_mode {
23453 LspInsertMode::Insert => false,
23454 LspInsertMode::Replace => true,
23455 LspInsertMode::ReplaceSubsequence => {
23456 let mut text_to_replace = buffer.chars_for_range(
23457 buffer.anchor_before(replace_range.start)
23458 ..buffer.anchor_after(replace_range.end),
23459 );
23460 let mut current_needle = text_to_replace.next();
23461 for haystack_ch in completion.label.text.chars() {
23462 if let Some(needle_ch) = current_needle
23463 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23464 {
23465 current_needle = text_to_replace.next();
23466 }
23467 }
23468 current_needle.is_none()
23469 }
23470 LspInsertMode::ReplaceSuffix => {
23471 if replace_range
23472 .end
23473 .cmp(cursor_position, &buffer_snapshot)
23474 .is_gt()
23475 {
23476 let range_after_cursor = *cursor_position..replace_range.end;
23477 let text_after_cursor = buffer
23478 .text_for_range(
23479 buffer.anchor_before(range_after_cursor.start)
23480 ..buffer.anchor_after(range_after_cursor.end),
23481 )
23482 .collect::<String>()
23483 .to_ascii_lowercase();
23484 completion
23485 .label
23486 .text
23487 .to_ascii_lowercase()
23488 .ends_with(&text_after_cursor)
23489 } else {
23490 true
23491 }
23492 }
23493 }
23494 }
23495 };
23496
23497 if should_replace {
23498 replace_range.clone()
23499 } else {
23500 insert_range.clone()
23501 }
23502 } else {
23503 replace_range.clone()
23504 }
23505 };
23506
23507 if range_to_replace
23508 .end
23509 .cmp(cursor_position, &buffer_snapshot)
23510 .is_lt()
23511 {
23512 range_to_replace.end = *cursor_position;
23513 }
23514
23515 let replace_range = range_to_replace.to_offset(buffer);
23516 CompletionEdit {
23517 new_text,
23518 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23519 snippet,
23520 }
23521}
23522
23523struct CompletionEdit {
23524 new_text: String,
23525 replace_range: Range<BufferOffset>,
23526 snippet: Option<Snippet>,
23527}
23528
23529fn comment_delimiter_for_newline(
23530 start_point: &Point,
23531 buffer: &MultiBufferSnapshot,
23532 language: &LanguageScope,
23533) -> Option<Arc<str>> {
23534 let delimiters = language.line_comment_prefixes();
23535 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
23536 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23537
23538 let num_of_whitespaces = snapshot
23539 .chars_for_range(range.clone())
23540 .take_while(|c| c.is_whitespace())
23541 .count();
23542 let comment_candidate = snapshot
23543 .chars_for_range(range.clone())
23544 .skip(num_of_whitespaces)
23545 .take(max_len_of_delimiter)
23546 .collect::<String>();
23547 let (delimiter, trimmed_len) = delimiters
23548 .iter()
23549 .filter_map(|delimiter| {
23550 let prefix = delimiter.trim_end();
23551 if comment_candidate.starts_with(prefix) {
23552 Some((delimiter, prefix.len()))
23553 } else {
23554 None
23555 }
23556 })
23557 .max_by_key(|(_, len)| *len)?;
23558
23559 if let Some(BlockCommentConfig {
23560 start: block_start, ..
23561 }) = language.block_comment()
23562 {
23563 let block_start_trimmed = block_start.trim_end();
23564 if block_start_trimmed.starts_with(delimiter.trim_end()) {
23565 let line_content = snapshot
23566 .chars_for_range(range)
23567 .skip(num_of_whitespaces)
23568 .take(block_start_trimmed.len())
23569 .collect::<String>();
23570
23571 if line_content.starts_with(block_start_trimmed) {
23572 return None;
23573 }
23574 }
23575 }
23576
23577 let cursor_is_placed_after_comment_marker =
23578 num_of_whitespaces + trimmed_len <= start_point.column as usize;
23579 if cursor_is_placed_after_comment_marker {
23580 Some(delimiter.clone())
23581 } else {
23582 None
23583 }
23584}
23585
23586fn documentation_delimiter_for_newline(
23587 start_point: &Point,
23588 buffer: &MultiBufferSnapshot,
23589 language: &LanguageScope,
23590 newline_config: &mut NewlineConfig,
23591) -> Option<Arc<str>> {
23592 let BlockCommentConfig {
23593 start: start_tag,
23594 end: end_tag,
23595 prefix: delimiter,
23596 tab_size: len,
23597 } = language.documentation_comment()?;
23598 let is_within_block_comment = buffer
23599 .language_scope_at(*start_point)
23600 .is_some_and(|scope| scope.override_name() == Some("comment"));
23601 if !is_within_block_comment {
23602 return None;
23603 }
23604
23605 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23606
23607 let num_of_whitespaces = snapshot
23608 .chars_for_range(range.clone())
23609 .take_while(|c| c.is_whitespace())
23610 .count();
23611
23612 // 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.
23613 let column = start_point.column;
23614 let cursor_is_after_start_tag = {
23615 let start_tag_len = start_tag.len();
23616 let start_tag_line = snapshot
23617 .chars_for_range(range.clone())
23618 .skip(num_of_whitespaces)
23619 .take(start_tag_len)
23620 .collect::<String>();
23621 if start_tag_line.starts_with(start_tag.as_ref()) {
23622 num_of_whitespaces + start_tag_len <= column as usize
23623 } else {
23624 false
23625 }
23626 };
23627
23628 let cursor_is_after_delimiter = {
23629 let delimiter_trim = delimiter.trim_end();
23630 let delimiter_line = snapshot
23631 .chars_for_range(range.clone())
23632 .skip(num_of_whitespaces)
23633 .take(delimiter_trim.len())
23634 .collect::<String>();
23635 if delimiter_line.starts_with(delimiter_trim) {
23636 num_of_whitespaces + delimiter_trim.len() <= column as usize
23637 } else {
23638 false
23639 }
23640 };
23641
23642 let mut needs_extra_line = false;
23643 let mut extra_line_additional_indent = IndentSize::spaces(0);
23644
23645 let cursor_is_before_end_tag_if_exists = {
23646 let mut char_position = 0u32;
23647 let mut end_tag_offset = None;
23648
23649 'outer: for chunk in snapshot.text_for_range(range) {
23650 if let Some(byte_pos) = chunk.find(&**end_tag) {
23651 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
23652 end_tag_offset = Some(char_position + chars_before_match);
23653 break 'outer;
23654 }
23655 char_position += chunk.chars().count() as u32;
23656 }
23657
23658 if let Some(end_tag_offset) = end_tag_offset {
23659 let cursor_is_before_end_tag = column <= end_tag_offset;
23660 if cursor_is_after_start_tag {
23661 if cursor_is_before_end_tag {
23662 needs_extra_line = true;
23663 }
23664 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
23665 if cursor_is_at_start_of_end_tag {
23666 extra_line_additional_indent.len = *len;
23667 }
23668 }
23669 cursor_is_before_end_tag
23670 } else {
23671 true
23672 }
23673 };
23674
23675 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
23676 && cursor_is_before_end_tag_if_exists
23677 {
23678 let additional_indent = if cursor_is_after_start_tag {
23679 IndentSize::spaces(*len)
23680 } else {
23681 IndentSize::spaces(0)
23682 };
23683
23684 *newline_config = NewlineConfig::Newline {
23685 additional_indent,
23686 extra_line_additional_indent: if needs_extra_line {
23687 Some(extra_line_additional_indent)
23688 } else {
23689 None
23690 },
23691 prevent_auto_indent: true,
23692 };
23693 Some(delimiter.clone())
23694 } else {
23695 None
23696 }
23697}
23698
23699const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
23700
23701fn list_delimiter_for_newline(
23702 start_point: &Point,
23703 buffer: &MultiBufferSnapshot,
23704 language: &LanguageScope,
23705 newline_config: &mut NewlineConfig,
23706) -> Option<Arc<str>> {
23707 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23708
23709 let num_of_whitespaces = snapshot
23710 .chars_for_range(range.clone())
23711 .take_while(|c| c.is_whitespace())
23712 .count();
23713
23714 let task_list_entries: Vec<_> = language
23715 .task_list()
23716 .into_iter()
23717 .flat_map(|config| {
23718 config
23719 .prefixes
23720 .iter()
23721 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
23722 })
23723 .collect();
23724 let unordered_list_entries: Vec<_> = language
23725 .unordered_list()
23726 .iter()
23727 .map(|marker| (marker.as_ref(), marker.as_ref()))
23728 .collect();
23729
23730 let all_entries: Vec<_> = task_list_entries
23731 .into_iter()
23732 .chain(unordered_list_entries)
23733 .collect();
23734
23735 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
23736 let candidate: String = snapshot
23737 .chars_for_range(range.clone())
23738 .skip(num_of_whitespaces)
23739 .take(max_prefix_len)
23740 .collect();
23741
23742 if let Some((prefix, continuation)) = all_entries
23743 .iter()
23744 .filter(|(prefix, _)| candidate.starts_with(*prefix))
23745 .max_by_key(|(prefix, _)| prefix.len())
23746 {
23747 let end_of_prefix = num_of_whitespaces + prefix.len();
23748 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
23749 let has_content_after_marker = snapshot
23750 .chars_for_range(range)
23751 .skip(end_of_prefix)
23752 .any(|c| !c.is_whitespace());
23753
23754 if has_content_after_marker && cursor_is_after_prefix {
23755 return Some((*continuation).into());
23756 }
23757
23758 if start_point.column as usize == end_of_prefix {
23759 if num_of_whitespaces == 0 {
23760 *newline_config = NewlineConfig::ClearCurrentLine;
23761 } else {
23762 *newline_config = NewlineConfig::UnindentCurrentLine {
23763 continuation: (*continuation).into(),
23764 };
23765 }
23766 }
23767
23768 return None;
23769 }
23770 }
23771
23772 let candidate: String = snapshot
23773 .chars_for_range(range.clone())
23774 .skip(num_of_whitespaces)
23775 .take(ORDERED_LIST_MAX_MARKER_LEN)
23776 .collect();
23777
23778 for ordered_config in language.ordered_list() {
23779 let regex = match Regex::new(&ordered_config.pattern) {
23780 Ok(r) => r,
23781 Err(_) => continue,
23782 };
23783
23784 if let Some(captures) = regex.captures(&candidate) {
23785 let full_match = captures.get(0)?;
23786 let marker_len = full_match.len();
23787 let end_of_prefix = num_of_whitespaces + marker_len;
23788 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
23789
23790 let has_content_after_marker = snapshot
23791 .chars_for_range(range)
23792 .skip(end_of_prefix)
23793 .any(|c| !c.is_whitespace());
23794
23795 if has_content_after_marker && cursor_is_after_prefix {
23796 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
23797 let continuation = ordered_config
23798 .format
23799 .replace("{1}", &(number + 1).to_string());
23800 return Some(continuation.into());
23801 }
23802
23803 if start_point.column as usize == end_of_prefix {
23804 let continuation = ordered_config.format.replace("{1}", "1");
23805 if num_of_whitespaces == 0 {
23806 *newline_config = NewlineConfig::ClearCurrentLine;
23807 } else {
23808 *newline_config = NewlineConfig::UnindentCurrentLine {
23809 continuation: continuation.into(),
23810 };
23811 }
23812 }
23813
23814 return None;
23815 }
23816 }
23817
23818 None
23819}
23820
23821fn is_list_prefix_row(
23822 row: MultiBufferRow,
23823 buffer: &MultiBufferSnapshot,
23824 language: &LanguageScope,
23825) -> bool {
23826 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
23827 return false;
23828 };
23829
23830 let num_of_whitespaces = snapshot
23831 .chars_for_range(range.clone())
23832 .take_while(|c| c.is_whitespace())
23833 .count();
23834
23835 let task_list_prefixes: Vec<_> = language
23836 .task_list()
23837 .into_iter()
23838 .flat_map(|config| {
23839 config
23840 .prefixes
23841 .iter()
23842 .map(|p| p.as_ref())
23843 .collect::<Vec<_>>()
23844 })
23845 .collect();
23846 let unordered_list_markers: Vec<_> = language
23847 .unordered_list()
23848 .iter()
23849 .map(|marker| marker.as_ref())
23850 .collect();
23851 let all_prefixes: Vec<_> = task_list_prefixes
23852 .into_iter()
23853 .chain(unordered_list_markers)
23854 .collect();
23855 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
23856 let candidate: String = snapshot
23857 .chars_for_range(range.clone())
23858 .skip(num_of_whitespaces)
23859 .take(max_prefix_len)
23860 .collect();
23861 if all_prefixes
23862 .iter()
23863 .any(|prefix| candidate.starts_with(*prefix))
23864 {
23865 return true;
23866 }
23867 }
23868
23869 let ordered_list_candidate: String = snapshot
23870 .chars_for_range(range)
23871 .skip(num_of_whitespaces)
23872 .take(ORDERED_LIST_MAX_MARKER_LEN)
23873 .collect();
23874 for ordered_config in language.ordered_list() {
23875 let regex = match Regex::new(&ordered_config.pattern) {
23876 Ok(r) => r,
23877 Err(_) => continue,
23878 };
23879 if let Some(captures) = regex.captures(&ordered_list_candidate) {
23880 return captures.get(0).is_some();
23881 }
23882 }
23883
23884 false
23885}
23886
23887#[derive(Debug)]
23888enum NewlineConfig {
23889 /// Insert newline with optional additional indent and optional extra blank line
23890 Newline {
23891 additional_indent: IndentSize,
23892 extra_line_additional_indent: Option<IndentSize>,
23893 prevent_auto_indent: bool,
23894 },
23895 /// Clear the current line
23896 ClearCurrentLine,
23897 /// Unindent the current line and add continuation
23898 UnindentCurrentLine { continuation: Arc<str> },
23899}
23900
23901impl NewlineConfig {
23902 fn has_extra_line(&self) -> bool {
23903 matches!(
23904 self,
23905 Self::Newline {
23906 extra_line_additional_indent: Some(_),
23907 ..
23908 }
23909 )
23910 }
23911
23912 fn insert_extra_newline_brackets(
23913 buffer: &MultiBufferSnapshot,
23914 range: Range<MultiBufferOffset>,
23915 language: &language::LanguageScope,
23916 ) -> bool {
23917 let leading_whitespace_len = buffer
23918 .reversed_chars_at(range.start)
23919 .take_while(|c| c.is_whitespace() && *c != '\n')
23920 .map(|c| c.len_utf8())
23921 .sum::<usize>();
23922 let trailing_whitespace_len = buffer
23923 .chars_at(range.end)
23924 .take_while(|c| c.is_whitespace() && *c != '\n')
23925 .map(|c| c.len_utf8())
23926 .sum::<usize>();
23927 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23928
23929 language.brackets().any(|(pair, enabled)| {
23930 let pair_start = pair.start.trim_end();
23931 let pair_end = pair.end.trim_start();
23932
23933 enabled
23934 && pair.newline
23935 && buffer.contains_str_at(range.end, pair_end)
23936 && buffer.contains_str_at(
23937 range.start.saturating_sub_usize(pair_start.len()),
23938 pair_start,
23939 )
23940 })
23941 }
23942
23943 fn insert_extra_newline_tree_sitter(
23944 buffer: &MultiBufferSnapshot,
23945 range: Range<MultiBufferOffset>,
23946 ) -> bool {
23947 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23948 [(buffer, range, _)] => (*buffer, range.clone()),
23949 _ => return false,
23950 };
23951 let pair = {
23952 let mut result: Option<BracketMatch<usize>> = None;
23953
23954 for pair in buffer
23955 .all_bracket_ranges(range.start.0..range.end.0)
23956 .filter(move |pair| {
23957 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23958 })
23959 {
23960 let len = pair.close_range.end - pair.open_range.start;
23961
23962 if let Some(existing) = &result {
23963 let existing_len = existing.close_range.end - existing.open_range.start;
23964 if len > existing_len {
23965 continue;
23966 }
23967 }
23968
23969 result = Some(pair);
23970 }
23971
23972 result
23973 };
23974 let Some(pair) = pair else {
23975 return false;
23976 };
23977 pair.newline_only
23978 && buffer
23979 .chars_for_range(pair.open_range.end..range.start.0)
23980 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23981 .all(|c| c.is_whitespace() && c != '\n')
23982 }
23983}
23984
23985fn update_uncommitted_diff_for_buffer(
23986 editor: Entity<Editor>,
23987 project: &Entity<Project>,
23988 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23989 buffer: Entity<MultiBuffer>,
23990 cx: &mut App,
23991) -> Task<()> {
23992 let mut tasks = Vec::new();
23993 project.update(cx, |project, cx| {
23994 for buffer in buffers {
23995 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23996 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23997 }
23998 }
23999 });
24000 cx.spawn(async move |cx| {
24001 let diffs = future::join_all(tasks).await;
24002 if editor.read_with(cx, |editor, _cx| editor.temporary_diff_override) {
24003 return;
24004 }
24005
24006 buffer.update(cx, |buffer, cx| {
24007 for diff in diffs.into_iter().flatten() {
24008 buffer.add_diff(diff, cx);
24009 }
24010 });
24011 })
24012}
24013
24014fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
24015 let tab_size = tab_size.get() as usize;
24016 let mut width = offset;
24017
24018 for ch in text.chars() {
24019 width += if ch == '\t' {
24020 tab_size - (width % tab_size)
24021 } else {
24022 1
24023 };
24024 }
24025
24026 width - offset
24027}
24028
24029#[cfg(test)]
24030mod tests {
24031 use super::*;
24032
24033 #[test]
24034 fn test_string_size_with_expanded_tabs() {
24035 let nz = |val| NonZeroU32::new(val).unwrap();
24036 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
24037 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
24038 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
24039 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
24040 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
24041 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
24042 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
24043 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
24044 }
24045}
24046
24047/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
24048struct WordBreakingTokenizer<'a> {
24049 input: &'a str,
24050}
24051
24052impl<'a> WordBreakingTokenizer<'a> {
24053 fn new(input: &'a str) -> Self {
24054 Self { input }
24055 }
24056}
24057
24058fn is_char_ideographic(ch: char) -> bool {
24059 use unicode_script::Script::*;
24060 use unicode_script::UnicodeScript;
24061 matches!(ch.script(), Han | Tangut | Yi)
24062}
24063
24064fn is_grapheme_ideographic(text: &str) -> bool {
24065 text.chars().any(is_char_ideographic)
24066}
24067
24068fn is_grapheme_whitespace(text: &str) -> bool {
24069 text.chars().any(|x| x.is_whitespace())
24070}
24071
24072fn should_stay_with_preceding_ideograph(text: &str) -> bool {
24073 text.chars()
24074 .next()
24075 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
24076}
24077
24078#[derive(PartialEq, Eq, Debug, Clone, Copy)]
24079enum WordBreakToken<'a> {
24080 Word { token: &'a str, grapheme_len: usize },
24081 InlineWhitespace { token: &'a str, grapheme_len: usize },
24082 Newline,
24083}
24084
24085impl<'a> Iterator for WordBreakingTokenizer<'a> {
24086 /// Yields a span, the count of graphemes in the token, and whether it was
24087 /// whitespace. Note that it also breaks at word boundaries.
24088 type Item = WordBreakToken<'a>;
24089
24090 fn next(&mut self) -> Option<Self::Item> {
24091 use unicode_segmentation::UnicodeSegmentation;
24092 if self.input.is_empty() {
24093 return None;
24094 }
24095
24096 let mut iter = self.input.graphemes(true).peekable();
24097 let mut offset = 0;
24098 let mut grapheme_len = 0;
24099 if let Some(first_grapheme) = iter.next() {
24100 let is_newline = first_grapheme == "\n";
24101 let is_whitespace = is_grapheme_whitespace(first_grapheme);
24102 offset += first_grapheme.len();
24103 grapheme_len += 1;
24104 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
24105 if let Some(grapheme) = iter.peek().copied()
24106 && should_stay_with_preceding_ideograph(grapheme)
24107 {
24108 offset += grapheme.len();
24109 grapheme_len += 1;
24110 }
24111 } else {
24112 let mut words = self.input[offset..].split_word_bound_indices().peekable();
24113 let mut next_word_bound = words.peek().copied();
24114 if next_word_bound.is_some_and(|(i, _)| i == 0) {
24115 next_word_bound = words.next();
24116 }
24117 while let Some(grapheme) = iter.peek().copied() {
24118 if next_word_bound.is_some_and(|(i, _)| i == offset) {
24119 break;
24120 };
24121 if is_grapheme_whitespace(grapheme) != is_whitespace
24122 || (grapheme == "\n") != is_newline
24123 {
24124 break;
24125 };
24126 offset += grapheme.len();
24127 grapheme_len += 1;
24128 iter.next();
24129 }
24130 }
24131 let token = &self.input[..offset];
24132 self.input = &self.input[offset..];
24133 if token == "\n" {
24134 Some(WordBreakToken::Newline)
24135 } else if is_whitespace {
24136 Some(WordBreakToken::InlineWhitespace {
24137 token,
24138 grapheme_len,
24139 })
24140 } else {
24141 Some(WordBreakToken::Word {
24142 token,
24143 grapheme_len,
24144 })
24145 }
24146 } else {
24147 None
24148 }
24149 }
24150}
24151
24152#[test]
24153fn test_word_breaking_tokenizer() {
24154 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
24155 ("", &[]),
24156 (" ", &[whitespace(" ", 2)]),
24157 ("Ʒ", &[word("Ʒ", 1)]),
24158 ("Ǽ", &[word("Ǽ", 1)]),
24159 ("⋑", &[word("⋑", 1)]),
24160 ("⋑⋑", &[word("⋑⋑", 2)]),
24161 (
24162 "原理,进而",
24163 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
24164 ),
24165 (
24166 "hello world",
24167 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
24168 ),
24169 (
24170 "hello, world",
24171 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
24172 ),
24173 (
24174 " hello world",
24175 &[
24176 whitespace(" ", 2),
24177 word("hello", 5),
24178 whitespace(" ", 1),
24179 word("world", 5),
24180 ],
24181 ),
24182 (
24183 "这是什么 \n 钢笔",
24184 &[
24185 word("这", 1),
24186 word("是", 1),
24187 word("什", 1),
24188 word("么", 1),
24189 whitespace(" ", 1),
24190 newline(),
24191 whitespace(" ", 1),
24192 word("钢", 1),
24193 word("笔", 1),
24194 ],
24195 ),
24196 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
24197 ];
24198
24199 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24200 WordBreakToken::Word {
24201 token,
24202 grapheme_len,
24203 }
24204 }
24205
24206 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24207 WordBreakToken::InlineWhitespace {
24208 token,
24209 grapheme_len,
24210 }
24211 }
24212
24213 fn newline() -> WordBreakToken<'static> {
24214 WordBreakToken::Newline
24215 }
24216
24217 for (input, result) in tests {
24218 assert_eq!(
24219 WordBreakingTokenizer::new(input)
24220 .collect::<Vec<_>>()
24221 .as_slice(),
24222 *result,
24223 );
24224 }
24225}
24226
24227fn wrap_with_prefix(
24228 first_line_prefix: String,
24229 subsequent_lines_prefix: String,
24230 unwrapped_text: String,
24231 wrap_column: usize,
24232 tab_size: NonZeroU32,
24233 preserve_existing_whitespace: bool,
24234) -> String {
24235 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
24236 let subsequent_lines_prefix_len =
24237 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
24238 let mut wrapped_text = String::new();
24239 let mut current_line = first_line_prefix;
24240 let mut is_first_line = true;
24241
24242 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
24243 let mut current_line_len = first_line_prefix_len;
24244 let mut in_whitespace = false;
24245 for token in tokenizer {
24246 let have_preceding_whitespace = in_whitespace;
24247 match token {
24248 WordBreakToken::Word {
24249 token,
24250 grapheme_len,
24251 } => {
24252 in_whitespace = false;
24253 let current_prefix_len = if is_first_line {
24254 first_line_prefix_len
24255 } else {
24256 subsequent_lines_prefix_len
24257 };
24258 if current_line_len + grapheme_len > wrap_column
24259 && current_line_len != current_prefix_len
24260 {
24261 wrapped_text.push_str(current_line.trim_end());
24262 wrapped_text.push('\n');
24263 is_first_line = false;
24264 current_line = subsequent_lines_prefix.clone();
24265 current_line_len = subsequent_lines_prefix_len;
24266 }
24267 current_line.push_str(token);
24268 current_line_len += grapheme_len;
24269 }
24270 WordBreakToken::InlineWhitespace {
24271 mut token,
24272 mut grapheme_len,
24273 } => {
24274 in_whitespace = true;
24275 if have_preceding_whitespace && !preserve_existing_whitespace {
24276 continue;
24277 }
24278 if !preserve_existing_whitespace {
24279 // Keep a single whitespace grapheme as-is
24280 if let Some(first) =
24281 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
24282 {
24283 token = first;
24284 } else {
24285 token = " ";
24286 }
24287 grapheme_len = 1;
24288 }
24289 let current_prefix_len = if is_first_line {
24290 first_line_prefix_len
24291 } else {
24292 subsequent_lines_prefix_len
24293 };
24294 if current_line_len + grapheme_len > wrap_column {
24295 wrapped_text.push_str(current_line.trim_end());
24296 wrapped_text.push('\n');
24297 is_first_line = false;
24298 current_line = subsequent_lines_prefix.clone();
24299 current_line_len = subsequent_lines_prefix_len;
24300 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
24301 current_line.push_str(token);
24302 current_line_len += grapheme_len;
24303 }
24304 }
24305 WordBreakToken::Newline => {
24306 in_whitespace = true;
24307 let current_prefix_len = if is_first_line {
24308 first_line_prefix_len
24309 } else {
24310 subsequent_lines_prefix_len
24311 };
24312 if preserve_existing_whitespace {
24313 wrapped_text.push_str(current_line.trim_end());
24314 wrapped_text.push('\n');
24315 is_first_line = false;
24316 current_line = subsequent_lines_prefix.clone();
24317 current_line_len = subsequent_lines_prefix_len;
24318 } else if have_preceding_whitespace {
24319 continue;
24320 } else if current_line_len + 1 > wrap_column
24321 && current_line_len != current_prefix_len
24322 {
24323 wrapped_text.push_str(current_line.trim_end());
24324 wrapped_text.push('\n');
24325 is_first_line = false;
24326 current_line = subsequent_lines_prefix.clone();
24327 current_line_len = subsequent_lines_prefix_len;
24328 } else if current_line_len != current_prefix_len {
24329 current_line.push(' ');
24330 current_line_len += 1;
24331 }
24332 }
24333 }
24334 }
24335
24336 if !current_line.is_empty() {
24337 wrapped_text.push_str(¤t_line);
24338 }
24339 wrapped_text
24340}
24341
24342#[test]
24343fn test_wrap_with_prefix() {
24344 assert_eq!(
24345 wrap_with_prefix(
24346 "# ".to_string(),
24347 "# ".to_string(),
24348 "abcdefg".to_string(),
24349 4,
24350 NonZeroU32::new(4).unwrap(),
24351 false,
24352 ),
24353 "# abcdefg"
24354 );
24355 assert_eq!(
24356 wrap_with_prefix(
24357 "".to_string(),
24358 "".to_string(),
24359 "\thello world".to_string(),
24360 8,
24361 NonZeroU32::new(4).unwrap(),
24362 false,
24363 ),
24364 "hello\nworld"
24365 );
24366 assert_eq!(
24367 wrap_with_prefix(
24368 "// ".to_string(),
24369 "// ".to_string(),
24370 "xx \nyy zz aa bb cc".to_string(),
24371 12,
24372 NonZeroU32::new(4).unwrap(),
24373 false,
24374 ),
24375 "// xx yy zz\n// aa bb cc"
24376 );
24377 assert_eq!(
24378 wrap_with_prefix(
24379 String::new(),
24380 String::new(),
24381 "这是什么 \n 钢笔".to_string(),
24382 3,
24383 NonZeroU32::new(4).unwrap(),
24384 false,
24385 ),
24386 "这是什\n么 钢\n笔"
24387 );
24388 assert_eq!(
24389 wrap_with_prefix(
24390 String::new(),
24391 String::new(),
24392 format!("foo{}bar", '\u{2009}'), // thin space
24393 80,
24394 NonZeroU32::new(4).unwrap(),
24395 false,
24396 ),
24397 format!("foo{}bar", '\u{2009}')
24398 );
24399}
24400
24401pub trait CollaborationHub {
24402 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
24403 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
24404 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
24405}
24406
24407impl CollaborationHub for Entity<Project> {
24408 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
24409 self.read(cx).collaborators()
24410 }
24411
24412 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
24413 self.read(cx).user_store().read(cx).participant_indices()
24414 }
24415
24416 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
24417 let this = self.read(cx);
24418 let user_ids = this.collaborators().values().map(|c| c.user_id);
24419 this.user_store().read(cx).participant_names(user_ids, cx)
24420 }
24421}
24422
24423pub trait SemanticsProvider {
24424 fn hover(
24425 &self,
24426 buffer: &Entity<Buffer>,
24427 position: text::Anchor,
24428 cx: &mut App,
24429 ) -> Option<Task<Option<Vec<project::Hover>>>>;
24430
24431 fn inline_values(
24432 &self,
24433 buffer_handle: Entity<Buffer>,
24434 range: Range<text::Anchor>,
24435 cx: &mut App,
24436 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24437
24438 fn applicable_inlay_chunks(
24439 &self,
24440 buffer: &Entity<Buffer>,
24441 ranges: &[Range<text::Anchor>],
24442 cx: &mut App,
24443 ) -> Vec<Range<BufferRow>>;
24444
24445 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24446
24447 fn inlay_hints(
24448 &self,
24449 invalidate: InvalidationStrategy,
24450 buffer: Entity<Buffer>,
24451 ranges: Vec<Range<text::Anchor>>,
24452 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24453 cx: &mut App,
24454 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24455
24456 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24457
24458 fn document_highlights(
24459 &self,
24460 buffer: &Entity<Buffer>,
24461 position: text::Anchor,
24462 cx: &mut App,
24463 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24464
24465 fn definitions(
24466 &self,
24467 buffer: &Entity<Buffer>,
24468 position: text::Anchor,
24469 kind: GotoDefinitionKind,
24470 cx: &mut App,
24471 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24472
24473 fn range_for_rename(
24474 &self,
24475 buffer: &Entity<Buffer>,
24476 position: text::Anchor,
24477 cx: &mut App,
24478 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24479
24480 fn perform_rename(
24481 &self,
24482 buffer: &Entity<Buffer>,
24483 position: text::Anchor,
24484 new_name: String,
24485 cx: &mut App,
24486 ) -> Option<Task<Result<ProjectTransaction>>>;
24487}
24488
24489pub trait CompletionProvider {
24490 fn completions(
24491 &self,
24492 excerpt_id: ExcerptId,
24493 buffer: &Entity<Buffer>,
24494 buffer_position: text::Anchor,
24495 trigger: CompletionContext,
24496 window: &mut Window,
24497 cx: &mut Context<Editor>,
24498 ) -> Task<Result<Vec<CompletionResponse>>>;
24499
24500 fn resolve_completions(
24501 &self,
24502 _buffer: Entity<Buffer>,
24503 _completion_indices: Vec<usize>,
24504 _completions: Rc<RefCell<Box<[Completion]>>>,
24505 _cx: &mut Context<Editor>,
24506 ) -> Task<Result<bool>> {
24507 Task::ready(Ok(false))
24508 }
24509
24510 fn apply_additional_edits_for_completion(
24511 &self,
24512 _buffer: Entity<Buffer>,
24513 _completions: Rc<RefCell<Box<[Completion]>>>,
24514 _completion_index: usize,
24515 _push_to_history: bool,
24516 _cx: &mut Context<Editor>,
24517 ) -> Task<Result<Option<language::Transaction>>> {
24518 Task::ready(Ok(None))
24519 }
24520
24521 fn is_completion_trigger(
24522 &self,
24523 buffer: &Entity<Buffer>,
24524 position: language::Anchor,
24525 text: &str,
24526 trigger_in_words: bool,
24527 cx: &mut Context<Editor>,
24528 ) -> bool;
24529
24530 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24531
24532 fn sort_completions(&self) -> bool {
24533 true
24534 }
24535
24536 fn filter_completions(&self) -> bool {
24537 true
24538 }
24539
24540 fn show_snippets(&self) -> bool {
24541 false
24542 }
24543}
24544
24545pub trait CodeActionProvider {
24546 fn id(&self) -> Arc<str>;
24547
24548 fn code_actions(
24549 &self,
24550 buffer: &Entity<Buffer>,
24551 range: Range<text::Anchor>,
24552 window: &mut Window,
24553 cx: &mut App,
24554 ) -> Task<Result<Vec<CodeAction>>>;
24555
24556 fn apply_code_action(
24557 &self,
24558 buffer_handle: Entity<Buffer>,
24559 action: CodeAction,
24560 excerpt_id: ExcerptId,
24561 push_to_history: bool,
24562 window: &mut Window,
24563 cx: &mut App,
24564 ) -> Task<Result<ProjectTransaction>>;
24565}
24566
24567impl CodeActionProvider for Entity<Project> {
24568 fn id(&self) -> Arc<str> {
24569 "project".into()
24570 }
24571
24572 fn code_actions(
24573 &self,
24574 buffer: &Entity<Buffer>,
24575 range: Range<text::Anchor>,
24576 _window: &mut Window,
24577 cx: &mut App,
24578 ) -> Task<Result<Vec<CodeAction>>> {
24579 self.update(cx, |project, cx| {
24580 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24581 let code_actions = project.code_actions(buffer, range, None, cx);
24582 cx.background_spawn(async move {
24583 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24584 Ok(code_lens_actions
24585 .context("code lens fetch")?
24586 .into_iter()
24587 .flatten()
24588 .chain(
24589 code_actions
24590 .context("code action fetch")?
24591 .into_iter()
24592 .flatten(),
24593 )
24594 .collect())
24595 })
24596 })
24597 }
24598
24599 fn apply_code_action(
24600 &self,
24601 buffer_handle: Entity<Buffer>,
24602 action: CodeAction,
24603 _excerpt_id: ExcerptId,
24604 push_to_history: bool,
24605 _window: &mut Window,
24606 cx: &mut App,
24607 ) -> Task<Result<ProjectTransaction>> {
24608 self.update(cx, |project, cx| {
24609 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24610 })
24611 }
24612}
24613
24614fn snippet_completions(
24615 project: &Project,
24616 buffer: &Entity<Buffer>,
24617 buffer_anchor: text::Anchor,
24618 classifier: CharClassifier,
24619 cx: &mut App,
24620) -> Task<Result<CompletionResponse>> {
24621 let languages = buffer.read(cx).languages_at(buffer_anchor);
24622 let snippet_store = project.snippets().read(cx);
24623
24624 let scopes: Vec<_> = languages
24625 .iter()
24626 .filter_map(|language| {
24627 let language_name = language.lsp_id();
24628 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24629
24630 if snippets.is_empty() {
24631 None
24632 } else {
24633 Some((language.default_scope(), snippets))
24634 }
24635 })
24636 .collect();
24637
24638 if scopes.is_empty() {
24639 return Task::ready(Ok(CompletionResponse {
24640 completions: vec![],
24641 display_options: CompletionDisplayOptions::default(),
24642 is_incomplete: false,
24643 }));
24644 }
24645
24646 let snapshot = buffer.read(cx).text_snapshot();
24647 let executor = cx.background_executor().clone();
24648
24649 cx.background_spawn(async move {
24650 let is_word_char = |c| classifier.is_word(c);
24651
24652 let mut is_incomplete = false;
24653 let mut completions: Vec<Completion> = Vec::new();
24654
24655 const MAX_PREFIX_LEN: usize = 128;
24656 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24657 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24658 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24659
24660 let max_buffer_window: String = snapshot
24661 .text_for_range(window_start..buffer_offset)
24662 .collect();
24663
24664 if max_buffer_window.is_empty() {
24665 return Ok(CompletionResponse {
24666 completions: vec![],
24667 display_options: CompletionDisplayOptions::default(),
24668 is_incomplete: true,
24669 });
24670 }
24671
24672 for (_scope, snippets) in scopes.into_iter() {
24673 // Sort snippets by word count to match longer snippet prefixes first.
24674 let mut sorted_snippet_candidates = snippets
24675 .iter()
24676 .enumerate()
24677 .flat_map(|(snippet_ix, snippet)| {
24678 snippet
24679 .prefix
24680 .iter()
24681 .enumerate()
24682 .map(move |(prefix_ix, prefix)| {
24683 let word_count =
24684 snippet_candidate_suffixes(prefix, is_word_char).count();
24685 ((snippet_ix, prefix_ix), prefix, word_count)
24686 })
24687 })
24688 .collect_vec();
24689 sorted_snippet_candidates
24690 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24691
24692 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24693
24694 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24695 .take(
24696 sorted_snippet_candidates
24697 .first()
24698 .map(|(_, _, word_count)| *word_count)
24699 .unwrap_or_default(),
24700 )
24701 .collect_vec();
24702
24703 const MAX_RESULTS: usize = 100;
24704 // Each match also remembers how many characters from the buffer it consumed
24705 let mut matches: Vec<(StringMatch, usize)> = vec![];
24706
24707 let mut snippet_list_cutoff_index = 0;
24708 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24709 let word_count = buffer_index + 1;
24710 // Increase `snippet_list_cutoff_index` until we have all of the
24711 // snippets with sufficiently many words.
24712 while sorted_snippet_candidates
24713 .get(snippet_list_cutoff_index)
24714 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24715 *snippet_word_count >= word_count
24716 })
24717 {
24718 snippet_list_cutoff_index += 1;
24719 }
24720
24721 // Take only the candidates with at least `word_count` many words
24722 let snippet_candidates_at_word_len =
24723 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24724
24725 let candidates = snippet_candidates_at_word_len
24726 .iter()
24727 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24728 .enumerate() // index in `sorted_snippet_candidates`
24729 // First char must match
24730 .filter(|(_ix, prefix)| {
24731 itertools::equal(
24732 prefix
24733 .chars()
24734 .next()
24735 .into_iter()
24736 .flat_map(|c| c.to_lowercase()),
24737 buffer_window
24738 .chars()
24739 .next()
24740 .into_iter()
24741 .flat_map(|c| c.to_lowercase()),
24742 )
24743 })
24744 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24745 .collect::<Vec<StringMatchCandidate>>();
24746
24747 matches.extend(
24748 fuzzy::match_strings(
24749 &candidates,
24750 &buffer_window,
24751 buffer_window.chars().any(|c| c.is_uppercase()),
24752 true,
24753 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24754 &Default::default(),
24755 executor.clone(),
24756 )
24757 .await
24758 .into_iter()
24759 .map(|string_match| (string_match, buffer_window.len())),
24760 );
24761
24762 if matches.len() >= MAX_RESULTS {
24763 break;
24764 }
24765 }
24766
24767 let to_lsp = |point: &text::Anchor| {
24768 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24769 point_to_lsp(end)
24770 };
24771 let lsp_end = to_lsp(&buffer_anchor);
24772
24773 if matches.len() >= MAX_RESULTS {
24774 is_incomplete = true;
24775 }
24776
24777 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24778 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24779 sorted_snippet_candidates[string_match.candidate_id];
24780 let snippet = &snippets[snippet_index];
24781 let start = buffer_offset - buffer_window_len;
24782 let start = snapshot.anchor_before(start);
24783 let range = start..buffer_anchor;
24784 let lsp_start = to_lsp(&start);
24785 let lsp_range = lsp::Range {
24786 start: lsp_start,
24787 end: lsp_end,
24788 };
24789 Completion {
24790 replace_range: range,
24791 new_text: snippet.body.clone(),
24792 source: CompletionSource::Lsp {
24793 insert_range: None,
24794 server_id: LanguageServerId(usize::MAX),
24795 resolved: true,
24796 lsp_completion: Box::new(lsp::CompletionItem {
24797 label: snippet.prefix.first().unwrap().clone(),
24798 kind: Some(CompletionItemKind::SNIPPET),
24799 label_details: snippet.description.as_ref().map(|description| {
24800 lsp::CompletionItemLabelDetails {
24801 detail: Some(description.clone()),
24802 description: None,
24803 }
24804 }),
24805 insert_text_format: Some(InsertTextFormat::SNIPPET),
24806 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24807 lsp::InsertReplaceEdit {
24808 new_text: snippet.body.clone(),
24809 insert: lsp_range,
24810 replace: lsp_range,
24811 },
24812 )),
24813 filter_text: Some(snippet.body.clone()),
24814 sort_text: Some(char::MAX.to_string()),
24815 ..lsp::CompletionItem::default()
24816 }),
24817 lsp_defaults: None,
24818 },
24819 label: CodeLabel {
24820 text: matching_prefix.clone(),
24821 runs: Vec::new(),
24822 filter_range: 0..matching_prefix.len(),
24823 },
24824 icon_path: None,
24825 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24826 single_line: snippet.name.clone().into(),
24827 plain_text: snippet
24828 .description
24829 .clone()
24830 .map(|description| description.into()),
24831 }),
24832 insert_text_mode: None,
24833 confirm: None,
24834 match_start: Some(start),
24835 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24836 }
24837 }));
24838 }
24839
24840 Ok(CompletionResponse {
24841 completions,
24842 display_options: CompletionDisplayOptions::default(),
24843 is_incomplete,
24844 })
24845 })
24846}
24847
24848impl CompletionProvider for Entity<Project> {
24849 fn completions(
24850 &self,
24851 _excerpt_id: ExcerptId,
24852 buffer: &Entity<Buffer>,
24853 buffer_position: text::Anchor,
24854 options: CompletionContext,
24855 _window: &mut Window,
24856 cx: &mut Context<Editor>,
24857 ) -> Task<Result<Vec<CompletionResponse>>> {
24858 self.update(cx, |project, cx| {
24859 let task = project.completions(buffer, buffer_position, options, cx);
24860 cx.background_spawn(task)
24861 })
24862 }
24863
24864 fn resolve_completions(
24865 &self,
24866 buffer: Entity<Buffer>,
24867 completion_indices: Vec<usize>,
24868 completions: Rc<RefCell<Box<[Completion]>>>,
24869 cx: &mut Context<Editor>,
24870 ) -> Task<Result<bool>> {
24871 self.update(cx, |project, cx| {
24872 project.lsp_store().update(cx, |lsp_store, cx| {
24873 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24874 })
24875 })
24876 }
24877
24878 fn apply_additional_edits_for_completion(
24879 &self,
24880 buffer: Entity<Buffer>,
24881 completions: Rc<RefCell<Box<[Completion]>>>,
24882 completion_index: usize,
24883 push_to_history: bool,
24884 cx: &mut Context<Editor>,
24885 ) -> Task<Result<Option<language::Transaction>>> {
24886 self.update(cx, |project, cx| {
24887 project.lsp_store().update(cx, |lsp_store, cx| {
24888 lsp_store.apply_additional_edits_for_completion(
24889 buffer,
24890 completions,
24891 completion_index,
24892 push_to_history,
24893 cx,
24894 )
24895 })
24896 })
24897 }
24898
24899 fn is_completion_trigger(
24900 &self,
24901 buffer: &Entity<Buffer>,
24902 position: language::Anchor,
24903 text: &str,
24904 trigger_in_words: bool,
24905 cx: &mut Context<Editor>,
24906 ) -> bool {
24907 let mut chars = text.chars();
24908 let char = if let Some(char) = chars.next() {
24909 char
24910 } else {
24911 return false;
24912 };
24913 if chars.next().is_some() {
24914 return false;
24915 }
24916
24917 let buffer = buffer.read(cx);
24918 let snapshot = buffer.snapshot();
24919 let classifier = snapshot
24920 .char_classifier_at(position)
24921 .scope_context(Some(CharScopeContext::Completion));
24922 if trigger_in_words && classifier.is_word(char) {
24923 return true;
24924 }
24925
24926 buffer.completion_triggers().contains(text)
24927 }
24928
24929 fn show_snippets(&self) -> bool {
24930 true
24931 }
24932}
24933
24934impl SemanticsProvider for Entity<Project> {
24935 fn hover(
24936 &self,
24937 buffer: &Entity<Buffer>,
24938 position: text::Anchor,
24939 cx: &mut App,
24940 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24941 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24942 }
24943
24944 fn document_highlights(
24945 &self,
24946 buffer: &Entity<Buffer>,
24947 position: text::Anchor,
24948 cx: &mut App,
24949 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24950 Some(self.update(cx, |project, cx| {
24951 project.document_highlights(buffer, position, cx)
24952 }))
24953 }
24954
24955 fn definitions(
24956 &self,
24957 buffer: &Entity<Buffer>,
24958 position: text::Anchor,
24959 kind: GotoDefinitionKind,
24960 cx: &mut App,
24961 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24962 Some(self.update(cx, |project, cx| match kind {
24963 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24964 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24965 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24966 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24967 }))
24968 }
24969
24970 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24971 self.update(cx, |project, cx| {
24972 if project
24973 .active_debug_session(cx)
24974 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24975 {
24976 return true;
24977 }
24978
24979 buffer.update(cx, |buffer, cx| {
24980 project.any_language_server_supports_inlay_hints(buffer, cx)
24981 })
24982 })
24983 }
24984
24985 fn inline_values(
24986 &self,
24987 buffer_handle: Entity<Buffer>,
24988 range: Range<text::Anchor>,
24989 cx: &mut App,
24990 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24991 self.update(cx, |project, cx| {
24992 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24993
24994 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24995 })
24996 }
24997
24998 fn applicable_inlay_chunks(
24999 &self,
25000 buffer: &Entity<Buffer>,
25001 ranges: &[Range<text::Anchor>],
25002 cx: &mut App,
25003 ) -> Vec<Range<BufferRow>> {
25004 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
25005 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
25006 })
25007 }
25008
25009 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
25010 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
25011 lsp_store.invalidate_inlay_hints(for_buffers)
25012 });
25013 }
25014
25015 fn inlay_hints(
25016 &self,
25017 invalidate: InvalidationStrategy,
25018 buffer: Entity<Buffer>,
25019 ranges: Vec<Range<text::Anchor>>,
25020 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
25021 cx: &mut App,
25022 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
25023 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
25024 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
25025 }))
25026 }
25027
25028 fn range_for_rename(
25029 &self,
25030 buffer: &Entity<Buffer>,
25031 position: text::Anchor,
25032 cx: &mut App,
25033 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
25034 Some(self.update(cx, |project, cx| {
25035 let buffer = buffer.clone();
25036 let task = project.prepare_rename(buffer.clone(), position, cx);
25037 cx.spawn(async move |_, cx| {
25038 Ok(match task.await? {
25039 PrepareRenameResponse::Success(range) => Some(range),
25040 PrepareRenameResponse::InvalidPosition => None,
25041 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
25042 // Fallback on using TreeSitter info to determine identifier range
25043 buffer.read_with(cx, |buffer, _| {
25044 let snapshot = buffer.snapshot();
25045 let (range, kind) = snapshot.surrounding_word(position, None);
25046 if kind != Some(CharKind::Word) {
25047 return None;
25048 }
25049 Some(
25050 snapshot.anchor_before(range.start)
25051 ..snapshot.anchor_after(range.end),
25052 )
25053 })
25054 }
25055 })
25056 })
25057 }))
25058 }
25059
25060 fn perform_rename(
25061 &self,
25062 buffer: &Entity<Buffer>,
25063 position: text::Anchor,
25064 new_name: String,
25065 cx: &mut App,
25066 ) -> Option<Task<Result<ProjectTransaction>>> {
25067 Some(self.update(cx, |project, cx| {
25068 project.perform_rename(buffer.clone(), position, new_name, cx)
25069 }))
25070 }
25071}
25072
25073fn consume_contiguous_rows(
25074 contiguous_row_selections: &mut Vec<Selection<Point>>,
25075 selection: &Selection<Point>,
25076 display_map: &DisplaySnapshot,
25077 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
25078) -> (MultiBufferRow, MultiBufferRow) {
25079 contiguous_row_selections.push(selection.clone());
25080 let start_row = starting_row(selection, display_map);
25081 let mut end_row = ending_row(selection, display_map);
25082
25083 while let Some(next_selection) = selections.peek() {
25084 if next_selection.start.row <= end_row.0 {
25085 end_row = ending_row(next_selection, display_map);
25086 contiguous_row_selections.push(selections.next().unwrap().clone());
25087 } else {
25088 break;
25089 }
25090 }
25091 (start_row, end_row)
25092}
25093
25094fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
25095 if selection.start.column > 0 {
25096 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
25097 } else {
25098 MultiBufferRow(selection.start.row)
25099 }
25100}
25101
25102fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
25103 if next_selection.end.column > 0 || next_selection.is_empty() {
25104 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
25105 } else {
25106 MultiBufferRow(next_selection.end.row)
25107 }
25108}
25109
25110impl EditorSnapshot {
25111 pub fn remote_selections_in_range<'a>(
25112 &'a self,
25113 range: &'a Range<Anchor>,
25114 collaboration_hub: &dyn CollaborationHub,
25115 cx: &'a App,
25116 ) -> impl 'a + Iterator<Item = RemoteSelection> {
25117 let participant_names = collaboration_hub.user_names(cx);
25118 let participant_indices = collaboration_hub.user_participant_indices(cx);
25119 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
25120 let collaborators_by_replica_id = collaborators_by_peer_id
25121 .values()
25122 .map(|collaborator| (collaborator.replica_id, collaborator))
25123 .collect::<HashMap<_, _>>();
25124 self.buffer_snapshot()
25125 .selections_in_range(range, false)
25126 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
25127 if replica_id == ReplicaId::AGENT {
25128 Some(RemoteSelection {
25129 replica_id,
25130 selection,
25131 cursor_shape,
25132 line_mode,
25133 collaborator_id: CollaboratorId::Agent,
25134 user_name: Some("Agent".into()),
25135 color: cx.theme().players().agent(),
25136 })
25137 } else {
25138 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
25139 let participant_index = participant_indices.get(&collaborator.user_id).copied();
25140 let user_name = participant_names.get(&collaborator.user_id).cloned();
25141 Some(RemoteSelection {
25142 replica_id,
25143 selection,
25144 cursor_shape,
25145 line_mode,
25146 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
25147 user_name,
25148 color: if let Some(index) = participant_index {
25149 cx.theme().players().color_for_participant(index.0)
25150 } else {
25151 cx.theme().players().absent()
25152 },
25153 })
25154 }
25155 })
25156 }
25157
25158 pub fn hunks_for_ranges(
25159 &self,
25160 ranges: impl IntoIterator<Item = Range<Point>>,
25161 ) -> Vec<MultiBufferDiffHunk> {
25162 let mut hunks = Vec::new();
25163 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
25164 HashMap::default();
25165 for query_range in ranges {
25166 let query_rows =
25167 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
25168 for hunk in self.buffer_snapshot().diff_hunks_in_range(
25169 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
25170 ) {
25171 // Include deleted hunks that are adjacent to the query range, because
25172 // otherwise they would be missed.
25173 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
25174 if hunk.status().is_deleted() {
25175 intersects_range |= hunk.row_range.start == query_rows.end;
25176 intersects_range |= hunk.row_range.end == query_rows.start;
25177 }
25178 if intersects_range {
25179 if !processed_buffer_rows
25180 .entry(hunk.buffer_id)
25181 .or_default()
25182 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
25183 {
25184 continue;
25185 }
25186 hunks.push(hunk);
25187 }
25188 }
25189 }
25190
25191 hunks
25192 }
25193
25194 fn display_diff_hunks_for_rows<'a>(
25195 &'a self,
25196 display_rows: Range<DisplayRow>,
25197 folded_buffers: &'a HashSet<BufferId>,
25198 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
25199 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
25200 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
25201
25202 self.buffer_snapshot()
25203 .diff_hunks_in_range(buffer_start..buffer_end)
25204 .filter_map(|hunk| {
25205 if folded_buffers.contains(&hunk.buffer_id) {
25206 return None;
25207 }
25208
25209 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
25210 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
25211
25212 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
25213 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
25214
25215 let display_hunk = if hunk_display_start.column() != 0 {
25216 DisplayDiffHunk::Folded {
25217 display_row: hunk_display_start.row(),
25218 }
25219 } else {
25220 let mut end_row = hunk_display_end.row();
25221 if hunk_display_end.column() > 0 {
25222 end_row.0 += 1;
25223 }
25224 let is_created_file = hunk.is_created_file();
25225
25226 DisplayDiffHunk::Unfolded {
25227 status: hunk.status(),
25228 diff_base_byte_range: hunk.diff_base_byte_range.start.0
25229 ..hunk.diff_base_byte_range.end.0,
25230 word_diffs: hunk.word_diffs,
25231 display_row_range: hunk_display_start.row()..end_row,
25232 multi_buffer_range: Anchor::range_in_buffer(
25233 hunk.excerpt_id,
25234 hunk.buffer_range,
25235 ),
25236 is_created_file,
25237 }
25238 };
25239
25240 Some(display_hunk)
25241 })
25242 }
25243
25244 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
25245 self.display_snapshot
25246 .buffer_snapshot()
25247 .language_at(position)
25248 }
25249
25250 pub fn is_focused(&self) -> bool {
25251 self.is_focused
25252 }
25253
25254 pub fn placeholder_text(&self) -> Option<String> {
25255 self.placeholder_display_snapshot
25256 .as_ref()
25257 .map(|display_map| display_map.text())
25258 }
25259
25260 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
25261 self.scroll_anchor.scroll_position(&self.display_snapshot)
25262 }
25263
25264 pub fn gutter_dimensions(
25265 &self,
25266 font_id: FontId,
25267 font_size: Pixels,
25268 style: &EditorStyle,
25269 window: &mut Window,
25270 cx: &App,
25271 ) -> GutterDimensions {
25272 if self.show_gutter
25273 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
25274 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
25275 {
25276 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
25277 matches!(
25278 ProjectSettings::get_global(cx).git.git_gutter,
25279 GitGutterSetting::TrackedFiles
25280 )
25281 });
25282 let gutter_settings = EditorSettings::get_global(cx).gutter;
25283 let show_line_numbers = self
25284 .show_line_numbers
25285 .unwrap_or(gutter_settings.line_numbers);
25286 let line_gutter_width = if show_line_numbers {
25287 // Avoid flicker-like gutter resizes when the line number gains another digit by
25288 // only resizing the gutter on files with > 10**min_line_number_digits lines.
25289 let min_width_for_number_on_gutter =
25290 ch_advance * gutter_settings.min_line_number_digits as f32;
25291 self.max_line_number_width(style, window)
25292 .max(min_width_for_number_on_gutter)
25293 } else {
25294 0.0.into()
25295 };
25296
25297 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
25298 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
25299
25300 let git_blame_entries_width =
25301 self.git_blame_gutter_max_author_length
25302 .map(|max_author_length| {
25303 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
25304 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
25305
25306 /// The number of characters to dedicate to gaps and margins.
25307 const SPACING_WIDTH: usize = 4;
25308
25309 let max_char_count = max_author_length.min(renderer.max_author_length())
25310 + ::git::SHORT_SHA_LENGTH
25311 + MAX_RELATIVE_TIMESTAMP.len()
25312 + SPACING_WIDTH;
25313
25314 ch_advance * max_char_count
25315 });
25316
25317 let is_singleton = self.buffer_snapshot().is_singleton();
25318
25319 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
25320 left_padding += if !is_singleton {
25321 ch_width * 4.0
25322 } else if show_runnables || show_breakpoints {
25323 ch_width * 3.0
25324 } else if show_git_gutter && show_line_numbers {
25325 ch_width * 2.0
25326 } else if show_git_gutter || show_line_numbers {
25327 ch_width
25328 } else {
25329 px(0.)
25330 };
25331
25332 let shows_folds = is_singleton && gutter_settings.folds;
25333
25334 let right_padding = if shows_folds && show_line_numbers {
25335 ch_width * 4.0
25336 } else if shows_folds || (!is_singleton && show_line_numbers) {
25337 ch_width * 3.0
25338 } else if show_line_numbers {
25339 ch_width
25340 } else {
25341 px(0.)
25342 };
25343
25344 GutterDimensions {
25345 left_padding,
25346 right_padding,
25347 width: line_gutter_width + left_padding + right_padding,
25348 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
25349 git_blame_entries_width,
25350 }
25351 } else if self.offset_content {
25352 GutterDimensions::default_with_margin(font_id, font_size, cx)
25353 } else {
25354 GutterDimensions::default()
25355 }
25356 }
25357
25358 pub fn render_crease_toggle(
25359 &self,
25360 buffer_row: MultiBufferRow,
25361 row_contains_cursor: bool,
25362 editor: Entity<Editor>,
25363 window: &mut Window,
25364 cx: &mut App,
25365 ) -> Option<AnyElement> {
25366 let folded = self.is_line_folded(buffer_row);
25367 let mut is_foldable = false;
25368
25369 if let Some(crease) = self
25370 .crease_snapshot
25371 .query_row(buffer_row, self.buffer_snapshot())
25372 {
25373 is_foldable = true;
25374 match crease {
25375 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
25376 if let Some(render_toggle) = render_toggle {
25377 let toggle_callback =
25378 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
25379 if folded {
25380 editor.update(cx, |editor, cx| {
25381 editor.fold_at(buffer_row, window, cx)
25382 });
25383 } else {
25384 editor.update(cx, |editor, cx| {
25385 editor.unfold_at(buffer_row, window, cx)
25386 });
25387 }
25388 });
25389 return Some((render_toggle)(
25390 buffer_row,
25391 folded,
25392 toggle_callback,
25393 window,
25394 cx,
25395 ));
25396 }
25397 }
25398 }
25399 }
25400
25401 is_foldable |= self.starts_indent(buffer_row);
25402
25403 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
25404 Some(
25405 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
25406 .toggle_state(folded)
25407 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
25408 if folded {
25409 this.unfold_at(buffer_row, window, cx);
25410 } else {
25411 this.fold_at(buffer_row, window, cx);
25412 }
25413 }))
25414 .into_any_element(),
25415 )
25416 } else {
25417 None
25418 }
25419 }
25420
25421 pub fn render_crease_trailer(
25422 &self,
25423 buffer_row: MultiBufferRow,
25424 window: &mut Window,
25425 cx: &mut App,
25426 ) -> Option<AnyElement> {
25427 let folded = self.is_line_folded(buffer_row);
25428 if let Crease::Inline { render_trailer, .. } = self
25429 .crease_snapshot
25430 .query_row(buffer_row, self.buffer_snapshot())?
25431 {
25432 let render_trailer = render_trailer.as_ref()?;
25433 Some(render_trailer(buffer_row, folded, window, cx))
25434 } else {
25435 None
25436 }
25437 }
25438
25439 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25440 let digit_count = self.widest_line_number().ilog10() + 1;
25441 column_pixels(style, digit_count as usize, window)
25442 }
25443
25444 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
25445 ///
25446 /// This is positive if `base` is before `line`.
25447 fn relative_line_delta(
25448 &self,
25449 base: DisplayRow,
25450 line: DisplayRow,
25451 consider_wrapped_lines: bool,
25452 ) -> i64 {
25453 let point = DisplayPoint::new(line, 0).to_point(self);
25454 self.relative_line_delta_to_point(base, point, consider_wrapped_lines)
25455 }
25456
25457 /// Returns the line delta from `base` to `point` in the multibuffer.
25458 ///
25459 /// This is positive if `base` is before `point`.
25460 pub fn relative_line_delta_to_point(
25461 &self,
25462 base: DisplayRow,
25463 point: Point,
25464 consider_wrapped_lines: bool,
25465 ) -> i64 {
25466 let base_point = DisplayPoint::new(base, 0).to_point(self);
25467 if consider_wrapped_lines {
25468 let wrap_snapshot = self.wrap_snapshot();
25469 let base_wrap_row = wrap_snapshot.make_wrap_point(base_point, Bias::Left).row();
25470 let wrap_row = wrap_snapshot.make_wrap_point(point, Bias::Left).row();
25471 wrap_row.0 as i64 - base_wrap_row.0 as i64
25472 } else {
25473 point.row as i64 - base_point.row as i64
25474 }
25475 }
25476
25477 /// Returns the unsigned relative line number to display for each row in `rows`.
25478 ///
25479 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
25480 pub fn calculate_relative_line_numbers(
25481 &self,
25482 rows: &Range<DisplayRow>,
25483 relative_to: DisplayRow,
25484 count_wrapped_lines: bool,
25485 ) -> HashMap<DisplayRow, u32> {
25486 let initial_offset = self.relative_line_delta(relative_to, rows.start, count_wrapped_lines);
25487
25488 self.row_infos(rows.start)
25489 .take(rows.len())
25490 .enumerate()
25491 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
25492 .filter(|(_row, row_info)| {
25493 row_info.buffer_row.is_some()
25494 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
25495 })
25496 .enumerate()
25497 .flat_map(|(i, (row, _row_info))| {
25498 (row != relative_to)
25499 .then_some((row, (initial_offset + i as i64).unsigned_abs() as u32))
25500 })
25501 .collect()
25502 }
25503}
25504
25505pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25506 let font_size = style.text.font_size.to_pixels(window.rem_size());
25507 let layout = window.text_system().shape_line(
25508 SharedString::from(" ".repeat(column)),
25509 font_size,
25510 &[TextRun {
25511 len: column,
25512 font: style.text.font(),
25513 color: Hsla::default(),
25514 ..Default::default()
25515 }],
25516 None,
25517 );
25518
25519 layout.width
25520}
25521
25522impl Deref for EditorSnapshot {
25523 type Target = DisplaySnapshot;
25524
25525 fn deref(&self) -> &Self::Target {
25526 &self.display_snapshot
25527 }
25528}
25529
25530#[derive(Clone, Debug, PartialEq, Eq)]
25531pub enum EditorEvent {
25532 InputIgnored {
25533 text: Arc<str>,
25534 },
25535 InputHandled {
25536 utf16_range_to_replace: Option<Range<isize>>,
25537 text: Arc<str>,
25538 },
25539 ExcerptsAdded {
25540 buffer: Entity<Buffer>,
25541 predecessor: ExcerptId,
25542 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25543 },
25544 ExcerptsRemoved {
25545 ids: Vec<ExcerptId>,
25546 removed_buffer_ids: Vec<BufferId>,
25547 },
25548 BufferFoldToggled {
25549 ids: Vec<ExcerptId>,
25550 folded: bool,
25551 },
25552 ExcerptsEdited {
25553 ids: Vec<ExcerptId>,
25554 },
25555 ExcerptsExpanded {
25556 ids: Vec<ExcerptId>,
25557 },
25558 ExpandExcerptsRequested {
25559 excerpt_ids: Vec<ExcerptId>,
25560 lines: u32,
25561 direction: ExpandExcerptDirection,
25562 },
25563 BufferEdited,
25564 Edited {
25565 transaction_id: clock::Lamport,
25566 },
25567 Reparsed(BufferId),
25568 Focused,
25569 FocusedIn,
25570 Blurred,
25571 DirtyChanged,
25572 Saved,
25573 TitleChanged,
25574 SelectionsChanged {
25575 local: bool,
25576 },
25577 ScrollPositionChanged {
25578 local: bool,
25579 autoscroll: bool,
25580 },
25581 TransactionUndone {
25582 transaction_id: clock::Lamport,
25583 },
25584 TransactionBegun {
25585 transaction_id: clock::Lamport,
25586 },
25587 CursorShapeChanged,
25588 BreadcrumbsChanged,
25589 PushedToNavHistory {
25590 anchor: Anchor,
25591 is_deactivate: bool,
25592 },
25593}
25594
25595impl EventEmitter<EditorEvent> for Editor {}
25596
25597impl Focusable for Editor {
25598 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25599 self.focus_handle.clone()
25600 }
25601}
25602
25603impl Render for Editor {
25604 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25605 EditorElement::new(&cx.entity(), self.create_style(cx))
25606 }
25607}
25608
25609impl EntityInputHandler for Editor {
25610 fn text_for_range(
25611 &mut self,
25612 range_utf16: Range<usize>,
25613 adjusted_range: &mut Option<Range<usize>>,
25614 _: &mut Window,
25615 cx: &mut Context<Self>,
25616 ) -> Option<String> {
25617 let snapshot = self.buffer.read(cx).read(cx);
25618 let start = snapshot.clip_offset_utf16(
25619 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25620 Bias::Left,
25621 );
25622 let end = snapshot.clip_offset_utf16(
25623 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25624 Bias::Right,
25625 );
25626 if (start.0.0..end.0.0) != range_utf16 {
25627 adjusted_range.replace(start.0.0..end.0.0);
25628 }
25629 Some(snapshot.text_for_range(start..end).collect())
25630 }
25631
25632 fn selected_text_range(
25633 &mut self,
25634 ignore_disabled_input: bool,
25635 _: &mut Window,
25636 cx: &mut Context<Self>,
25637 ) -> Option<UTF16Selection> {
25638 // Prevent the IME menu from appearing when holding down an alphabetic key
25639 // while input is disabled.
25640 if !ignore_disabled_input && !self.input_enabled {
25641 return None;
25642 }
25643
25644 let selection = self
25645 .selections
25646 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25647 let range = selection.range();
25648
25649 Some(UTF16Selection {
25650 range: range.start.0.0..range.end.0.0,
25651 reversed: selection.reversed,
25652 })
25653 }
25654
25655 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25656 let snapshot = self.buffer.read(cx).read(cx);
25657 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25658 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25659 }
25660
25661 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25662 self.clear_highlights::<InputComposition>(cx);
25663 self.ime_transaction.take();
25664 }
25665
25666 fn replace_text_in_range(
25667 &mut self,
25668 range_utf16: Option<Range<usize>>,
25669 text: &str,
25670 window: &mut Window,
25671 cx: &mut Context<Self>,
25672 ) {
25673 if !self.input_enabled {
25674 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25675 return;
25676 }
25677
25678 self.transact(window, cx, |this, window, cx| {
25679 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25680 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25681 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25682 Some(this.selection_replacement_ranges(range_utf16, cx))
25683 } else {
25684 this.marked_text_ranges(cx)
25685 };
25686
25687 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25688 let newest_selection_id = this.selections.newest_anchor().id;
25689 this.selections
25690 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25691 .iter()
25692 .zip(ranges_to_replace.iter())
25693 .find_map(|(selection, range)| {
25694 if selection.id == newest_selection_id {
25695 Some(
25696 (range.start.0.0 as isize - selection.head().0.0 as isize)
25697 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25698 )
25699 } else {
25700 None
25701 }
25702 })
25703 });
25704
25705 cx.emit(EditorEvent::InputHandled {
25706 utf16_range_to_replace: range_to_replace,
25707 text: text.into(),
25708 });
25709
25710 if let Some(new_selected_ranges) = new_selected_ranges {
25711 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25712 selections.select_ranges(new_selected_ranges)
25713 });
25714 this.backspace(&Default::default(), window, cx);
25715 }
25716
25717 this.handle_input(text, window, cx);
25718 });
25719
25720 if let Some(transaction) = self.ime_transaction {
25721 self.buffer.update(cx, |buffer, cx| {
25722 buffer.group_until_transaction(transaction, cx);
25723 });
25724 }
25725
25726 self.unmark_text(window, cx);
25727 }
25728
25729 fn replace_and_mark_text_in_range(
25730 &mut self,
25731 range_utf16: Option<Range<usize>>,
25732 text: &str,
25733 new_selected_range_utf16: Option<Range<usize>>,
25734 window: &mut Window,
25735 cx: &mut Context<Self>,
25736 ) {
25737 if !self.input_enabled {
25738 return;
25739 }
25740
25741 let transaction = self.transact(window, cx, |this, window, cx| {
25742 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25743 let snapshot = this.buffer.read(cx).read(cx);
25744 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25745 for marked_range in &mut marked_ranges {
25746 marked_range.end = marked_range.start + relative_range_utf16.end;
25747 marked_range.start += relative_range_utf16.start;
25748 marked_range.start =
25749 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25750 marked_range.end =
25751 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25752 }
25753 }
25754 Some(marked_ranges)
25755 } else if let Some(range_utf16) = range_utf16 {
25756 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25757 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25758 Some(this.selection_replacement_ranges(range_utf16, cx))
25759 } else {
25760 None
25761 };
25762
25763 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25764 let newest_selection_id = this.selections.newest_anchor().id;
25765 this.selections
25766 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25767 .iter()
25768 .zip(ranges_to_replace.iter())
25769 .find_map(|(selection, range)| {
25770 if selection.id == newest_selection_id {
25771 Some(
25772 (range.start.0.0 as isize - selection.head().0.0 as isize)
25773 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25774 )
25775 } else {
25776 None
25777 }
25778 })
25779 });
25780
25781 cx.emit(EditorEvent::InputHandled {
25782 utf16_range_to_replace: range_to_replace,
25783 text: text.into(),
25784 });
25785
25786 if let Some(ranges) = ranges_to_replace {
25787 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25788 s.select_ranges(ranges)
25789 });
25790 }
25791
25792 let marked_ranges = {
25793 let snapshot = this.buffer.read(cx).read(cx);
25794 this.selections
25795 .disjoint_anchors_arc()
25796 .iter()
25797 .map(|selection| {
25798 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25799 })
25800 .collect::<Vec<_>>()
25801 };
25802
25803 if text.is_empty() {
25804 this.unmark_text(window, cx);
25805 } else {
25806 this.highlight_text::<InputComposition>(
25807 marked_ranges.clone(),
25808 HighlightStyle {
25809 underline: Some(UnderlineStyle {
25810 thickness: px(1.),
25811 color: None,
25812 wavy: false,
25813 }),
25814 ..Default::default()
25815 },
25816 cx,
25817 );
25818 }
25819
25820 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25821 let use_autoclose = this.use_autoclose;
25822 let use_auto_surround = this.use_auto_surround;
25823 this.set_use_autoclose(false);
25824 this.set_use_auto_surround(false);
25825 this.handle_input(text, window, cx);
25826 this.set_use_autoclose(use_autoclose);
25827 this.set_use_auto_surround(use_auto_surround);
25828
25829 if let Some(new_selected_range) = new_selected_range_utf16 {
25830 let snapshot = this.buffer.read(cx).read(cx);
25831 let new_selected_ranges = marked_ranges
25832 .into_iter()
25833 .map(|marked_range| {
25834 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25835 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25836 insertion_start.0 + new_selected_range.start,
25837 ));
25838 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25839 insertion_start.0 + new_selected_range.end,
25840 ));
25841 snapshot.clip_offset_utf16(new_start, Bias::Left)
25842 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25843 })
25844 .collect::<Vec<_>>();
25845
25846 drop(snapshot);
25847 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25848 selections.select_ranges(new_selected_ranges)
25849 });
25850 }
25851 });
25852
25853 self.ime_transaction = self.ime_transaction.or(transaction);
25854 if let Some(transaction) = self.ime_transaction {
25855 self.buffer.update(cx, |buffer, cx| {
25856 buffer.group_until_transaction(transaction, cx);
25857 });
25858 }
25859
25860 if self.text_highlights::<InputComposition>(cx).is_none() {
25861 self.ime_transaction.take();
25862 }
25863 }
25864
25865 fn bounds_for_range(
25866 &mut self,
25867 range_utf16: Range<usize>,
25868 element_bounds: gpui::Bounds<Pixels>,
25869 window: &mut Window,
25870 cx: &mut Context<Self>,
25871 ) -> Option<gpui::Bounds<Pixels>> {
25872 let text_layout_details = self.text_layout_details(window);
25873 let CharacterDimensions {
25874 em_width,
25875 em_advance,
25876 line_height,
25877 } = self.character_dimensions(window);
25878
25879 let snapshot = self.snapshot(window, cx);
25880 let scroll_position = snapshot.scroll_position();
25881 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25882
25883 let start =
25884 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25885 let x = Pixels::from(
25886 ScrollOffset::from(
25887 snapshot.x_for_display_point(start, &text_layout_details)
25888 + self.gutter_dimensions.full_width(),
25889 ) - scroll_left,
25890 );
25891 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25892
25893 Some(Bounds {
25894 origin: element_bounds.origin + point(x, y),
25895 size: size(em_width, line_height),
25896 })
25897 }
25898
25899 fn character_index_for_point(
25900 &mut self,
25901 point: gpui::Point<Pixels>,
25902 _window: &mut Window,
25903 _cx: &mut Context<Self>,
25904 ) -> Option<usize> {
25905 let position_map = self.last_position_map.as_ref()?;
25906 if !position_map.text_hitbox.contains(&point) {
25907 return None;
25908 }
25909 let display_point = position_map.point_for_position(point).previous_valid;
25910 let anchor = position_map
25911 .snapshot
25912 .display_point_to_anchor(display_point, Bias::Left);
25913 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25914 Some(utf16_offset.0.0)
25915 }
25916
25917 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25918 self.input_enabled
25919 }
25920}
25921
25922trait SelectionExt {
25923 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25924 fn spanned_rows(
25925 &self,
25926 include_end_if_at_line_start: bool,
25927 map: &DisplaySnapshot,
25928 ) -> Range<MultiBufferRow>;
25929}
25930
25931impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25932 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25933 let start = self
25934 .start
25935 .to_point(map.buffer_snapshot())
25936 .to_display_point(map);
25937 let end = self
25938 .end
25939 .to_point(map.buffer_snapshot())
25940 .to_display_point(map);
25941 if self.reversed {
25942 end..start
25943 } else {
25944 start..end
25945 }
25946 }
25947
25948 fn spanned_rows(
25949 &self,
25950 include_end_if_at_line_start: bool,
25951 map: &DisplaySnapshot,
25952 ) -> Range<MultiBufferRow> {
25953 let start = self.start.to_point(map.buffer_snapshot());
25954 let mut end = self.end.to_point(map.buffer_snapshot());
25955 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25956 end.row -= 1;
25957 }
25958
25959 let buffer_start = map.prev_line_boundary(start).0;
25960 let buffer_end = map.next_line_boundary(end).0;
25961 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25962 }
25963}
25964
25965impl<T: InvalidationRegion> InvalidationStack<T> {
25966 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25967 where
25968 S: Clone + ToOffset,
25969 {
25970 while let Some(region) = self.last() {
25971 let all_selections_inside_invalidation_ranges =
25972 if selections.len() == region.ranges().len() {
25973 selections
25974 .iter()
25975 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25976 .all(|(selection, invalidation_range)| {
25977 let head = selection.head().to_offset(buffer);
25978 invalidation_range.start <= head && invalidation_range.end >= head
25979 })
25980 } else {
25981 false
25982 };
25983
25984 if all_selections_inside_invalidation_ranges {
25985 break;
25986 } else {
25987 self.pop();
25988 }
25989 }
25990 }
25991}
25992
25993impl<T> Default for InvalidationStack<T> {
25994 fn default() -> Self {
25995 Self(Default::default())
25996 }
25997}
25998
25999impl<T> Deref for InvalidationStack<T> {
26000 type Target = Vec<T>;
26001
26002 fn deref(&self) -> &Self::Target {
26003 &self.0
26004 }
26005}
26006
26007impl<T> DerefMut for InvalidationStack<T> {
26008 fn deref_mut(&mut self) -> &mut Self::Target {
26009 &mut self.0
26010 }
26011}
26012
26013impl InvalidationRegion for SnippetState {
26014 fn ranges(&self) -> &[Range<Anchor>] {
26015 &self.ranges[self.active_index]
26016 }
26017}
26018
26019fn edit_prediction_edit_text(
26020 current_snapshot: &BufferSnapshot,
26021 edits: &[(Range<Anchor>, impl AsRef<str>)],
26022 edit_preview: &EditPreview,
26023 include_deletions: bool,
26024 cx: &App,
26025) -> HighlightedText {
26026 let edits = edits
26027 .iter()
26028 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
26029 .collect::<Vec<_>>();
26030
26031 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
26032}
26033
26034fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
26035 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
26036 // Just show the raw edit text with basic styling
26037 let mut text = String::new();
26038 let mut highlights = Vec::new();
26039
26040 let insertion_highlight_style = HighlightStyle {
26041 color: Some(cx.theme().colors().text),
26042 ..Default::default()
26043 };
26044
26045 for (_, edit_text) in edits {
26046 let start_offset = text.len();
26047 text.push_str(edit_text);
26048 let end_offset = text.len();
26049
26050 if start_offset < end_offset {
26051 highlights.push((start_offset..end_offset, insertion_highlight_style));
26052 }
26053 }
26054
26055 HighlightedText {
26056 text: text.into(),
26057 highlights,
26058 }
26059}
26060
26061pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
26062 match severity {
26063 lsp::DiagnosticSeverity::ERROR => colors.error,
26064 lsp::DiagnosticSeverity::WARNING => colors.warning,
26065 lsp::DiagnosticSeverity::INFORMATION => colors.info,
26066 lsp::DiagnosticSeverity::HINT => colors.info,
26067 _ => colors.ignored,
26068 }
26069}
26070
26071pub fn styled_runs_for_code_label<'a>(
26072 label: &'a CodeLabel,
26073 syntax_theme: &'a theme::SyntaxTheme,
26074 local_player: &'a theme::PlayerColor,
26075) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
26076 let fade_out = HighlightStyle {
26077 fade_out: Some(0.35),
26078 ..Default::default()
26079 };
26080
26081 let mut prev_end = label.filter_range.end;
26082 label
26083 .runs
26084 .iter()
26085 .enumerate()
26086 .flat_map(move |(ix, (range, highlight_id))| {
26087 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
26088 HighlightStyle {
26089 color: Some(local_player.cursor),
26090 ..Default::default()
26091 }
26092 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
26093 HighlightStyle {
26094 background_color: Some(local_player.selection),
26095 ..Default::default()
26096 }
26097 } else if let Some(style) = highlight_id.style(syntax_theme) {
26098 style
26099 } else {
26100 return Default::default();
26101 };
26102 let muted_style = style.highlight(fade_out);
26103
26104 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
26105 if range.start >= label.filter_range.end {
26106 if range.start > prev_end {
26107 runs.push((prev_end..range.start, fade_out));
26108 }
26109 runs.push((range.clone(), muted_style));
26110 } else if range.end <= label.filter_range.end {
26111 runs.push((range.clone(), style));
26112 } else {
26113 runs.push((range.start..label.filter_range.end, style));
26114 runs.push((label.filter_range.end..range.end, muted_style));
26115 }
26116 prev_end = cmp::max(prev_end, range.end);
26117
26118 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
26119 runs.push((prev_end..label.text.len(), fade_out));
26120 }
26121
26122 runs
26123 })
26124}
26125
26126pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
26127 let mut prev_index = 0;
26128 let mut prev_codepoint: Option<char> = None;
26129 text.char_indices()
26130 .chain([(text.len(), '\0')])
26131 .filter_map(move |(index, codepoint)| {
26132 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26133 let is_boundary = index == text.len()
26134 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
26135 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
26136 if is_boundary {
26137 let chunk = &text[prev_index..index];
26138 prev_index = index;
26139 Some(chunk)
26140 } else {
26141 None
26142 }
26143 })
26144}
26145
26146/// Given a string of text immediately before the cursor, iterates over possible
26147/// strings a snippet could match to. More precisely: returns an iterator over
26148/// suffixes of `text` created by splitting at word boundaries (before & after
26149/// every non-word character).
26150///
26151/// Shorter suffixes are returned first.
26152pub(crate) fn snippet_candidate_suffixes(
26153 text: &str,
26154 is_word_char: impl Fn(char) -> bool,
26155) -> impl std::iter::Iterator<Item = &str> {
26156 let mut prev_index = text.len();
26157 let mut prev_codepoint = None;
26158 text.char_indices()
26159 .rev()
26160 .chain([(0, '\0')])
26161 .filter_map(move |(index, codepoint)| {
26162 let prev_index = std::mem::replace(&mut prev_index, index);
26163 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26164 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
26165 None
26166 } else {
26167 let chunk = &text[prev_index..]; // go to end of string
26168 Some(chunk)
26169 }
26170 })
26171}
26172
26173pub trait RangeToAnchorExt: Sized {
26174 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
26175
26176 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
26177 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
26178 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
26179 }
26180}
26181
26182impl<T: ToOffset> RangeToAnchorExt for Range<T> {
26183 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
26184 let start_offset = self.start.to_offset(snapshot);
26185 let end_offset = self.end.to_offset(snapshot);
26186 if start_offset == end_offset {
26187 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
26188 } else {
26189 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
26190 }
26191 }
26192}
26193
26194pub trait RowExt {
26195 fn as_f64(&self) -> f64;
26196
26197 fn next_row(&self) -> Self;
26198
26199 fn previous_row(&self) -> Self;
26200
26201 fn minus(&self, other: Self) -> u32;
26202}
26203
26204impl RowExt for DisplayRow {
26205 fn as_f64(&self) -> f64 {
26206 self.0 as _
26207 }
26208
26209 fn next_row(&self) -> Self {
26210 Self(self.0 + 1)
26211 }
26212
26213 fn previous_row(&self) -> Self {
26214 Self(self.0.saturating_sub(1))
26215 }
26216
26217 fn minus(&self, other: Self) -> u32 {
26218 self.0 - other.0
26219 }
26220}
26221
26222impl RowExt for MultiBufferRow {
26223 fn as_f64(&self) -> f64 {
26224 self.0 as _
26225 }
26226
26227 fn next_row(&self) -> Self {
26228 Self(self.0 + 1)
26229 }
26230
26231 fn previous_row(&self) -> Self {
26232 Self(self.0.saturating_sub(1))
26233 }
26234
26235 fn minus(&self, other: Self) -> u32 {
26236 self.0 - other.0
26237 }
26238}
26239
26240trait RowRangeExt {
26241 type Row;
26242
26243 fn len(&self) -> usize;
26244
26245 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
26246}
26247
26248impl RowRangeExt for Range<MultiBufferRow> {
26249 type Row = MultiBufferRow;
26250
26251 fn len(&self) -> usize {
26252 (self.end.0 - self.start.0) as usize
26253 }
26254
26255 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
26256 (self.start.0..self.end.0).map(MultiBufferRow)
26257 }
26258}
26259
26260impl RowRangeExt for Range<DisplayRow> {
26261 type Row = DisplayRow;
26262
26263 fn len(&self) -> usize {
26264 (self.end.0 - self.start.0) as usize
26265 }
26266
26267 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
26268 (self.start.0..self.end.0).map(DisplayRow)
26269 }
26270}
26271
26272/// If select range has more than one line, we
26273/// just point the cursor to range.start.
26274fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
26275 if range.start.row == range.end.row {
26276 range
26277 } else {
26278 range.start..range.start
26279 }
26280}
26281pub struct KillRing(ClipboardItem);
26282impl Global for KillRing {}
26283
26284const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
26285
26286enum BreakpointPromptEditAction {
26287 Log,
26288 Condition,
26289 HitCondition,
26290}
26291
26292struct BreakpointPromptEditor {
26293 pub(crate) prompt: Entity<Editor>,
26294 editor: WeakEntity<Editor>,
26295 breakpoint_anchor: Anchor,
26296 breakpoint: Breakpoint,
26297 edit_action: BreakpointPromptEditAction,
26298 block_ids: HashSet<CustomBlockId>,
26299 editor_margins: Arc<Mutex<EditorMargins>>,
26300 _subscriptions: Vec<Subscription>,
26301}
26302
26303impl BreakpointPromptEditor {
26304 const MAX_LINES: u8 = 4;
26305
26306 fn new(
26307 editor: WeakEntity<Editor>,
26308 breakpoint_anchor: Anchor,
26309 breakpoint: Breakpoint,
26310 edit_action: BreakpointPromptEditAction,
26311 window: &mut Window,
26312 cx: &mut Context<Self>,
26313 ) -> Self {
26314 let base_text = match edit_action {
26315 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
26316 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
26317 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
26318 }
26319 .map(|msg| msg.to_string())
26320 .unwrap_or_default();
26321
26322 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
26323 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26324
26325 let prompt = cx.new(|cx| {
26326 let mut prompt = Editor::new(
26327 EditorMode::AutoHeight {
26328 min_lines: 1,
26329 max_lines: Some(Self::MAX_LINES as usize),
26330 },
26331 buffer,
26332 None,
26333 window,
26334 cx,
26335 );
26336 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
26337 prompt.set_show_cursor_when_unfocused(false, cx);
26338 prompt.set_placeholder_text(
26339 match edit_action {
26340 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
26341 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
26342 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
26343 },
26344 window,
26345 cx,
26346 );
26347
26348 prompt
26349 });
26350
26351 Self {
26352 prompt,
26353 editor,
26354 breakpoint_anchor,
26355 breakpoint,
26356 edit_action,
26357 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
26358 block_ids: Default::default(),
26359 _subscriptions: vec![],
26360 }
26361 }
26362
26363 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
26364 self.block_ids.extend(block_ids)
26365 }
26366
26367 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
26368 if let Some(editor) = self.editor.upgrade() {
26369 let message = self
26370 .prompt
26371 .read(cx)
26372 .buffer
26373 .read(cx)
26374 .as_singleton()
26375 .expect("A multi buffer in breakpoint prompt isn't possible")
26376 .read(cx)
26377 .as_rope()
26378 .to_string();
26379
26380 editor.update(cx, |editor, cx| {
26381 editor.edit_breakpoint_at_anchor(
26382 self.breakpoint_anchor,
26383 self.breakpoint.clone(),
26384 match self.edit_action {
26385 BreakpointPromptEditAction::Log => {
26386 BreakpointEditAction::EditLogMessage(message.into())
26387 }
26388 BreakpointPromptEditAction::Condition => {
26389 BreakpointEditAction::EditCondition(message.into())
26390 }
26391 BreakpointPromptEditAction::HitCondition => {
26392 BreakpointEditAction::EditHitCondition(message.into())
26393 }
26394 },
26395 cx,
26396 );
26397
26398 editor.remove_blocks(self.block_ids.clone(), None, cx);
26399 cx.focus_self(window);
26400 });
26401 }
26402 }
26403
26404 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
26405 self.editor
26406 .update(cx, |editor, cx| {
26407 editor.remove_blocks(self.block_ids.clone(), None, cx);
26408 window.focus(&editor.focus_handle, cx);
26409 })
26410 .log_err();
26411 }
26412
26413 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
26414 let settings = ThemeSettings::get_global(cx);
26415 let text_style = TextStyle {
26416 color: if self.prompt.read(cx).read_only(cx) {
26417 cx.theme().colors().text_disabled
26418 } else {
26419 cx.theme().colors().text
26420 },
26421 font_family: settings.buffer_font.family.clone(),
26422 font_fallbacks: settings.buffer_font.fallbacks.clone(),
26423 font_size: settings.buffer_font_size(cx).into(),
26424 font_weight: settings.buffer_font.weight,
26425 line_height: relative(settings.buffer_line_height.value()),
26426 ..Default::default()
26427 };
26428 EditorElement::new(
26429 &self.prompt,
26430 EditorStyle {
26431 background: cx.theme().colors().editor_background,
26432 local_player: cx.theme().players().local(),
26433 text: text_style,
26434 ..Default::default()
26435 },
26436 )
26437 }
26438}
26439
26440impl Render for BreakpointPromptEditor {
26441 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26442 let editor_margins = *self.editor_margins.lock();
26443 let gutter_dimensions = editor_margins.gutter;
26444 h_flex()
26445 .key_context("Editor")
26446 .bg(cx.theme().colors().editor_background)
26447 .border_y_1()
26448 .border_color(cx.theme().status().info_border)
26449 .size_full()
26450 .py(window.line_height() / 2.5)
26451 .on_action(cx.listener(Self::confirm))
26452 .on_action(cx.listener(Self::cancel))
26453 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
26454 .child(div().flex_1().child(self.render_prompt_editor(cx)))
26455 }
26456}
26457
26458impl Focusable for BreakpointPromptEditor {
26459 fn focus_handle(&self, cx: &App) -> FocusHandle {
26460 self.prompt.focus_handle(cx)
26461 }
26462}
26463
26464fn all_edits_insertions_or_deletions(
26465 edits: &Vec<(Range<Anchor>, Arc<str>)>,
26466 snapshot: &MultiBufferSnapshot,
26467) -> bool {
26468 let mut all_insertions = true;
26469 let mut all_deletions = true;
26470
26471 for (range, new_text) in edits.iter() {
26472 let range_is_empty = range.to_offset(snapshot).is_empty();
26473 let text_is_empty = new_text.is_empty();
26474
26475 if range_is_empty != text_is_empty {
26476 if range_is_empty {
26477 all_deletions = false;
26478 } else {
26479 all_insertions = false;
26480 }
26481 } else {
26482 return false;
26483 }
26484
26485 if !all_insertions && !all_deletions {
26486 return false;
26487 }
26488 }
26489 all_insertions || all_deletions
26490}
26491
26492struct MissingEditPredictionKeybindingTooltip;
26493
26494impl Render for MissingEditPredictionKeybindingTooltip {
26495 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26496 ui::tooltip_container(cx, |container, cx| {
26497 container
26498 .flex_shrink_0()
26499 .max_w_80()
26500 .min_h(rems_from_px(124.))
26501 .justify_between()
26502 .child(
26503 v_flex()
26504 .flex_1()
26505 .text_ui_sm(cx)
26506 .child(Label::new("Conflict with Accept Keybinding"))
26507 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26508 )
26509 .child(
26510 h_flex()
26511 .pb_1()
26512 .gap_1()
26513 .items_end()
26514 .w_full()
26515 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26516 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26517 }))
26518 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26519 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26520 })),
26521 )
26522 })
26523 }
26524}
26525
26526#[derive(Debug, Clone, Copy, PartialEq)]
26527pub struct LineHighlight {
26528 pub background: Background,
26529 pub border: Option<gpui::Hsla>,
26530 pub include_gutter: bool,
26531 pub type_id: Option<TypeId>,
26532}
26533
26534struct LineManipulationResult {
26535 pub new_text: String,
26536 pub line_count_before: usize,
26537 pub line_count_after: usize,
26538}
26539
26540fn render_diff_hunk_controls(
26541 row: u32,
26542 status: &DiffHunkStatus,
26543 hunk_range: Range<Anchor>,
26544 is_created_file: bool,
26545 line_height: Pixels,
26546 editor: &Entity<Editor>,
26547 _window: &mut Window,
26548 cx: &mut App,
26549) -> AnyElement {
26550 h_flex()
26551 .h(line_height)
26552 .mr_1()
26553 .gap_1()
26554 .px_0p5()
26555 .pb_1()
26556 .border_x_1()
26557 .border_b_1()
26558 .border_color(cx.theme().colors().border_variant)
26559 .rounded_b_lg()
26560 .bg(cx.theme().colors().editor_background)
26561 .gap_1()
26562 .block_mouse_except_scroll()
26563 .shadow_md()
26564 .child(if status.has_secondary_hunk() {
26565 Button::new(("stage", row as u64), "Stage")
26566 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26567 .tooltip({
26568 let focus_handle = editor.focus_handle(cx);
26569 move |_window, cx| {
26570 Tooltip::for_action_in(
26571 "Stage Hunk",
26572 &::git::ToggleStaged,
26573 &focus_handle,
26574 cx,
26575 )
26576 }
26577 })
26578 .on_click({
26579 let editor = editor.clone();
26580 move |_event, _window, cx| {
26581 editor.update(cx, |editor, cx| {
26582 editor.stage_or_unstage_diff_hunks(
26583 true,
26584 vec![hunk_range.start..hunk_range.start],
26585 cx,
26586 );
26587 });
26588 }
26589 })
26590 } else {
26591 Button::new(("unstage", row as u64), "Unstage")
26592 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26593 .tooltip({
26594 let focus_handle = editor.focus_handle(cx);
26595 move |_window, cx| {
26596 Tooltip::for_action_in(
26597 "Unstage Hunk",
26598 &::git::ToggleStaged,
26599 &focus_handle,
26600 cx,
26601 )
26602 }
26603 })
26604 .on_click({
26605 let editor = editor.clone();
26606 move |_event, _window, cx| {
26607 editor.update(cx, |editor, cx| {
26608 editor.stage_or_unstage_diff_hunks(
26609 false,
26610 vec![hunk_range.start..hunk_range.start],
26611 cx,
26612 );
26613 });
26614 }
26615 })
26616 })
26617 .child(
26618 Button::new(("restore", row as u64), "Restore")
26619 .tooltip({
26620 let focus_handle = editor.focus_handle(cx);
26621 move |_window, cx| {
26622 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26623 }
26624 })
26625 .on_click({
26626 let editor = editor.clone();
26627 move |_event, window, cx| {
26628 editor.update(cx, |editor, cx| {
26629 let snapshot = editor.snapshot(window, cx);
26630 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26631 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26632 });
26633 }
26634 })
26635 .disabled(is_created_file),
26636 )
26637 .when(
26638 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26639 |el| {
26640 el.child(
26641 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26642 .shape(IconButtonShape::Square)
26643 .icon_size(IconSize::Small)
26644 // .disabled(!has_multiple_hunks)
26645 .tooltip({
26646 let focus_handle = editor.focus_handle(cx);
26647 move |_window, cx| {
26648 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26649 }
26650 })
26651 .on_click({
26652 let editor = editor.clone();
26653 move |_event, window, cx| {
26654 editor.update(cx, |editor, cx| {
26655 let snapshot = editor.snapshot(window, cx);
26656 let position =
26657 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26658 editor.go_to_hunk_before_or_after_position(
26659 &snapshot,
26660 position,
26661 Direction::Next,
26662 window,
26663 cx,
26664 );
26665 editor.expand_selected_diff_hunks(cx);
26666 });
26667 }
26668 }),
26669 )
26670 .child(
26671 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26672 .shape(IconButtonShape::Square)
26673 .icon_size(IconSize::Small)
26674 // .disabled(!has_multiple_hunks)
26675 .tooltip({
26676 let focus_handle = editor.focus_handle(cx);
26677 move |_window, cx| {
26678 Tooltip::for_action_in(
26679 "Previous Hunk",
26680 &GoToPreviousHunk,
26681 &focus_handle,
26682 cx,
26683 )
26684 }
26685 })
26686 .on_click({
26687 let editor = editor.clone();
26688 move |_event, window, cx| {
26689 editor.update(cx, |editor, cx| {
26690 let snapshot = editor.snapshot(window, cx);
26691 let point =
26692 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26693 editor.go_to_hunk_before_or_after_position(
26694 &snapshot,
26695 point,
26696 Direction::Prev,
26697 window,
26698 cx,
26699 );
26700 editor.expand_selected_diff_hunks(cx);
26701 });
26702 }
26703 }),
26704 )
26705 },
26706 )
26707 .into_any_element()
26708}
26709
26710pub fn multibuffer_context_lines(cx: &App) -> u32 {
26711 EditorSettings::try_get(cx)
26712 .map(|settings| settings.excerpt_context_lines)
26713 .unwrap_or(2)
26714 .min(32)
26715}