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
5516 .update(cx, |buffer, _| {
5517 buffer.push_transaction(transaction, Instant::now());
5518 buffer.finalize_last_transaction();
5519 })
5520 .ok();
5521 }
5522 editor.update(cx, |editor, cx| {
5523 editor.refresh_document_highlights(cx);
5524 })?;
5525 }
5526 Ok(())
5527 }))
5528 }
5529
5530 pub fn show_word_completions(
5531 &mut self,
5532 _: &ShowWordCompletions,
5533 window: &mut Window,
5534 cx: &mut Context<Self>,
5535 ) {
5536 self.open_or_update_completions_menu(
5537 Some(CompletionsMenuSource::Words {
5538 ignore_threshold: true,
5539 }),
5540 None,
5541 false,
5542 window,
5543 cx,
5544 );
5545 }
5546
5547 pub fn show_completions(
5548 &mut self,
5549 _: &ShowCompletions,
5550 window: &mut Window,
5551 cx: &mut Context<Self>,
5552 ) {
5553 self.open_or_update_completions_menu(None, None, false, window, cx);
5554 }
5555
5556 fn open_or_update_completions_menu(
5557 &mut self,
5558 requested_source: Option<CompletionsMenuSource>,
5559 trigger: Option<String>,
5560 trigger_in_words: bool,
5561 window: &mut Window,
5562 cx: &mut Context<Self>,
5563 ) {
5564 if self.pending_rename.is_some() {
5565 return;
5566 }
5567
5568 let completions_source = self
5569 .context_menu
5570 .borrow()
5571 .as_ref()
5572 .and_then(|menu| match menu {
5573 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5574 CodeContextMenu::CodeActions(_) => None,
5575 });
5576
5577 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5578
5579 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5580 // inserted and selected. To handle that case, the start of the selection is used so that
5581 // the menu starts with all choices.
5582 let position = self
5583 .selections
5584 .newest_anchor()
5585 .start
5586 .bias_right(&multibuffer_snapshot);
5587 if position.diff_base_anchor.is_some() {
5588 return;
5589 }
5590 let buffer_position = multibuffer_snapshot.anchor_before(position);
5591 let Some(buffer) = buffer_position
5592 .text_anchor
5593 .buffer_id
5594 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5595 else {
5596 return;
5597 };
5598 let buffer_snapshot = buffer.read(cx).snapshot();
5599
5600 let menu_is_open = matches!(
5601 self.context_menu.borrow().as_ref(),
5602 Some(CodeContextMenu::Completions(_))
5603 );
5604
5605 let language = buffer_snapshot
5606 .language_at(buffer_position.text_anchor)
5607 .map(|language| language.name());
5608
5609 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5610 let completion_settings = language_settings.completions.clone();
5611
5612 let show_completions_on_input = self
5613 .show_completions_on_input_override
5614 .unwrap_or(language_settings.show_completions_on_input);
5615 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5616 return;
5617 }
5618
5619 let query: Option<Arc<String>> =
5620 Self::completion_query(&multibuffer_snapshot, buffer_position)
5621 .map(|query| query.into());
5622
5623 drop(multibuffer_snapshot);
5624
5625 // Hide the current completions menu when query is empty. Without this, cached
5626 // completions from before the trigger char may be reused (#32774).
5627 if query.is_none() && menu_is_open {
5628 self.hide_context_menu(window, cx);
5629 }
5630
5631 let mut ignore_word_threshold = false;
5632 let provider = match requested_source {
5633 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5634 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5635 ignore_word_threshold = ignore_threshold;
5636 None
5637 }
5638 Some(CompletionsMenuSource::SnippetChoices)
5639 | Some(CompletionsMenuSource::SnippetsOnly) => {
5640 log::error!("bug: SnippetChoices requested_source is not handled");
5641 None
5642 }
5643 };
5644
5645 let sort_completions = provider
5646 .as_ref()
5647 .is_some_and(|provider| provider.sort_completions());
5648
5649 let filter_completions = provider
5650 .as_ref()
5651 .is_none_or(|provider| provider.filter_completions());
5652
5653 let was_snippets_only = matches!(
5654 completions_source,
5655 Some(CompletionsMenuSource::SnippetsOnly)
5656 );
5657
5658 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5659 if filter_completions {
5660 menu.filter(
5661 query.clone().unwrap_or_default(),
5662 buffer_position.text_anchor,
5663 &buffer,
5664 provider.clone(),
5665 window,
5666 cx,
5667 );
5668 }
5669 // When `is_incomplete` is false, no need to re-query completions when the current query
5670 // is a suffix of the initial query.
5671 let was_complete = !menu.is_incomplete;
5672 if was_complete && !was_snippets_only {
5673 // If the new query is a suffix of the old query (typing more characters) and
5674 // the previous result was complete, the existing completions can be filtered.
5675 //
5676 // Note that snippet completions are always complete.
5677 let query_matches = match (&menu.initial_query, &query) {
5678 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5679 (None, _) => true,
5680 _ => false,
5681 };
5682 if query_matches {
5683 let position_matches = if menu.initial_position == position {
5684 true
5685 } else {
5686 let snapshot = self.buffer.read(cx).read(cx);
5687 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5688 };
5689 if position_matches {
5690 return;
5691 }
5692 }
5693 }
5694 };
5695
5696 let Anchor {
5697 excerpt_id: buffer_excerpt_id,
5698 text_anchor: buffer_position,
5699 ..
5700 } = buffer_position;
5701
5702 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5703 buffer_snapshot.surrounding_word(buffer_position, None)
5704 {
5705 let word_to_exclude = buffer_snapshot
5706 .text_for_range(word_range.clone())
5707 .collect::<String>();
5708 (
5709 buffer_snapshot.anchor_before(word_range.start)
5710 ..buffer_snapshot.anchor_after(buffer_position),
5711 Some(word_to_exclude),
5712 )
5713 } else {
5714 (buffer_position..buffer_position, None)
5715 };
5716
5717 let show_completion_documentation = buffer_snapshot
5718 .settings_at(buffer_position, cx)
5719 .show_completion_documentation;
5720
5721 // The document can be large, so stay in reasonable bounds when searching for words,
5722 // otherwise completion pop-up might be slow to appear.
5723 const WORD_LOOKUP_ROWS: u32 = 5_000;
5724 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5725 let min_word_search = buffer_snapshot.clip_point(
5726 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5727 Bias::Left,
5728 );
5729 let max_word_search = buffer_snapshot.clip_point(
5730 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5731 Bias::Right,
5732 );
5733 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5734 ..buffer_snapshot.point_to_offset(max_word_search);
5735
5736 let skip_digits = query
5737 .as_ref()
5738 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5739
5740 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5741 trigger.as_ref().is_none_or(|trigger| {
5742 provider.is_completion_trigger(
5743 &buffer,
5744 position.text_anchor,
5745 trigger,
5746 trigger_in_words,
5747 cx,
5748 )
5749 })
5750 });
5751
5752 let provider_responses = if let Some(provider) = &provider
5753 && load_provider_completions
5754 {
5755 let trigger_character =
5756 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5757 let completion_context = CompletionContext {
5758 trigger_kind: match &trigger_character {
5759 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5760 None => CompletionTriggerKind::INVOKED,
5761 },
5762 trigger_character,
5763 };
5764
5765 provider.completions(
5766 buffer_excerpt_id,
5767 &buffer,
5768 buffer_position,
5769 completion_context,
5770 window,
5771 cx,
5772 )
5773 } else {
5774 Task::ready(Ok(Vec::new()))
5775 };
5776
5777 let load_word_completions = if !self.word_completions_enabled {
5778 false
5779 } else if requested_source
5780 == Some(CompletionsMenuSource::Words {
5781 ignore_threshold: true,
5782 })
5783 {
5784 true
5785 } else {
5786 load_provider_completions
5787 && completion_settings.words != WordsCompletionMode::Disabled
5788 && (ignore_word_threshold || {
5789 let words_min_length = completion_settings.words_min_length;
5790 // check whether word has at least `words_min_length` characters
5791 let query_chars = query.iter().flat_map(|q| q.chars());
5792 query_chars.take(words_min_length).count() == words_min_length
5793 })
5794 };
5795
5796 let mut words = if load_word_completions {
5797 cx.background_spawn({
5798 let buffer_snapshot = buffer_snapshot.clone();
5799 async move {
5800 buffer_snapshot.words_in_range(WordsQuery {
5801 fuzzy_contents: None,
5802 range: word_search_range,
5803 skip_digits,
5804 })
5805 }
5806 })
5807 } else {
5808 Task::ready(BTreeMap::default())
5809 };
5810
5811 let snippets = if let Some(provider) = &provider
5812 && provider.show_snippets()
5813 && let Some(project) = self.project()
5814 {
5815 let char_classifier = buffer_snapshot
5816 .char_classifier_at(buffer_position)
5817 .scope_context(Some(CharScopeContext::Completion));
5818 project.update(cx, |project, cx| {
5819 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5820 })
5821 } else {
5822 Task::ready(Ok(CompletionResponse {
5823 completions: Vec::new(),
5824 display_options: Default::default(),
5825 is_incomplete: false,
5826 }))
5827 };
5828
5829 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5830
5831 let id = post_inc(&mut self.next_completion_id);
5832 let task = cx.spawn_in(window, async move |editor, cx| {
5833 let Ok(()) = editor.update(cx, |this, _| {
5834 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5835 }) else {
5836 return;
5837 };
5838
5839 // TODO: Ideally completions from different sources would be selectively re-queried, so
5840 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5841 let mut completions = Vec::new();
5842 let mut is_incomplete = false;
5843 let mut display_options: Option<CompletionDisplayOptions> = None;
5844 if let Some(provider_responses) = provider_responses.await.log_err()
5845 && !provider_responses.is_empty()
5846 {
5847 for response in provider_responses {
5848 completions.extend(response.completions);
5849 is_incomplete = is_incomplete || response.is_incomplete;
5850 match display_options.as_mut() {
5851 None => {
5852 display_options = Some(response.display_options);
5853 }
5854 Some(options) => options.merge(&response.display_options),
5855 }
5856 }
5857 if completion_settings.words == WordsCompletionMode::Fallback {
5858 words = Task::ready(BTreeMap::default());
5859 }
5860 }
5861 let display_options = display_options.unwrap_or_default();
5862
5863 let mut words = words.await;
5864 if let Some(word_to_exclude) = &word_to_exclude {
5865 words.remove(word_to_exclude);
5866 }
5867 for lsp_completion in &completions {
5868 words.remove(&lsp_completion.new_text);
5869 }
5870 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5871 replace_range: word_replace_range.clone(),
5872 new_text: word.clone(),
5873 label: CodeLabel::plain(word, None),
5874 match_start: None,
5875 snippet_deduplication_key: None,
5876 icon_path: None,
5877 documentation: None,
5878 source: CompletionSource::BufferWord {
5879 word_range,
5880 resolved: false,
5881 },
5882 insert_text_mode: Some(InsertTextMode::AS_IS),
5883 confirm: None,
5884 }));
5885
5886 completions.extend(
5887 snippets
5888 .await
5889 .into_iter()
5890 .flat_map(|response| response.completions),
5891 );
5892
5893 let menu = if completions.is_empty() {
5894 None
5895 } else {
5896 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5897 let languages = editor
5898 .workspace
5899 .as_ref()
5900 .and_then(|(workspace, _)| workspace.upgrade())
5901 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5902 let menu = CompletionsMenu::new(
5903 id,
5904 requested_source.unwrap_or(if load_provider_completions {
5905 CompletionsMenuSource::Normal
5906 } else {
5907 CompletionsMenuSource::SnippetsOnly
5908 }),
5909 sort_completions,
5910 show_completion_documentation,
5911 position,
5912 query.clone(),
5913 is_incomplete,
5914 buffer.clone(),
5915 completions.into(),
5916 editor
5917 .context_menu()
5918 .borrow_mut()
5919 .as_ref()
5920 .map(|menu| menu.primary_scroll_handle()),
5921 display_options,
5922 snippet_sort_order,
5923 languages,
5924 language,
5925 cx,
5926 );
5927
5928 let query = if filter_completions { query } else { None };
5929 let matches_task = menu.do_async_filtering(
5930 query.unwrap_or_default(),
5931 buffer_position,
5932 &buffer,
5933 cx,
5934 );
5935 (menu, matches_task)
5936 }) else {
5937 return;
5938 };
5939
5940 let matches = matches_task.await;
5941
5942 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5943 // Newer menu already set, so exit.
5944 if let Some(CodeContextMenu::Completions(prev_menu)) =
5945 editor.context_menu.borrow().as_ref()
5946 && prev_menu.id > id
5947 {
5948 return;
5949 };
5950
5951 // Only valid to take prev_menu because either the new menu is immediately set
5952 // below, or the menu is hidden.
5953 if let Some(CodeContextMenu::Completions(prev_menu)) =
5954 editor.context_menu.borrow_mut().take()
5955 {
5956 let position_matches =
5957 if prev_menu.initial_position == menu.initial_position {
5958 true
5959 } else {
5960 let snapshot = editor.buffer.read(cx).read(cx);
5961 prev_menu.initial_position.to_offset(&snapshot)
5962 == menu.initial_position.to_offset(&snapshot)
5963 };
5964 if position_matches {
5965 // Preserve markdown cache before `set_filter_results` because it will
5966 // try to populate the documentation cache.
5967 menu.preserve_markdown_cache(prev_menu);
5968 }
5969 };
5970
5971 menu.set_filter_results(matches, provider, window, cx);
5972 }) else {
5973 return;
5974 };
5975
5976 menu.visible().then_some(menu)
5977 };
5978
5979 editor
5980 .update_in(cx, |editor, window, cx| {
5981 if editor.focus_handle.is_focused(window)
5982 && let Some(menu) = menu
5983 {
5984 *editor.context_menu.borrow_mut() =
5985 Some(CodeContextMenu::Completions(menu));
5986
5987 crate::hover_popover::hide_hover(editor, cx);
5988 if editor.show_edit_predictions_in_menu() {
5989 editor.update_visible_edit_prediction(window, cx);
5990 } else {
5991 editor.discard_edit_prediction(false, cx);
5992 }
5993
5994 cx.notify();
5995 return;
5996 }
5997
5998 if editor.completion_tasks.len() <= 1 {
5999 // If there are no more completion tasks and the last menu was empty, we should hide it.
6000 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6001 // If it was already hidden and we don't show edit predictions in the menu,
6002 // we should also show the edit prediction when available.
6003 if was_hidden && editor.show_edit_predictions_in_menu() {
6004 editor.update_visible_edit_prediction(window, cx);
6005 }
6006 }
6007 })
6008 .ok();
6009 });
6010
6011 self.completion_tasks.push((id, task));
6012 }
6013
6014 #[cfg(feature = "test-support")]
6015 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6016 let menu = self.context_menu.borrow();
6017 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6018 let completions = menu.completions.borrow();
6019 Some(completions.to_vec())
6020 } else {
6021 None
6022 }
6023 }
6024
6025 pub fn with_completions_menu_matching_id<R>(
6026 &self,
6027 id: CompletionId,
6028 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6029 ) -> R {
6030 let mut context_menu = self.context_menu.borrow_mut();
6031 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6032 return f(None);
6033 };
6034 if completions_menu.id != id {
6035 return f(None);
6036 }
6037 f(Some(completions_menu))
6038 }
6039
6040 pub fn confirm_completion(
6041 &mut self,
6042 action: &ConfirmCompletion,
6043 window: &mut Window,
6044 cx: &mut Context<Self>,
6045 ) -> Option<Task<Result<()>>> {
6046 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6047 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6048 }
6049
6050 pub fn confirm_completion_insert(
6051 &mut self,
6052 _: &ConfirmCompletionInsert,
6053 window: &mut Window,
6054 cx: &mut Context<Self>,
6055 ) -> Option<Task<Result<()>>> {
6056 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6057 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6058 }
6059
6060 pub fn confirm_completion_replace(
6061 &mut self,
6062 _: &ConfirmCompletionReplace,
6063 window: &mut Window,
6064 cx: &mut Context<Self>,
6065 ) -> Option<Task<Result<()>>> {
6066 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6067 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6068 }
6069
6070 pub fn compose_completion(
6071 &mut self,
6072 action: &ComposeCompletion,
6073 window: &mut Window,
6074 cx: &mut Context<Self>,
6075 ) -> Option<Task<Result<()>>> {
6076 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6077 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6078 }
6079
6080 fn do_completion(
6081 &mut self,
6082 item_ix: Option<usize>,
6083 intent: CompletionIntent,
6084 window: &mut Window,
6085 cx: &mut Context<Editor>,
6086 ) -> Option<Task<Result<()>>> {
6087 use language::ToOffset as _;
6088
6089 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6090 else {
6091 return None;
6092 };
6093
6094 let candidate_id = {
6095 let entries = completions_menu.entries.borrow();
6096 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6097 if self.show_edit_predictions_in_menu() {
6098 self.discard_edit_prediction(true, cx);
6099 }
6100 mat.candidate_id
6101 };
6102
6103 let completion = completions_menu
6104 .completions
6105 .borrow()
6106 .get(candidate_id)?
6107 .clone();
6108 cx.stop_propagation();
6109
6110 let buffer_handle = completions_menu.buffer.clone();
6111
6112 let CompletionEdit {
6113 new_text,
6114 snippet,
6115 replace_range,
6116 } = process_completion_for_edit(
6117 &completion,
6118 intent,
6119 &buffer_handle,
6120 &completions_menu.initial_position.text_anchor,
6121 cx,
6122 );
6123
6124 let buffer = buffer_handle.read(cx);
6125 let snapshot = self.buffer.read(cx).snapshot(cx);
6126 let newest_anchor = self.selections.newest_anchor();
6127 let replace_range_multibuffer = {
6128 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6129 excerpt.map_range_from_buffer(replace_range.clone())
6130 };
6131 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6132 return None;
6133 }
6134
6135 let old_text = buffer
6136 .text_for_range(replace_range.clone())
6137 .collect::<String>();
6138 let lookbehind = newest_anchor
6139 .start
6140 .text_anchor
6141 .to_offset(buffer)
6142 .saturating_sub(replace_range.start.0);
6143 let lookahead = replace_range
6144 .end
6145 .0
6146 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6147 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6148 let suffix = &old_text[lookbehind.min(old_text.len())..];
6149
6150 let selections = self
6151 .selections
6152 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6153 let mut ranges = Vec::new();
6154 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6155
6156 for selection in &selections {
6157 let range = if selection.id == newest_anchor.id {
6158 replace_range_multibuffer.clone()
6159 } else {
6160 let mut range = selection.range();
6161
6162 // if prefix is present, don't duplicate it
6163 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6164 range.start = range.start.saturating_sub_usize(lookbehind);
6165
6166 // if suffix is also present, mimic the newest cursor and replace it
6167 if selection.id != newest_anchor.id
6168 && snapshot.contains_str_at(range.end, suffix)
6169 {
6170 range.end += lookahead;
6171 }
6172 }
6173 range
6174 };
6175
6176 ranges.push(range.clone());
6177
6178 if !self.linked_edit_ranges.is_empty() {
6179 let start_anchor = snapshot.anchor_before(range.start);
6180 let end_anchor = snapshot.anchor_after(range.end);
6181 if let Some(ranges) = self
6182 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6183 {
6184 for (buffer, edits) in ranges {
6185 linked_edits
6186 .entry(buffer.clone())
6187 .or_default()
6188 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6189 }
6190 }
6191 }
6192 }
6193
6194 let common_prefix_len = old_text
6195 .chars()
6196 .zip(new_text.chars())
6197 .take_while(|(a, b)| a == b)
6198 .map(|(a, _)| a.len_utf8())
6199 .sum::<usize>();
6200
6201 cx.emit(EditorEvent::InputHandled {
6202 utf16_range_to_replace: None,
6203 text: new_text[common_prefix_len..].into(),
6204 });
6205
6206 self.transact(window, cx, |editor, window, cx| {
6207 if let Some(mut snippet) = snippet {
6208 snippet.text = new_text.to_string();
6209 editor
6210 .insert_snippet(&ranges, snippet, window, cx)
6211 .log_err();
6212 } else {
6213 editor.buffer.update(cx, |multi_buffer, cx| {
6214 let auto_indent = match completion.insert_text_mode {
6215 Some(InsertTextMode::AS_IS) => None,
6216 _ => editor.autoindent_mode.clone(),
6217 };
6218 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6219 multi_buffer.edit(edits, auto_indent, cx);
6220 });
6221 }
6222 for (buffer, edits) in linked_edits {
6223 buffer.update(cx, |buffer, cx| {
6224 let snapshot = buffer.snapshot();
6225 let edits = edits
6226 .into_iter()
6227 .map(|(range, text)| {
6228 use text::ToPoint as TP;
6229 let end_point = TP::to_point(&range.end, &snapshot);
6230 let start_point = TP::to_point(&range.start, &snapshot);
6231 (start_point..end_point, text)
6232 })
6233 .sorted_by_key(|(range, _)| range.start);
6234 buffer.edit(edits, None, cx);
6235 })
6236 }
6237
6238 editor.refresh_edit_prediction(true, false, window, cx);
6239 });
6240 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6241
6242 let show_new_completions_on_confirm = completion
6243 .confirm
6244 .as_ref()
6245 .is_some_and(|confirm| confirm(intent, window, cx));
6246 if show_new_completions_on_confirm {
6247 self.open_or_update_completions_menu(None, None, false, window, cx);
6248 }
6249
6250 let provider = self.completion_provider.as_ref()?;
6251
6252 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6253 let command = lsp_store.as_ref().and_then(|lsp_store| {
6254 let CompletionSource::Lsp {
6255 lsp_completion,
6256 server_id,
6257 ..
6258 } = &completion.source
6259 else {
6260 return None;
6261 };
6262 let lsp_command = lsp_completion.command.as_ref()?;
6263 let available_commands = lsp_store
6264 .read(cx)
6265 .lsp_server_capabilities
6266 .get(server_id)
6267 .and_then(|server_capabilities| {
6268 server_capabilities
6269 .execute_command_provider
6270 .as_ref()
6271 .map(|options| options.commands.as_slice())
6272 })?;
6273 if available_commands.contains(&lsp_command.command) {
6274 Some(CodeAction {
6275 server_id: *server_id,
6276 range: language::Anchor::MIN..language::Anchor::MIN,
6277 lsp_action: LspAction::Command(lsp_command.clone()),
6278 resolved: false,
6279 })
6280 } else {
6281 None
6282 }
6283 });
6284
6285 drop(completion);
6286 let apply_edits = provider.apply_additional_edits_for_completion(
6287 buffer_handle.clone(),
6288 completions_menu.completions.clone(),
6289 candidate_id,
6290 true,
6291 cx,
6292 );
6293
6294 let editor_settings = EditorSettings::get_global(cx);
6295 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6296 // After the code completion is finished, users often want to know what signatures are needed.
6297 // so we should automatically call signature_help
6298 self.show_signature_help(&ShowSignatureHelp, window, cx);
6299 }
6300
6301 Some(cx.spawn_in(window, async move |editor, cx| {
6302 apply_edits.await?;
6303
6304 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6305 let title = command.lsp_action.title().to_owned();
6306 let project_transaction = lsp_store
6307 .update(cx, |lsp_store, cx| {
6308 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6309 })?
6310 .await
6311 .context("applying post-completion command")?;
6312 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6313 Self::open_project_transaction(
6314 &editor,
6315 workspace.downgrade(),
6316 project_transaction,
6317 title,
6318 cx,
6319 )
6320 .await?;
6321 }
6322 }
6323
6324 Ok(())
6325 }))
6326 }
6327
6328 pub fn toggle_code_actions(
6329 &mut self,
6330 action: &ToggleCodeActions,
6331 window: &mut Window,
6332 cx: &mut Context<Self>,
6333 ) {
6334 let quick_launch = action.quick_launch;
6335 let mut context_menu = self.context_menu.borrow_mut();
6336 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6337 if code_actions.deployed_from == action.deployed_from {
6338 // Toggle if we're selecting the same one
6339 *context_menu = None;
6340 cx.notify();
6341 return;
6342 } else {
6343 // Otherwise, clear it and start a new one
6344 *context_menu = None;
6345 cx.notify();
6346 }
6347 }
6348 drop(context_menu);
6349 let snapshot = self.snapshot(window, cx);
6350 let deployed_from = action.deployed_from.clone();
6351 let action = action.clone();
6352 self.completion_tasks.clear();
6353 self.discard_edit_prediction(false, cx);
6354
6355 let multibuffer_point = match &action.deployed_from {
6356 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6357 DisplayPoint::new(*row, 0).to_point(&snapshot)
6358 }
6359 _ => self
6360 .selections
6361 .newest::<Point>(&snapshot.display_snapshot)
6362 .head(),
6363 };
6364 let Some((buffer, buffer_row)) = snapshot
6365 .buffer_snapshot()
6366 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6367 .and_then(|(buffer_snapshot, range)| {
6368 self.buffer()
6369 .read(cx)
6370 .buffer(buffer_snapshot.remote_id())
6371 .map(|buffer| (buffer, range.start.row))
6372 })
6373 else {
6374 return;
6375 };
6376 let buffer_id = buffer.read(cx).remote_id();
6377 let tasks = self
6378 .tasks
6379 .get(&(buffer_id, buffer_row))
6380 .map(|t| Arc::new(t.to_owned()));
6381
6382 if !self.focus_handle.is_focused(window) {
6383 return;
6384 }
6385 let project = self.project.clone();
6386
6387 let code_actions_task = match deployed_from {
6388 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6389 _ => self.code_actions(buffer_row, window, cx),
6390 };
6391
6392 let runnable_task = match deployed_from {
6393 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6394 _ => {
6395 let mut task_context_task = Task::ready(None);
6396 if let Some(tasks) = &tasks
6397 && let Some(project) = project
6398 {
6399 task_context_task =
6400 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6401 }
6402
6403 cx.spawn_in(window, {
6404 let buffer = buffer.clone();
6405 async move |editor, cx| {
6406 let task_context = task_context_task.await;
6407
6408 let resolved_tasks =
6409 tasks
6410 .zip(task_context.clone())
6411 .map(|(tasks, task_context)| ResolvedTasks {
6412 templates: tasks.resolve(&task_context).collect(),
6413 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6414 multibuffer_point.row,
6415 tasks.column,
6416 )),
6417 });
6418 let debug_scenarios = editor
6419 .update(cx, |editor, cx| {
6420 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6421 })?
6422 .await;
6423 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6424 }
6425 })
6426 }
6427 };
6428
6429 cx.spawn_in(window, async move |editor, cx| {
6430 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6431 let code_actions = code_actions_task.await;
6432 let spawn_straight_away = quick_launch
6433 && resolved_tasks
6434 .as_ref()
6435 .is_some_and(|tasks| tasks.templates.len() == 1)
6436 && code_actions
6437 .as_ref()
6438 .is_none_or(|actions| actions.is_empty())
6439 && debug_scenarios.is_empty();
6440
6441 editor.update_in(cx, |editor, window, cx| {
6442 crate::hover_popover::hide_hover(editor, cx);
6443 let actions = CodeActionContents::new(
6444 resolved_tasks,
6445 code_actions,
6446 debug_scenarios,
6447 task_context.unwrap_or_default(),
6448 );
6449
6450 // Don't show the menu if there are no actions available
6451 if actions.is_empty() {
6452 cx.notify();
6453 return Task::ready(Ok(()));
6454 }
6455
6456 *editor.context_menu.borrow_mut() =
6457 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6458 buffer,
6459 actions,
6460 selected_item: Default::default(),
6461 scroll_handle: UniformListScrollHandle::default(),
6462 deployed_from,
6463 }));
6464 cx.notify();
6465 if spawn_straight_away
6466 && let Some(task) = editor.confirm_code_action(
6467 &ConfirmCodeAction { item_ix: Some(0) },
6468 window,
6469 cx,
6470 )
6471 {
6472 return task;
6473 }
6474
6475 Task::ready(Ok(()))
6476 })
6477 })
6478 .detach_and_log_err(cx);
6479 }
6480
6481 fn debug_scenarios(
6482 &mut self,
6483 resolved_tasks: &Option<ResolvedTasks>,
6484 buffer: &Entity<Buffer>,
6485 cx: &mut App,
6486 ) -> Task<Vec<task::DebugScenario>> {
6487 maybe!({
6488 let project = self.project()?;
6489 let dap_store = project.read(cx).dap_store();
6490 let mut scenarios = vec![];
6491 let resolved_tasks = resolved_tasks.as_ref()?;
6492 let buffer = buffer.read(cx);
6493 let language = buffer.language()?;
6494 let file = buffer.file();
6495 let debug_adapter = language_settings(language.name().into(), file, cx)
6496 .debuggers
6497 .first()
6498 .map(SharedString::from)
6499 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6500
6501 dap_store.update(cx, |dap_store, cx| {
6502 for (_, task) in &resolved_tasks.templates {
6503 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6504 task.original_task().clone(),
6505 debug_adapter.clone().into(),
6506 task.display_label().to_owned().into(),
6507 cx,
6508 );
6509 scenarios.push(maybe_scenario);
6510 }
6511 });
6512 Some(cx.background_spawn(async move {
6513 futures::future::join_all(scenarios)
6514 .await
6515 .into_iter()
6516 .flatten()
6517 .collect::<Vec<_>>()
6518 }))
6519 })
6520 .unwrap_or_else(|| Task::ready(vec![]))
6521 }
6522
6523 fn code_actions(
6524 &mut self,
6525 buffer_row: u32,
6526 window: &mut Window,
6527 cx: &mut Context<Self>,
6528 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6529 let mut task = self.code_actions_task.take();
6530 cx.spawn_in(window, async move |editor, cx| {
6531 while let Some(prev_task) = task {
6532 prev_task.await.log_err();
6533 task = editor
6534 .update(cx, |this, _| this.code_actions_task.take())
6535 .ok()?;
6536 }
6537
6538 editor
6539 .update(cx, |editor, cx| {
6540 editor
6541 .available_code_actions
6542 .clone()
6543 .and_then(|(location, code_actions)| {
6544 let snapshot = location.buffer.read(cx).snapshot();
6545 let point_range = location.range.to_point(&snapshot);
6546 let point_range = point_range.start.row..=point_range.end.row;
6547 if point_range.contains(&buffer_row) {
6548 Some(code_actions)
6549 } else {
6550 None
6551 }
6552 })
6553 })
6554 .ok()
6555 .flatten()
6556 })
6557 }
6558
6559 pub fn confirm_code_action(
6560 &mut self,
6561 action: &ConfirmCodeAction,
6562 window: &mut Window,
6563 cx: &mut Context<Self>,
6564 ) -> Option<Task<Result<()>>> {
6565 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6566
6567 let actions_menu =
6568 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6569 menu
6570 } else {
6571 return None;
6572 };
6573
6574 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6575 let action = actions_menu.actions.get(action_ix)?;
6576 let title = action.label();
6577 let buffer = actions_menu.buffer;
6578 let workspace = self.workspace()?;
6579
6580 match action {
6581 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6582 workspace.update(cx, |workspace, cx| {
6583 workspace.schedule_resolved_task(
6584 task_source_kind,
6585 resolved_task,
6586 false,
6587 window,
6588 cx,
6589 );
6590
6591 Some(Task::ready(Ok(())))
6592 })
6593 }
6594 CodeActionsItem::CodeAction {
6595 excerpt_id,
6596 action,
6597 provider,
6598 } => {
6599 let apply_code_action =
6600 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6601 let workspace = workspace.downgrade();
6602 Some(cx.spawn_in(window, async move |editor, cx| {
6603 let project_transaction = apply_code_action.await?;
6604 Self::open_project_transaction(
6605 &editor,
6606 workspace,
6607 project_transaction,
6608 title,
6609 cx,
6610 )
6611 .await
6612 }))
6613 }
6614 CodeActionsItem::DebugScenario(scenario) => {
6615 let context = actions_menu.actions.context;
6616
6617 workspace.update(cx, |workspace, cx| {
6618 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6619 workspace.start_debug_session(
6620 scenario,
6621 context,
6622 Some(buffer),
6623 None,
6624 window,
6625 cx,
6626 );
6627 });
6628 Some(Task::ready(Ok(())))
6629 }
6630 }
6631 }
6632
6633 fn open_transaction_for_hidden_buffers(
6634 workspace: Entity<Workspace>,
6635 transaction: ProjectTransaction,
6636 title: String,
6637 window: &mut Window,
6638 cx: &mut Context<Self>,
6639 ) {
6640 if transaction.0.is_empty() {
6641 return;
6642 }
6643
6644 let edited_buffers_already_open = {
6645 let other_editors: Vec<Entity<Editor>> = workspace
6646 .read(cx)
6647 .panes()
6648 .iter()
6649 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6650 .filter(|editor| editor.entity_id() != cx.entity_id())
6651 .collect();
6652
6653 transaction.0.keys().all(|buffer| {
6654 other_editors.iter().any(|editor| {
6655 let multi_buffer = editor.read(cx).buffer();
6656 multi_buffer.read(cx).is_singleton()
6657 && multi_buffer
6658 .read(cx)
6659 .as_singleton()
6660 .map_or(false, |singleton| {
6661 singleton.entity_id() == buffer.entity_id()
6662 })
6663 })
6664 })
6665 };
6666 if !edited_buffers_already_open {
6667 let workspace = workspace.downgrade();
6668 cx.defer_in(window, move |_, window, cx| {
6669 cx.spawn_in(window, async move |editor, cx| {
6670 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6671 .await
6672 .ok()
6673 })
6674 .detach();
6675 });
6676 }
6677 }
6678
6679 pub async fn open_project_transaction(
6680 editor: &WeakEntity<Editor>,
6681 workspace: WeakEntity<Workspace>,
6682 transaction: ProjectTransaction,
6683 title: String,
6684 cx: &mut AsyncWindowContext,
6685 ) -> Result<()> {
6686 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6687 cx.update(|_, cx| {
6688 entries.sort_unstable_by_key(|(buffer, _)| {
6689 buffer.read(cx).file().map(|f| f.path().clone())
6690 });
6691 })?;
6692 if entries.is_empty() {
6693 return Ok(());
6694 }
6695
6696 // If the project transaction's edits are all contained within this editor, then
6697 // avoid opening a new editor to display them.
6698
6699 if let [(buffer, transaction)] = &*entries {
6700 let excerpt = editor.update(cx, |editor, cx| {
6701 editor
6702 .buffer()
6703 .read(cx)
6704 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6705 })?;
6706 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6707 && excerpted_buffer == *buffer
6708 {
6709 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6710 let excerpt_range = excerpt_range.to_offset(buffer);
6711 buffer
6712 .edited_ranges_for_transaction::<usize>(transaction)
6713 .all(|range| {
6714 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6715 })
6716 })?;
6717
6718 if all_edits_within_excerpt {
6719 return Ok(());
6720 }
6721 }
6722 }
6723
6724 let mut ranges_to_highlight = Vec::new();
6725 let excerpt_buffer = cx.new(|cx| {
6726 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6727 for (buffer_handle, transaction) in &entries {
6728 let edited_ranges = buffer_handle
6729 .read(cx)
6730 .edited_ranges_for_transaction::<Point>(transaction)
6731 .collect::<Vec<_>>();
6732 let (ranges, _) = multibuffer.set_excerpts_for_path(
6733 PathKey::for_buffer(buffer_handle, cx),
6734 buffer_handle.clone(),
6735 edited_ranges,
6736 multibuffer_context_lines(cx),
6737 cx,
6738 );
6739
6740 ranges_to_highlight.extend(ranges);
6741 }
6742 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6743 multibuffer
6744 })?;
6745
6746 workspace.update_in(cx, |workspace, window, cx| {
6747 let project = workspace.project().clone();
6748 let editor =
6749 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6750 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6751 editor.update(cx, |editor, cx| {
6752 editor.highlight_background::<Self>(
6753 &ranges_to_highlight,
6754 |_, theme| theme.colors().editor_highlighted_line_background,
6755 cx,
6756 );
6757 });
6758 })?;
6759
6760 Ok(())
6761 }
6762
6763 pub fn clear_code_action_providers(&mut self) {
6764 self.code_action_providers.clear();
6765 self.available_code_actions.take();
6766 }
6767
6768 pub fn add_code_action_provider(
6769 &mut self,
6770 provider: Rc<dyn CodeActionProvider>,
6771 window: &mut Window,
6772 cx: &mut Context<Self>,
6773 ) {
6774 if self
6775 .code_action_providers
6776 .iter()
6777 .any(|existing_provider| existing_provider.id() == provider.id())
6778 {
6779 return;
6780 }
6781
6782 self.code_action_providers.push(provider);
6783 self.refresh_code_actions(window, cx);
6784 }
6785
6786 pub fn remove_code_action_provider(
6787 &mut self,
6788 id: Arc<str>,
6789 window: &mut Window,
6790 cx: &mut Context<Self>,
6791 ) {
6792 self.code_action_providers
6793 .retain(|provider| provider.id() != id);
6794 self.refresh_code_actions(window, cx);
6795 }
6796
6797 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6798 !self.code_action_providers.is_empty()
6799 && EditorSettings::get_global(cx).toolbar.code_actions
6800 }
6801
6802 pub fn has_available_code_actions(&self) -> bool {
6803 self.available_code_actions
6804 .as_ref()
6805 .is_some_and(|(_, actions)| !actions.is_empty())
6806 }
6807
6808 fn render_inline_code_actions(
6809 &self,
6810 icon_size: ui::IconSize,
6811 display_row: DisplayRow,
6812 is_active: bool,
6813 cx: &mut Context<Self>,
6814 ) -> AnyElement {
6815 let show_tooltip = !self.context_menu_visible();
6816 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6817 .icon_size(icon_size)
6818 .shape(ui::IconButtonShape::Square)
6819 .icon_color(ui::Color::Hidden)
6820 .toggle_state(is_active)
6821 .when(show_tooltip, |this| {
6822 this.tooltip({
6823 let focus_handle = self.focus_handle.clone();
6824 move |_window, cx| {
6825 Tooltip::for_action_in(
6826 "Toggle Code Actions",
6827 &ToggleCodeActions {
6828 deployed_from: None,
6829 quick_launch: false,
6830 },
6831 &focus_handle,
6832 cx,
6833 )
6834 }
6835 })
6836 })
6837 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6838 window.focus(&editor.focus_handle(cx), cx);
6839 editor.toggle_code_actions(
6840 &crate::actions::ToggleCodeActions {
6841 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6842 display_row,
6843 )),
6844 quick_launch: false,
6845 },
6846 window,
6847 cx,
6848 );
6849 }))
6850 .into_any_element()
6851 }
6852
6853 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6854 &self.context_menu
6855 }
6856
6857 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6858 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6859 cx.background_executor()
6860 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6861 .await;
6862
6863 let (start_buffer, start, _, end, newest_selection) = this
6864 .update(cx, |this, cx| {
6865 let newest_selection = this.selections.newest_anchor().clone();
6866 if newest_selection.head().diff_base_anchor.is_some() {
6867 return None;
6868 }
6869 let display_snapshot = this.display_snapshot(cx);
6870 let newest_selection_adjusted =
6871 this.selections.newest_adjusted(&display_snapshot);
6872 let buffer = this.buffer.read(cx);
6873
6874 let (start_buffer, start) =
6875 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6876 let (end_buffer, end) =
6877 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6878
6879 Some((start_buffer, start, end_buffer, end, newest_selection))
6880 })?
6881 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6882 .context(
6883 "Expected selection to lie in a single buffer when refreshing code actions",
6884 )?;
6885 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6886 let providers = this.code_action_providers.clone();
6887 let tasks = this
6888 .code_action_providers
6889 .iter()
6890 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6891 .collect::<Vec<_>>();
6892 (providers, tasks)
6893 })?;
6894
6895 let mut actions = Vec::new();
6896 for (provider, provider_actions) in
6897 providers.into_iter().zip(future::join_all(tasks).await)
6898 {
6899 if let Some(provider_actions) = provider_actions.log_err() {
6900 actions.extend(provider_actions.into_iter().map(|action| {
6901 AvailableCodeAction {
6902 excerpt_id: newest_selection.start.excerpt_id,
6903 action,
6904 provider: provider.clone(),
6905 }
6906 }));
6907 }
6908 }
6909
6910 this.update(cx, |this, cx| {
6911 this.available_code_actions = if actions.is_empty() {
6912 None
6913 } else {
6914 Some((
6915 Location {
6916 buffer: start_buffer,
6917 range: start..end,
6918 },
6919 actions.into(),
6920 ))
6921 };
6922 cx.notify();
6923 })
6924 }));
6925 }
6926
6927 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6928 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6929 self.show_git_blame_inline = false;
6930
6931 self.show_git_blame_inline_delay_task =
6932 Some(cx.spawn_in(window, async move |this, cx| {
6933 cx.background_executor().timer(delay).await;
6934
6935 this.update(cx, |this, cx| {
6936 this.show_git_blame_inline = true;
6937 cx.notify();
6938 })
6939 .log_err();
6940 }));
6941 }
6942 }
6943
6944 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6945 let snapshot = self.snapshot(window, cx);
6946 let cursor = self
6947 .selections
6948 .newest::<Point>(&snapshot.display_snapshot)
6949 .head();
6950 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6951 else {
6952 return;
6953 };
6954
6955 if self.blame.is_none() {
6956 self.start_git_blame(true, window, cx);
6957 }
6958 let Some(blame) = self.blame.as_ref() else {
6959 return;
6960 };
6961
6962 let row_info = RowInfo {
6963 buffer_id: Some(buffer.remote_id()),
6964 buffer_row: Some(point.row),
6965 ..Default::default()
6966 };
6967 let Some((buffer, blame_entry)) = blame
6968 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6969 .flatten()
6970 else {
6971 return;
6972 };
6973
6974 let anchor = self.selections.newest_anchor().head();
6975 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6976 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6977 self.show_blame_popover(
6978 buffer,
6979 &blame_entry,
6980 position + last_bounds.origin,
6981 true,
6982 cx,
6983 );
6984 };
6985 }
6986
6987 fn show_blame_popover(
6988 &mut self,
6989 buffer: BufferId,
6990 blame_entry: &BlameEntry,
6991 position: gpui::Point<Pixels>,
6992 ignore_timeout: bool,
6993 cx: &mut Context<Self>,
6994 ) {
6995 if let Some(state) = &mut self.inline_blame_popover {
6996 state.hide_task.take();
6997 } else {
6998 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6999 let blame_entry = blame_entry.clone();
7000 let show_task = cx.spawn(async move |editor, cx| {
7001 if !ignore_timeout {
7002 cx.background_executor()
7003 .timer(std::time::Duration::from_millis(blame_popover_delay))
7004 .await;
7005 }
7006 editor
7007 .update(cx, |editor, cx| {
7008 editor.inline_blame_popover_show_task.take();
7009 let Some(blame) = editor.blame.as_ref() else {
7010 return;
7011 };
7012 let blame = blame.read(cx);
7013 let details = blame.details_for_entry(buffer, &blame_entry);
7014 let markdown = cx.new(|cx| {
7015 Markdown::new(
7016 details
7017 .as_ref()
7018 .map(|message| message.message.clone())
7019 .unwrap_or_default(),
7020 None,
7021 None,
7022 cx,
7023 )
7024 });
7025 editor.inline_blame_popover = Some(InlineBlamePopover {
7026 position,
7027 hide_task: None,
7028 popover_bounds: None,
7029 popover_state: InlineBlamePopoverState {
7030 scroll_handle: ScrollHandle::new(),
7031 commit_message: details,
7032 markdown,
7033 },
7034 keyboard_grace: ignore_timeout,
7035 });
7036 cx.notify();
7037 })
7038 .ok();
7039 });
7040 self.inline_blame_popover_show_task = Some(show_task);
7041 }
7042 }
7043
7044 pub fn has_mouse_context_menu(&self) -> bool {
7045 self.mouse_context_menu.is_some()
7046 }
7047
7048 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7049 self.inline_blame_popover_show_task.take();
7050 if let Some(state) = &mut self.inline_blame_popover {
7051 let hide_task = cx.spawn(async move |editor, cx| {
7052 if !ignore_timeout {
7053 cx.background_executor()
7054 .timer(std::time::Duration::from_millis(100))
7055 .await;
7056 }
7057 editor
7058 .update(cx, |editor, cx| {
7059 editor.inline_blame_popover.take();
7060 cx.notify();
7061 })
7062 .ok();
7063 });
7064 state.hide_task = Some(hide_task);
7065 true
7066 } else {
7067 false
7068 }
7069 }
7070
7071 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7072 if self.pending_rename.is_some() {
7073 return None;
7074 }
7075
7076 let provider = self.semantics_provider.clone()?;
7077 let buffer = self.buffer.read(cx);
7078 let newest_selection = self.selections.newest_anchor().clone();
7079 let cursor_position = newest_selection.head();
7080 let (cursor_buffer, cursor_buffer_position) =
7081 buffer.text_anchor_for_position(cursor_position, cx)?;
7082 let (tail_buffer, tail_buffer_position) =
7083 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7084 if cursor_buffer != tail_buffer {
7085 return None;
7086 }
7087
7088 let snapshot = cursor_buffer.read(cx).snapshot();
7089 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7090 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7091 if start_word_range != end_word_range {
7092 self.document_highlights_task.take();
7093 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7094 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7095 return None;
7096 }
7097
7098 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7099 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7100 cx.background_executor()
7101 .timer(Duration::from_millis(debounce))
7102 .await;
7103
7104 let highlights = if let Some(highlights) = cx
7105 .update(|cx| {
7106 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7107 })
7108 .ok()
7109 .flatten()
7110 {
7111 highlights.await.log_err()
7112 } else {
7113 None
7114 };
7115
7116 if let Some(highlights) = highlights {
7117 this.update(cx, |this, cx| {
7118 if this.pending_rename.is_some() {
7119 return;
7120 }
7121
7122 let buffer = this.buffer.read(cx);
7123 if buffer
7124 .text_anchor_for_position(cursor_position, cx)
7125 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7126 {
7127 return;
7128 }
7129
7130 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7131 let mut write_ranges = Vec::new();
7132 let mut read_ranges = Vec::new();
7133 for highlight in highlights {
7134 let buffer_id = cursor_buffer.read(cx).remote_id();
7135 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7136 {
7137 let start = highlight
7138 .range
7139 .start
7140 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7141 let end = highlight
7142 .range
7143 .end
7144 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7145 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7146 continue;
7147 }
7148
7149 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7150 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7151 write_ranges.push(range);
7152 } else {
7153 read_ranges.push(range);
7154 }
7155 }
7156 }
7157
7158 this.highlight_background::<DocumentHighlightRead>(
7159 &read_ranges,
7160 |_, theme| theme.colors().editor_document_highlight_read_background,
7161 cx,
7162 );
7163 this.highlight_background::<DocumentHighlightWrite>(
7164 &write_ranges,
7165 |_, theme| theme.colors().editor_document_highlight_write_background,
7166 cx,
7167 );
7168 cx.notify();
7169 })
7170 .log_err();
7171 }
7172 }));
7173 None
7174 }
7175
7176 fn prepare_highlight_query_from_selection(
7177 &mut self,
7178 window: &Window,
7179 cx: &mut Context<Editor>,
7180 ) -> Option<(String, Range<Anchor>)> {
7181 if matches!(self.mode, EditorMode::SingleLine) {
7182 return None;
7183 }
7184 if !EditorSettings::get_global(cx).selection_highlight {
7185 return None;
7186 }
7187 if self.selections.count() != 1 || self.selections.line_mode() {
7188 return None;
7189 }
7190 let snapshot = self.snapshot(window, cx);
7191 let selection = self.selections.newest::<Point>(&snapshot);
7192 // If the selection spans multiple rows OR it is empty
7193 if selection.start.row != selection.end.row
7194 || selection.start.column == selection.end.column
7195 {
7196 return None;
7197 }
7198 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7199 let query = snapshot
7200 .buffer_snapshot()
7201 .text_for_range(selection_anchor_range.clone())
7202 .collect::<String>();
7203 if query.trim().is_empty() {
7204 return None;
7205 }
7206 Some((query, selection_anchor_range))
7207 }
7208
7209 #[ztracing::instrument(skip_all)]
7210 fn update_selection_occurrence_highlights(
7211 &mut self,
7212 query_text: String,
7213 query_range: Range<Anchor>,
7214 multi_buffer_range_to_query: Range<Point>,
7215 use_debounce: bool,
7216 window: &mut Window,
7217 cx: &mut Context<Editor>,
7218 ) -> Task<()> {
7219 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7220 cx.spawn_in(window, async move |editor, cx| {
7221 if use_debounce {
7222 cx.background_executor()
7223 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7224 .await;
7225 }
7226 let match_task = cx.background_spawn(async move {
7227 let buffer_ranges = multi_buffer_snapshot
7228 .range_to_buffer_ranges(multi_buffer_range_to_query)
7229 .into_iter()
7230 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7231 let mut match_ranges = Vec::new();
7232 let Ok(regex) = project::search::SearchQuery::text(
7233 query_text.clone(),
7234 false,
7235 false,
7236 false,
7237 Default::default(),
7238 Default::default(),
7239 false,
7240 None,
7241 ) else {
7242 return Vec::default();
7243 };
7244 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7245 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7246 match_ranges.extend(
7247 regex
7248 .search(
7249 buffer_snapshot,
7250 Some(search_range.start.0..search_range.end.0),
7251 )
7252 .await
7253 .into_iter()
7254 .filter_map(|match_range| {
7255 let match_start = buffer_snapshot
7256 .anchor_after(search_range.start + match_range.start);
7257 let match_end = buffer_snapshot
7258 .anchor_before(search_range.start + match_range.end);
7259 let match_anchor_range =
7260 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7261 (match_anchor_range != query_range).then_some(match_anchor_range)
7262 }),
7263 );
7264 }
7265 match_ranges
7266 });
7267 let match_ranges = match_task.await;
7268 editor
7269 .update_in(cx, |editor, _, cx| {
7270 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7271 if !match_ranges.is_empty() {
7272 editor.highlight_background::<SelectedTextHighlight>(
7273 &match_ranges,
7274 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7275 cx,
7276 )
7277 }
7278 })
7279 .log_err();
7280 })
7281 }
7282
7283 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7284 struct NewlineFold;
7285 let type_id = std::any::TypeId::of::<NewlineFold>();
7286 if !self.mode.is_single_line() {
7287 return;
7288 }
7289 let snapshot = self.snapshot(window, cx);
7290 if snapshot.buffer_snapshot().max_point().row == 0 {
7291 return;
7292 }
7293 let task = cx.background_spawn(async move {
7294 let new_newlines = snapshot
7295 .buffer_chars_at(MultiBufferOffset(0))
7296 .filter_map(|(c, i)| {
7297 if c == '\n' {
7298 Some(
7299 snapshot.buffer_snapshot().anchor_after(i)
7300 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7301 )
7302 } else {
7303 None
7304 }
7305 })
7306 .collect::<Vec<_>>();
7307 let existing_newlines = snapshot
7308 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7309 .filter_map(|fold| {
7310 if fold.placeholder.type_tag == Some(type_id) {
7311 Some(fold.range.start..fold.range.end)
7312 } else {
7313 None
7314 }
7315 })
7316 .collect::<Vec<_>>();
7317
7318 (new_newlines, existing_newlines)
7319 });
7320 self.folding_newlines = cx.spawn(async move |this, cx| {
7321 let (new_newlines, existing_newlines) = task.await;
7322 if new_newlines == existing_newlines {
7323 return;
7324 }
7325 let placeholder = FoldPlaceholder {
7326 render: Arc::new(move |_, _, cx| {
7327 div()
7328 .bg(cx.theme().status().hint_background)
7329 .border_b_1()
7330 .size_full()
7331 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7332 .border_color(cx.theme().status().hint)
7333 .child("\\n")
7334 .into_any()
7335 }),
7336 constrain_width: false,
7337 merge_adjacent: false,
7338 type_tag: Some(type_id),
7339 };
7340 let creases = new_newlines
7341 .into_iter()
7342 .map(|range| Crease::simple(range, placeholder.clone()))
7343 .collect();
7344 this.update(cx, |this, cx| {
7345 this.display_map.update(cx, |display_map, cx| {
7346 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7347 display_map.fold(creases, cx);
7348 });
7349 })
7350 .ok();
7351 });
7352 }
7353
7354 #[ztracing::instrument(skip_all)]
7355 fn refresh_selected_text_highlights(
7356 &mut self,
7357 on_buffer_edit: bool,
7358 window: &mut Window,
7359 cx: &mut Context<Editor>,
7360 ) {
7361 let Some((query_text, query_range)) =
7362 self.prepare_highlight_query_from_selection(window, cx)
7363 else {
7364 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7365 self.quick_selection_highlight_task.take();
7366 self.debounced_selection_highlight_task.take();
7367 return;
7368 };
7369 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7370 if on_buffer_edit
7371 || self
7372 .quick_selection_highlight_task
7373 .as_ref()
7374 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7375 {
7376 let multi_buffer_visible_start = self
7377 .scroll_manager
7378 .anchor()
7379 .anchor
7380 .to_point(&multi_buffer_snapshot);
7381 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7382 multi_buffer_visible_start
7383 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7384 Bias::Left,
7385 );
7386 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7387 self.quick_selection_highlight_task = Some((
7388 query_range.clone(),
7389 self.update_selection_occurrence_highlights(
7390 query_text.clone(),
7391 query_range.clone(),
7392 multi_buffer_visible_range,
7393 false,
7394 window,
7395 cx,
7396 ),
7397 ));
7398 }
7399 if on_buffer_edit
7400 || self
7401 .debounced_selection_highlight_task
7402 .as_ref()
7403 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7404 {
7405 let multi_buffer_start = multi_buffer_snapshot
7406 .anchor_before(MultiBufferOffset(0))
7407 .to_point(&multi_buffer_snapshot);
7408 let multi_buffer_end = multi_buffer_snapshot
7409 .anchor_after(multi_buffer_snapshot.len())
7410 .to_point(&multi_buffer_snapshot);
7411 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7412 self.debounced_selection_highlight_task = Some((
7413 query_range.clone(),
7414 self.update_selection_occurrence_highlights(
7415 query_text,
7416 query_range,
7417 multi_buffer_full_range,
7418 true,
7419 window,
7420 cx,
7421 ),
7422 ));
7423 }
7424 }
7425
7426 pub fn refresh_edit_prediction(
7427 &mut self,
7428 debounce: bool,
7429 user_requested: bool,
7430 window: &mut Window,
7431 cx: &mut Context<Self>,
7432 ) -> Option<()> {
7433 if DisableAiSettings::get_global(cx).disable_ai {
7434 return None;
7435 }
7436
7437 let provider = self.edit_prediction_provider()?;
7438 let cursor = self.selections.newest_anchor().head();
7439 let (buffer, cursor_buffer_position) =
7440 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7441
7442 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7443 self.discard_edit_prediction(false, cx);
7444 return None;
7445 }
7446
7447 self.update_visible_edit_prediction(window, cx);
7448
7449 if !user_requested
7450 && (!self.should_show_edit_predictions()
7451 || !self.is_focused(window)
7452 || buffer.read(cx).is_empty())
7453 {
7454 self.discard_edit_prediction(false, cx);
7455 return None;
7456 }
7457
7458 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7459 Some(())
7460 }
7461
7462 fn show_edit_predictions_in_menu(&self) -> bool {
7463 match self.edit_prediction_settings {
7464 EditPredictionSettings::Disabled => false,
7465 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7466 }
7467 }
7468
7469 pub fn edit_predictions_enabled(&self) -> bool {
7470 match self.edit_prediction_settings {
7471 EditPredictionSettings::Disabled => false,
7472 EditPredictionSettings::Enabled { .. } => true,
7473 }
7474 }
7475
7476 fn edit_prediction_requires_modifier(&self) -> bool {
7477 match self.edit_prediction_settings {
7478 EditPredictionSettings::Disabled => false,
7479 EditPredictionSettings::Enabled {
7480 preview_requires_modifier,
7481 ..
7482 } => preview_requires_modifier,
7483 }
7484 }
7485
7486 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7487 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7488 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7489 self.discard_edit_prediction(false, cx);
7490 } else {
7491 let selection = self.selections.newest_anchor();
7492 let cursor = selection.head();
7493
7494 if let Some((buffer, cursor_buffer_position)) =
7495 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7496 {
7497 self.edit_prediction_settings =
7498 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7499 }
7500 }
7501 }
7502
7503 fn edit_prediction_settings_at_position(
7504 &self,
7505 buffer: &Entity<Buffer>,
7506 buffer_position: language::Anchor,
7507 cx: &App,
7508 ) -> EditPredictionSettings {
7509 if !self.mode.is_full()
7510 || !self.show_edit_predictions_override.unwrap_or(true)
7511 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7512 {
7513 return EditPredictionSettings::Disabled;
7514 }
7515
7516 let buffer = buffer.read(cx);
7517
7518 let file = buffer.file();
7519
7520 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7521 return EditPredictionSettings::Disabled;
7522 };
7523
7524 let by_provider = matches!(
7525 self.menu_edit_predictions_policy,
7526 MenuEditPredictionsPolicy::ByProvider
7527 );
7528
7529 let show_in_menu = by_provider
7530 && self
7531 .edit_prediction_provider
7532 .as_ref()
7533 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7534
7535 let preview_requires_modifier =
7536 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7537
7538 EditPredictionSettings::Enabled {
7539 show_in_menu,
7540 preview_requires_modifier,
7541 }
7542 }
7543
7544 fn should_show_edit_predictions(&self) -> bool {
7545 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7546 }
7547
7548 pub fn edit_prediction_preview_is_active(&self) -> bool {
7549 matches!(
7550 self.edit_prediction_preview,
7551 EditPredictionPreview::Active { .. }
7552 )
7553 }
7554
7555 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7556 let cursor = self.selections.newest_anchor().head();
7557 if let Some((buffer, cursor_position)) =
7558 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7559 {
7560 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7561 } else {
7562 false
7563 }
7564 }
7565
7566 pub fn supports_minimap(&self, cx: &App) -> bool {
7567 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7568 }
7569
7570 fn edit_predictions_enabled_in_buffer(
7571 &self,
7572 buffer: &Entity<Buffer>,
7573 buffer_position: language::Anchor,
7574 cx: &App,
7575 ) -> bool {
7576 maybe!({
7577 if self.read_only(cx) {
7578 return Some(false);
7579 }
7580 let provider = self.edit_prediction_provider()?;
7581 if !provider.is_enabled(buffer, buffer_position, cx) {
7582 return Some(false);
7583 }
7584 let buffer = buffer.read(cx);
7585 let Some(file) = buffer.file() else {
7586 return Some(true);
7587 };
7588 let settings = all_language_settings(Some(file), cx);
7589 Some(settings.edit_predictions_enabled_for_file(file, cx))
7590 })
7591 .unwrap_or(false)
7592 }
7593
7594 pub fn show_edit_prediction(
7595 &mut self,
7596 _: &ShowEditPrediction,
7597 window: &mut Window,
7598 cx: &mut Context<Self>,
7599 ) {
7600 if !self.has_active_edit_prediction() {
7601 self.refresh_edit_prediction(false, true, window, cx);
7602 return;
7603 }
7604
7605 self.update_visible_edit_prediction(window, cx);
7606 }
7607
7608 pub fn display_cursor_names(
7609 &mut self,
7610 _: &DisplayCursorNames,
7611 window: &mut Window,
7612 cx: &mut Context<Self>,
7613 ) {
7614 self.show_cursor_names(window, cx);
7615 }
7616
7617 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7618 self.show_cursor_names = true;
7619 cx.notify();
7620 cx.spawn_in(window, async move |this, cx| {
7621 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7622 this.update(cx, |this, cx| {
7623 this.show_cursor_names = false;
7624 cx.notify()
7625 })
7626 .ok()
7627 })
7628 .detach();
7629 }
7630
7631 pub fn accept_partial_edit_prediction(
7632 &mut self,
7633 granularity: EditPredictionGranularity,
7634 window: &mut Window,
7635 cx: &mut Context<Self>,
7636 ) {
7637 if self.show_edit_predictions_in_menu() {
7638 self.hide_context_menu(window, cx);
7639 }
7640
7641 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7642 return;
7643 };
7644
7645 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7646 return;
7647 }
7648
7649 match &active_edit_prediction.completion {
7650 EditPrediction::MoveWithin { target, .. } => {
7651 let target = *target;
7652
7653 if matches!(granularity, EditPredictionGranularity::Full) {
7654 if let Some(position_map) = &self.last_position_map {
7655 let target_row = target.to_display_point(&position_map.snapshot).row();
7656 let is_visible = position_map.visible_row_range.contains(&target_row);
7657
7658 if is_visible || !self.edit_prediction_requires_modifier() {
7659 self.unfold_ranges(&[target..target], true, false, cx);
7660 self.change_selections(
7661 SelectionEffects::scroll(Autoscroll::newest()),
7662 window,
7663 cx,
7664 |selections| {
7665 selections.select_anchor_ranges([target..target]);
7666 },
7667 );
7668 self.clear_row_highlights::<EditPredictionPreview>();
7669 self.edit_prediction_preview
7670 .set_previous_scroll_position(None);
7671 } else {
7672 // Highlight and request scroll
7673 self.edit_prediction_preview
7674 .set_previous_scroll_position(Some(
7675 position_map.snapshot.scroll_anchor,
7676 ));
7677 self.highlight_rows::<EditPredictionPreview>(
7678 target..target,
7679 cx.theme().colors().editor_highlighted_line_background,
7680 RowHighlightOptions {
7681 autoscroll: true,
7682 ..Default::default()
7683 },
7684 cx,
7685 );
7686 self.request_autoscroll(Autoscroll::fit(), cx);
7687 }
7688 }
7689 } else {
7690 self.change_selections(
7691 SelectionEffects::scroll(Autoscroll::newest()),
7692 window,
7693 cx,
7694 |selections| {
7695 selections.select_anchor_ranges([target..target]);
7696 },
7697 );
7698 }
7699 }
7700 EditPrediction::MoveOutside { snapshot, target } => {
7701 if let Some(workspace) = self.workspace() {
7702 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7703 .detach_and_log_err(cx);
7704 }
7705 }
7706 EditPrediction::Edit { edits, .. } => {
7707 self.report_edit_prediction_event(
7708 active_edit_prediction.completion_id.clone(),
7709 true,
7710 cx,
7711 );
7712
7713 match granularity {
7714 EditPredictionGranularity::Full => {
7715 if let Some(provider) = self.edit_prediction_provider() {
7716 provider.accept(cx);
7717 }
7718
7719 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7720 let snapshot = self.buffer.read(cx).snapshot(cx);
7721 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7722
7723 self.buffer.update(cx, |buffer, cx| {
7724 buffer.edit(edits.iter().cloned(), None, cx)
7725 });
7726
7727 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7728 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7729 });
7730
7731 let selections = self.selections.disjoint_anchors_arc();
7732 if let Some(transaction_id_now) =
7733 self.buffer.read(cx).last_transaction_id(cx)
7734 {
7735 if transaction_id_prev != Some(transaction_id_now) {
7736 self.selection_history
7737 .insert_transaction(transaction_id_now, selections);
7738 }
7739 }
7740
7741 self.update_visible_edit_prediction(window, cx);
7742 if self.active_edit_prediction.is_none() {
7743 self.refresh_edit_prediction(true, true, window, cx);
7744 }
7745 cx.notify();
7746 }
7747 _ => {
7748 let snapshot = self.buffer.read(cx).snapshot(cx);
7749 let cursor_offset = self
7750 .selections
7751 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7752 .head();
7753
7754 let insertion = edits.iter().find_map(|(range, text)| {
7755 let range = range.to_offset(&snapshot);
7756 if range.is_empty() && range.start == cursor_offset {
7757 Some(text)
7758 } else {
7759 None
7760 }
7761 });
7762
7763 if let Some(text) = insertion {
7764 let text_to_insert = match granularity {
7765 EditPredictionGranularity::Word => {
7766 let mut partial = text
7767 .chars()
7768 .by_ref()
7769 .take_while(|c| c.is_alphabetic())
7770 .collect::<String>();
7771 if partial.is_empty() {
7772 partial = text
7773 .chars()
7774 .by_ref()
7775 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7776 .collect::<String>();
7777 }
7778 partial
7779 }
7780 EditPredictionGranularity::Line => {
7781 if let Some(line) = text.split_inclusive('\n').next() {
7782 line.to_string()
7783 } else {
7784 text.to_string()
7785 }
7786 }
7787 EditPredictionGranularity::Full => unreachable!(),
7788 };
7789
7790 cx.emit(EditorEvent::InputHandled {
7791 utf16_range_to_replace: None,
7792 text: text_to_insert.clone().into(),
7793 });
7794
7795 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7796 self.refresh_edit_prediction(true, true, window, cx);
7797 cx.notify();
7798 } else {
7799 self.accept_partial_edit_prediction(
7800 EditPredictionGranularity::Full,
7801 window,
7802 cx,
7803 );
7804 }
7805 }
7806 }
7807 }
7808 }
7809
7810 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7811 }
7812
7813 pub fn accept_next_word_edit_prediction(
7814 &mut self,
7815 _: &AcceptNextWordEditPrediction,
7816 window: &mut Window,
7817 cx: &mut Context<Self>,
7818 ) {
7819 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7820 }
7821
7822 pub fn accept_next_line_edit_prediction(
7823 &mut self,
7824 _: &AcceptNextLineEditPrediction,
7825 window: &mut Window,
7826 cx: &mut Context<Self>,
7827 ) {
7828 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7829 }
7830
7831 pub fn accept_edit_prediction(
7832 &mut self,
7833 _: &AcceptEditPrediction,
7834 window: &mut Window,
7835 cx: &mut Context<Self>,
7836 ) {
7837 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7838 }
7839
7840 fn discard_edit_prediction(
7841 &mut self,
7842 should_report_edit_prediction_event: bool,
7843 cx: &mut Context<Self>,
7844 ) -> bool {
7845 if should_report_edit_prediction_event {
7846 let completion_id = self
7847 .active_edit_prediction
7848 .as_ref()
7849 .and_then(|active_completion| active_completion.completion_id.clone());
7850
7851 self.report_edit_prediction_event(completion_id, false, cx);
7852 }
7853
7854 if let Some(provider) = self.edit_prediction_provider() {
7855 provider.discard(cx);
7856 }
7857
7858 self.take_active_edit_prediction(cx)
7859 }
7860
7861 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7862 let Some(provider) = self.edit_prediction_provider() else {
7863 return;
7864 };
7865
7866 let Some((_, buffer, _)) = self
7867 .buffer
7868 .read(cx)
7869 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7870 else {
7871 return;
7872 };
7873
7874 let extension = buffer
7875 .read(cx)
7876 .file()
7877 .and_then(|file| Some(file.path().extension()?.to_string()));
7878
7879 let event_type = match accepted {
7880 true => "Edit Prediction Accepted",
7881 false => "Edit Prediction Discarded",
7882 };
7883 telemetry::event!(
7884 event_type,
7885 provider = provider.name(),
7886 prediction_id = id,
7887 suggestion_accepted = accepted,
7888 file_extension = extension,
7889 );
7890 }
7891
7892 fn open_editor_at_anchor(
7893 snapshot: &language::BufferSnapshot,
7894 target: language::Anchor,
7895 workspace: &Entity<Workspace>,
7896 window: &mut Window,
7897 cx: &mut App,
7898 ) -> Task<Result<()>> {
7899 workspace.update(cx, |workspace, cx| {
7900 let path = snapshot.file().map(|file| file.full_path(cx));
7901 let Some(path) =
7902 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7903 else {
7904 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7905 };
7906 let target = text::ToPoint::to_point(&target, snapshot);
7907 let item = workspace.open_path(path, None, true, window, cx);
7908 window.spawn(cx, async move |cx| {
7909 let Some(editor) = item.await?.downcast::<Editor>() else {
7910 return Ok(());
7911 };
7912 editor
7913 .update_in(cx, |editor, window, cx| {
7914 editor.go_to_singleton_buffer_point(target, window, cx);
7915 })
7916 .ok();
7917 anyhow::Ok(())
7918 })
7919 })
7920 }
7921
7922 pub fn has_active_edit_prediction(&self) -> bool {
7923 self.active_edit_prediction.is_some()
7924 }
7925
7926 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7927 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7928 return false;
7929 };
7930
7931 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7932 self.clear_highlights::<EditPredictionHighlight>(cx);
7933 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7934 true
7935 }
7936
7937 /// Returns true when we're displaying the edit prediction popover below the cursor
7938 /// like we are not previewing and the LSP autocomplete menu is visible
7939 /// or we are in `when_holding_modifier` mode.
7940 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7941 if self.edit_prediction_preview_is_active()
7942 || !self.show_edit_predictions_in_menu()
7943 || !self.edit_predictions_enabled()
7944 {
7945 return false;
7946 }
7947
7948 if self.has_visible_completions_menu() {
7949 return true;
7950 }
7951
7952 has_completion && self.edit_prediction_requires_modifier()
7953 }
7954
7955 fn handle_modifiers_changed(
7956 &mut self,
7957 modifiers: Modifiers,
7958 position_map: &PositionMap,
7959 window: &mut Window,
7960 cx: &mut Context<Self>,
7961 ) {
7962 // Ensure that the edit prediction preview is updated, even when not
7963 // enabled, if there's an active edit prediction preview.
7964 if self.show_edit_predictions_in_menu()
7965 || matches!(
7966 self.edit_prediction_preview,
7967 EditPredictionPreview::Active { .. }
7968 )
7969 {
7970 self.update_edit_prediction_preview(&modifiers, window, cx);
7971 }
7972
7973 self.update_selection_mode(&modifiers, position_map, window, cx);
7974
7975 let mouse_position = window.mouse_position();
7976 if !position_map.text_hitbox.is_hovered(window) {
7977 return;
7978 }
7979
7980 self.update_hovered_link(
7981 position_map.point_for_position(mouse_position),
7982 &position_map.snapshot,
7983 modifiers,
7984 window,
7985 cx,
7986 )
7987 }
7988
7989 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7990 match EditorSettings::get_global(cx).multi_cursor_modifier {
7991 MultiCursorModifier::Alt => modifiers.secondary(),
7992 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7993 }
7994 }
7995
7996 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7997 match EditorSettings::get_global(cx).multi_cursor_modifier {
7998 MultiCursorModifier::Alt => modifiers.alt,
7999 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8000 }
8001 }
8002
8003 fn columnar_selection_mode(
8004 modifiers: &Modifiers,
8005 cx: &mut Context<Self>,
8006 ) -> Option<ColumnarMode> {
8007 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8008 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8009 Some(ColumnarMode::FromMouse)
8010 } else if Self::is_alt_pressed(modifiers, cx) {
8011 Some(ColumnarMode::FromSelection)
8012 } else {
8013 None
8014 }
8015 } else {
8016 None
8017 }
8018 }
8019
8020 fn update_selection_mode(
8021 &mut self,
8022 modifiers: &Modifiers,
8023 position_map: &PositionMap,
8024 window: &mut Window,
8025 cx: &mut Context<Self>,
8026 ) {
8027 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8028 return;
8029 };
8030 if self.selections.pending_anchor().is_none() {
8031 return;
8032 }
8033
8034 let mouse_position = window.mouse_position();
8035 let point_for_position = position_map.point_for_position(mouse_position);
8036 let position = point_for_position.previous_valid;
8037
8038 self.select(
8039 SelectPhase::BeginColumnar {
8040 position,
8041 reset: false,
8042 mode,
8043 goal_column: point_for_position.exact_unclipped.column(),
8044 },
8045 window,
8046 cx,
8047 );
8048 }
8049
8050 fn update_edit_prediction_preview(
8051 &mut self,
8052 modifiers: &Modifiers,
8053 window: &mut Window,
8054 cx: &mut Context<Self>,
8055 ) {
8056 let mut modifiers_held = false;
8057
8058 // Check bindings for all granularities.
8059 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8060 let granularities = [
8061 EditPredictionGranularity::Full,
8062 EditPredictionGranularity::Line,
8063 EditPredictionGranularity::Word,
8064 ];
8065
8066 for granularity in granularities {
8067 if let Some(keystroke) = self
8068 .accept_edit_prediction_keybind(granularity, window, cx)
8069 .keystroke()
8070 {
8071 modifiers_held = modifiers_held
8072 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8073 }
8074 }
8075
8076 if modifiers_held {
8077 if matches!(
8078 self.edit_prediction_preview,
8079 EditPredictionPreview::Inactive { .. }
8080 ) {
8081 self.edit_prediction_preview = EditPredictionPreview::Active {
8082 previous_scroll_position: None,
8083 since: Instant::now(),
8084 };
8085
8086 self.update_visible_edit_prediction(window, cx);
8087 cx.notify();
8088 }
8089 } else if let EditPredictionPreview::Active {
8090 previous_scroll_position,
8091 since,
8092 } = self.edit_prediction_preview
8093 {
8094 if let (Some(previous_scroll_position), Some(position_map)) =
8095 (previous_scroll_position, self.last_position_map.as_ref())
8096 {
8097 self.set_scroll_position(
8098 previous_scroll_position
8099 .scroll_position(&position_map.snapshot.display_snapshot),
8100 window,
8101 cx,
8102 );
8103 }
8104
8105 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8106 released_too_fast: since.elapsed() < Duration::from_millis(200),
8107 };
8108 self.clear_row_highlights::<EditPredictionPreview>();
8109 self.update_visible_edit_prediction(window, cx);
8110 cx.notify();
8111 }
8112 }
8113
8114 fn update_visible_edit_prediction(
8115 &mut self,
8116 _window: &mut Window,
8117 cx: &mut Context<Self>,
8118 ) -> Option<()> {
8119 if DisableAiSettings::get_global(cx).disable_ai {
8120 return None;
8121 }
8122
8123 if self.ime_transaction.is_some() {
8124 self.discard_edit_prediction(false, cx);
8125 return None;
8126 }
8127
8128 let selection = self.selections.newest_anchor();
8129 let cursor = selection.head();
8130 let multibuffer = self.buffer.read(cx).snapshot(cx);
8131 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8132 let excerpt_id = cursor.excerpt_id;
8133
8134 let show_in_menu = self.show_edit_predictions_in_menu();
8135 let completions_menu_has_precedence = !show_in_menu
8136 && (self.context_menu.borrow().is_some()
8137 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8138
8139 if completions_menu_has_precedence
8140 || !offset_selection.is_empty()
8141 || self
8142 .active_edit_prediction
8143 .as_ref()
8144 .is_some_and(|completion| {
8145 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8146 return false;
8147 };
8148 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8149 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8150 !invalidation_range.contains(&offset_selection.head())
8151 })
8152 {
8153 self.discard_edit_prediction(false, cx);
8154 return None;
8155 }
8156
8157 self.take_active_edit_prediction(cx);
8158 let Some(provider) = self.edit_prediction_provider() else {
8159 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8160 return None;
8161 };
8162
8163 let (buffer, cursor_buffer_position) =
8164 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8165
8166 self.edit_prediction_settings =
8167 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8168
8169 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8170
8171 if self.edit_prediction_indent_conflict {
8172 let cursor_point = cursor.to_point(&multibuffer);
8173 let mut suggested_indent = None;
8174 multibuffer.suggested_indents_callback(
8175 cursor_point.row..cursor_point.row + 1,
8176 |_, indent| {
8177 suggested_indent = Some(indent);
8178 ControlFlow::Break(())
8179 },
8180 cx,
8181 );
8182
8183 if let Some(indent) = suggested_indent
8184 && indent.len == cursor_point.column
8185 {
8186 self.edit_prediction_indent_conflict = false;
8187 }
8188 }
8189
8190 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8191
8192 let (completion_id, edits, edit_preview) = match edit_prediction {
8193 edit_prediction_types::EditPrediction::Local {
8194 id,
8195 edits,
8196 edit_preview,
8197 } => (id, edits, edit_preview),
8198 edit_prediction_types::EditPrediction::Jump {
8199 id,
8200 snapshot,
8201 target,
8202 } => {
8203 if let Some(provider) = &self.edit_prediction_provider {
8204 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8205 }
8206 self.stale_edit_prediction_in_menu = None;
8207 self.active_edit_prediction = Some(EditPredictionState {
8208 inlay_ids: vec![],
8209 completion: EditPrediction::MoveOutside { snapshot, target },
8210 completion_id: id,
8211 invalidation_range: None,
8212 });
8213 cx.notify();
8214 return Some(());
8215 }
8216 };
8217
8218 let edits = edits
8219 .into_iter()
8220 .flat_map(|(range, new_text)| {
8221 Some((
8222 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8223 new_text,
8224 ))
8225 })
8226 .collect::<Vec<_>>();
8227 if edits.is_empty() {
8228 return None;
8229 }
8230
8231 let first_edit_start = edits.first().unwrap().0.start;
8232 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8233 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8234
8235 let last_edit_end = edits.last().unwrap().0.end;
8236 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8237 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8238
8239 let cursor_row = cursor.to_point(&multibuffer).row;
8240
8241 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8242
8243 let mut inlay_ids = Vec::new();
8244 let invalidation_row_range;
8245 let move_invalidation_row_range = if cursor_row < edit_start_row {
8246 Some(cursor_row..edit_end_row)
8247 } else if cursor_row > edit_end_row {
8248 Some(edit_start_row..cursor_row)
8249 } else {
8250 None
8251 };
8252 let supports_jump = self
8253 .edit_prediction_provider
8254 .as_ref()
8255 .map(|provider| provider.provider.supports_jump_to_edit())
8256 .unwrap_or(true);
8257
8258 let is_move = supports_jump
8259 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8260 let completion = if is_move {
8261 if let Some(provider) = &self.edit_prediction_provider {
8262 provider.provider.did_show(SuggestionDisplayType::Jump, cx);
8263 }
8264 invalidation_row_range =
8265 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8266 let target = first_edit_start;
8267 EditPrediction::MoveWithin { target, snapshot }
8268 } else {
8269 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8270 && !self.edit_predictions_hidden_for_vim_mode;
8271
8272 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8273 if provider.show_tab_accept_marker() {
8274 EditDisplayMode::TabAccept
8275 } else {
8276 EditDisplayMode::Inline
8277 }
8278 } else {
8279 EditDisplayMode::DiffPopover
8280 };
8281
8282 if show_completions_in_buffer {
8283 if let Some(provider) = &self.edit_prediction_provider {
8284 let suggestion_display_type = match display_mode {
8285 EditDisplayMode::DiffPopover => SuggestionDisplayType::DiffPopover,
8286 EditDisplayMode::Inline | EditDisplayMode::TabAccept => {
8287 SuggestionDisplayType::GhostText
8288 }
8289 };
8290 provider.provider.did_show(suggestion_display_type, cx);
8291 }
8292 if edits
8293 .iter()
8294 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8295 {
8296 let mut inlays = Vec::new();
8297 for (range, new_text) in &edits {
8298 let inlay = Inlay::edit_prediction(
8299 post_inc(&mut self.next_inlay_id),
8300 range.start,
8301 new_text.as_ref(),
8302 );
8303 inlay_ids.push(inlay.id);
8304 inlays.push(inlay);
8305 }
8306
8307 self.splice_inlays(&[], inlays, cx);
8308 } else {
8309 let background_color = cx.theme().status().deleted_background;
8310 self.highlight_text::<EditPredictionHighlight>(
8311 edits.iter().map(|(range, _)| range.clone()).collect(),
8312 HighlightStyle {
8313 background_color: Some(background_color),
8314 ..Default::default()
8315 },
8316 cx,
8317 );
8318 }
8319 }
8320
8321 invalidation_row_range = edit_start_row..edit_end_row;
8322
8323 EditPrediction::Edit {
8324 edits,
8325 edit_preview,
8326 display_mode,
8327 snapshot,
8328 }
8329 };
8330
8331 let invalidation_range = multibuffer
8332 .anchor_before(Point::new(invalidation_row_range.start, 0))
8333 ..multibuffer.anchor_after(Point::new(
8334 invalidation_row_range.end,
8335 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8336 ));
8337
8338 self.stale_edit_prediction_in_menu = None;
8339 self.active_edit_prediction = Some(EditPredictionState {
8340 inlay_ids,
8341 completion,
8342 completion_id,
8343 invalidation_range: Some(invalidation_range),
8344 });
8345
8346 cx.notify();
8347
8348 Some(())
8349 }
8350
8351 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8352 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8353 }
8354
8355 fn clear_tasks(&mut self) {
8356 self.tasks.clear()
8357 }
8358
8359 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8360 if self.tasks.insert(key, value).is_some() {
8361 // This case should hopefully be rare, but just in case...
8362 log::error!(
8363 "multiple different run targets found on a single line, only the last target will be rendered"
8364 )
8365 }
8366 }
8367
8368 /// Get all display points of breakpoints that will be rendered within editor
8369 ///
8370 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8371 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8372 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8373 fn active_breakpoints(
8374 &self,
8375 range: Range<DisplayRow>,
8376 window: &mut Window,
8377 cx: &mut Context<Self>,
8378 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8379 let mut breakpoint_display_points = HashMap::default();
8380
8381 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8382 return breakpoint_display_points;
8383 };
8384
8385 let snapshot = self.snapshot(window, cx);
8386
8387 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8388 let Some(project) = self.project() else {
8389 return breakpoint_display_points;
8390 };
8391
8392 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8393 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8394
8395 for (buffer_snapshot, range, excerpt_id) in
8396 multi_buffer_snapshot.range_to_buffer_ranges(range)
8397 {
8398 let Some(buffer) = project
8399 .read(cx)
8400 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8401 else {
8402 continue;
8403 };
8404 let breakpoints = breakpoint_store.read(cx).breakpoints(
8405 &buffer,
8406 Some(
8407 buffer_snapshot.anchor_before(range.start)
8408 ..buffer_snapshot.anchor_after(range.end),
8409 ),
8410 buffer_snapshot,
8411 cx,
8412 );
8413 for (breakpoint, state) in breakpoints {
8414 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8415 let position = multi_buffer_anchor
8416 .to_point(&multi_buffer_snapshot)
8417 .to_display_point(&snapshot);
8418
8419 breakpoint_display_points.insert(
8420 position.row(),
8421 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8422 );
8423 }
8424 }
8425
8426 breakpoint_display_points
8427 }
8428
8429 fn breakpoint_context_menu(
8430 &self,
8431 anchor: Anchor,
8432 window: &mut Window,
8433 cx: &mut Context<Self>,
8434 ) -> Entity<ui::ContextMenu> {
8435 let weak_editor = cx.weak_entity();
8436 let focus_handle = self.focus_handle(cx);
8437
8438 let row = self
8439 .buffer
8440 .read(cx)
8441 .snapshot(cx)
8442 .summary_for_anchor::<Point>(&anchor)
8443 .row;
8444
8445 let breakpoint = self
8446 .breakpoint_at_row(row, window, cx)
8447 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8448
8449 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8450 "Edit Log Breakpoint"
8451 } else {
8452 "Set Log Breakpoint"
8453 };
8454
8455 let condition_breakpoint_msg = if breakpoint
8456 .as_ref()
8457 .is_some_and(|bp| bp.1.condition.is_some())
8458 {
8459 "Edit Condition Breakpoint"
8460 } else {
8461 "Set Condition Breakpoint"
8462 };
8463
8464 let hit_condition_breakpoint_msg = if breakpoint
8465 .as_ref()
8466 .is_some_and(|bp| bp.1.hit_condition.is_some())
8467 {
8468 "Edit Hit Condition Breakpoint"
8469 } else {
8470 "Set Hit Condition Breakpoint"
8471 };
8472
8473 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8474 "Unset Breakpoint"
8475 } else {
8476 "Set Breakpoint"
8477 };
8478
8479 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8480
8481 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8482 BreakpointState::Enabled => Some("Disable"),
8483 BreakpointState::Disabled => Some("Enable"),
8484 });
8485
8486 let (anchor, breakpoint) =
8487 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8488
8489 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8490 menu.on_blur_subscription(Subscription::new(|| {}))
8491 .context(focus_handle)
8492 .when(run_to_cursor, |this| {
8493 let weak_editor = weak_editor.clone();
8494 this.entry("Run to cursor", None, move |window, cx| {
8495 weak_editor
8496 .update(cx, |editor, cx| {
8497 editor.change_selections(
8498 SelectionEffects::no_scroll(),
8499 window,
8500 cx,
8501 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8502 );
8503 })
8504 .ok();
8505
8506 window.dispatch_action(Box::new(RunToCursor), cx);
8507 })
8508 .separator()
8509 })
8510 .when_some(toggle_state_msg, |this, msg| {
8511 this.entry(msg, None, {
8512 let weak_editor = weak_editor.clone();
8513 let breakpoint = breakpoint.clone();
8514 move |_window, cx| {
8515 weak_editor
8516 .update(cx, |this, cx| {
8517 this.edit_breakpoint_at_anchor(
8518 anchor,
8519 breakpoint.as_ref().clone(),
8520 BreakpointEditAction::InvertState,
8521 cx,
8522 );
8523 })
8524 .log_err();
8525 }
8526 })
8527 })
8528 .entry(set_breakpoint_msg, None, {
8529 let weak_editor = weak_editor.clone();
8530 let breakpoint = breakpoint.clone();
8531 move |_window, cx| {
8532 weak_editor
8533 .update(cx, |this, cx| {
8534 this.edit_breakpoint_at_anchor(
8535 anchor,
8536 breakpoint.as_ref().clone(),
8537 BreakpointEditAction::Toggle,
8538 cx,
8539 );
8540 })
8541 .log_err();
8542 }
8543 })
8544 .entry(log_breakpoint_msg, None, {
8545 let breakpoint = breakpoint.clone();
8546 let weak_editor = weak_editor.clone();
8547 move |window, cx| {
8548 weak_editor
8549 .update(cx, |this, cx| {
8550 this.add_edit_breakpoint_block(
8551 anchor,
8552 breakpoint.as_ref(),
8553 BreakpointPromptEditAction::Log,
8554 window,
8555 cx,
8556 );
8557 })
8558 .log_err();
8559 }
8560 })
8561 .entry(condition_breakpoint_msg, None, {
8562 let breakpoint = breakpoint.clone();
8563 let weak_editor = weak_editor.clone();
8564 move |window, cx| {
8565 weak_editor
8566 .update(cx, |this, cx| {
8567 this.add_edit_breakpoint_block(
8568 anchor,
8569 breakpoint.as_ref(),
8570 BreakpointPromptEditAction::Condition,
8571 window,
8572 cx,
8573 );
8574 })
8575 .log_err();
8576 }
8577 })
8578 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8579 weak_editor
8580 .update(cx, |this, cx| {
8581 this.add_edit_breakpoint_block(
8582 anchor,
8583 breakpoint.as_ref(),
8584 BreakpointPromptEditAction::HitCondition,
8585 window,
8586 cx,
8587 );
8588 })
8589 .log_err();
8590 })
8591 })
8592 }
8593
8594 fn render_breakpoint(
8595 &self,
8596 position: Anchor,
8597 row: DisplayRow,
8598 breakpoint: &Breakpoint,
8599 state: Option<BreakpointSessionState>,
8600 cx: &mut Context<Self>,
8601 ) -> IconButton {
8602 let is_rejected = state.is_some_and(|s| !s.verified);
8603 // Is it a breakpoint that shows up when hovering over gutter?
8604 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8605 (false, false),
8606 |PhantomBreakpointIndicator {
8607 is_active,
8608 display_row,
8609 collides_with_existing_breakpoint,
8610 }| {
8611 (
8612 is_active && display_row == row,
8613 collides_with_existing_breakpoint,
8614 )
8615 },
8616 );
8617
8618 let (color, icon) = {
8619 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8620 (false, false) => ui::IconName::DebugBreakpoint,
8621 (true, false) => ui::IconName::DebugLogBreakpoint,
8622 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8623 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8624 };
8625
8626 let color = cx.theme().colors();
8627
8628 let color = if is_phantom {
8629 if collides_with_existing {
8630 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8631 } else {
8632 Color::Hint
8633 }
8634 } else if is_rejected {
8635 Color::Disabled
8636 } else {
8637 Color::Debugger
8638 };
8639
8640 (color, icon)
8641 };
8642
8643 let breakpoint = Arc::from(breakpoint.clone());
8644
8645 let alt_as_text = gpui::Keystroke {
8646 modifiers: Modifiers::secondary_key(),
8647 ..Default::default()
8648 };
8649 let primary_action_text = if breakpoint.is_disabled() {
8650 "Enable breakpoint"
8651 } else if is_phantom && !collides_with_existing {
8652 "Set breakpoint"
8653 } else {
8654 "Unset breakpoint"
8655 };
8656 let focus_handle = self.focus_handle.clone();
8657
8658 let meta = if is_rejected {
8659 SharedString::from("No executable code is associated with this line.")
8660 } else if collides_with_existing && !breakpoint.is_disabled() {
8661 SharedString::from(format!(
8662 "{alt_as_text}-click to disable,\nright-click for more options."
8663 ))
8664 } else {
8665 SharedString::from("Right-click for more options.")
8666 };
8667 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8668 .icon_size(IconSize::XSmall)
8669 .size(ui::ButtonSize::None)
8670 .when(is_rejected, |this| {
8671 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8672 })
8673 .icon_color(color)
8674 .style(ButtonStyle::Transparent)
8675 .on_click(cx.listener({
8676 move |editor, event: &ClickEvent, window, cx| {
8677 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8678 BreakpointEditAction::InvertState
8679 } else {
8680 BreakpointEditAction::Toggle
8681 };
8682
8683 window.focus(&editor.focus_handle(cx), cx);
8684 editor.edit_breakpoint_at_anchor(
8685 position,
8686 breakpoint.as_ref().clone(),
8687 edit_action,
8688 cx,
8689 );
8690 }
8691 }))
8692 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8693 editor.set_breakpoint_context_menu(
8694 row,
8695 Some(position),
8696 event.position(),
8697 window,
8698 cx,
8699 );
8700 }))
8701 .tooltip(move |_window, cx| {
8702 Tooltip::with_meta_in(
8703 primary_action_text,
8704 Some(&ToggleBreakpoint),
8705 meta.clone(),
8706 &focus_handle,
8707 cx,
8708 )
8709 })
8710 }
8711
8712 fn build_tasks_context(
8713 project: &Entity<Project>,
8714 buffer: &Entity<Buffer>,
8715 buffer_row: u32,
8716 tasks: &Arc<RunnableTasks>,
8717 cx: &mut Context<Self>,
8718 ) -> Task<Option<task::TaskContext>> {
8719 let position = Point::new(buffer_row, tasks.column);
8720 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8721 let location = Location {
8722 buffer: buffer.clone(),
8723 range: range_start..range_start,
8724 };
8725 // Fill in the environmental variables from the tree-sitter captures
8726 let mut captured_task_variables = TaskVariables::default();
8727 for (capture_name, value) in tasks.extra_variables.clone() {
8728 captured_task_variables.insert(
8729 task::VariableName::Custom(capture_name.into()),
8730 value.clone(),
8731 );
8732 }
8733 project.update(cx, |project, cx| {
8734 project.task_store().update(cx, |task_store, cx| {
8735 task_store.task_context_for_location(captured_task_variables, location, cx)
8736 })
8737 })
8738 }
8739
8740 pub fn spawn_nearest_task(
8741 &mut self,
8742 action: &SpawnNearestTask,
8743 window: &mut Window,
8744 cx: &mut Context<Self>,
8745 ) {
8746 let Some((workspace, _)) = self.workspace.clone() else {
8747 return;
8748 };
8749 let Some(project) = self.project.clone() else {
8750 return;
8751 };
8752
8753 // Try to find a closest, enclosing node using tree-sitter that has a task
8754 let Some((buffer, buffer_row, tasks)) = self
8755 .find_enclosing_node_task(cx)
8756 // Or find the task that's closest in row-distance.
8757 .or_else(|| self.find_closest_task(cx))
8758 else {
8759 return;
8760 };
8761
8762 let reveal_strategy = action.reveal;
8763 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8764 cx.spawn_in(window, async move |_, cx| {
8765 let context = task_context.await?;
8766 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8767
8768 let resolved = &mut resolved_task.resolved;
8769 resolved.reveal = reveal_strategy;
8770
8771 workspace
8772 .update_in(cx, |workspace, window, cx| {
8773 workspace.schedule_resolved_task(
8774 task_source_kind,
8775 resolved_task,
8776 false,
8777 window,
8778 cx,
8779 );
8780 })
8781 .ok()
8782 })
8783 .detach();
8784 }
8785
8786 fn find_closest_task(
8787 &mut self,
8788 cx: &mut Context<Self>,
8789 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8790 let cursor_row = self
8791 .selections
8792 .newest_adjusted(&self.display_snapshot(cx))
8793 .head()
8794 .row;
8795
8796 let ((buffer_id, row), tasks) = self
8797 .tasks
8798 .iter()
8799 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8800
8801 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8802 let tasks = Arc::new(tasks.to_owned());
8803 Some((buffer, *row, tasks))
8804 }
8805
8806 fn find_enclosing_node_task(
8807 &mut self,
8808 cx: &mut Context<Self>,
8809 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8810 let snapshot = self.buffer.read(cx).snapshot(cx);
8811 let offset = self
8812 .selections
8813 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8814 .head();
8815 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8816 let offset = excerpt.map_offset_to_buffer(offset);
8817 let buffer_id = excerpt.buffer().remote_id();
8818
8819 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8820 let mut cursor = layer.node().walk();
8821
8822 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8823 if cursor.node().end_byte() == offset.0 {
8824 cursor.goto_next_sibling();
8825 }
8826 }
8827
8828 // Ascend to the smallest ancestor that contains the range and has a task.
8829 loop {
8830 let node = cursor.node();
8831 let node_range = node.byte_range();
8832 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8833
8834 // Check if this node contains our offset
8835 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8836 // If it contains offset, check for task
8837 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8838 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8839 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8840 }
8841 }
8842
8843 if !cursor.goto_parent() {
8844 break;
8845 }
8846 }
8847 None
8848 }
8849
8850 fn render_run_indicator(
8851 &self,
8852 _style: &EditorStyle,
8853 is_active: bool,
8854 row: DisplayRow,
8855 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8856 cx: &mut Context<Self>,
8857 ) -> IconButton {
8858 let color = Color::Muted;
8859 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8860
8861 IconButton::new(
8862 ("run_indicator", row.0 as usize),
8863 ui::IconName::PlayOutlined,
8864 )
8865 .shape(ui::IconButtonShape::Square)
8866 .icon_size(IconSize::XSmall)
8867 .icon_color(color)
8868 .toggle_state(is_active)
8869 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8870 let quick_launch = match e {
8871 ClickEvent::Keyboard(_) => true,
8872 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8873 };
8874
8875 window.focus(&editor.focus_handle(cx), cx);
8876 editor.toggle_code_actions(
8877 &ToggleCodeActions {
8878 deployed_from: Some(CodeActionSource::RunMenu(row)),
8879 quick_launch,
8880 },
8881 window,
8882 cx,
8883 );
8884 }))
8885 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8886 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8887 }))
8888 }
8889
8890 pub fn context_menu_visible(&self) -> bool {
8891 !self.edit_prediction_preview_is_active()
8892 && self
8893 .context_menu
8894 .borrow()
8895 .as_ref()
8896 .is_some_and(|menu| menu.visible())
8897 }
8898
8899 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8900 self.context_menu
8901 .borrow()
8902 .as_ref()
8903 .map(|menu| menu.origin())
8904 }
8905
8906 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8907 self.context_menu_options = Some(options);
8908 }
8909
8910 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8911 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8912
8913 fn render_edit_prediction_popover(
8914 &mut self,
8915 text_bounds: &Bounds<Pixels>,
8916 content_origin: gpui::Point<Pixels>,
8917 right_margin: Pixels,
8918 editor_snapshot: &EditorSnapshot,
8919 visible_row_range: Range<DisplayRow>,
8920 scroll_top: ScrollOffset,
8921 scroll_bottom: ScrollOffset,
8922 line_layouts: &[LineWithInvisibles],
8923 line_height: Pixels,
8924 scroll_position: gpui::Point<ScrollOffset>,
8925 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8926 newest_selection_head: Option<DisplayPoint>,
8927 editor_width: Pixels,
8928 style: &EditorStyle,
8929 window: &mut Window,
8930 cx: &mut App,
8931 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8932 if self.mode().is_minimap() {
8933 return None;
8934 }
8935 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8936
8937 if self.edit_prediction_visible_in_cursor_popover(true) {
8938 return None;
8939 }
8940
8941 match &active_edit_prediction.completion {
8942 EditPrediction::MoveWithin { target, .. } => {
8943 let target_display_point = target.to_display_point(editor_snapshot);
8944
8945 if self.edit_prediction_requires_modifier() {
8946 if !self.edit_prediction_preview_is_active() {
8947 return None;
8948 }
8949
8950 self.render_edit_prediction_modifier_jump_popover(
8951 text_bounds,
8952 content_origin,
8953 visible_row_range,
8954 line_layouts,
8955 line_height,
8956 scroll_pixel_position,
8957 newest_selection_head,
8958 target_display_point,
8959 window,
8960 cx,
8961 )
8962 } else {
8963 self.render_edit_prediction_eager_jump_popover(
8964 text_bounds,
8965 content_origin,
8966 editor_snapshot,
8967 visible_row_range,
8968 scroll_top,
8969 scroll_bottom,
8970 line_height,
8971 scroll_pixel_position,
8972 target_display_point,
8973 editor_width,
8974 window,
8975 cx,
8976 )
8977 }
8978 }
8979 EditPrediction::Edit {
8980 display_mode: EditDisplayMode::Inline,
8981 ..
8982 } => None,
8983 EditPrediction::Edit {
8984 display_mode: EditDisplayMode::TabAccept,
8985 edits,
8986 ..
8987 } => {
8988 let range = &edits.first()?.0;
8989 let target_display_point = range.end.to_display_point(editor_snapshot);
8990
8991 self.render_edit_prediction_end_of_line_popover(
8992 "Accept",
8993 editor_snapshot,
8994 visible_row_range,
8995 target_display_point,
8996 line_height,
8997 scroll_pixel_position,
8998 content_origin,
8999 editor_width,
9000 window,
9001 cx,
9002 )
9003 }
9004 EditPrediction::Edit {
9005 edits,
9006 edit_preview,
9007 display_mode: EditDisplayMode::DiffPopover,
9008 snapshot,
9009 } => self.render_edit_prediction_diff_popover(
9010 text_bounds,
9011 content_origin,
9012 right_margin,
9013 editor_snapshot,
9014 visible_row_range,
9015 line_layouts,
9016 line_height,
9017 scroll_position,
9018 scroll_pixel_position,
9019 newest_selection_head,
9020 editor_width,
9021 style,
9022 edits,
9023 edit_preview,
9024 snapshot,
9025 window,
9026 cx,
9027 ),
9028 EditPrediction::MoveOutside { snapshot, .. } => {
9029 let mut element = self
9030 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9031 .into_any();
9032
9033 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9034 let origin_x = text_bounds.size.width - size.width - px(30.);
9035 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9036 element.prepaint_at(origin, window, cx);
9037
9038 Some((element, origin))
9039 }
9040 }
9041 }
9042
9043 fn render_edit_prediction_modifier_jump_popover(
9044 &mut self,
9045 text_bounds: &Bounds<Pixels>,
9046 content_origin: gpui::Point<Pixels>,
9047 visible_row_range: Range<DisplayRow>,
9048 line_layouts: &[LineWithInvisibles],
9049 line_height: Pixels,
9050 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9051 newest_selection_head: Option<DisplayPoint>,
9052 target_display_point: DisplayPoint,
9053 window: &mut Window,
9054 cx: &mut App,
9055 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9056 let scrolled_content_origin =
9057 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9058
9059 const SCROLL_PADDING_Y: Pixels = px(12.);
9060
9061 if target_display_point.row() < visible_row_range.start {
9062 return self.render_edit_prediction_scroll_popover(
9063 |_| SCROLL_PADDING_Y,
9064 IconName::ArrowUp,
9065 visible_row_range,
9066 line_layouts,
9067 newest_selection_head,
9068 scrolled_content_origin,
9069 window,
9070 cx,
9071 );
9072 } else if target_display_point.row() >= visible_row_range.end {
9073 return self.render_edit_prediction_scroll_popover(
9074 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9075 IconName::ArrowDown,
9076 visible_row_range,
9077 line_layouts,
9078 newest_selection_head,
9079 scrolled_content_origin,
9080 window,
9081 cx,
9082 );
9083 }
9084
9085 const POLE_WIDTH: Pixels = px(2.);
9086
9087 let line_layout =
9088 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9089 let target_column = target_display_point.column() as usize;
9090
9091 let target_x = line_layout.x_for_index(target_column);
9092 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9093 - scroll_pixel_position.y;
9094
9095 let flag_on_right = target_x < text_bounds.size.width / 2.;
9096
9097 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9098 border_color.l += 0.001;
9099
9100 let mut element = v_flex()
9101 .items_end()
9102 .when(flag_on_right, |el| el.items_start())
9103 .child(if flag_on_right {
9104 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9105 .rounded_bl(px(0.))
9106 .rounded_tl(px(0.))
9107 .border_l_2()
9108 .border_color(border_color)
9109 } else {
9110 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9111 .rounded_br(px(0.))
9112 .rounded_tr(px(0.))
9113 .border_r_2()
9114 .border_color(border_color)
9115 })
9116 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9117 .into_any();
9118
9119 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9120
9121 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9122 - point(
9123 if flag_on_right {
9124 POLE_WIDTH
9125 } else {
9126 size.width - POLE_WIDTH
9127 },
9128 size.height - line_height,
9129 );
9130
9131 origin.x = origin.x.max(content_origin.x);
9132
9133 element.prepaint_at(origin, window, cx);
9134
9135 Some((element, origin))
9136 }
9137
9138 fn render_edit_prediction_scroll_popover(
9139 &mut self,
9140 to_y: impl Fn(Size<Pixels>) -> Pixels,
9141 scroll_icon: IconName,
9142 visible_row_range: Range<DisplayRow>,
9143 line_layouts: &[LineWithInvisibles],
9144 newest_selection_head: Option<DisplayPoint>,
9145 scrolled_content_origin: gpui::Point<Pixels>,
9146 window: &mut Window,
9147 cx: &mut App,
9148 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9149 let mut element = self
9150 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9151 .into_any();
9152
9153 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9154
9155 let cursor = newest_selection_head?;
9156 let cursor_row_layout =
9157 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9158 let cursor_column = cursor.column() as usize;
9159
9160 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9161
9162 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9163
9164 element.prepaint_at(origin, window, cx);
9165 Some((element, origin))
9166 }
9167
9168 fn render_edit_prediction_eager_jump_popover(
9169 &mut self,
9170 text_bounds: &Bounds<Pixels>,
9171 content_origin: gpui::Point<Pixels>,
9172 editor_snapshot: &EditorSnapshot,
9173 visible_row_range: Range<DisplayRow>,
9174 scroll_top: ScrollOffset,
9175 scroll_bottom: ScrollOffset,
9176 line_height: Pixels,
9177 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9178 target_display_point: DisplayPoint,
9179 editor_width: Pixels,
9180 window: &mut Window,
9181 cx: &mut App,
9182 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9183 if target_display_point.row().as_f64() < scroll_top {
9184 let mut element = self
9185 .render_edit_prediction_line_popover(
9186 "Jump to Edit",
9187 Some(IconName::ArrowUp),
9188 window,
9189 cx,
9190 )
9191 .into_any();
9192
9193 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9194 let offset = point(
9195 (text_bounds.size.width - size.width) / 2.,
9196 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9197 );
9198
9199 let origin = text_bounds.origin + offset;
9200 element.prepaint_at(origin, window, cx);
9201 Some((element, origin))
9202 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9203 let mut element = self
9204 .render_edit_prediction_line_popover(
9205 "Jump to Edit",
9206 Some(IconName::ArrowDown),
9207 window,
9208 cx,
9209 )
9210 .into_any();
9211
9212 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9213 let offset = point(
9214 (text_bounds.size.width - size.width) / 2.,
9215 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9216 );
9217
9218 let origin = text_bounds.origin + offset;
9219 element.prepaint_at(origin, window, cx);
9220 Some((element, origin))
9221 } else {
9222 self.render_edit_prediction_end_of_line_popover(
9223 "Jump to Edit",
9224 editor_snapshot,
9225 visible_row_range,
9226 target_display_point,
9227 line_height,
9228 scroll_pixel_position,
9229 content_origin,
9230 editor_width,
9231 window,
9232 cx,
9233 )
9234 }
9235 }
9236
9237 fn render_edit_prediction_end_of_line_popover(
9238 self: &mut Editor,
9239 label: &'static str,
9240 editor_snapshot: &EditorSnapshot,
9241 visible_row_range: Range<DisplayRow>,
9242 target_display_point: DisplayPoint,
9243 line_height: Pixels,
9244 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9245 content_origin: gpui::Point<Pixels>,
9246 editor_width: Pixels,
9247 window: &mut Window,
9248 cx: &mut App,
9249 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9250 let target_line_end = DisplayPoint::new(
9251 target_display_point.row(),
9252 editor_snapshot.line_len(target_display_point.row()),
9253 );
9254
9255 let mut element = self
9256 .render_edit_prediction_line_popover(label, None, window, cx)
9257 .into_any();
9258
9259 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9260
9261 let line_origin =
9262 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9263
9264 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9265 let mut origin = start_point
9266 + line_origin
9267 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9268 origin.x = origin.x.max(content_origin.x);
9269
9270 let max_x = content_origin.x + editor_width - size.width;
9271
9272 if origin.x > max_x {
9273 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9274
9275 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9276 origin.y += offset;
9277 IconName::ArrowUp
9278 } else {
9279 origin.y -= offset;
9280 IconName::ArrowDown
9281 };
9282
9283 element = self
9284 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9285 .into_any();
9286
9287 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9288
9289 origin.x = content_origin.x + editor_width - size.width - px(2.);
9290 }
9291
9292 element.prepaint_at(origin, window, cx);
9293 Some((element, origin))
9294 }
9295
9296 fn render_edit_prediction_diff_popover(
9297 self: &Editor,
9298 text_bounds: &Bounds<Pixels>,
9299 content_origin: gpui::Point<Pixels>,
9300 right_margin: Pixels,
9301 editor_snapshot: &EditorSnapshot,
9302 visible_row_range: Range<DisplayRow>,
9303 line_layouts: &[LineWithInvisibles],
9304 line_height: Pixels,
9305 scroll_position: gpui::Point<ScrollOffset>,
9306 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9307 newest_selection_head: Option<DisplayPoint>,
9308 editor_width: Pixels,
9309 style: &EditorStyle,
9310 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9311 edit_preview: &Option<language::EditPreview>,
9312 snapshot: &language::BufferSnapshot,
9313 window: &mut Window,
9314 cx: &mut App,
9315 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9316 let edit_start = edits
9317 .first()
9318 .unwrap()
9319 .0
9320 .start
9321 .to_display_point(editor_snapshot);
9322 let edit_end = edits
9323 .last()
9324 .unwrap()
9325 .0
9326 .end
9327 .to_display_point(editor_snapshot);
9328
9329 let is_visible = visible_row_range.contains(&edit_start.row())
9330 || visible_row_range.contains(&edit_end.row());
9331 if !is_visible {
9332 return None;
9333 }
9334
9335 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9336 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9337 } else {
9338 // Fallback for providers without edit_preview
9339 crate::edit_prediction_fallback_text(edits, cx)
9340 };
9341
9342 let styled_text = highlighted_edits.to_styled_text(&style.text);
9343 let line_count = highlighted_edits.text.lines().count();
9344
9345 const BORDER_WIDTH: Pixels = px(1.);
9346
9347 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9348 let has_keybind = keybind.is_some();
9349
9350 let mut element = h_flex()
9351 .items_start()
9352 .child(
9353 h_flex()
9354 .bg(cx.theme().colors().editor_background)
9355 .border(BORDER_WIDTH)
9356 .shadow_xs()
9357 .border_color(cx.theme().colors().border)
9358 .rounded_l_lg()
9359 .when(line_count > 1, |el| el.rounded_br_lg())
9360 .pr_1()
9361 .child(styled_text),
9362 )
9363 .child(
9364 h_flex()
9365 .h(line_height + BORDER_WIDTH * 2.)
9366 .px_1p5()
9367 .gap_1()
9368 // Workaround: For some reason, there's a gap if we don't do this
9369 .ml(-BORDER_WIDTH)
9370 .shadow(vec![gpui::BoxShadow {
9371 color: gpui::black().opacity(0.05),
9372 offset: point(px(1.), px(1.)),
9373 blur_radius: px(2.),
9374 spread_radius: px(0.),
9375 }])
9376 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9377 .border(BORDER_WIDTH)
9378 .border_color(cx.theme().colors().border)
9379 .rounded_r_lg()
9380 .id("edit_prediction_diff_popover_keybind")
9381 .when(!has_keybind, |el| {
9382 let status_colors = cx.theme().status();
9383
9384 el.bg(status_colors.error_background)
9385 .border_color(status_colors.error.opacity(0.6))
9386 .child(Icon::new(IconName::Info).color(Color::Error))
9387 .cursor_default()
9388 .hoverable_tooltip(move |_window, cx| {
9389 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9390 })
9391 })
9392 .children(keybind),
9393 )
9394 .into_any();
9395
9396 let longest_row =
9397 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9398 let longest_line_width = if visible_row_range.contains(&longest_row) {
9399 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9400 } else {
9401 layout_line(
9402 longest_row,
9403 editor_snapshot,
9404 style,
9405 editor_width,
9406 |_| false,
9407 window,
9408 cx,
9409 )
9410 .width
9411 };
9412
9413 let viewport_bounds =
9414 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9415 right: -right_margin,
9416 ..Default::default()
9417 });
9418
9419 let x_after_longest = Pixels::from(
9420 ScrollPixelOffset::from(
9421 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9422 ) - scroll_pixel_position.x,
9423 );
9424
9425 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9426
9427 // Fully visible if it can be displayed within the window (allow overlapping other
9428 // panes). However, this is only allowed if the popover starts within text_bounds.
9429 let can_position_to_the_right = x_after_longest < text_bounds.right()
9430 && x_after_longest + element_bounds.width < viewport_bounds.right();
9431
9432 let mut origin = if can_position_to_the_right {
9433 point(
9434 x_after_longest,
9435 text_bounds.origin.y
9436 + Pixels::from(
9437 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9438 - scroll_pixel_position.y,
9439 ),
9440 )
9441 } else {
9442 let cursor_row = newest_selection_head.map(|head| head.row());
9443 let above_edit = edit_start
9444 .row()
9445 .0
9446 .checked_sub(line_count as u32)
9447 .map(DisplayRow);
9448 let below_edit = Some(edit_end.row() + 1);
9449 let above_cursor =
9450 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9451 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9452
9453 // Place the edit popover adjacent to the edit if there is a location
9454 // available that is onscreen and does not obscure the cursor. Otherwise,
9455 // place it adjacent to the cursor.
9456 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9457 .into_iter()
9458 .flatten()
9459 .find(|&start_row| {
9460 let end_row = start_row + line_count as u32;
9461 visible_row_range.contains(&start_row)
9462 && visible_row_range.contains(&end_row)
9463 && cursor_row
9464 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9465 })?;
9466
9467 content_origin
9468 + point(
9469 Pixels::from(-scroll_pixel_position.x),
9470 Pixels::from(
9471 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9472 ),
9473 )
9474 };
9475
9476 origin.x -= BORDER_WIDTH;
9477
9478 window.defer_draw(element, origin, 1);
9479
9480 // Do not return an element, since it will already be drawn due to defer_draw.
9481 None
9482 }
9483
9484 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9485 px(30.)
9486 }
9487
9488 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9489 if self.read_only(cx) {
9490 cx.theme().players().read_only()
9491 } else {
9492 self.style.as_ref().unwrap().local_player
9493 }
9494 }
9495
9496 fn render_edit_prediction_accept_keybind(
9497 &self,
9498 window: &mut Window,
9499 cx: &mut App,
9500 ) -> Option<AnyElement> {
9501 let accept_binding =
9502 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9503 let accept_keystroke = accept_binding.keystroke()?;
9504
9505 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9506
9507 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9508 Color::Accent
9509 } else {
9510 Color::Muted
9511 };
9512
9513 h_flex()
9514 .px_0p5()
9515 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9516 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9517 .text_size(TextSize::XSmall.rems(cx))
9518 .child(h_flex().children(ui::render_modifiers(
9519 accept_keystroke.modifiers(),
9520 PlatformStyle::platform(),
9521 Some(modifiers_color),
9522 Some(IconSize::XSmall.rems().into()),
9523 true,
9524 )))
9525 .when(is_platform_style_mac, |parent| {
9526 parent.child(accept_keystroke.key().to_string())
9527 })
9528 .when(!is_platform_style_mac, |parent| {
9529 parent.child(
9530 Key::new(
9531 util::capitalize(accept_keystroke.key()),
9532 Some(Color::Default),
9533 )
9534 .size(Some(IconSize::XSmall.rems().into())),
9535 )
9536 })
9537 .into_any()
9538 .into()
9539 }
9540
9541 fn render_edit_prediction_line_popover(
9542 &self,
9543 label: impl Into<SharedString>,
9544 icon: Option<IconName>,
9545 window: &mut Window,
9546 cx: &mut App,
9547 ) -> Stateful<Div> {
9548 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9549
9550 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9551 let has_keybind = keybind.is_some();
9552
9553 h_flex()
9554 .id("ep-line-popover")
9555 .py_0p5()
9556 .pl_1()
9557 .pr(padding_right)
9558 .gap_1()
9559 .rounded_md()
9560 .border_1()
9561 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9562 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9563 .shadow_xs()
9564 .when(!has_keybind, |el| {
9565 let status_colors = cx.theme().status();
9566
9567 el.bg(status_colors.error_background)
9568 .border_color(status_colors.error.opacity(0.6))
9569 .pl_2()
9570 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9571 .cursor_default()
9572 .hoverable_tooltip(move |_window, cx| {
9573 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9574 })
9575 })
9576 .children(keybind)
9577 .child(
9578 Label::new(label)
9579 .size(LabelSize::Small)
9580 .when(!has_keybind, |el| {
9581 el.color(cx.theme().status().error.into()).strikethrough()
9582 }),
9583 )
9584 .when(!has_keybind, |el| {
9585 el.child(
9586 h_flex().ml_1().child(
9587 Icon::new(IconName::Info)
9588 .size(IconSize::Small)
9589 .color(cx.theme().status().error.into()),
9590 ),
9591 )
9592 })
9593 .when_some(icon, |element, icon| {
9594 element.child(
9595 div()
9596 .mt(px(1.5))
9597 .child(Icon::new(icon).size(IconSize::Small)),
9598 )
9599 })
9600 }
9601
9602 fn render_edit_prediction_jump_outside_popover(
9603 &self,
9604 snapshot: &BufferSnapshot,
9605 window: &mut Window,
9606 cx: &mut App,
9607 ) -> Stateful<Div> {
9608 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9609 let has_keybind = keybind.is_some();
9610
9611 let file_name = snapshot
9612 .file()
9613 .map(|file| SharedString::new(file.file_name(cx)))
9614 .unwrap_or(SharedString::new_static("untitled"));
9615
9616 h_flex()
9617 .id("ep-jump-outside-popover")
9618 .py_1()
9619 .px_2()
9620 .gap_1()
9621 .rounded_md()
9622 .border_1()
9623 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9624 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9625 .shadow_xs()
9626 .when(!has_keybind, |el| {
9627 let status_colors = cx.theme().status();
9628
9629 el.bg(status_colors.error_background)
9630 .border_color(status_colors.error.opacity(0.6))
9631 .pl_2()
9632 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9633 .cursor_default()
9634 .hoverable_tooltip(move |_window, cx| {
9635 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9636 })
9637 })
9638 .children(keybind)
9639 .child(
9640 Label::new(file_name)
9641 .size(LabelSize::Small)
9642 .buffer_font(cx)
9643 .when(!has_keybind, |el| {
9644 el.color(cx.theme().status().error.into()).strikethrough()
9645 }),
9646 )
9647 .when(!has_keybind, |el| {
9648 el.child(
9649 h_flex().ml_1().child(
9650 Icon::new(IconName::Info)
9651 .size(IconSize::Small)
9652 .color(cx.theme().status().error.into()),
9653 ),
9654 )
9655 })
9656 .child(
9657 div()
9658 .mt(px(1.5))
9659 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9660 )
9661 }
9662
9663 fn edit_prediction_line_popover_bg_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.1))
9667 }
9668
9669 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9670 let accent_color = cx.theme().colors().text_accent;
9671 let editor_bg_color = cx.theme().colors().editor_background;
9672 editor_bg_color.blend(accent_color.opacity(0.6))
9673 }
9674 fn get_prediction_provider_icon_name(
9675 provider: &Option<RegisteredEditPredictionDelegate>,
9676 ) -> IconName {
9677 match provider {
9678 Some(provider) => match provider.provider.name() {
9679 "copilot" => IconName::Copilot,
9680 "supermaven" => IconName::Supermaven,
9681 _ => IconName::ZedPredict,
9682 },
9683 None => IconName::ZedPredict,
9684 }
9685 }
9686
9687 fn render_edit_prediction_cursor_popover(
9688 &self,
9689 min_width: Pixels,
9690 max_width: Pixels,
9691 cursor_point: Point,
9692 style: &EditorStyle,
9693 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9694 _window: &Window,
9695 cx: &mut Context<Editor>,
9696 ) -> Option<AnyElement> {
9697 let provider = self.edit_prediction_provider.as_ref()?;
9698 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9699
9700 let is_refreshing = provider.provider.is_refreshing(cx);
9701
9702 fn pending_completion_container(icon: IconName) -> Div {
9703 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9704 }
9705
9706 let completion = match &self.active_edit_prediction {
9707 Some(prediction) => {
9708 if !self.has_visible_completions_menu() {
9709 const RADIUS: Pixels = px(6.);
9710 const BORDER_WIDTH: Pixels = px(1.);
9711
9712 return Some(
9713 h_flex()
9714 .elevation_2(cx)
9715 .border(BORDER_WIDTH)
9716 .border_color(cx.theme().colors().border)
9717 .when(accept_keystroke.is_none(), |el| {
9718 el.border_color(cx.theme().status().error)
9719 })
9720 .rounded(RADIUS)
9721 .rounded_tl(px(0.))
9722 .overflow_hidden()
9723 .child(div().px_1p5().child(match &prediction.completion {
9724 EditPrediction::MoveWithin { target, snapshot } => {
9725 use text::ToPoint as _;
9726 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9727 {
9728 Icon::new(IconName::ZedPredictDown)
9729 } else {
9730 Icon::new(IconName::ZedPredictUp)
9731 }
9732 }
9733 EditPrediction::MoveOutside { .. } => {
9734 // TODO [zeta2] custom icon for external jump?
9735 Icon::new(provider_icon)
9736 }
9737 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9738 }))
9739 .child(
9740 h_flex()
9741 .gap_1()
9742 .py_1()
9743 .px_2()
9744 .rounded_r(RADIUS - BORDER_WIDTH)
9745 .border_l_1()
9746 .border_color(cx.theme().colors().border)
9747 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9748 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9749 el.child(
9750 Label::new("Hold")
9751 .size(LabelSize::Small)
9752 .when(accept_keystroke.is_none(), |el| {
9753 el.strikethrough()
9754 })
9755 .line_height_style(LineHeightStyle::UiLabel),
9756 )
9757 })
9758 .id("edit_prediction_cursor_popover_keybind")
9759 .when(accept_keystroke.is_none(), |el| {
9760 let status_colors = cx.theme().status();
9761
9762 el.bg(status_colors.error_background)
9763 .border_color(status_colors.error.opacity(0.6))
9764 .child(Icon::new(IconName::Info).color(Color::Error))
9765 .cursor_default()
9766 .hoverable_tooltip(move |_window, cx| {
9767 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9768 .into()
9769 })
9770 })
9771 .when_some(
9772 accept_keystroke.as_ref(),
9773 |el, accept_keystroke| {
9774 el.child(h_flex().children(ui::render_modifiers(
9775 accept_keystroke.modifiers(),
9776 PlatformStyle::platform(),
9777 Some(Color::Default),
9778 Some(IconSize::XSmall.rems().into()),
9779 false,
9780 )))
9781 },
9782 ),
9783 )
9784 .into_any(),
9785 );
9786 }
9787
9788 self.render_edit_prediction_cursor_popover_preview(
9789 prediction,
9790 cursor_point,
9791 style,
9792 cx,
9793 )?
9794 }
9795
9796 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9797 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9798 stale_completion,
9799 cursor_point,
9800 style,
9801 cx,
9802 )?,
9803
9804 None => pending_completion_container(provider_icon)
9805 .child(Label::new("...").size(LabelSize::Small)),
9806 },
9807
9808 None => pending_completion_container(provider_icon)
9809 .child(Label::new("...").size(LabelSize::Small)),
9810 };
9811
9812 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9813 completion
9814 .with_animation(
9815 "loading-completion",
9816 Animation::new(Duration::from_secs(2))
9817 .repeat()
9818 .with_easing(pulsating_between(0.4, 0.8)),
9819 |label, delta| label.opacity(delta),
9820 )
9821 .into_any_element()
9822 } else {
9823 completion.into_any_element()
9824 };
9825
9826 let has_completion = self.active_edit_prediction.is_some();
9827
9828 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9829 Some(
9830 h_flex()
9831 .min_w(min_width)
9832 .max_w(max_width)
9833 .flex_1()
9834 .elevation_2(cx)
9835 .border_color(cx.theme().colors().border)
9836 .child(
9837 div()
9838 .flex_1()
9839 .py_1()
9840 .px_2()
9841 .overflow_hidden()
9842 .child(completion),
9843 )
9844 .when_some(accept_keystroke, |el, accept_keystroke| {
9845 if !accept_keystroke.modifiers().modified() {
9846 return el;
9847 }
9848
9849 el.child(
9850 h_flex()
9851 .h_full()
9852 .border_l_1()
9853 .rounded_r_lg()
9854 .border_color(cx.theme().colors().border)
9855 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9856 .gap_1()
9857 .py_1()
9858 .px_2()
9859 .child(
9860 h_flex()
9861 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9862 .when(is_platform_style_mac, |parent| parent.gap_1())
9863 .child(h_flex().children(ui::render_modifiers(
9864 accept_keystroke.modifiers(),
9865 PlatformStyle::platform(),
9866 Some(if !has_completion {
9867 Color::Muted
9868 } else {
9869 Color::Default
9870 }),
9871 None,
9872 false,
9873 ))),
9874 )
9875 .child(Label::new("Preview").into_any_element())
9876 .opacity(if has_completion { 1.0 } else { 0.4 }),
9877 )
9878 })
9879 .into_any(),
9880 )
9881 }
9882
9883 fn render_edit_prediction_cursor_popover_preview(
9884 &self,
9885 completion: &EditPredictionState,
9886 cursor_point: Point,
9887 style: &EditorStyle,
9888 cx: &mut Context<Editor>,
9889 ) -> Option<Div> {
9890 use text::ToPoint as _;
9891
9892 fn render_relative_row_jump(
9893 prefix: impl Into<String>,
9894 current_row: u32,
9895 target_row: u32,
9896 ) -> Div {
9897 let (row_diff, arrow) = if target_row < current_row {
9898 (current_row - target_row, IconName::ArrowUp)
9899 } else {
9900 (target_row - current_row, IconName::ArrowDown)
9901 };
9902
9903 h_flex()
9904 .child(
9905 Label::new(format!("{}{}", prefix.into(), row_diff))
9906 .color(Color::Muted)
9907 .size(LabelSize::Small),
9908 )
9909 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9910 }
9911
9912 let supports_jump = self
9913 .edit_prediction_provider
9914 .as_ref()
9915 .map(|provider| provider.provider.supports_jump_to_edit())
9916 .unwrap_or(true);
9917
9918 match &completion.completion {
9919 EditPrediction::MoveWithin {
9920 target, snapshot, ..
9921 } => {
9922 if !supports_jump {
9923 return None;
9924 }
9925
9926 Some(
9927 h_flex()
9928 .px_2()
9929 .gap_2()
9930 .flex_1()
9931 .child(
9932 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9933 Icon::new(IconName::ZedPredictDown)
9934 } else {
9935 Icon::new(IconName::ZedPredictUp)
9936 },
9937 )
9938 .child(Label::new("Jump to Edit")),
9939 )
9940 }
9941 EditPrediction::MoveOutside { snapshot, .. } => {
9942 let file_name = snapshot
9943 .file()
9944 .map(|file| file.file_name(cx))
9945 .unwrap_or("untitled");
9946 Some(
9947 h_flex()
9948 .px_2()
9949 .gap_2()
9950 .flex_1()
9951 .child(Icon::new(IconName::ZedPredict))
9952 .child(Label::new(format!("Jump to {file_name}"))),
9953 )
9954 }
9955 EditPrediction::Edit {
9956 edits,
9957 edit_preview,
9958 snapshot,
9959 display_mode: _,
9960 } => {
9961 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9962
9963 let (highlighted_edits, has_more_lines) =
9964 if let Some(edit_preview) = edit_preview.as_ref() {
9965 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9966 .first_line_preview()
9967 } else {
9968 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9969 };
9970
9971 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9972 .with_default_highlights(&style.text, highlighted_edits.highlights);
9973
9974 let preview = h_flex()
9975 .gap_1()
9976 .min_w_16()
9977 .child(styled_text)
9978 .when(has_more_lines, |parent| parent.child("…"));
9979
9980 let left = if supports_jump && first_edit_row != cursor_point.row {
9981 render_relative_row_jump("", cursor_point.row, first_edit_row)
9982 .into_any_element()
9983 } else {
9984 let icon_name =
9985 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9986 Icon::new(icon_name).into_any_element()
9987 };
9988
9989 Some(
9990 h_flex()
9991 .h_full()
9992 .flex_1()
9993 .gap_2()
9994 .pr_1()
9995 .overflow_x_hidden()
9996 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9997 .child(left)
9998 .child(preview),
9999 )
10000 }
10001 }
10002 }
10003
10004 pub fn render_context_menu(
10005 &mut self,
10006 max_height_in_lines: u32,
10007 window: &mut Window,
10008 cx: &mut Context<Editor>,
10009 ) -> Option<AnyElement> {
10010 let menu = self.context_menu.borrow();
10011 let menu = menu.as_ref()?;
10012 if !menu.visible() {
10013 return None;
10014 };
10015 self.style
10016 .as_ref()
10017 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10018 }
10019
10020 fn render_context_menu_aside(
10021 &mut self,
10022 max_size: Size<Pixels>,
10023 window: &mut Window,
10024 cx: &mut Context<Editor>,
10025 ) -> Option<AnyElement> {
10026 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10027 if menu.visible() {
10028 menu.render_aside(max_size, window, cx)
10029 } else {
10030 None
10031 }
10032 })
10033 }
10034
10035 fn hide_context_menu(
10036 &mut self,
10037 window: &mut Window,
10038 cx: &mut Context<Self>,
10039 ) -> Option<CodeContextMenu> {
10040 cx.notify();
10041 self.completion_tasks.clear();
10042 let context_menu = self.context_menu.borrow_mut().take();
10043 self.stale_edit_prediction_in_menu.take();
10044 self.update_visible_edit_prediction(window, cx);
10045 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10046 && let Some(completion_provider) = &self.completion_provider
10047 {
10048 completion_provider.selection_changed(None, window, cx);
10049 }
10050 context_menu
10051 }
10052
10053 fn show_snippet_choices(
10054 &mut self,
10055 choices: &Vec<String>,
10056 selection: Range<Anchor>,
10057 cx: &mut Context<Self>,
10058 ) {
10059 let Some((_, buffer, _)) = self
10060 .buffer()
10061 .read(cx)
10062 .excerpt_containing(selection.start, cx)
10063 else {
10064 return;
10065 };
10066 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10067 else {
10068 return;
10069 };
10070 if buffer != end_buffer {
10071 log::error!("expected anchor range to have matching buffer IDs");
10072 return;
10073 }
10074
10075 let id = post_inc(&mut self.next_completion_id);
10076 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10077 let mut context_menu = self.context_menu.borrow_mut();
10078 let old_menu = context_menu.take();
10079 *context_menu = Some(CodeContextMenu::Completions(
10080 CompletionsMenu::new_snippet_choices(
10081 id,
10082 true,
10083 choices,
10084 selection,
10085 buffer,
10086 old_menu.map(|menu| menu.primary_scroll_handle()),
10087 snippet_sort_order,
10088 ),
10089 ));
10090 }
10091
10092 pub fn insert_snippet(
10093 &mut self,
10094 insertion_ranges: &[Range<MultiBufferOffset>],
10095 snippet: Snippet,
10096 window: &mut Window,
10097 cx: &mut Context<Self>,
10098 ) -> Result<()> {
10099 struct Tabstop<T> {
10100 is_end_tabstop: bool,
10101 ranges: Vec<Range<T>>,
10102 choices: Option<Vec<String>>,
10103 }
10104
10105 let tabstops = self.buffer.update(cx, |buffer, cx| {
10106 let snippet_text: Arc<str> = snippet.text.clone().into();
10107 let edits = insertion_ranges
10108 .iter()
10109 .cloned()
10110 .map(|range| (range, snippet_text.clone()));
10111 let autoindent_mode = AutoindentMode::Block {
10112 original_indent_columns: Vec::new(),
10113 };
10114 buffer.edit(edits, Some(autoindent_mode), cx);
10115
10116 let snapshot = &*buffer.read(cx);
10117 let snippet = &snippet;
10118 snippet
10119 .tabstops
10120 .iter()
10121 .map(|tabstop| {
10122 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10123 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10124 });
10125 let mut tabstop_ranges = tabstop
10126 .ranges
10127 .iter()
10128 .flat_map(|tabstop_range| {
10129 let mut delta = 0_isize;
10130 insertion_ranges.iter().map(move |insertion_range| {
10131 let insertion_start = insertion_range.start + delta;
10132 delta += snippet.text.len() as isize
10133 - (insertion_range.end - insertion_range.start) as isize;
10134
10135 let start =
10136 (insertion_start + tabstop_range.start).min(snapshot.len());
10137 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10138 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10139 })
10140 })
10141 .collect::<Vec<_>>();
10142 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10143
10144 Tabstop {
10145 is_end_tabstop,
10146 ranges: tabstop_ranges,
10147 choices: tabstop.choices.clone(),
10148 }
10149 })
10150 .collect::<Vec<_>>()
10151 });
10152 if let Some(tabstop) = tabstops.first() {
10153 self.change_selections(Default::default(), window, cx, |s| {
10154 // Reverse order so that the first range is the newest created selection.
10155 // Completions will use it and autoscroll will prioritize it.
10156 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10157 });
10158
10159 if let Some(choices) = &tabstop.choices
10160 && let Some(selection) = tabstop.ranges.first()
10161 {
10162 self.show_snippet_choices(choices, selection.clone(), cx)
10163 }
10164
10165 // If we're already at the last tabstop and it's at the end of the snippet,
10166 // we're done, we don't need to keep the state around.
10167 if !tabstop.is_end_tabstop {
10168 let choices = tabstops
10169 .iter()
10170 .map(|tabstop| tabstop.choices.clone())
10171 .collect();
10172
10173 let ranges = tabstops
10174 .into_iter()
10175 .map(|tabstop| tabstop.ranges)
10176 .collect::<Vec<_>>();
10177
10178 self.snippet_stack.push(SnippetState {
10179 active_index: 0,
10180 ranges,
10181 choices,
10182 });
10183 }
10184
10185 // Check whether the just-entered snippet ends with an auto-closable bracket.
10186 if self.autoclose_regions.is_empty() {
10187 let snapshot = self.buffer.read(cx).snapshot(cx);
10188 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10189 let selection_head = selection.head();
10190 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10191 continue;
10192 };
10193
10194 let mut bracket_pair = None;
10195 let max_lookup_length = scope
10196 .brackets()
10197 .map(|(pair, _)| {
10198 pair.start
10199 .as_str()
10200 .chars()
10201 .count()
10202 .max(pair.end.as_str().chars().count())
10203 })
10204 .max();
10205 if let Some(max_lookup_length) = max_lookup_length {
10206 let next_text = snapshot
10207 .chars_at(selection_head)
10208 .take(max_lookup_length)
10209 .collect::<String>();
10210 let prev_text = snapshot
10211 .reversed_chars_at(selection_head)
10212 .take(max_lookup_length)
10213 .collect::<String>();
10214
10215 for (pair, enabled) in scope.brackets() {
10216 if enabled
10217 && pair.close
10218 && prev_text.starts_with(pair.start.as_str())
10219 && next_text.starts_with(pair.end.as_str())
10220 {
10221 bracket_pair = Some(pair.clone());
10222 break;
10223 }
10224 }
10225 }
10226
10227 if let Some(pair) = bracket_pair {
10228 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10229 let autoclose_enabled =
10230 self.use_autoclose && snapshot_settings.use_autoclose;
10231 if autoclose_enabled {
10232 let start = snapshot.anchor_after(selection_head);
10233 let end = snapshot.anchor_after(selection_head);
10234 self.autoclose_regions.push(AutocloseRegion {
10235 selection_id: selection.id,
10236 range: start..end,
10237 pair,
10238 });
10239 }
10240 }
10241 }
10242 }
10243 }
10244 Ok(())
10245 }
10246
10247 pub fn move_to_next_snippet_tabstop(
10248 &mut self,
10249 window: &mut Window,
10250 cx: &mut Context<Self>,
10251 ) -> bool {
10252 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10253 }
10254
10255 pub fn move_to_prev_snippet_tabstop(
10256 &mut self,
10257 window: &mut Window,
10258 cx: &mut Context<Self>,
10259 ) -> bool {
10260 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10261 }
10262
10263 pub fn move_to_snippet_tabstop(
10264 &mut self,
10265 bias: Bias,
10266 window: &mut Window,
10267 cx: &mut Context<Self>,
10268 ) -> bool {
10269 if let Some(mut snippet) = self.snippet_stack.pop() {
10270 match bias {
10271 Bias::Left => {
10272 if snippet.active_index > 0 {
10273 snippet.active_index -= 1;
10274 } else {
10275 self.snippet_stack.push(snippet);
10276 return false;
10277 }
10278 }
10279 Bias::Right => {
10280 if snippet.active_index + 1 < snippet.ranges.len() {
10281 snippet.active_index += 1;
10282 } else {
10283 self.snippet_stack.push(snippet);
10284 return false;
10285 }
10286 }
10287 }
10288 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10289 self.change_selections(Default::default(), window, cx, |s| {
10290 // Reverse order so that the first range is the newest created selection.
10291 // Completions will use it and autoscroll will prioritize it.
10292 s.select_ranges(current_ranges.iter().rev().cloned())
10293 });
10294
10295 if let Some(choices) = &snippet.choices[snippet.active_index]
10296 && let Some(selection) = current_ranges.first()
10297 {
10298 self.show_snippet_choices(choices, selection.clone(), cx);
10299 }
10300
10301 // If snippet state is not at the last tabstop, push it back on the stack
10302 if snippet.active_index + 1 < snippet.ranges.len() {
10303 self.snippet_stack.push(snippet);
10304 }
10305 return true;
10306 }
10307 }
10308
10309 false
10310 }
10311
10312 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10313 self.transact(window, cx, |this, window, cx| {
10314 this.select_all(&SelectAll, window, cx);
10315 this.insert("", window, cx);
10316 });
10317 }
10318
10319 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10320 if self.read_only(cx) {
10321 return;
10322 }
10323 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10324 self.transact(window, cx, |this, window, cx| {
10325 this.select_autoclose_pair(window, cx);
10326
10327 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10328
10329 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10330 if !this.linked_edit_ranges.is_empty() {
10331 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10332 let snapshot = this.buffer.read(cx).snapshot(cx);
10333
10334 for selection in selections.iter() {
10335 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10336 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10337 if selection_start.buffer_id != selection_end.buffer_id {
10338 continue;
10339 }
10340 if let Some(ranges) =
10341 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10342 {
10343 for (buffer, entries) in ranges {
10344 linked_ranges.entry(buffer).or_default().extend(entries);
10345 }
10346 }
10347 }
10348 }
10349
10350 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10351 for selection in &mut selections {
10352 if selection.is_empty() {
10353 let old_head = selection.head();
10354 let mut new_head =
10355 movement::left(&display_map, old_head.to_display_point(&display_map))
10356 .to_point(&display_map);
10357 if let Some((buffer, line_buffer_range)) = display_map
10358 .buffer_snapshot()
10359 .buffer_line_for_row(MultiBufferRow(old_head.row))
10360 {
10361 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10362 let indent_len = match indent_size.kind {
10363 IndentKind::Space => {
10364 buffer.settings_at(line_buffer_range.start, cx).tab_size
10365 }
10366 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10367 };
10368 if old_head.column <= indent_size.len && old_head.column > 0 {
10369 let indent_len = indent_len.get();
10370 new_head = cmp::min(
10371 new_head,
10372 MultiBufferPoint::new(
10373 old_head.row,
10374 ((old_head.column - 1) / indent_len) * indent_len,
10375 ),
10376 );
10377 }
10378 }
10379
10380 selection.set_head(new_head, SelectionGoal::None);
10381 }
10382 }
10383
10384 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10385 this.insert("", window, cx);
10386 let empty_str: Arc<str> = Arc::from("");
10387 for (buffer, edits) in linked_ranges {
10388 let snapshot = buffer.read(cx).snapshot();
10389 use text::ToPoint as TP;
10390
10391 let edits = edits
10392 .into_iter()
10393 .map(|range| {
10394 let end_point = TP::to_point(&range.end, &snapshot);
10395 let mut start_point = TP::to_point(&range.start, &snapshot);
10396
10397 if end_point == start_point {
10398 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10399 .saturating_sub(1);
10400 start_point =
10401 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10402 };
10403
10404 (start_point..end_point, empty_str.clone())
10405 })
10406 .sorted_by_key(|(range, _)| range.start)
10407 .collect::<Vec<_>>();
10408 buffer.update(cx, |this, cx| {
10409 this.edit(edits, None, cx);
10410 })
10411 }
10412 this.refresh_edit_prediction(true, false, window, cx);
10413 refresh_linked_ranges(this, window, cx);
10414 });
10415 }
10416
10417 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10418 if self.read_only(cx) {
10419 return;
10420 }
10421 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10422 self.transact(window, cx, |this, window, cx| {
10423 this.change_selections(Default::default(), window, cx, |s| {
10424 s.move_with(|map, selection| {
10425 if selection.is_empty() {
10426 let cursor = movement::right(map, selection.head());
10427 selection.end = cursor;
10428 selection.reversed = true;
10429 selection.goal = SelectionGoal::None;
10430 }
10431 })
10432 });
10433 this.insert("", window, cx);
10434 this.refresh_edit_prediction(true, false, window, cx);
10435 });
10436 }
10437
10438 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10439 if self.mode.is_single_line() {
10440 cx.propagate();
10441 return;
10442 }
10443
10444 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10445 if self.move_to_prev_snippet_tabstop(window, cx) {
10446 return;
10447 }
10448 self.outdent(&Outdent, window, cx);
10449 }
10450
10451 pub fn next_snippet_tabstop(
10452 &mut self,
10453 _: &NextSnippetTabstop,
10454 window: &mut Window,
10455 cx: &mut Context<Self>,
10456 ) {
10457 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10458 cx.propagate();
10459 return;
10460 }
10461
10462 if self.move_to_next_snippet_tabstop(window, cx) {
10463 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10464 return;
10465 }
10466 cx.propagate();
10467 }
10468
10469 pub fn previous_snippet_tabstop(
10470 &mut self,
10471 _: &PreviousSnippetTabstop,
10472 window: &mut Window,
10473 cx: &mut Context<Self>,
10474 ) {
10475 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10476 cx.propagate();
10477 return;
10478 }
10479
10480 if self.move_to_prev_snippet_tabstop(window, cx) {
10481 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10482 return;
10483 }
10484 cx.propagate();
10485 }
10486
10487 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10488 if self.mode.is_single_line() {
10489 cx.propagate();
10490 return;
10491 }
10492
10493 if self.move_to_next_snippet_tabstop(window, cx) {
10494 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10495 return;
10496 }
10497 if self.read_only(cx) {
10498 return;
10499 }
10500 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10501 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10502 let buffer = self.buffer.read(cx);
10503 let snapshot = buffer.snapshot(cx);
10504 let rows_iter = selections.iter().map(|s| s.head().row);
10505 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10506
10507 let has_some_cursor_in_whitespace = selections
10508 .iter()
10509 .filter(|selection| selection.is_empty())
10510 .any(|selection| {
10511 let cursor = selection.head();
10512 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10513 cursor.column < current_indent.len
10514 });
10515
10516 let mut edits = Vec::new();
10517 let mut prev_edited_row = 0;
10518 let mut row_delta = 0;
10519 for selection in &mut selections {
10520 if selection.start.row != prev_edited_row {
10521 row_delta = 0;
10522 }
10523 prev_edited_row = selection.end.row;
10524
10525 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10526 if selection.is_empty() {
10527 let cursor = selection.head();
10528 let settings = buffer.language_settings_at(cursor, cx);
10529 if settings.indent_list_on_tab {
10530 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10531 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10532 row_delta = Self::indent_selection(
10533 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10534 );
10535 continue;
10536 }
10537 }
10538 }
10539 }
10540
10541 // If the selection is non-empty, then increase the indentation of the selected lines.
10542 if !selection.is_empty() {
10543 row_delta =
10544 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10545 continue;
10546 }
10547
10548 let cursor = selection.head();
10549 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10550 if let Some(suggested_indent) =
10551 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10552 {
10553 // Don't do anything if already at suggested indent
10554 // and there is any other cursor which is not
10555 if has_some_cursor_in_whitespace
10556 && cursor.column == current_indent.len
10557 && current_indent.len == suggested_indent.len
10558 {
10559 continue;
10560 }
10561
10562 // Adjust line and move cursor to suggested indent
10563 // if cursor is not at suggested indent
10564 if cursor.column < suggested_indent.len
10565 && cursor.column <= current_indent.len
10566 && current_indent.len <= suggested_indent.len
10567 {
10568 selection.start = Point::new(cursor.row, suggested_indent.len);
10569 selection.end = selection.start;
10570 if row_delta == 0 {
10571 edits.extend(Buffer::edit_for_indent_size_adjustment(
10572 cursor.row,
10573 current_indent,
10574 suggested_indent,
10575 ));
10576 row_delta = suggested_indent.len - current_indent.len;
10577 }
10578 continue;
10579 }
10580
10581 // If current indent is more than suggested indent
10582 // only move cursor to current indent and skip indent
10583 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10584 selection.start = Point::new(cursor.row, current_indent.len);
10585 selection.end = selection.start;
10586 continue;
10587 }
10588 }
10589
10590 // Otherwise, insert a hard or soft tab.
10591 let settings = buffer.language_settings_at(cursor, cx);
10592 let tab_size = if settings.hard_tabs {
10593 IndentSize::tab()
10594 } else {
10595 let tab_size = settings.tab_size.get();
10596 let indent_remainder = snapshot
10597 .text_for_range(Point::new(cursor.row, 0)..cursor)
10598 .flat_map(str::chars)
10599 .fold(row_delta % tab_size, |counter: u32, c| {
10600 if c == '\t' {
10601 0
10602 } else {
10603 (counter + 1) % tab_size
10604 }
10605 });
10606
10607 let chars_to_next_tab_stop = tab_size - indent_remainder;
10608 IndentSize::spaces(chars_to_next_tab_stop)
10609 };
10610 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10611 selection.end = selection.start;
10612 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10613 row_delta += tab_size.len;
10614 }
10615
10616 self.transact(window, cx, |this, window, cx| {
10617 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10618 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10619 this.refresh_edit_prediction(true, false, window, cx);
10620 });
10621 }
10622
10623 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10624 if self.read_only(cx) {
10625 return;
10626 }
10627 if self.mode.is_single_line() {
10628 cx.propagate();
10629 return;
10630 }
10631
10632 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10633 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10634 let mut prev_edited_row = 0;
10635 let mut row_delta = 0;
10636 let mut edits = Vec::new();
10637 let buffer = self.buffer.read(cx);
10638 let snapshot = buffer.snapshot(cx);
10639 for selection in &mut selections {
10640 if selection.start.row != prev_edited_row {
10641 row_delta = 0;
10642 }
10643 prev_edited_row = selection.end.row;
10644
10645 row_delta =
10646 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10647 }
10648
10649 self.transact(window, cx, |this, window, cx| {
10650 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10651 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10652 });
10653 }
10654
10655 fn indent_selection(
10656 buffer: &MultiBuffer,
10657 snapshot: &MultiBufferSnapshot,
10658 selection: &mut Selection<Point>,
10659 edits: &mut Vec<(Range<Point>, String)>,
10660 delta_for_start_row: u32,
10661 cx: &App,
10662 ) -> u32 {
10663 let settings = buffer.language_settings_at(selection.start, cx);
10664 let tab_size = settings.tab_size.get();
10665 let indent_kind = if settings.hard_tabs {
10666 IndentKind::Tab
10667 } else {
10668 IndentKind::Space
10669 };
10670 let mut start_row = selection.start.row;
10671 let mut end_row = selection.end.row + 1;
10672
10673 // If a selection ends at the beginning of a line, don't indent
10674 // that last line.
10675 if selection.end.column == 0 && selection.end.row > selection.start.row {
10676 end_row -= 1;
10677 }
10678
10679 // Avoid re-indenting a row that has already been indented by a
10680 // previous selection, but still update this selection's column
10681 // to reflect that indentation.
10682 if delta_for_start_row > 0 {
10683 start_row += 1;
10684 selection.start.column += delta_for_start_row;
10685 if selection.end.row == selection.start.row {
10686 selection.end.column += delta_for_start_row;
10687 }
10688 }
10689
10690 let mut delta_for_end_row = 0;
10691 let has_multiple_rows = start_row + 1 != end_row;
10692 for row in start_row..end_row {
10693 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10694 let indent_delta = match (current_indent.kind, indent_kind) {
10695 (IndentKind::Space, IndentKind::Space) => {
10696 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10697 IndentSize::spaces(columns_to_next_tab_stop)
10698 }
10699 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10700 (_, IndentKind::Tab) => IndentSize::tab(),
10701 };
10702
10703 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10704 0
10705 } else {
10706 selection.start.column
10707 };
10708 let row_start = Point::new(row, start);
10709 edits.push((
10710 row_start..row_start,
10711 indent_delta.chars().collect::<String>(),
10712 ));
10713
10714 // Update this selection's endpoints to reflect the indentation.
10715 if row == selection.start.row {
10716 selection.start.column += indent_delta.len;
10717 }
10718 if row == selection.end.row {
10719 selection.end.column += indent_delta.len;
10720 delta_for_end_row = indent_delta.len;
10721 }
10722 }
10723
10724 if selection.start.row == selection.end.row {
10725 delta_for_start_row + delta_for_end_row
10726 } else {
10727 delta_for_end_row
10728 }
10729 }
10730
10731 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10732 if self.read_only(cx) {
10733 return;
10734 }
10735 if self.mode.is_single_line() {
10736 cx.propagate();
10737 return;
10738 }
10739
10740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10741 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10742 let selections = self.selections.all::<Point>(&display_map);
10743 let mut deletion_ranges = Vec::new();
10744 let mut last_outdent = None;
10745 {
10746 let buffer = self.buffer.read(cx);
10747 let snapshot = buffer.snapshot(cx);
10748 for selection in &selections {
10749 let settings = buffer.language_settings_at(selection.start, cx);
10750 let tab_size = settings.tab_size.get();
10751 let mut rows = selection.spanned_rows(false, &display_map);
10752
10753 // Avoid re-outdenting a row that has already been outdented by a
10754 // previous selection.
10755 if let Some(last_row) = last_outdent
10756 && last_row == rows.start
10757 {
10758 rows.start = rows.start.next_row();
10759 }
10760 let has_multiple_rows = rows.len() > 1;
10761 for row in rows.iter_rows() {
10762 let indent_size = snapshot.indent_size_for_line(row);
10763 if indent_size.len > 0 {
10764 let deletion_len = match indent_size.kind {
10765 IndentKind::Space => {
10766 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10767 if columns_to_prev_tab_stop == 0 {
10768 tab_size
10769 } else {
10770 columns_to_prev_tab_stop
10771 }
10772 }
10773 IndentKind::Tab => 1,
10774 };
10775 let start = if has_multiple_rows
10776 || deletion_len > selection.start.column
10777 || indent_size.len < selection.start.column
10778 {
10779 0
10780 } else {
10781 selection.start.column - deletion_len
10782 };
10783 deletion_ranges.push(
10784 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10785 );
10786 last_outdent = Some(row);
10787 }
10788 }
10789 }
10790 }
10791
10792 self.transact(window, cx, |this, window, cx| {
10793 this.buffer.update(cx, |buffer, cx| {
10794 let empty_str: Arc<str> = Arc::default();
10795 buffer.edit(
10796 deletion_ranges
10797 .into_iter()
10798 .map(|range| (range, empty_str.clone())),
10799 None,
10800 cx,
10801 );
10802 });
10803 let selections = this
10804 .selections
10805 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10806 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10807 });
10808 }
10809
10810 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10811 if self.read_only(cx) {
10812 return;
10813 }
10814 if self.mode.is_single_line() {
10815 cx.propagate();
10816 return;
10817 }
10818
10819 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10820 let selections = self
10821 .selections
10822 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10823 .into_iter()
10824 .map(|s| s.range());
10825
10826 self.transact(window, cx, |this, window, cx| {
10827 this.buffer.update(cx, |buffer, cx| {
10828 buffer.autoindent_ranges(selections, cx);
10829 });
10830 let selections = this
10831 .selections
10832 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10833 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10834 });
10835 }
10836
10837 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10838 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10839 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10840 let selections = self.selections.all::<Point>(&display_map);
10841
10842 let mut new_cursors = Vec::new();
10843 let mut edit_ranges = Vec::new();
10844 let mut selections = selections.iter().peekable();
10845 while let Some(selection) = selections.next() {
10846 let mut rows = selection.spanned_rows(false, &display_map);
10847
10848 // Accumulate contiguous regions of rows that we want to delete.
10849 while let Some(next_selection) = selections.peek() {
10850 let next_rows = next_selection.spanned_rows(false, &display_map);
10851 if next_rows.start <= rows.end {
10852 rows.end = next_rows.end;
10853 selections.next().unwrap();
10854 } else {
10855 break;
10856 }
10857 }
10858
10859 let buffer = display_map.buffer_snapshot();
10860 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10861 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10862 // If there's a line after the range, delete the \n from the end of the row range
10863 (
10864 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10865 rows.end,
10866 )
10867 } else {
10868 // If there isn't a line after the range, delete the \n from the line before the
10869 // start of the row range
10870 edit_start = edit_start.saturating_sub_usize(1);
10871 (buffer.len(), rows.start.previous_row())
10872 };
10873
10874 let text_layout_details = self.text_layout_details(window);
10875 let x = display_map.x_for_display_point(
10876 selection.head().to_display_point(&display_map),
10877 &text_layout_details,
10878 );
10879 let row = Point::new(target_row.0, 0)
10880 .to_display_point(&display_map)
10881 .row();
10882 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10883
10884 new_cursors.push((
10885 selection.id,
10886 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10887 SelectionGoal::None,
10888 ));
10889 edit_ranges.push(edit_start..edit_end);
10890 }
10891
10892 self.transact(window, cx, |this, window, cx| {
10893 let buffer = this.buffer.update(cx, |buffer, cx| {
10894 let empty_str: Arc<str> = Arc::default();
10895 buffer.edit(
10896 edit_ranges
10897 .into_iter()
10898 .map(|range| (range, empty_str.clone())),
10899 None,
10900 cx,
10901 );
10902 buffer.snapshot(cx)
10903 });
10904 let new_selections = new_cursors
10905 .into_iter()
10906 .map(|(id, cursor, goal)| {
10907 let cursor = cursor.to_point(&buffer);
10908 Selection {
10909 id,
10910 start: cursor,
10911 end: cursor,
10912 reversed: false,
10913 goal,
10914 }
10915 })
10916 .collect();
10917
10918 this.change_selections(Default::default(), window, cx, |s| {
10919 s.select(new_selections);
10920 });
10921 });
10922 }
10923
10924 pub fn join_lines_impl(
10925 &mut self,
10926 insert_whitespace: bool,
10927 window: &mut Window,
10928 cx: &mut Context<Self>,
10929 ) {
10930 if self.read_only(cx) {
10931 return;
10932 }
10933 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10934 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10935 let start = MultiBufferRow(selection.start.row);
10936 // Treat single line selections as if they include the next line. Otherwise this action
10937 // would do nothing for single line selections individual cursors.
10938 let end = if selection.start.row == selection.end.row {
10939 MultiBufferRow(selection.start.row + 1)
10940 } else {
10941 MultiBufferRow(selection.end.row)
10942 };
10943
10944 if let Some(last_row_range) = row_ranges.last_mut()
10945 && start <= last_row_range.end
10946 {
10947 last_row_range.end = end;
10948 continue;
10949 }
10950 row_ranges.push(start..end);
10951 }
10952
10953 let snapshot = self.buffer.read(cx).snapshot(cx);
10954 let mut cursor_positions = Vec::new();
10955 for row_range in &row_ranges {
10956 let anchor = snapshot.anchor_before(Point::new(
10957 row_range.end.previous_row().0,
10958 snapshot.line_len(row_range.end.previous_row()),
10959 ));
10960 cursor_positions.push(anchor..anchor);
10961 }
10962
10963 self.transact(window, cx, |this, window, cx| {
10964 for row_range in row_ranges.into_iter().rev() {
10965 for row in row_range.iter_rows().rev() {
10966 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10967 let next_line_row = row.next_row();
10968 let indent = snapshot.indent_size_for_line(next_line_row);
10969 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10970
10971 let replace =
10972 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10973 " "
10974 } else {
10975 ""
10976 };
10977
10978 this.buffer.update(cx, |buffer, cx| {
10979 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10980 });
10981 }
10982 }
10983
10984 this.change_selections(Default::default(), window, cx, |s| {
10985 s.select_anchor_ranges(cursor_positions)
10986 });
10987 });
10988 }
10989
10990 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10991 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10992 self.join_lines_impl(true, window, cx);
10993 }
10994
10995 pub fn sort_lines_case_sensitive(
10996 &mut self,
10997 _: &SortLinesCaseSensitive,
10998 window: &mut Window,
10999 cx: &mut Context<Self>,
11000 ) {
11001 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11002 }
11003
11004 pub fn sort_lines_by_length(
11005 &mut self,
11006 _: &SortLinesByLength,
11007 window: &mut Window,
11008 cx: &mut Context<Self>,
11009 ) {
11010 self.manipulate_immutable_lines(window, cx, |lines| {
11011 lines.sort_by_key(|&line| line.chars().count())
11012 })
11013 }
11014
11015 pub fn sort_lines_case_insensitive(
11016 &mut self,
11017 _: &SortLinesCaseInsensitive,
11018 window: &mut Window,
11019 cx: &mut Context<Self>,
11020 ) {
11021 self.manipulate_immutable_lines(window, cx, |lines| {
11022 lines.sort_by_key(|line| line.to_lowercase())
11023 })
11024 }
11025
11026 pub fn unique_lines_case_insensitive(
11027 &mut self,
11028 _: &UniqueLinesCaseInsensitive,
11029 window: &mut Window,
11030 cx: &mut Context<Self>,
11031 ) {
11032 self.manipulate_immutable_lines(window, cx, |lines| {
11033 let mut seen = HashSet::default();
11034 lines.retain(|line| seen.insert(line.to_lowercase()));
11035 })
11036 }
11037
11038 pub fn unique_lines_case_sensitive(
11039 &mut self,
11040 _: &UniqueLinesCaseSensitive,
11041 window: &mut Window,
11042 cx: &mut Context<Self>,
11043 ) {
11044 self.manipulate_immutable_lines(window, cx, |lines| {
11045 let mut seen = HashSet::default();
11046 lines.retain(|line| seen.insert(*line));
11047 })
11048 }
11049
11050 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11051 let snapshot = self.buffer.read(cx).snapshot(cx);
11052 for selection in self.selections.disjoint_anchors_arc().iter() {
11053 if snapshot
11054 .language_at(selection.start)
11055 .and_then(|lang| lang.config().wrap_characters.as_ref())
11056 .is_some()
11057 {
11058 return true;
11059 }
11060 }
11061 false
11062 }
11063
11064 fn wrap_selections_in_tag(
11065 &mut self,
11066 _: &WrapSelectionsInTag,
11067 window: &mut Window,
11068 cx: &mut Context<Self>,
11069 ) {
11070 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11071
11072 let snapshot = self.buffer.read(cx).snapshot(cx);
11073
11074 let mut edits = Vec::new();
11075 let mut boundaries = Vec::new();
11076
11077 for selection in self
11078 .selections
11079 .all_adjusted(&self.display_snapshot(cx))
11080 .iter()
11081 {
11082 let Some(wrap_config) = snapshot
11083 .language_at(selection.start)
11084 .and_then(|lang| lang.config().wrap_characters.clone())
11085 else {
11086 continue;
11087 };
11088
11089 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11090 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11091
11092 let start_before = snapshot.anchor_before(selection.start);
11093 let end_after = snapshot.anchor_after(selection.end);
11094
11095 edits.push((start_before..start_before, open_tag));
11096 edits.push((end_after..end_after, close_tag));
11097
11098 boundaries.push((
11099 start_before,
11100 end_after,
11101 wrap_config.start_prefix.len(),
11102 wrap_config.end_suffix.len(),
11103 ));
11104 }
11105
11106 if edits.is_empty() {
11107 return;
11108 }
11109
11110 self.transact(window, cx, |this, window, cx| {
11111 let buffer = this.buffer.update(cx, |buffer, cx| {
11112 buffer.edit(edits, None, cx);
11113 buffer.snapshot(cx)
11114 });
11115
11116 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11117 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11118 boundaries.into_iter()
11119 {
11120 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11121 let close_offset = end_after
11122 .to_offset(&buffer)
11123 .saturating_sub_usize(end_suffix_len);
11124 new_selections.push(open_offset..open_offset);
11125 new_selections.push(close_offset..close_offset);
11126 }
11127
11128 this.change_selections(Default::default(), window, cx, |s| {
11129 s.select_ranges(new_selections);
11130 });
11131
11132 this.request_autoscroll(Autoscroll::fit(), cx);
11133 });
11134 }
11135
11136 pub fn toggle_read_only(
11137 &mut self,
11138 _: &workspace::ToggleReadOnlyFile,
11139 _: &mut Window,
11140 cx: &mut Context<Self>,
11141 ) {
11142 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11143 buffer.update(cx, |buffer, cx| {
11144 buffer.set_capability(
11145 match buffer.capability() {
11146 Capability::ReadWrite => Capability::Read,
11147 Capability::Read => Capability::ReadWrite,
11148 Capability::ReadOnly => Capability::ReadOnly,
11149 },
11150 cx,
11151 );
11152 })
11153 }
11154 }
11155
11156 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11157 let Some(project) = self.project.clone() else {
11158 return;
11159 };
11160 self.reload(project, window, cx)
11161 .detach_and_notify_err(window, cx);
11162 }
11163
11164 pub fn restore_file(
11165 &mut self,
11166 _: &::git::RestoreFile,
11167 window: &mut Window,
11168 cx: &mut Context<Self>,
11169 ) {
11170 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11171 let mut buffer_ids = HashSet::default();
11172 let snapshot = self.buffer().read(cx).snapshot(cx);
11173 for selection in self
11174 .selections
11175 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11176 {
11177 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11178 }
11179
11180 let buffer = self.buffer().read(cx);
11181 let ranges = buffer_ids
11182 .into_iter()
11183 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11184 .collect::<Vec<_>>();
11185
11186 self.restore_hunks_in_ranges(ranges, window, cx);
11187 }
11188
11189 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11190 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11191 let selections = self
11192 .selections
11193 .all(&self.display_snapshot(cx))
11194 .into_iter()
11195 .map(|s| s.range())
11196 .collect();
11197 self.restore_hunks_in_ranges(selections, window, cx);
11198 }
11199
11200 pub fn restore_hunks_in_ranges(
11201 &mut self,
11202 ranges: Vec<Range<Point>>,
11203 window: &mut Window,
11204 cx: &mut Context<Editor>,
11205 ) {
11206 let mut revert_changes = HashMap::default();
11207 let chunk_by = self
11208 .snapshot(window, cx)
11209 .hunks_for_ranges(ranges)
11210 .into_iter()
11211 .chunk_by(|hunk| hunk.buffer_id);
11212 for (buffer_id, hunks) in &chunk_by {
11213 let hunks = hunks.collect::<Vec<_>>();
11214 for hunk in &hunks {
11215 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11216 }
11217 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11218 }
11219 drop(chunk_by);
11220 if !revert_changes.is_empty() {
11221 self.transact(window, cx, |editor, window, cx| {
11222 editor.restore(revert_changes, window, cx);
11223 });
11224 }
11225 }
11226
11227 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11228 if let Some(status) = self
11229 .addons
11230 .iter()
11231 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11232 {
11233 return Some(status);
11234 }
11235 self.project
11236 .as_ref()?
11237 .read(cx)
11238 .status_for_buffer_id(buffer_id, cx)
11239 }
11240
11241 pub fn open_active_item_in_terminal(
11242 &mut self,
11243 _: &OpenInTerminal,
11244 window: &mut Window,
11245 cx: &mut Context<Self>,
11246 ) {
11247 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11248 let project_path = buffer.read(cx).project_path(cx)?;
11249 let project = self.project()?.read(cx);
11250 let entry = project.entry_for_path(&project_path, cx)?;
11251 let parent = match &entry.canonical_path {
11252 Some(canonical_path) => canonical_path.to_path_buf(),
11253 None => project.absolute_path(&project_path, cx)?,
11254 }
11255 .parent()?
11256 .to_path_buf();
11257 Some(parent)
11258 }) {
11259 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11260 }
11261 }
11262
11263 fn set_breakpoint_context_menu(
11264 &mut self,
11265 display_row: DisplayRow,
11266 position: Option<Anchor>,
11267 clicked_point: gpui::Point<Pixels>,
11268 window: &mut Window,
11269 cx: &mut Context<Self>,
11270 ) {
11271 let source = self
11272 .buffer
11273 .read(cx)
11274 .snapshot(cx)
11275 .anchor_before(Point::new(display_row.0, 0u32));
11276
11277 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11278
11279 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11280 self,
11281 source,
11282 clicked_point,
11283 context_menu,
11284 window,
11285 cx,
11286 );
11287 }
11288
11289 fn add_edit_breakpoint_block(
11290 &mut self,
11291 anchor: Anchor,
11292 breakpoint: &Breakpoint,
11293 edit_action: BreakpointPromptEditAction,
11294 window: &mut Window,
11295 cx: &mut Context<Self>,
11296 ) {
11297 let weak_editor = cx.weak_entity();
11298 let bp_prompt = cx.new(|cx| {
11299 BreakpointPromptEditor::new(
11300 weak_editor,
11301 anchor,
11302 breakpoint.clone(),
11303 edit_action,
11304 window,
11305 cx,
11306 )
11307 });
11308
11309 let height = bp_prompt.update(cx, |this, cx| {
11310 this.prompt
11311 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11312 });
11313 let cloned_prompt = bp_prompt.clone();
11314 let blocks = vec![BlockProperties {
11315 style: BlockStyle::Sticky,
11316 placement: BlockPlacement::Above(anchor),
11317 height: Some(height),
11318 render: Arc::new(move |cx| {
11319 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11320 cloned_prompt.clone().into_any_element()
11321 }),
11322 priority: 0,
11323 }];
11324
11325 let focus_handle = bp_prompt.focus_handle(cx);
11326 window.focus(&focus_handle, cx);
11327
11328 let block_ids = self.insert_blocks(blocks, None, cx);
11329 bp_prompt.update(cx, |prompt, _| {
11330 prompt.add_block_ids(block_ids);
11331 });
11332 }
11333
11334 pub(crate) fn breakpoint_at_row(
11335 &self,
11336 row: u32,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) -> Option<(Anchor, Breakpoint)> {
11340 let snapshot = self.snapshot(window, cx);
11341 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11342
11343 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11344 }
11345
11346 pub(crate) fn breakpoint_at_anchor(
11347 &self,
11348 breakpoint_position: Anchor,
11349 snapshot: &EditorSnapshot,
11350 cx: &mut Context<Self>,
11351 ) -> Option<(Anchor, Breakpoint)> {
11352 let buffer = self
11353 .buffer
11354 .read(cx)
11355 .buffer_for_anchor(breakpoint_position, cx)?;
11356
11357 let enclosing_excerpt = breakpoint_position.excerpt_id;
11358 let buffer_snapshot = buffer.read(cx).snapshot();
11359
11360 let row = buffer_snapshot
11361 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11362 .row;
11363
11364 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11365 let anchor_end = snapshot
11366 .buffer_snapshot()
11367 .anchor_after(Point::new(row, line_len));
11368
11369 self.breakpoint_store
11370 .as_ref()?
11371 .read_with(cx, |breakpoint_store, cx| {
11372 breakpoint_store
11373 .breakpoints(
11374 &buffer,
11375 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11376 &buffer_snapshot,
11377 cx,
11378 )
11379 .next()
11380 .and_then(|(bp, _)| {
11381 let breakpoint_row = buffer_snapshot
11382 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11383 .row;
11384
11385 if breakpoint_row == row {
11386 snapshot
11387 .buffer_snapshot()
11388 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11389 .map(|position| (position, bp.bp.clone()))
11390 } else {
11391 None
11392 }
11393 })
11394 })
11395 }
11396
11397 pub fn edit_log_breakpoint(
11398 &mut self,
11399 _: &EditLogBreakpoint,
11400 window: &mut Window,
11401 cx: &mut Context<Self>,
11402 ) {
11403 if self.breakpoint_store.is_none() {
11404 return;
11405 }
11406
11407 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11408 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11409 message: None,
11410 state: BreakpointState::Enabled,
11411 condition: None,
11412 hit_condition: None,
11413 });
11414
11415 self.add_edit_breakpoint_block(
11416 anchor,
11417 &breakpoint,
11418 BreakpointPromptEditAction::Log,
11419 window,
11420 cx,
11421 );
11422 }
11423 }
11424
11425 fn breakpoints_at_cursors(
11426 &self,
11427 window: &mut Window,
11428 cx: &mut Context<Self>,
11429 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11430 let snapshot = self.snapshot(window, cx);
11431 let cursors = self
11432 .selections
11433 .disjoint_anchors_arc()
11434 .iter()
11435 .map(|selection| {
11436 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11437
11438 let breakpoint_position = self
11439 .breakpoint_at_row(cursor_position.row, window, cx)
11440 .map(|bp| bp.0)
11441 .unwrap_or_else(|| {
11442 snapshot
11443 .display_snapshot
11444 .buffer_snapshot()
11445 .anchor_after(Point::new(cursor_position.row, 0))
11446 });
11447
11448 let breakpoint = self
11449 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11450 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11451
11452 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11453 })
11454 // 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.
11455 .collect::<HashMap<Anchor, _>>();
11456
11457 cursors.into_iter().collect()
11458 }
11459
11460 pub fn enable_breakpoint(
11461 &mut self,
11462 _: &crate::actions::EnableBreakpoint,
11463 window: &mut Window,
11464 cx: &mut Context<Self>,
11465 ) {
11466 if self.breakpoint_store.is_none() {
11467 return;
11468 }
11469
11470 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11471 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11472 continue;
11473 };
11474 self.edit_breakpoint_at_anchor(
11475 anchor,
11476 breakpoint,
11477 BreakpointEditAction::InvertState,
11478 cx,
11479 );
11480 }
11481 }
11482
11483 pub fn disable_breakpoint(
11484 &mut self,
11485 _: &crate::actions::DisableBreakpoint,
11486 window: &mut Window,
11487 cx: &mut Context<Self>,
11488 ) {
11489 if self.breakpoint_store.is_none() {
11490 return;
11491 }
11492
11493 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11494 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11495 continue;
11496 };
11497 self.edit_breakpoint_at_anchor(
11498 anchor,
11499 breakpoint,
11500 BreakpointEditAction::InvertState,
11501 cx,
11502 );
11503 }
11504 }
11505
11506 pub fn toggle_breakpoint(
11507 &mut self,
11508 _: &crate::actions::ToggleBreakpoint,
11509 window: &mut Window,
11510 cx: &mut Context<Self>,
11511 ) {
11512 if self.breakpoint_store.is_none() {
11513 return;
11514 }
11515
11516 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11517 if let Some(breakpoint) = breakpoint {
11518 self.edit_breakpoint_at_anchor(
11519 anchor,
11520 breakpoint,
11521 BreakpointEditAction::Toggle,
11522 cx,
11523 );
11524 } else {
11525 self.edit_breakpoint_at_anchor(
11526 anchor,
11527 Breakpoint::new_standard(),
11528 BreakpointEditAction::Toggle,
11529 cx,
11530 );
11531 }
11532 }
11533 }
11534
11535 pub fn edit_breakpoint_at_anchor(
11536 &mut self,
11537 breakpoint_position: Anchor,
11538 breakpoint: Breakpoint,
11539 edit_action: BreakpointEditAction,
11540 cx: &mut Context<Self>,
11541 ) {
11542 let Some(breakpoint_store) = &self.breakpoint_store else {
11543 return;
11544 };
11545
11546 let Some(buffer) = self
11547 .buffer
11548 .read(cx)
11549 .buffer_for_anchor(breakpoint_position, cx)
11550 else {
11551 return;
11552 };
11553
11554 breakpoint_store.update(cx, |breakpoint_store, cx| {
11555 breakpoint_store.toggle_breakpoint(
11556 buffer,
11557 BreakpointWithPosition {
11558 position: breakpoint_position.text_anchor,
11559 bp: breakpoint,
11560 },
11561 edit_action,
11562 cx,
11563 );
11564 });
11565
11566 cx.notify();
11567 }
11568
11569 #[cfg(any(test, feature = "test-support"))]
11570 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11571 self.breakpoint_store.clone()
11572 }
11573
11574 pub fn prepare_restore_change(
11575 &self,
11576 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11577 hunk: &MultiBufferDiffHunk,
11578 cx: &mut App,
11579 ) -> Option<()> {
11580 if hunk.is_created_file() {
11581 return None;
11582 }
11583 let buffer = self.buffer.read(cx);
11584 let diff = buffer.diff_for(hunk.buffer_id)?;
11585 let buffer = buffer.buffer(hunk.buffer_id)?;
11586 let buffer = buffer.read(cx);
11587 let original_text = diff
11588 .read(cx)
11589 .base_text(cx)
11590 .as_rope()
11591 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11592 let buffer_snapshot = buffer.snapshot();
11593 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11594 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11595 probe
11596 .0
11597 .start
11598 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11599 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11600 }) {
11601 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11602 Some(())
11603 } else {
11604 None
11605 }
11606 }
11607
11608 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11609 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11610 }
11611
11612 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11613 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11614 }
11615
11616 pub fn rotate_selections_forward(
11617 &mut self,
11618 _: &RotateSelectionsForward,
11619 window: &mut Window,
11620 cx: &mut Context<Self>,
11621 ) {
11622 self.rotate_selections(window, cx, false)
11623 }
11624
11625 pub fn rotate_selections_backward(
11626 &mut self,
11627 _: &RotateSelectionsBackward,
11628 window: &mut Window,
11629 cx: &mut Context<Self>,
11630 ) {
11631 self.rotate_selections(window, cx, true)
11632 }
11633
11634 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11635 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11636 let display_snapshot = self.display_snapshot(cx);
11637 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11638
11639 if selections.len() < 2 {
11640 return;
11641 }
11642
11643 let (edits, new_selections) = {
11644 let buffer = self.buffer.read(cx).read(cx);
11645 let has_selections = selections.iter().any(|s| !s.is_empty());
11646 if has_selections {
11647 let mut selected_texts: Vec<String> = selections
11648 .iter()
11649 .map(|selection| {
11650 buffer
11651 .text_for_range(selection.start..selection.end)
11652 .collect()
11653 })
11654 .collect();
11655
11656 if reverse {
11657 selected_texts.rotate_left(1);
11658 } else {
11659 selected_texts.rotate_right(1);
11660 }
11661
11662 let mut offset_delta: i64 = 0;
11663 let mut new_selections = Vec::new();
11664 let edits: Vec<_> = selections
11665 .iter()
11666 .zip(selected_texts.iter())
11667 .map(|(selection, new_text)| {
11668 let old_len = (selection.end.0 - selection.start.0) as i64;
11669 let new_len = new_text.len() as i64;
11670 let adjusted_start =
11671 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11672 let adjusted_end =
11673 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11674
11675 new_selections.push(Selection {
11676 id: selection.id,
11677 start: adjusted_start,
11678 end: adjusted_end,
11679 reversed: selection.reversed,
11680 goal: selection.goal,
11681 });
11682
11683 offset_delta += new_len - old_len;
11684 (selection.start..selection.end, new_text.clone())
11685 })
11686 .collect();
11687 (edits, new_selections)
11688 } else {
11689 let mut all_rows: Vec<u32> = selections
11690 .iter()
11691 .map(|selection| buffer.offset_to_point(selection.start).row)
11692 .collect();
11693 all_rows.sort_unstable();
11694 all_rows.dedup();
11695
11696 if all_rows.len() < 2 {
11697 return;
11698 }
11699
11700 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11701 .iter()
11702 .map(|&row| {
11703 let start = Point::new(row, 0);
11704 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11705 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11706 })
11707 .collect();
11708
11709 let mut line_texts: Vec<String> = line_ranges
11710 .iter()
11711 .map(|range| buffer.text_for_range(range.clone()).collect())
11712 .collect();
11713
11714 if reverse {
11715 line_texts.rotate_left(1);
11716 } else {
11717 line_texts.rotate_right(1);
11718 }
11719
11720 let edits = line_ranges
11721 .iter()
11722 .zip(line_texts.iter())
11723 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11724 .collect();
11725
11726 let num_rows = all_rows.len();
11727 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11728 .iter()
11729 .enumerate()
11730 .map(|(i, &row)| (row, i))
11731 .collect();
11732
11733 // Compute new line start offsets after rotation (handles CRLF)
11734 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11735 let first_line_start = line_ranges[0].start.0;
11736 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11737 for text in line_texts.iter().take(num_rows - 1) {
11738 let prev_start = *new_line_starts.last().unwrap();
11739 new_line_starts.push(prev_start + text.len() + newline_len);
11740 }
11741
11742 let new_selections = selections
11743 .iter()
11744 .map(|selection| {
11745 let point = buffer.offset_to_point(selection.start);
11746 let old_index = row_to_index[&point.row];
11747 let new_index = if reverse {
11748 (old_index + num_rows - 1) % num_rows
11749 } else {
11750 (old_index + 1) % num_rows
11751 };
11752 let new_offset =
11753 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11754 Selection {
11755 id: selection.id,
11756 start: new_offset,
11757 end: new_offset,
11758 reversed: selection.reversed,
11759 goal: selection.goal,
11760 }
11761 })
11762 .collect();
11763
11764 (edits, new_selections)
11765 }
11766 };
11767
11768 self.transact(window, cx, |this, window, cx| {
11769 this.buffer.update(cx, |buffer, cx| {
11770 buffer.edit(edits, None, cx);
11771 });
11772 this.change_selections(Default::default(), window, cx, |s| {
11773 s.select(new_selections);
11774 });
11775 });
11776 }
11777
11778 fn manipulate_lines<M>(
11779 &mut self,
11780 window: &mut Window,
11781 cx: &mut Context<Self>,
11782 mut manipulate: M,
11783 ) where
11784 M: FnMut(&str) -> LineManipulationResult,
11785 {
11786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11787
11788 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11789 let buffer = self.buffer.read(cx).snapshot(cx);
11790
11791 let mut edits = Vec::new();
11792
11793 let selections = self.selections.all::<Point>(&display_map);
11794 let mut selections = selections.iter().peekable();
11795 let mut contiguous_row_selections = Vec::new();
11796 let mut new_selections = Vec::new();
11797 let mut added_lines = 0;
11798 let mut removed_lines = 0;
11799
11800 while let Some(selection) = selections.next() {
11801 let (start_row, end_row) = consume_contiguous_rows(
11802 &mut contiguous_row_selections,
11803 selection,
11804 &display_map,
11805 &mut selections,
11806 );
11807
11808 let start_point = Point::new(start_row.0, 0);
11809 let end_point = Point::new(
11810 end_row.previous_row().0,
11811 buffer.line_len(end_row.previous_row()),
11812 );
11813 let text = buffer
11814 .text_for_range(start_point..end_point)
11815 .collect::<String>();
11816
11817 let LineManipulationResult {
11818 new_text,
11819 line_count_before,
11820 line_count_after,
11821 } = manipulate(&text);
11822
11823 edits.push((start_point..end_point, new_text));
11824
11825 // Selections must change based on added and removed line count
11826 let start_row =
11827 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11828 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11829 new_selections.push(Selection {
11830 id: selection.id,
11831 start: start_row,
11832 end: end_row,
11833 goal: SelectionGoal::None,
11834 reversed: selection.reversed,
11835 });
11836
11837 if line_count_after > line_count_before {
11838 added_lines += line_count_after - line_count_before;
11839 } else if line_count_before > line_count_after {
11840 removed_lines += line_count_before - line_count_after;
11841 }
11842 }
11843
11844 self.transact(window, cx, |this, window, cx| {
11845 let buffer = this.buffer.update(cx, |buffer, cx| {
11846 buffer.edit(edits, None, cx);
11847 buffer.snapshot(cx)
11848 });
11849
11850 // Recalculate offsets on newly edited buffer
11851 let new_selections = new_selections
11852 .iter()
11853 .map(|s| {
11854 let start_point = Point::new(s.start.0, 0);
11855 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11856 Selection {
11857 id: s.id,
11858 start: buffer.point_to_offset(start_point),
11859 end: buffer.point_to_offset(end_point),
11860 goal: s.goal,
11861 reversed: s.reversed,
11862 }
11863 })
11864 .collect();
11865
11866 this.change_selections(Default::default(), window, cx, |s| {
11867 s.select(new_selections);
11868 });
11869
11870 this.request_autoscroll(Autoscroll::fit(), cx);
11871 });
11872 }
11873
11874 fn manipulate_immutable_lines<Fn>(
11875 &mut self,
11876 window: &mut Window,
11877 cx: &mut Context<Self>,
11878 mut callback: Fn,
11879 ) where
11880 Fn: FnMut(&mut Vec<&str>),
11881 {
11882 self.manipulate_lines(window, cx, |text| {
11883 let mut lines: Vec<&str> = text.split('\n').collect();
11884 let line_count_before = lines.len();
11885
11886 callback(&mut lines);
11887
11888 LineManipulationResult {
11889 new_text: lines.join("\n"),
11890 line_count_before,
11891 line_count_after: lines.len(),
11892 }
11893 });
11894 }
11895
11896 fn manipulate_mutable_lines<Fn>(
11897 &mut self,
11898 window: &mut Window,
11899 cx: &mut Context<Self>,
11900 mut callback: Fn,
11901 ) where
11902 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11903 {
11904 self.manipulate_lines(window, cx, |text| {
11905 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11906 let line_count_before = lines.len();
11907
11908 callback(&mut lines);
11909
11910 LineManipulationResult {
11911 new_text: lines.join("\n"),
11912 line_count_before,
11913 line_count_after: lines.len(),
11914 }
11915 });
11916 }
11917
11918 pub fn convert_indentation_to_spaces(
11919 &mut self,
11920 _: &ConvertIndentationToSpaces,
11921 window: &mut Window,
11922 cx: &mut Context<Self>,
11923 ) {
11924 let settings = self.buffer.read(cx).language_settings(cx);
11925 let tab_size = settings.tab_size.get() as usize;
11926
11927 self.manipulate_mutable_lines(window, cx, |lines| {
11928 // Allocates a reasonably sized scratch buffer once for the whole loop
11929 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11930 // Avoids recomputing spaces that could be inserted many times
11931 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11932 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11933 .collect();
11934
11935 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11936 let mut chars = line.as_ref().chars();
11937 let mut col = 0;
11938 let mut changed = false;
11939
11940 for ch in chars.by_ref() {
11941 match ch {
11942 ' ' => {
11943 reindented_line.push(' ');
11944 col += 1;
11945 }
11946 '\t' => {
11947 // \t are converted to spaces depending on the current column
11948 let spaces_len = tab_size - (col % tab_size);
11949 reindented_line.extend(&space_cache[spaces_len - 1]);
11950 col += spaces_len;
11951 changed = true;
11952 }
11953 _ => {
11954 // If we dont append before break, the character is consumed
11955 reindented_line.push(ch);
11956 break;
11957 }
11958 }
11959 }
11960
11961 if !changed {
11962 reindented_line.clear();
11963 continue;
11964 }
11965 // Append the rest of the line and replace old reference with new one
11966 reindented_line.extend(chars);
11967 *line = Cow::Owned(reindented_line.clone());
11968 reindented_line.clear();
11969 }
11970 });
11971 }
11972
11973 pub fn convert_indentation_to_tabs(
11974 &mut self,
11975 _: &ConvertIndentationToTabs,
11976 window: &mut Window,
11977 cx: &mut Context<Self>,
11978 ) {
11979 let settings = self.buffer.read(cx).language_settings(cx);
11980 let tab_size = settings.tab_size.get() as usize;
11981
11982 self.manipulate_mutable_lines(window, cx, |lines| {
11983 // Allocates a reasonably sized buffer once for the whole loop
11984 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11985 // Avoids recomputing spaces that could be inserted many times
11986 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11987 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11988 .collect();
11989
11990 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11991 let mut chars = line.chars();
11992 let mut spaces_count = 0;
11993 let mut first_non_indent_char = None;
11994 let mut changed = false;
11995
11996 for ch in chars.by_ref() {
11997 match ch {
11998 ' ' => {
11999 // Keep track of spaces. Append \t when we reach tab_size
12000 spaces_count += 1;
12001 changed = true;
12002 if spaces_count == tab_size {
12003 reindented_line.push('\t');
12004 spaces_count = 0;
12005 }
12006 }
12007 '\t' => {
12008 reindented_line.push('\t');
12009 spaces_count = 0;
12010 }
12011 _ => {
12012 // Dont append it yet, we might have remaining spaces
12013 first_non_indent_char = Some(ch);
12014 break;
12015 }
12016 }
12017 }
12018
12019 if !changed {
12020 reindented_line.clear();
12021 continue;
12022 }
12023 // Remaining spaces that didn't make a full tab stop
12024 if spaces_count > 0 {
12025 reindented_line.extend(&space_cache[spaces_count - 1]);
12026 }
12027 // If we consume an extra character that was not indentation, add it back
12028 if let Some(extra_char) = first_non_indent_char {
12029 reindented_line.push(extra_char);
12030 }
12031 // Append the rest of the line and replace old reference with new one
12032 reindented_line.extend(chars);
12033 *line = Cow::Owned(reindented_line.clone());
12034 reindented_line.clear();
12035 }
12036 });
12037 }
12038
12039 pub fn convert_to_upper_case(
12040 &mut self,
12041 _: &ConvertToUpperCase,
12042 window: &mut Window,
12043 cx: &mut Context<Self>,
12044 ) {
12045 self.manipulate_text(window, cx, |text| text.to_uppercase())
12046 }
12047
12048 pub fn convert_to_lower_case(
12049 &mut self,
12050 _: &ConvertToLowerCase,
12051 window: &mut Window,
12052 cx: &mut Context<Self>,
12053 ) {
12054 self.manipulate_text(window, cx, |text| text.to_lowercase())
12055 }
12056
12057 pub fn convert_to_title_case(
12058 &mut self,
12059 _: &ConvertToTitleCase,
12060 window: &mut Window,
12061 cx: &mut Context<Self>,
12062 ) {
12063 self.manipulate_text(window, cx, |text| {
12064 text.split('\n')
12065 .map(|line| line.to_case(Case::Title))
12066 .join("\n")
12067 })
12068 }
12069
12070 pub fn convert_to_snake_case(
12071 &mut self,
12072 _: &ConvertToSnakeCase,
12073 window: &mut Window,
12074 cx: &mut Context<Self>,
12075 ) {
12076 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12077 }
12078
12079 pub fn convert_to_kebab_case(
12080 &mut self,
12081 _: &ConvertToKebabCase,
12082 window: &mut Window,
12083 cx: &mut Context<Self>,
12084 ) {
12085 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12086 }
12087
12088 pub fn convert_to_upper_camel_case(
12089 &mut self,
12090 _: &ConvertToUpperCamelCase,
12091 window: &mut Window,
12092 cx: &mut Context<Self>,
12093 ) {
12094 self.manipulate_text(window, cx, |text| {
12095 text.split('\n')
12096 .map(|line| line.to_case(Case::UpperCamel))
12097 .join("\n")
12098 })
12099 }
12100
12101 pub fn convert_to_lower_camel_case(
12102 &mut self,
12103 _: &ConvertToLowerCamelCase,
12104 window: &mut Window,
12105 cx: &mut Context<Self>,
12106 ) {
12107 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12108 }
12109
12110 pub fn convert_to_opposite_case(
12111 &mut self,
12112 _: &ConvertToOppositeCase,
12113 window: &mut Window,
12114 cx: &mut Context<Self>,
12115 ) {
12116 self.manipulate_text(window, cx, |text| {
12117 text.chars()
12118 .fold(String::with_capacity(text.len()), |mut t, c| {
12119 if c.is_uppercase() {
12120 t.extend(c.to_lowercase());
12121 } else {
12122 t.extend(c.to_uppercase());
12123 }
12124 t
12125 })
12126 })
12127 }
12128
12129 pub fn convert_to_sentence_case(
12130 &mut self,
12131 _: &ConvertToSentenceCase,
12132 window: &mut Window,
12133 cx: &mut Context<Self>,
12134 ) {
12135 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12136 }
12137
12138 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12139 self.manipulate_text(window, cx, |text| {
12140 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12141 if has_upper_case_characters {
12142 text.to_lowercase()
12143 } else {
12144 text.to_uppercase()
12145 }
12146 })
12147 }
12148
12149 pub fn convert_to_rot13(
12150 &mut self,
12151 _: &ConvertToRot13,
12152 window: &mut Window,
12153 cx: &mut Context<Self>,
12154 ) {
12155 self.manipulate_text(window, cx, |text| {
12156 text.chars()
12157 .map(|c| match c {
12158 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12159 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12160 _ => c,
12161 })
12162 .collect()
12163 })
12164 }
12165
12166 pub fn convert_to_rot47(
12167 &mut self,
12168 _: &ConvertToRot47,
12169 window: &mut Window,
12170 cx: &mut Context<Self>,
12171 ) {
12172 self.manipulate_text(window, cx, |text| {
12173 text.chars()
12174 .map(|c| {
12175 let code_point = c as u32;
12176 if code_point >= 33 && code_point <= 126 {
12177 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12178 }
12179 c
12180 })
12181 .collect()
12182 })
12183 }
12184
12185 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12186 where
12187 Fn: FnMut(&str) -> String,
12188 {
12189 let buffer = self.buffer.read(cx).snapshot(cx);
12190
12191 let mut new_selections = Vec::new();
12192 let mut edits = Vec::new();
12193 let mut selection_adjustment = 0isize;
12194
12195 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12196 let selection_is_empty = selection.is_empty();
12197
12198 let (start, end) = if selection_is_empty {
12199 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12200 (word_range.start, word_range.end)
12201 } else {
12202 (
12203 buffer.point_to_offset(selection.start),
12204 buffer.point_to_offset(selection.end),
12205 )
12206 };
12207
12208 let text = buffer.text_for_range(start..end).collect::<String>();
12209 let old_length = text.len() as isize;
12210 let text = callback(&text);
12211
12212 new_selections.push(Selection {
12213 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12214 end: MultiBufferOffset(
12215 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12216 ),
12217 goal: SelectionGoal::None,
12218 id: selection.id,
12219 reversed: selection.reversed,
12220 });
12221
12222 selection_adjustment += old_length - text.len() as isize;
12223
12224 edits.push((start..end, text));
12225 }
12226
12227 self.transact(window, cx, |this, window, cx| {
12228 this.buffer.update(cx, |buffer, cx| {
12229 buffer.edit(edits, None, cx);
12230 });
12231
12232 this.change_selections(Default::default(), window, cx, |s| {
12233 s.select(new_selections);
12234 });
12235
12236 this.request_autoscroll(Autoscroll::fit(), cx);
12237 });
12238 }
12239
12240 pub fn move_selection_on_drop(
12241 &mut self,
12242 selection: &Selection<Anchor>,
12243 target: DisplayPoint,
12244 is_cut: bool,
12245 window: &mut Window,
12246 cx: &mut Context<Self>,
12247 ) {
12248 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12249 let buffer = display_map.buffer_snapshot();
12250 let mut edits = Vec::new();
12251 let insert_point = display_map
12252 .clip_point(target, Bias::Left)
12253 .to_point(&display_map);
12254 let text = buffer
12255 .text_for_range(selection.start..selection.end)
12256 .collect::<String>();
12257 if is_cut {
12258 edits.push(((selection.start..selection.end), String::new()));
12259 }
12260 let insert_anchor = buffer.anchor_before(insert_point);
12261 edits.push(((insert_anchor..insert_anchor), text));
12262 let last_edit_start = insert_anchor.bias_left(buffer);
12263 let last_edit_end = insert_anchor.bias_right(buffer);
12264 self.transact(window, cx, |this, window, cx| {
12265 this.buffer.update(cx, |buffer, cx| {
12266 buffer.edit(edits, None, cx);
12267 });
12268 this.change_selections(Default::default(), window, cx, |s| {
12269 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12270 });
12271 });
12272 }
12273
12274 pub fn clear_selection_drag_state(&mut self) {
12275 self.selection_drag_state = SelectionDragState::None;
12276 }
12277
12278 pub fn duplicate(
12279 &mut self,
12280 upwards: bool,
12281 whole_lines: bool,
12282 window: &mut Window,
12283 cx: &mut Context<Self>,
12284 ) {
12285 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12286
12287 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12288 let buffer = display_map.buffer_snapshot();
12289 let selections = self.selections.all::<Point>(&display_map);
12290
12291 let mut edits = Vec::new();
12292 let mut selections_iter = selections.iter().peekable();
12293 while let Some(selection) = selections_iter.next() {
12294 let mut rows = selection.spanned_rows(false, &display_map);
12295 // duplicate line-wise
12296 if whole_lines || selection.start == selection.end {
12297 // Avoid duplicating the same lines twice.
12298 while let Some(next_selection) = selections_iter.peek() {
12299 let next_rows = next_selection.spanned_rows(false, &display_map);
12300 if next_rows.start < rows.end {
12301 rows.end = next_rows.end;
12302 selections_iter.next().unwrap();
12303 } else {
12304 break;
12305 }
12306 }
12307
12308 // Copy the text from the selected row region and splice it either at the start
12309 // or end of the region.
12310 let start = Point::new(rows.start.0, 0);
12311 let end = Point::new(
12312 rows.end.previous_row().0,
12313 buffer.line_len(rows.end.previous_row()),
12314 );
12315
12316 let mut text = buffer.text_for_range(start..end).collect::<String>();
12317
12318 let insert_location = if upwards {
12319 // When duplicating upward, we need to insert before the current line.
12320 // If we're on the last line and it doesn't end with a newline,
12321 // we need to add a newline before the duplicated content.
12322 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12323 && buffer.max_point().column > 0
12324 && !text.ends_with('\n');
12325
12326 if needs_leading_newline {
12327 text.insert(0, '\n');
12328 end
12329 } else {
12330 text.push('\n');
12331 Point::new(rows.start.0, 0)
12332 }
12333 } else {
12334 text.push('\n');
12335 start
12336 };
12337 edits.push((insert_location..insert_location, text));
12338 } else {
12339 // duplicate character-wise
12340 let start = selection.start;
12341 let end = selection.end;
12342 let text = buffer.text_for_range(start..end).collect::<String>();
12343 edits.push((selection.end..selection.end, text));
12344 }
12345 }
12346
12347 self.transact(window, cx, |this, window, cx| {
12348 this.buffer.update(cx, |buffer, cx| {
12349 buffer.edit(edits, None, cx);
12350 });
12351
12352 // When duplicating upward with whole lines, move the cursor to the duplicated line
12353 if upwards && whole_lines {
12354 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12355
12356 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12357 let mut new_ranges = Vec::new();
12358 let selections = s.all::<Point>(&display_map);
12359 let mut selections_iter = selections.iter().peekable();
12360
12361 while let Some(first_selection) = selections_iter.next() {
12362 // Group contiguous selections together to find the total row span
12363 let mut group_selections = vec![first_selection];
12364 let mut rows = first_selection.spanned_rows(false, &display_map);
12365
12366 while let Some(next_selection) = selections_iter.peek() {
12367 let next_rows = next_selection.spanned_rows(false, &display_map);
12368 if next_rows.start < rows.end {
12369 rows.end = next_rows.end;
12370 group_selections.push(selections_iter.next().unwrap());
12371 } else {
12372 break;
12373 }
12374 }
12375
12376 let row_count = rows.end.0 - rows.start.0;
12377
12378 // Move all selections in this group up by the total number of duplicated rows
12379 for selection in group_selections {
12380 let new_start = Point::new(
12381 selection.start.row.saturating_sub(row_count),
12382 selection.start.column,
12383 );
12384
12385 let new_end = Point::new(
12386 selection.end.row.saturating_sub(row_count),
12387 selection.end.column,
12388 );
12389
12390 new_ranges.push(new_start..new_end);
12391 }
12392 }
12393
12394 s.select_ranges(new_ranges);
12395 });
12396 }
12397
12398 this.request_autoscroll(Autoscroll::fit(), cx);
12399 });
12400 }
12401
12402 pub fn duplicate_line_up(
12403 &mut self,
12404 _: &DuplicateLineUp,
12405 window: &mut Window,
12406 cx: &mut Context<Self>,
12407 ) {
12408 self.duplicate(true, true, window, cx);
12409 }
12410
12411 pub fn duplicate_line_down(
12412 &mut self,
12413 _: &DuplicateLineDown,
12414 window: &mut Window,
12415 cx: &mut Context<Self>,
12416 ) {
12417 self.duplicate(false, true, window, cx);
12418 }
12419
12420 pub fn duplicate_selection(
12421 &mut self,
12422 _: &DuplicateSelection,
12423 window: &mut Window,
12424 cx: &mut Context<Self>,
12425 ) {
12426 self.duplicate(false, false, window, cx);
12427 }
12428
12429 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12430 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12431 if self.mode.is_single_line() {
12432 cx.propagate();
12433 return;
12434 }
12435
12436 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12437 let buffer = self.buffer.read(cx).snapshot(cx);
12438
12439 let mut edits = Vec::new();
12440 let mut unfold_ranges = Vec::new();
12441 let mut refold_creases = Vec::new();
12442
12443 let selections = self.selections.all::<Point>(&display_map);
12444 let mut selections = selections.iter().peekable();
12445 let mut contiguous_row_selections = Vec::new();
12446 let mut new_selections = Vec::new();
12447
12448 while let Some(selection) = selections.next() {
12449 // Find all the selections that span a contiguous row range
12450 let (start_row, end_row) = consume_contiguous_rows(
12451 &mut contiguous_row_selections,
12452 selection,
12453 &display_map,
12454 &mut selections,
12455 );
12456
12457 // Move the text spanned by the row range to be before the line preceding the row range
12458 if start_row.0 > 0 {
12459 let range_to_move = Point::new(
12460 start_row.previous_row().0,
12461 buffer.line_len(start_row.previous_row()),
12462 )
12463 ..Point::new(
12464 end_row.previous_row().0,
12465 buffer.line_len(end_row.previous_row()),
12466 );
12467 let insertion_point = display_map
12468 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12469 .0;
12470
12471 // Don't move lines across excerpts
12472 if buffer
12473 .excerpt_containing(insertion_point..range_to_move.end)
12474 .is_some()
12475 {
12476 let text = buffer
12477 .text_for_range(range_to_move.clone())
12478 .flat_map(|s| s.chars())
12479 .skip(1)
12480 .chain(['\n'])
12481 .collect::<String>();
12482
12483 edits.push((
12484 buffer.anchor_after(range_to_move.start)
12485 ..buffer.anchor_before(range_to_move.end),
12486 String::new(),
12487 ));
12488 let insertion_anchor = buffer.anchor_after(insertion_point);
12489 edits.push((insertion_anchor..insertion_anchor, text));
12490
12491 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12492
12493 // Move selections up
12494 new_selections.extend(contiguous_row_selections.drain(..).map(
12495 |mut selection| {
12496 selection.start.row -= row_delta;
12497 selection.end.row -= row_delta;
12498 selection
12499 },
12500 ));
12501
12502 // Move folds up
12503 unfold_ranges.push(range_to_move.clone());
12504 for fold in display_map.folds_in_range(
12505 buffer.anchor_before(range_to_move.start)
12506 ..buffer.anchor_after(range_to_move.end),
12507 ) {
12508 let mut start = fold.range.start.to_point(&buffer);
12509 let mut end = fold.range.end.to_point(&buffer);
12510 start.row -= row_delta;
12511 end.row -= row_delta;
12512 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12513 }
12514 }
12515 }
12516
12517 // If we didn't move line(s), preserve the existing selections
12518 new_selections.append(&mut contiguous_row_selections);
12519 }
12520
12521 self.transact(window, cx, |this, window, cx| {
12522 this.unfold_ranges(&unfold_ranges, true, true, cx);
12523 this.buffer.update(cx, |buffer, cx| {
12524 for (range, text) in edits {
12525 buffer.edit([(range, text)], None, cx);
12526 }
12527 });
12528 this.fold_creases(refold_creases, true, window, cx);
12529 this.change_selections(Default::default(), window, cx, |s| {
12530 s.select(new_selections);
12531 })
12532 });
12533 }
12534
12535 pub fn move_line_down(
12536 &mut self,
12537 _: &MoveLineDown,
12538 window: &mut Window,
12539 cx: &mut Context<Self>,
12540 ) {
12541 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12542 if self.mode.is_single_line() {
12543 cx.propagate();
12544 return;
12545 }
12546
12547 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12548 let buffer = self.buffer.read(cx).snapshot(cx);
12549
12550 let mut edits = Vec::new();
12551 let mut unfold_ranges = Vec::new();
12552 let mut refold_creases = Vec::new();
12553
12554 let selections = self.selections.all::<Point>(&display_map);
12555 let mut selections = selections.iter().peekable();
12556 let mut contiguous_row_selections = Vec::new();
12557 let mut new_selections = Vec::new();
12558
12559 while let Some(selection) = selections.next() {
12560 // Find all the selections that span a contiguous row range
12561 let (start_row, end_row) = consume_contiguous_rows(
12562 &mut contiguous_row_selections,
12563 selection,
12564 &display_map,
12565 &mut selections,
12566 );
12567
12568 // Move the text spanned by the row range to be after the last line of the row range
12569 if end_row.0 <= buffer.max_point().row {
12570 let range_to_move =
12571 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12572 let insertion_point = display_map
12573 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12574 .0;
12575
12576 // Don't move lines across excerpt boundaries
12577 if buffer
12578 .excerpt_containing(range_to_move.start..insertion_point)
12579 .is_some()
12580 {
12581 let mut text = String::from("\n");
12582 text.extend(buffer.text_for_range(range_to_move.clone()));
12583 text.pop(); // Drop trailing newline
12584 edits.push((
12585 buffer.anchor_after(range_to_move.start)
12586 ..buffer.anchor_before(range_to_move.end),
12587 String::new(),
12588 ));
12589 let insertion_anchor = buffer.anchor_after(insertion_point);
12590 edits.push((insertion_anchor..insertion_anchor, text));
12591
12592 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12593
12594 // Move selections down
12595 new_selections.extend(contiguous_row_selections.drain(..).map(
12596 |mut selection| {
12597 selection.start.row += row_delta;
12598 selection.end.row += row_delta;
12599 selection
12600 },
12601 ));
12602
12603 // Move folds down
12604 unfold_ranges.push(range_to_move.clone());
12605 for fold in display_map.folds_in_range(
12606 buffer.anchor_before(range_to_move.start)
12607 ..buffer.anchor_after(range_to_move.end),
12608 ) {
12609 let mut start = fold.range.start.to_point(&buffer);
12610 let mut end = fold.range.end.to_point(&buffer);
12611 start.row += row_delta;
12612 end.row += row_delta;
12613 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12614 }
12615 }
12616 }
12617
12618 // If we didn't move line(s), preserve the existing selections
12619 new_selections.append(&mut contiguous_row_selections);
12620 }
12621
12622 self.transact(window, cx, |this, window, cx| {
12623 this.unfold_ranges(&unfold_ranges, true, true, cx);
12624 this.buffer.update(cx, |buffer, cx| {
12625 for (range, text) in edits {
12626 buffer.edit([(range, text)], None, cx);
12627 }
12628 });
12629 this.fold_creases(refold_creases, true, window, cx);
12630 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12631 });
12632 }
12633
12634 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12635 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12636 let text_layout_details = &self.text_layout_details(window);
12637 self.transact(window, cx, |this, window, cx| {
12638 let edits = this.change_selections(Default::default(), window, cx, |s| {
12639 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12640 s.move_with(|display_map, selection| {
12641 if !selection.is_empty() {
12642 return;
12643 }
12644
12645 let mut head = selection.head();
12646 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12647 if head.column() == display_map.line_len(head.row()) {
12648 transpose_offset = display_map
12649 .buffer_snapshot()
12650 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12651 }
12652
12653 if transpose_offset == MultiBufferOffset(0) {
12654 return;
12655 }
12656
12657 *head.column_mut() += 1;
12658 head = display_map.clip_point(head, Bias::Right);
12659 let goal = SelectionGoal::HorizontalPosition(
12660 display_map
12661 .x_for_display_point(head, text_layout_details)
12662 .into(),
12663 );
12664 selection.collapse_to(head, goal);
12665
12666 let transpose_start = display_map
12667 .buffer_snapshot()
12668 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12669 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12670 let transpose_end = display_map
12671 .buffer_snapshot()
12672 .clip_offset(transpose_offset + 1usize, Bias::Right);
12673 if let Some(ch) = display_map
12674 .buffer_snapshot()
12675 .chars_at(transpose_start)
12676 .next()
12677 {
12678 edits.push((transpose_start..transpose_offset, String::new()));
12679 edits.push((transpose_end..transpose_end, ch.to_string()));
12680 }
12681 }
12682 });
12683 edits
12684 });
12685 this.buffer
12686 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12687 let selections = this
12688 .selections
12689 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12690 this.change_selections(Default::default(), window, cx, |s| {
12691 s.select(selections);
12692 });
12693 });
12694 }
12695
12696 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12697 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12698 if self.mode.is_single_line() {
12699 cx.propagate();
12700 return;
12701 }
12702
12703 self.rewrap_impl(RewrapOptions::default(), cx)
12704 }
12705
12706 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12707 let buffer = self.buffer.read(cx).snapshot(cx);
12708 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12709
12710 #[derive(Clone, Debug, PartialEq)]
12711 enum CommentFormat {
12712 /// single line comment, with prefix for line
12713 Line(String),
12714 /// single line within a block comment, with prefix for line
12715 BlockLine(String),
12716 /// a single line of a block comment that includes the initial delimiter
12717 BlockCommentWithStart(BlockCommentConfig),
12718 /// a single line of a block comment that includes the ending delimiter
12719 BlockCommentWithEnd(BlockCommentConfig),
12720 }
12721
12722 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12723 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12724 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12725 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12726 .peekable();
12727
12728 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12729 row
12730 } else {
12731 return Vec::new();
12732 };
12733
12734 let language_settings = buffer.language_settings_at(selection.head(), cx);
12735 let language_scope = buffer.language_scope_at(selection.head());
12736
12737 let indent_and_prefix_for_row =
12738 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12739 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12740 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12741 &language_scope
12742 {
12743 let indent_end = Point::new(row, indent.len);
12744 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12745 let line_text_after_indent = buffer
12746 .text_for_range(indent_end..line_end)
12747 .collect::<String>();
12748
12749 let is_within_comment_override = buffer
12750 .language_scope_at(indent_end)
12751 .is_some_and(|scope| scope.override_name() == Some("comment"));
12752 let comment_delimiters = if is_within_comment_override {
12753 // we are within a comment syntax node, but we don't
12754 // yet know what kind of comment: block, doc or line
12755 match (
12756 language_scope.documentation_comment(),
12757 language_scope.block_comment(),
12758 ) {
12759 (Some(config), _) | (_, Some(config))
12760 if buffer.contains_str_at(indent_end, &config.start) =>
12761 {
12762 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12763 }
12764 (Some(config), _) | (_, Some(config))
12765 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12766 {
12767 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12768 }
12769 (Some(config), _) | (_, Some(config))
12770 if buffer.contains_str_at(indent_end, &config.prefix) =>
12771 {
12772 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12773 }
12774 (_, _) => language_scope
12775 .line_comment_prefixes()
12776 .iter()
12777 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12778 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12779 }
12780 } else {
12781 // we not in an overridden comment node, but we may
12782 // be within a non-overridden line comment node
12783 language_scope
12784 .line_comment_prefixes()
12785 .iter()
12786 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12787 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12788 };
12789
12790 let rewrap_prefix = language_scope
12791 .rewrap_prefixes()
12792 .iter()
12793 .find_map(|prefix_regex| {
12794 prefix_regex.find(&line_text_after_indent).map(|mat| {
12795 if mat.start() == 0 {
12796 Some(mat.as_str().to_string())
12797 } else {
12798 None
12799 }
12800 })
12801 })
12802 .flatten();
12803 (comment_delimiters, rewrap_prefix)
12804 } else {
12805 (None, None)
12806 };
12807 (indent, comment_prefix, rewrap_prefix)
12808 };
12809
12810 let mut ranges = Vec::new();
12811 let from_empty_selection = selection.is_empty();
12812
12813 let mut current_range_start = first_row;
12814 let mut prev_row = first_row;
12815 let (
12816 mut current_range_indent,
12817 mut current_range_comment_delimiters,
12818 mut current_range_rewrap_prefix,
12819 ) = indent_and_prefix_for_row(first_row);
12820
12821 for row in non_blank_rows_iter.skip(1) {
12822 let has_paragraph_break = row > prev_row + 1;
12823
12824 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12825 indent_and_prefix_for_row(row);
12826
12827 let has_indent_change = row_indent != current_range_indent;
12828 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12829
12830 let has_boundary_change = has_comment_change
12831 || row_rewrap_prefix.is_some()
12832 || (has_indent_change && current_range_comment_delimiters.is_some());
12833
12834 if has_paragraph_break || has_boundary_change {
12835 ranges.push((
12836 language_settings.clone(),
12837 Point::new(current_range_start, 0)
12838 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12839 current_range_indent,
12840 current_range_comment_delimiters.clone(),
12841 current_range_rewrap_prefix.clone(),
12842 from_empty_selection,
12843 ));
12844 current_range_start = row;
12845 current_range_indent = row_indent;
12846 current_range_comment_delimiters = row_comment_delimiters;
12847 current_range_rewrap_prefix = row_rewrap_prefix;
12848 }
12849 prev_row = row;
12850 }
12851
12852 ranges.push((
12853 language_settings.clone(),
12854 Point::new(current_range_start, 0)
12855 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12856 current_range_indent,
12857 current_range_comment_delimiters,
12858 current_range_rewrap_prefix,
12859 from_empty_selection,
12860 ));
12861
12862 ranges
12863 });
12864
12865 let mut edits = Vec::new();
12866 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12867
12868 for (
12869 language_settings,
12870 wrap_range,
12871 mut indent_size,
12872 comment_prefix,
12873 rewrap_prefix,
12874 from_empty_selection,
12875 ) in wrap_ranges
12876 {
12877 let mut start_row = wrap_range.start.row;
12878 let mut end_row = wrap_range.end.row;
12879
12880 // Skip selections that overlap with a range that has already been rewrapped.
12881 let selection_range = start_row..end_row;
12882 if rewrapped_row_ranges
12883 .iter()
12884 .any(|range| range.overlaps(&selection_range))
12885 {
12886 continue;
12887 }
12888
12889 let tab_size = language_settings.tab_size;
12890
12891 let (line_prefix, inside_comment) = match &comment_prefix {
12892 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12893 (Some(prefix.as_str()), true)
12894 }
12895 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12896 (Some(prefix.as_ref()), true)
12897 }
12898 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12899 start: _,
12900 end: _,
12901 prefix,
12902 tab_size,
12903 })) => {
12904 indent_size.len += tab_size;
12905 (Some(prefix.as_ref()), true)
12906 }
12907 None => (None, false),
12908 };
12909 let indent_prefix = indent_size.chars().collect::<String>();
12910 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12911
12912 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12913 RewrapBehavior::InComments => inside_comment,
12914 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12915 RewrapBehavior::Anywhere => true,
12916 };
12917
12918 let should_rewrap = options.override_language_settings
12919 || allow_rewrap_based_on_language
12920 || self.hard_wrap.is_some();
12921 if !should_rewrap {
12922 continue;
12923 }
12924
12925 if from_empty_selection {
12926 'expand_upwards: while start_row > 0 {
12927 let prev_row = start_row - 1;
12928 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12929 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12930 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12931 {
12932 start_row = prev_row;
12933 } else {
12934 break 'expand_upwards;
12935 }
12936 }
12937
12938 'expand_downwards: while end_row < buffer.max_point().row {
12939 let next_row = end_row + 1;
12940 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12941 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12942 && !buffer.is_line_blank(MultiBufferRow(next_row))
12943 {
12944 end_row = next_row;
12945 } else {
12946 break 'expand_downwards;
12947 }
12948 }
12949 }
12950
12951 let start = Point::new(start_row, 0);
12952 let start_offset = ToOffset::to_offset(&start, &buffer);
12953 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12954 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12955 let mut first_line_delimiter = None;
12956 let mut last_line_delimiter = None;
12957 let Some(lines_without_prefixes) = selection_text
12958 .lines()
12959 .enumerate()
12960 .map(|(ix, line)| {
12961 let line_trimmed = line.trim_start();
12962 if rewrap_prefix.is_some() && ix > 0 {
12963 Ok(line_trimmed)
12964 } else if let Some(
12965 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12966 start,
12967 prefix,
12968 end,
12969 tab_size,
12970 })
12971 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12972 start,
12973 prefix,
12974 end,
12975 tab_size,
12976 }),
12977 ) = &comment_prefix
12978 {
12979 let line_trimmed = line_trimmed
12980 .strip_prefix(start.as_ref())
12981 .map(|s| {
12982 let mut indent_size = indent_size;
12983 indent_size.len -= tab_size;
12984 let indent_prefix: String = indent_size.chars().collect();
12985 first_line_delimiter = Some((indent_prefix, start));
12986 s.trim_start()
12987 })
12988 .unwrap_or(line_trimmed);
12989 let line_trimmed = line_trimmed
12990 .strip_suffix(end.as_ref())
12991 .map(|s| {
12992 last_line_delimiter = Some(end);
12993 s.trim_end()
12994 })
12995 .unwrap_or(line_trimmed);
12996 let line_trimmed = line_trimmed
12997 .strip_prefix(prefix.as_ref())
12998 .unwrap_or(line_trimmed);
12999 Ok(line_trimmed)
13000 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13001 line_trimmed.strip_prefix(prefix).with_context(|| {
13002 format!("line did not start with prefix {prefix:?}: {line:?}")
13003 })
13004 } else {
13005 line_trimmed
13006 .strip_prefix(&line_prefix.trim_start())
13007 .with_context(|| {
13008 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13009 })
13010 }
13011 })
13012 .collect::<Result<Vec<_>, _>>()
13013 .log_err()
13014 else {
13015 continue;
13016 };
13017
13018 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13019 buffer
13020 .language_settings_at(Point::new(start_row, 0), cx)
13021 .preferred_line_length as usize
13022 });
13023
13024 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13025 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13026 } else {
13027 line_prefix.clone()
13028 };
13029
13030 let wrapped_text = {
13031 let mut wrapped_text = wrap_with_prefix(
13032 line_prefix,
13033 subsequent_lines_prefix,
13034 lines_without_prefixes.join("\n"),
13035 wrap_column,
13036 tab_size,
13037 options.preserve_existing_whitespace,
13038 );
13039
13040 if let Some((indent, delimiter)) = first_line_delimiter {
13041 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13042 }
13043 if let Some(last_line) = last_line_delimiter {
13044 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13045 }
13046
13047 wrapped_text
13048 };
13049
13050 // TODO: should always use char-based diff while still supporting cursor behavior that
13051 // matches vim.
13052 let mut diff_options = DiffOptions::default();
13053 if options.override_language_settings {
13054 diff_options.max_word_diff_len = 0;
13055 diff_options.max_word_diff_line_count = 0;
13056 } else {
13057 diff_options.max_word_diff_len = usize::MAX;
13058 diff_options.max_word_diff_line_count = usize::MAX;
13059 }
13060
13061 for (old_range, new_text) in
13062 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13063 {
13064 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13065 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13066 edits.push((edit_start..edit_end, new_text));
13067 }
13068
13069 rewrapped_row_ranges.push(start_row..=end_row);
13070 }
13071
13072 self.buffer
13073 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13074 }
13075
13076 pub fn cut_common(
13077 &mut self,
13078 cut_no_selection_line: bool,
13079 window: &mut Window,
13080 cx: &mut Context<Self>,
13081 ) -> ClipboardItem {
13082 let mut text = String::new();
13083 let buffer = self.buffer.read(cx).snapshot(cx);
13084 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13085 let mut clipboard_selections = Vec::with_capacity(selections.len());
13086 {
13087 let max_point = buffer.max_point();
13088 let mut is_first = true;
13089 let mut prev_selection_was_entire_line = false;
13090 for selection in &mut selections {
13091 let is_entire_line =
13092 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13093 if is_entire_line {
13094 selection.start = Point::new(selection.start.row, 0);
13095 if !selection.is_empty() && selection.end.column == 0 {
13096 selection.end = cmp::min(max_point, selection.end);
13097 } else {
13098 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13099 }
13100 selection.goal = SelectionGoal::None;
13101 }
13102 if is_first {
13103 is_first = false;
13104 } else if !prev_selection_was_entire_line {
13105 text += "\n";
13106 }
13107 prev_selection_was_entire_line = is_entire_line;
13108 let mut len = 0;
13109 for chunk in buffer.text_for_range(selection.start..selection.end) {
13110 text.push_str(chunk);
13111 len += chunk.len();
13112 }
13113
13114 clipboard_selections.push(ClipboardSelection::for_buffer(
13115 len,
13116 is_entire_line,
13117 selection.range(),
13118 &buffer,
13119 self.project.as_ref(),
13120 cx,
13121 ));
13122 }
13123 }
13124
13125 self.transact(window, cx, |this, window, cx| {
13126 this.change_selections(Default::default(), window, cx, |s| {
13127 s.select(selections);
13128 });
13129 this.insert("", window, cx);
13130 });
13131 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13132 }
13133
13134 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13135 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13136 let item = self.cut_common(true, window, cx);
13137 cx.write_to_clipboard(item);
13138 }
13139
13140 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13141 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13142 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13143 s.move_with(|snapshot, sel| {
13144 if sel.is_empty() {
13145 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13146 }
13147 if sel.is_empty() {
13148 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13149 }
13150 });
13151 });
13152 let item = self.cut_common(false, window, cx);
13153 cx.set_global(KillRing(item))
13154 }
13155
13156 pub fn kill_ring_yank(
13157 &mut self,
13158 _: &KillRingYank,
13159 window: &mut Window,
13160 cx: &mut Context<Self>,
13161 ) {
13162 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13163 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13164 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13165 (kill_ring.text().to_string(), kill_ring.metadata_json())
13166 } else {
13167 return;
13168 }
13169 } else {
13170 return;
13171 };
13172 self.do_paste(&text, metadata, false, window, cx);
13173 }
13174
13175 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13176 self.do_copy(true, cx);
13177 }
13178
13179 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13180 self.do_copy(false, cx);
13181 }
13182
13183 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13184 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13185 let buffer = self.buffer.read(cx).read(cx);
13186 let mut text = String::new();
13187
13188 let mut clipboard_selections = Vec::with_capacity(selections.len());
13189 {
13190 let max_point = buffer.max_point();
13191 let mut is_first = true;
13192 let mut prev_selection_was_entire_line = false;
13193 for selection in &selections {
13194 let mut start = selection.start;
13195 let mut end = selection.end;
13196 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13197 let mut add_trailing_newline = false;
13198 if is_entire_line {
13199 start = Point::new(start.row, 0);
13200 let next_line_start = Point::new(end.row + 1, 0);
13201 if next_line_start <= max_point {
13202 end = next_line_start;
13203 } else {
13204 // We're on the last line without a trailing newline.
13205 // Copy to the end of the line and add a newline afterwards.
13206 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13207 add_trailing_newline = true;
13208 }
13209 }
13210
13211 let mut trimmed_selections = Vec::new();
13212 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13213 let row = MultiBufferRow(start.row);
13214 let first_indent = buffer.indent_size_for_line(row);
13215 if first_indent.len == 0 || start.column > first_indent.len {
13216 trimmed_selections.push(start..end);
13217 } else {
13218 trimmed_selections.push(
13219 Point::new(row.0, first_indent.len)
13220 ..Point::new(row.0, buffer.line_len(row)),
13221 );
13222 for row in start.row + 1..=end.row {
13223 let mut line_len = buffer.line_len(MultiBufferRow(row));
13224 if row == end.row {
13225 line_len = end.column;
13226 }
13227 if line_len == 0 {
13228 trimmed_selections
13229 .push(Point::new(row, 0)..Point::new(row, line_len));
13230 continue;
13231 }
13232 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13233 if row_indent_size.len >= first_indent.len {
13234 trimmed_selections.push(
13235 Point::new(row, first_indent.len)..Point::new(row, line_len),
13236 );
13237 } else {
13238 trimmed_selections.clear();
13239 trimmed_selections.push(start..end);
13240 break;
13241 }
13242 }
13243 }
13244 } else {
13245 trimmed_selections.push(start..end);
13246 }
13247
13248 for trimmed_range in trimmed_selections {
13249 if is_first {
13250 is_first = false;
13251 } else if !prev_selection_was_entire_line {
13252 text += "\n";
13253 }
13254 prev_selection_was_entire_line = is_entire_line;
13255 let mut len = 0;
13256 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13257 text.push_str(chunk);
13258 len += chunk.len();
13259 }
13260 if add_trailing_newline {
13261 text.push('\n');
13262 len += 1;
13263 }
13264 clipboard_selections.push(ClipboardSelection::for_buffer(
13265 len,
13266 is_entire_line,
13267 trimmed_range,
13268 &buffer,
13269 self.project.as_ref(),
13270 cx,
13271 ));
13272 }
13273 }
13274 }
13275
13276 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13277 text,
13278 clipboard_selections,
13279 ));
13280 }
13281
13282 pub fn do_paste(
13283 &mut self,
13284 text: &String,
13285 clipboard_selections: Option<Vec<ClipboardSelection>>,
13286 handle_entire_lines: bool,
13287 window: &mut Window,
13288 cx: &mut Context<Self>,
13289 ) {
13290 if self.read_only(cx) {
13291 return;
13292 }
13293
13294 let clipboard_text = Cow::Borrowed(text.as_str());
13295
13296 self.transact(window, cx, |this, window, cx| {
13297 let had_active_edit_prediction = this.has_active_edit_prediction();
13298 let display_map = this.display_snapshot(cx);
13299 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13300 let cursor_offset = this
13301 .selections
13302 .last::<MultiBufferOffset>(&display_map)
13303 .head();
13304
13305 if let Some(mut clipboard_selections) = clipboard_selections {
13306 let all_selections_were_entire_line =
13307 clipboard_selections.iter().all(|s| s.is_entire_line);
13308 let first_selection_indent_column =
13309 clipboard_selections.first().map(|s| s.first_line_indent);
13310 if clipboard_selections.len() != old_selections.len() {
13311 clipboard_selections.drain(..);
13312 }
13313 let mut auto_indent_on_paste = true;
13314
13315 this.buffer.update(cx, |buffer, cx| {
13316 let snapshot = buffer.read(cx);
13317 auto_indent_on_paste = snapshot
13318 .language_settings_at(cursor_offset, cx)
13319 .auto_indent_on_paste;
13320
13321 let mut start_offset = 0;
13322 let mut edits = Vec::new();
13323 let mut original_indent_columns = Vec::new();
13324 for (ix, selection) in old_selections.iter().enumerate() {
13325 let to_insert;
13326 let entire_line;
13327 let original_indent_column;
13328 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13329 let end_offset = start_offset + clipboard_selection.len;
13330 to_insert = &clipboard_text[start_offset..end_offset];
13331 entire_line = clipboard_selection.is_entire_line;
13332 start_offset = if entire_line {
13333 end_offset
13334 } else {
13335 end_offset + 1
13336 };
13337 original_indent_column = Some(clipboard_selection.first_line_indent);
13338 } else {
13339 to_insert = &*clipboard_text;
13340 entire_line = all_selections_were_entire_line;
13341 original_indent_column = first_selection_indent_column
13342 }
13343
13344 let (range, to_insert) =
13345 if selection.is_empty() && handle_entire_lines && entire_line {
13346 // If the corresponding selection was empty when this slice of the
13347 // clipboard text was written, then the entire line containing the
13348 // selection was copied. If this selection is also currently empty,
13349 // then paste the line before the current line of the buffer.
13350 let column = selection.start.to_point(&snapshot).column as usize;
13351 let line_start = selection.start - column;
13352 (line_start..line_start, Cow::Borrowed(to_insert))
13353 } else {
13354 let language = snapshot.language_at(selection.head());
13355 let range = selection.range();
13356 if let Some(language) = language
13357 && language.name() == "Markdown".into()
13358 {
13359 edit_for_markdown_paste(
13360 &snapshot,
13361 range,
13362 to_insert,
13363 url::Url::parse(to_insert).ok(),
13364 )
13365 } else {
13366 (range, Cow::Borrowed(to_insert))
13367 }
13368 };
13369
13370 edits.push((range, to_insert));
13371 original_indent_columns.push(original_indent_column);
13372 }
13373 drop(snapshot);
13374
13375 buffer.edit(
13376 edits,
13377 if auto_indent_on_paste {
13378 Some(AutoindentMode::Block {
13379 original_indent_columns,
13380 })
13381 } else {
13382 None
13383 },
13384 cx,
13385 );
13386 });
13387
13388 let selections = this
13389 .selections
13390 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13391 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13392 } else {
13393 let url = url::Url::parse(&clipboard_text).ok();
13394
13395 let auto_indent_mode = if !clipboard_text.is_empty() {
13396 Some(AutoindentMode::Block {
13397 original_indent_columns: Vec::new(),
13398 })
13399 } else {
13400 None
13401 };
13402
13403 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13404 let snapshot = buffer.snapshot(cx);
13405
13406 let anchors = old_selections
13407 .iter()
13408 .map(|s| {
13409 let anchor = snapshot.anchor_after(s.head());
13410 s.map(|_| anchor)
13411 })
13412 .collect::<Vec<_>>();
13413
13414 let mut edits = Vec::new();
13415
13416 for selection in old_selections.iter() {
13417 let language = snapshot.language_at(selection.head());
13418 let range = selection.range();
13419
13420 let (edit_range, edit_text) = if let Some(language) = language
13421 && language.name() == "Markdown".into()
13422 {
13423 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13424 } else {
13425 (range, clipboard_text.clone())
13426 };
13427
13428 edits.push((edit_range, edit_text));
13429 }
13430
13431 drop(snapshot);
13432 buffer.edit(edits, auto_indent_mode, cx);
13433
13434 anchors
13435 });
13436
13437 this.change_selections(Default::default(), window, cx, |s| {
13438 s.select_anchors(selection_anchors);
13439 });
13440 }
13441
13442 // 🤔 | .. | show_in_menu |
13443 // | .. | true true
13444 // | had_edit_prediction | false true
13445
13446 let trigger_in_words =
13447 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13448
13449 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13450 });
13451 }
13452
13453 pub fn diff_clipboard_with_selection(
13454 &mut self,
13455 _: &DiffClipboardWithSelection,
13456 window: &mut Window,
13457 cx: &mut Context<Self>,
13458 ) {
13459 let selections = self
13460 .selections
13461 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13462
13463 if selections.is_empty() {
13464 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13465 return;
13466 };
13467
13468 let clipboard_text = match cx.read_from_clipboard() {
13469 Some(item) => match item.entries().first() {
13470 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13471 _ => None,
13472 },
13473 None => None,
13474 };
13475
13476 let Some(clipboard_text) = clipboard_text else {
13477 log::warn!("Clipboard doesn't contain text.");
13478 return;
13479 };
13480
13481 window.dispatch_action(
13482 Box::new(DiffClipboardWithSelectionData {
13483 clipboard_text,
13484 editor: cx.entity(),
13485 }),
13486 cx,
13487 );
13488 }
13489
13490 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13491 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13492 if let Some(item) = cx.read_from_clipboard() {
13493 let entries = item.entries();
13494
13495 match entries.first() {
13496 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13497 // of all the pasted entries.
13498 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13499 .do_paste(
13500 clipboard_string.text(),
13501 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13502 true,
13503 window,
13504 cx,
13505 ),
13506 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13507 }
13508 }
13509 }
13510
13511 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13512 if self.read_only(cx) {
13513 return;
13514 }
13515
13516 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13517
13518 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13519 if let Some((selections, _)) =
13520 self.selection_history.transaction(transaction_id).cloned()
13521 {
13522 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13523 s.select_anchors(selections.to_vec());
13524 });
13525 } else {
13526 log::error!(
13527 "No entry in selection_history found for undo. \
13528 This may correspond to a bug where undo does not update the selection. \
13529 If this is occurring, please add details to \
13530 https://github.com/zed-industries/zed/issues/22692"
13531 );
13532 }
13533 self.request_autoscroll(Autoscroll::fit(), cx);
13534 self.unmark_text(window, cx);
13535 self.refresh_edit_prediction(true, false, window, cx);
13536 cx.emit(EditorEvent::Edited { transaction_id });
13537 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13538 }
13539 }
13540
13541 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13542 if self.read_only(cx) {
13543 return;
13544 }
13545
13546 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13547
13548 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13549 if let Some((_, Some(selections))) =
13550 self.selection_history.transaction(transaction_id).cloned()
13551 {
13552 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13553 s.select_anchors(selections.to_vec());
13554 });
13555 } else {
13556 log::error!(
13557 "No entry in selection_history found for redo. \
13558 This may correspond to a bug where undo does not update the selection. \
13559 If this is occurring, please add details to \
13560 https://github.com/zed-industries/zed/issues/22692"
13561 );
13562 }
13563 self.request_autoscroll(Autoscroll::fit(), cx);
13564 self.unmark_text(window, cx);
13565 self.refresh_edit_prediction(true, false, window, cx);
13566 cx.emit(EditorEvent::Edited { transaction_id });
13567 }
13568 }
13569
13570 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13571 self.buffer
13572 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13573 }
13574
13575 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13576 self.buffer
13577 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13578 }
13579
13580 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13581 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13582 self.change_selections(Default::default(), window, cx, |s| {
13583 s.move_with(|map, selection| {
13584 let cursor = if selection.is_empty() {
13585 movement::left(map, selection.start)
13586 } else {
13587 selection.start
13588 };
13589 selection.collapse_to(cursor, SelectionGoal::None);
13590 });
13591 })
13592 }
13593
13594 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13595 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13596 self.change_selections(Default::default(), window, cx, |s| {
13597 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13598 })
13599 }
13600
13601 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13602 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13603 self.change_selections(Default::default(), window, cx, |s| {
13604 s.move_with(|map, selection| {
13605 let cursor = if selection.is_empty() {
13606 movement::right(map, selection.end)
13607 } else {
13608 selection.end
13609 };
13610 selection.collapse_to(cursor, SelectionGoal::None)
13611 });
13612 })
13613 }
13614
13615 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13616 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13617 self.change_selections(Default::default(), window, cx, |s| {
13618 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13619 });
13620 }
13621
13622 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13623 if self.take_rename(true, window, cx).is_some() {
13624 return;
13625 }
13626
13627 if self.mode.is_single_line() {
13628 cx.propagate();
13629 return;
13630 }
13631
13632 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13633
13634 let text_layout_details = &self.text_layout_details(window);
13635 let selection_count = self.selections.count();
13636 let first_selection = self.selections.first_anchor();
13637
13638 self.change_selections(Default::default(), window, cx, |s| {
13639 s.move_with(|map, selection| {
13640 if !selection.is_empty() {
13641 selection.goal = SelectionGoal::None;
13642 }
13643 let (cursor, goal) = movement::up(
13644 map,
13645 selection.start,
13646 selection.goal,
13647 false,
13648 text_layout_details,
13649 );
13650 selection.collapse_to(cursor, goal);
13651 });
13652 });
13653
13654 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13655 {
13656 cx.propagate();
13657 }
13658 }
13659
13660 pub fn move_up_by_lines(
13661 &mut self,
13662 action: &MoveUpByLines,
13663 window: &mut Window,
13664 cx: &mut Context<Self>,
13665 ) {
13666 if self.take_rename(true, window, cx).is_some() {
13667 return;
13668 }
13669
13670 if self.mode.is_single_line() {
13671 cx.propagate();
13672 return;
13673 }
13674
13675 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13676
13677 let text_layout_details = &self.text_layout_details(window);
13678
13679 self.change_selections(Default::default(), window, cx, |s| {
13680 s.move_with(|map, selection| {
13681 if !selection.is_empty() {
13682 selection.goal = SelectionGoal::None;
13683 }
13684 let (cursor, goal) = movement::up_by_rows(
13685 map,
13686 selection.start,
13687 action.lines,
13688 selection.goal,
13689 false,
13690 text_layout_details,
13691 );
13692 selection.collapse_to(cursor, goal);
13693 });
13694 })
13695 }
13696
13697 pub fn move_down_by_lines(
13698 &mut self,
13699 action: &MoveDownByLines,
13700 window: &mut Window,
13701 cx: &mut Context<Self>,
13702 ) {
13703 if self.take_rename(true, window, cx).is_some() {
13704 return;
13705 }
13706
13707 if self.mode.is_single_line() {
13708 cx.propagate();
13709 return;
13710 }
13711
13712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13713
13714 let text_layout_details = &self.text_layout_details(window);
13715
13716 self.change_selections(Default::default(), window, cx, |s| {
13717 s.move_with(|map, selection| {
13718 if !selection.is_empty() {
13719 selection.goal = SelectionGoal::None;
13720 }
13721 let (cursor, goal) = movement::down_by_rows(
13722 map,
13723 selection.start,
13724 action.lines,
13725 selection.goal,
13726 false,
13727 text_layout_details,
13728 );
13729 selection.collapse_to(cursor, goal);
13730 });
13731 })
13732 }
13733
13734 pub fn select_down_by_lines(
13735 &mut self,
13736 action: &SelectDownByLines,
13737 window: &mut Window,
13738 cx: &mut Context<Self>,
13739 ) {
13740 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13741 let text_layout_details = &self.text_layout_details(window);
13742 self.change_selections(Default::default(), window, cx, |s| {
13743 s.move_heads_with(|map, head, goal| {
13744 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13745 })
13746 })
13747 }
13748
13749 pub fn select_up_by_lines(
13750 &mut self,
13751 action: &SelectUpByLines,
13752 window: &mut Window,
13753 cx: &mut Context<Self>,
13754 ) {
13755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13756 let text_layout_details = &self.text_layout_details(window);
13757 self.change_selections(Default::default(), window, cx, |s| {
13758 s.move_heads_with(|map, head, goal| {
13759 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13760 })
13761 })
13762 }
13763
13764 pub fn select_page_up(
13765 &mut self,
13766 _: &SelectPageUp,
13767 window: &mut Window,
13768 cx: &mut Context<Self>,
13769 ) {
13770 let Some(row_count) = self.visible_row_count() else {
13771 return;
13772 };
13773
13774 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13775
13776 let text_layout_details = &self.text_layout_details(window);
13777
13778 self.change_selections(Default::default(), window, cx, |s| {
13779 s.move_heads_with(|map, head, goal| {
13780 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13781 })
13782 })
13783 }
13784
13785 pub fn move_page_up(
13786 &mut self,
13787 action: &MovePageUp,
13788 window: &mut Window,
13789 cx: &mut Context<Self>,
13790 ) {
13791 if self.take_rename(true, window, cx).is_some() {
13792 return;
13793 }
13794
13795 if self
13796 .context_menu
13797 .borrow_mut()
13798 .as_mut()
13799 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13800 .unwrap_or(false)
13801 {
13802 return;
13803 }
13804
13805 if matches!(self.mode, EditorMode::SingleLine) {
13806 cx.propagate();
13807 return;
13808 }
13809
13810 let Some(row_count) = self.visible_row_count() else {
13811 return;
13812 };
13813
13814 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13815
13816 let effects = if action.center_cursor {
13817 SelectionEffects::scroll(Autoscroll::center())
13818 } else {
13819 SelectionEffects::default()
13820 };
13821
13822 let text_layout_details = &self.text_layout_details(window);
13823
13824 self.change_selections(effects, window, cx, |s| {
13825 s.move_with(|map, selection| {
13826 if !selection.is_empty() {
13827 selection.goal = SelectionGoal::None;
13828 }
13829 let (cursor, goal) = movement::up_by_rows(
13830 map,
13831 selection.end,
13832 row_count,
13833 selection.goal,
13834 false,
13835 text_layout_details,
13836 );
13837 selection.collapse_to(cursor, goal);
13838 });
13839 });
13840 }
13841
13842 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13843 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13844 let text_layout_details = &self.text_layout_details(window);
13845 self.change_selections(Default::default(), window, cx, |s| {
13846 s.move_heads_with(|map, head, goal| {
13847 movement::up(map, head, goal, false, text_layout_details)
13848 })
13849 })
13850 }
13851
13852 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13853 self.take_rename(true, window, cx);
13854
13855 if self.mode.is_single_line() {
13856 cx.propagate();
13857 return;
13858 }
13859
13860 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13861
13862 let text_layout_details = &self.text_layout_details(window);
13863 let selection_count = self.selections.count();
13864 let first_selection = self.selections.first_anchor();
13865
13866 self.change_selections(Default::default(), window, cx, |s| {
13867 s.move_with(|map, selection| {
13868 if !selection.is_empty() {
13869 selection.goal = SelectionGoal::None;
13870 }
13871 let (cursor, goal) = movement::down(
13872 map,
13873 selection.end,
13874 selection.goal,
13875 false,
13876 text_layout_details,
13877 );
13878 selection.collapse_to(cursor, goal);
13879 });
13880 });
13881
13882 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13883 {
13884 cx.propagate();
13885 }
13886 }
13887
13888 pub fn select_page_down(
13889 &mut self,
13890 _: &SelectPageDown,
13891 window: &mut Window,
13892 cx: &mut Context<Self>,
13893 ) {
13894 let Some(row_count) = self.visible_row_count() else {
13895 return;
13896 };
13897
13898 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13899
13900 let text_layout_details = &self.text_layout_details(window);
13901
13902 self.change_selections(Default::default(), window, cx, |s| {
13903 s.move_heads_with(|map, head, goal| {
13904 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13905 })
13906 })
13907 }
13908
13909 pub fn move_page_down(
13910 &mut self,
13911 action: &MovePageDown,
13912 window: &mut Window,
13913 cx: &mut Context<Self>,
13914 ) {
13915 if self.take_rename(true, window, cx).is_some() {
13916 return;
13917 }
13918
13919 if self
13920 .context_menu
13921 .borrow_mut()
13922 .as_mut()
13923 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13924 .unwrap_or(false)
13925 {
13926 return;
13927 }
13928
13929 if matches!(self.mode, EditorMode::SingleLine) {
13930 cx.propagate();
13931 return;
13932 }
13933
13934 let Some(row_count) = self.visible_row_count() else {
13935 return;
13936 };
13937
13938 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13939
13940 let effects = if action.center_cursor {
13941 SelectionEffects::scroll(Autoscroll::center())
13942 } else {
13943 SelectionEffects::default()
13944 };
13945
13946 let text_layout_details = &self.text_layout_details(window);
13947 self.change_selections(effects, window, cx, |s| {
13948 s.move_with(|map, selection| {
13949 if !selection.is_empty() {
13950 selection.goal = SelectionGoal::None;
13951 }
13952 let (cursor, goal) = movement::down_by_rows(
13953 map,
13954 selection.end,
13955 row_count,
13956 selection.goal,
13957 false,
13958 text_layout_details,
13959 );
13960 selection.collapse_to(cursor, goal);
13961 });
13962 });
13963 }
13964
13965 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13966 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13967 let text_layout_details = &self.text_layout_details(window);
13968 self.change_selections(Default::default(), window, cx, |s| {
13969 s.move_heads_with(|map, head, goal| {
13970 movement::down(map, head, goal, false, text_layout_details)
13971 })
13972 });
13973 }
13974
13975 pub fn context_menu_first(
13976 &mut self,
13977 _: &ContextMenuFirst,
13978 window: &mut Window,
13979 cx: &mut Context<Self>,
13980 ) {
13981 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13982 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13983 }
13984 }
13985
13986 pub fn context_menu_prev(
13987 &mut self,
13988 _: &ContextMenuPrevious,
13989 window: &mut Window,
13990 cx: &mut Context<Self>,
13991 ) {
13992 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13993 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13994 }
13995 }
13996
13997 pub fn context_menu_next(
13998 &mut self,
13999 _: &ContextMenuNext,
14000 window: &mut Window,
14001 cx: &mut Context<Self>,
14002 ) {
14003 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14004 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14005 }
14006 }
14007
14008 pub fn context_menu_last(
14009 &mut self,
14010 _: &ContextMenuLast,
14011 window: &mut Window,
14012 cx: &mut Context<Self>,
14013 ) {
14014 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14015 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14016 }
14017 }
14018
14019 pub fn signature_help_prev(
14020 &mut self,
14021 _: &SignatureHelpPrevious,
14022 _: &mut Window,
14023 cx: &mut Context<Self>,
14024 ) {
14025 if let Some(popover) = self.signature_help_state.popover_mut() {
14026 if popover.current_signature == 0 {
14027 popover.current_signature = popover.signatures.len() - 1;
14028 } else {
14029 popover.current_signature -= 1;
14030 }
14031 cx.notify();
14032 }
14033 }
14034
14035 pub fn signature_help_next(
14036 &mut self,
14037 _: &SignatureHelpNext,
14038 _: &mut Window,
14039 cx: &mut Context<Self>,
14040 ) {
14041 if let Some(popover) = self.signature_help_state.popover_mut() {
14042 if popover.current_signature + 1 == popover.signatures.len() {
14043 popover.current_signature = 0;
14044 } else {
14045 popover.current_signature += 1;
14046 }
14047 cx.notify();
14048 }
14049 }
14050
14051 pub fn move_to_previous_word_start(
14052 &mut self,
14053 _: &MoveToPreviousWordStart,
14054 window: &mut Window,
14055 cx: &mut Context<Self>,
14056 ) {
14057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14058 self.change_selections(Default::default(), window, cx, |s| {
14059 s.move_cursors_with(|map, head, _| {
14060 (
14061 movement::previous_word_start(map, head),
14062 SelectionGoal::None,
14063 )
14064 });
14065 })
14066 }
14067
14068 pub fn move_to_previous_subword_start(
14069 &mut self,
14070 _: &MoveToPreviousSubwordStart,
14071 window: &mut Window,
14072 cx: &mut Context<Self>,
14073 ) {
14074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14075 self.change_selections(Default::default(), window, cx, |s| {
14076 s.move_cursors_with(|map, head, _| {
14077 (
14078 movement::previous_subword_start(map, head),
14079 SelectionGoal::None,
14080 )
14081 });
14082 })
14083 }
14084
14085 pub fn select_to_previous_word_start(
14086 &mut self,
14087 _: &SelectToPreviousWordStart,
14088 window: &mut Window,
14089 cx: &mut Context<Self>,
14090 ) {
14091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14092 self.change_selections(Default::default(), window, cx, |s| {
14093 s.move_heads_with(|map, head, _| {
14094 (
14095 movement::previous_word_start(map, head),
14096 SelectionGoal::None,
14097 )
14098 });
14099 })
14100 }
14101
14102 pub fn select_to_previous_subword_start(
14103 &mut self,
14104 _: &SelectToPreviousSubwordStart,
14105 window: &mut Window,
14106 cx: &mut Context<Self>,
14107 ) {
14108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14109 self.change_selections(Default::default(), window, cx, |s| {
14110 s.move_heads_with(|map, head, _| {
14111 (
14112 movement::previous_subword_start(map, head),
14113 SelectionGoal::None,
14114 )
14115 });
14116 })
14117 }
14118
14119 pub fn delete_to_previous_word_start(
14120 &mut self,
14121 action: &DeleteToPreviousWordStart,
14122 window: &mut Window,
14123 cx: &mut Context<Self>,
14124 ) {
14125 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14126 self.transact(window, cx, |this, window, cx| {
14127 this.select_autoclose_pair(window, cx);
14128 this.change_selections(Default::default(), window, cx, |s| {
14129 s.move_with(|map, selection| {
14130 if selection.is_empty() {
14131 let mut cursor = if action.ignore_newlines {
14132 movement::previous_word_start(map, selection.head())
14133 } else {
14134 movement::previous_word_start_or_newline(map, selection.head())
14135 };
14136 cursor = movement::adjust_greedy_deletion(
14137 map,
14138 selection.head(),
14139 cursor,
14140 action.ignore_brackets,
14141 );
14142 selection.set_head(cursor, SelectionGoal::None);
14143 }
14144 });
14145 });
14146 this.insert("", window, cx);
14147 });
14148 }
14149
14150 pub fn delete_to_previous_subword_start(
14151 &mut self,
14152 _: &DeleteToPreviousSubwordStart,
14153 window: &mut Window,
14154 cx: &mut Context<Self>,
14155 ) {
14156 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14157 self.transact(window, cx, |this, window, cx| {
14158 this.select_autoclose_pair(window, cx);
14159 this.change_selections(Default::default(), window, cx, |s| {
14160 s.move_with(|map, selection| {
14161 if selection.is_empty() {
14162 let mut cursor = movement::previous_subword_start(map, selection.head());
14163 cursor =
14164 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14165 selection.set_head(cursor, SelectionGoal::None);
14166 }
14167 });
14168 });
14169 this.insert("", window, cx);
14170 });
14171 }
14172
14173 pub fn move_to_next_word_end(
14174 &mut self,
14175 _: &MoveToNextWordEnd,
14176 window: &mut Window,
14177 cx: &mut Context<Self>,
14178 ) {
14179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14180 self.change_selections(Default::default(), window, cx, |s| {
14181 s.move_cursors_with(|map, head, _| {
14182 (movement::next_word_end(map, head), SelectionGoal::None)
14183 });
14184 })
14185 }
14186
14187 pub fn move_to_next_subword_end(
14188 &mut self,
14189 _: &MoveToNextSubwordEnd,
14190 window: &mut Window,
14191 cx: &mut Context<Self>,
14192 ) {
14193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14194 self.change_selections(Default::default(), window, cx, |s| {
14195 s.move_cursors_with(|map, head, _| {
14196 (movement::next_subword_end(map, head), SelectionGoal::None)
14197 });
14198 })
14199 }
14200
14201 pub fn select_to_next_word_end(
14202 &mut self,
14203 _: &SelectToNextWordEnd,
14204 window: &mut Window,
14205 cx: &mut Context<Self>,
14206 ) {
14207 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14208 self.change_selections(Default::default(), window, cx, |s| {
14209 s.move_heads_with(|map, head, _| {
14210 (movement::next_word_end(map, head), SelectionGoal::None)
14211 });
14212 })
14213 }
14214
14215 pub fn select_to_next_subword_end(
14216 &mut self,
14217 _: &SelectToNextSubwordEnd,
14218 window: &mut Window,
14219 cx: &mut Context<Self>,
14220 ) {
14221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14222 self.change_selections(Default::default(), window, cx, |s| {
14223 s.move_heads_with(|map, head, _| {
14224 (movement::next_subword_end(map, head), SelectionGoal::None)
14225 });
14226 })
14227 }
14228
14229 pub fn delete_to_next_word_end(
14230 &mut self,
14231 action: &DeleteToNextWordEnd,
14232 window: &mut Window,
14233 cx: &mut Context<Self>,
14234 ) {
14235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14236 self.transact(window, cx, |this, window, cx| {
14237 this.change_selections(Default::default(), window, cx, |s| {
14238 s.move_with(|map, selection| {
14239 if selection.is_empty() {
14240 let mut cursor = if action.ignore_newlines {
14241 movement::next_word_end(map, selection.head())
14242 } else {
14243 movement::next_word_end_or_newline(map, selection.head())
14244 };
14245 cursor = movement::adjust_greedy_deletion(
14246 map,
14247 selection.head(),
14248 cursor,
14249 action.ignore_brackets,
14250 );
14251 selection.set_head(cursor, SelectionGoal::None);
14252 }
14253 });
14254 });
14255 this.insert("", window, cx);
14256 });
14257 }
14258
14259 pub fn delete_to_next_subword_end(
14260 &mut self,
14261 _: &DeleteToNextSubwordEnd,
14262 window: &mut Window,
14263 cx: &mut Context<Self>,
14264 ) {
14265 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14266 self.transact(window, cx, |this, window, cx| {
14267 this.change_selections(Default::default(), window, cx, |s| {
14268 s.move_with(|map, selection| {
14269 if selection.is_empty() {
14270 let mut cursor = movement::next_subword_end(map, selection.head());
14271 cursor =
14272 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14273 selection.set_head(cursor, SelectionGoal::None);
14274 }
14275 });
14276 });
14277 this.insert("", window, cx);
14278 });
14279 }
14280
14281 pub fn move_to_beginning_of_line(
14282 &mut self,
14283 action: &MoveToBeginningOfLine,
14284 window: &mut Window,
14285 cx: &mut Context<Self>,
14286 ) {
14287 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14288 self.change_selections(Default::default(), window, cx, |s| {
14289 s.move_cursors_with(|map, head, _| {
14290 (
14291 movement::indented_line_beginning(
14292 map,
14293 head,
14294 action.stop_at_soft_wraps,
14295 action.stop_at_indent,
14296 ),
14297 SelectionGoal::None,
14298 )
14299 });
14300 })
14301 }
14302
14303 pub fn select_to_beginning_of_line(
14304 &mut self,
14305 action: &SelectToBeginningOfLine,
14306 window: &mut Window,
14307 cx: &mut Context<Self>,
14308 ) {
14309 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14310 self.change_selections(Default::default(), window, cx, |s| {
14311 s.move_heads_with(|map, head, _| {
14312 (
14313 movement::indented_line_beginning(
14314 map,
14315 head,
14316 action.stop_at_soft_wraps,
14317 action.stop_at_indent,
14318 ),
14319 SelectionGoal::None,
14320 )
14321 });
14322 });
14323 }
14324
14325 pub fn delete_to_beginning_of_line(
14326 &mut self,
14327 action: &DeleteToBeginningOfLine,
14328 window: &mut Window,
14329 cx: &mut Context<Self>,
14330 ) {
14331 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14332 self.transact(window, cx, |this, window, cx| {
14333 this.change_selections(Default::default(), window, cx, |s| {
14334 s.move_with(|_, selection| {
14335 selection.reversed = true;
14336 });
14337 });
14338
14339 this.select_to_beginning_of_line(
14340 &SelectToBeginningOfLine {
14341 stop_at_soft_wraps: false,
14342 stop_at_indent: action.stop_at_indent,
14343 },
14344 window,
14345 cx,
14346 );
14347 this.backspace(&Backspace, window, cx);
14348 });
14349 }
14350
14351 pub fn move_to_end_of_line(
14352 &mut self,
14353 action: &MoveToEndOfLine,
14354 window: &mut Window,
14355 cx: &mut Context<Self>,
14356 ) {
14357 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14358 self.change_selections(Default::default(), window, cx, |s| {
14359 s.move_cursors_with(|map, head, _| {
14360 (
14361 movement::line_end(map, head, action.stop_at_soft_wraps),
14362 SelectionGoal::None,
14363 )
14364 });
14365 })
14366 }
14367
14368 pub fn select_to_end_of_line(
14369 &mut self,
14370 action: &SelectToEndOfLine,
14371 window: &mut Window,
14372 cx: &mut Context<Self>,
14373 ) {
14374 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14375 self.change_selections(Default::default(), window, cx, |s| {
14376 s.move_heads_with(|map, head, _| {
14377 (
14378 movement::line_end(map, head, action.stop_at_soft_wraps),
14379 SelectionGoal::None,
14380 )
14381 });
14382 })
14383 }
14384
14385 pub fn delete_to_end_of_line(
14386 &mut self,
14387 _: &DeleteToEndOfLine,
14388 window: &mut Window,
14389 cx: &mut Context<Self>,
14390 ) {
14391 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14392 self.transact(window, cx, |this, window, cx| {
14393 this.select_to_end_of_line(
14394 &SelectToEndOfLine {
14395 stop_at_soft_wraps: false,
14396 },
14397 window,
14398 cx,
14399 );
14400 this.delete(&Delete, window, cx);
14401 });
14402 }
14403
14404 pub fn cut_to_end_of_line(
14405 &mut self,
14406 action: &CutToEndOfLine,
14407 window: &mut Window,
14408 cx: &mut Context<Self>,
14409 ) {
14410 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14411 self.transact(window, cx, |this, window, cx| {
14412 this.select_to_end_of_line(
14413 &SelectToEndOfLine {
14414 stop_at_soft_wraps: false,
14415 },
14416 window,
14417 cx,
14418 );
14419 if !action.stop_at_newlines {
14420 this.change_selections(Default::default(), window, cx, |s| {
14421 s.move_with(|_, sel| {
14422 if sel.is_empty() {
14423 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14424 }
14425 });
14426 });
14427 }
14428 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14429 let item = this.cut_common(false, window, cx);
14430 cx.write_to_clipboard(item);
14431 });
14432 }
14433
14434 pub fn move_to_start_of_paragraph(
14435 &mut self,
14436 _: &MoveToStartOfParagraph,
14437 window: &mut Window,
14438 cx: &mut Context<Self>,
14439 ) {
14440 if matches!(self.mode, EditorMode::SingleLine) {
14441 cx.propagate();
14442 return;
14443 }
14444 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14445 self.change_selections(Default::default(), window, cx, |s| {
14446 s.move_with(|map, selection| {
14447 selection.collapse_to(
14448 movement::start_of_paragraph(map, selection.head(), 1),
14449 SelectionGoal::None,
14450 )
14451 });
14452 })
14453 }
14454
14455 pub fn move_to_end_of_paragraph(
14456 &mut self,
14457 _: &MoveToEndOfParagraph,
14458 window: &mut Window,
14459 cx: &mut Context<Self>,
14460 ) {
14461 if matches!(self.mode, EditorMode::SingleLine) {
14462 cx.propagate();
14463 return;
14464 }
14465 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14466 self.change_selections(Default::default(), window, cx, |s| {
14467 s.move_with(|map, selection| {
14468 selection.collapse_to(
14469 movement::end_of_paragraph(map, selection.head(), 1),
14470 SelectionGoal::None,
14471 )
14472 });
14473 })
14474 }
14475
14476 pub fn select_to_start_of_paragraph(
14477 &mut self,
14478 _: &SelectToStartOfParagraph,
14479 window: &mut Window,
14480 cx: &mut Context<Self>,
14481 ) {
14482 if matches!(self.mode, EditorMode::SingleLine) {
14483 cx.propagate();
14484 return;
14485 }
14486 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14487 self.change_selections(Default::default(), window, cx, |s| {
14488 s.move_heads_with(|map, head, _| {
14489 (
14490 movement::start_of_paragraph(map, head, 1),
14491 SelectionGoal::None,
14492 )
14493 });
14494 })
14495 }
14496
14497 pub fn select_to_end_of_paragraph(
14498 &mut self,
14499 _: &SelectToEndOfParagraph,
14500 window: &mut Window,
14501 cx: &mut Context<Self>,
14502 ) {
14503 if matches!(self.mode, EditorMode::SingleLine) {
14504 cx.propagate();
14505 return;
14506 }
14507 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14508 self.change_selections(Default::default(), window, cx, |s| {
14509 s.move_heads_with(|map, head, _| {
14510 (
14511 movement::end_of_paragraph(map, head, 1),
14512 SelectionGoal::None,
14513 )
14514 });
14515 })
14516 }
14517
14518 pub fn move_to_start_of_excerpt(
14519 &mut self,
14520 _: &MoveToStartOfExcerpt,
14521 window: &mut Window,
14522 cx: &mut Context<Self>,
14523 ) {
14524 if matches!(self.mode, EditorMode::SingleLine) {
14525 cx.propagate();
14526 return;
14527 }
14528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14529 self.change_selections(Default::default(), window, cx, |s| {
14530 s.move_with(|map, selection| {
14531 selection.collapse_to(
14532 movement::start_of_excerpt(
14533 map,
14534 selection.head(),
14535 workspace::searchable::Direction::Prev,
14536 ),
14537 SelectionGoal::None,
14538 )
14539 });
14540 })
14541 }
14542
14543 pub fn move_to_start_of_next_excerpt(
14544 &mut self,
14545 _: &MoveToStartOfNextExcerpt,
14546 window: &mut Window,
14547 cx: &mut Context<Self>,
14548 ) {
14549 if matches!(self.mode, EditorMode::SingleLine) {
14550 cx.propagate();
14551 return;
14552 }
14553
14554 self.change_selections(Default::default(), window, cx, |s| {
14555 s.move_with(|map, selection| {
14556 selection.collapse_to(
14557 movement::start_of_excerpt(
14558 map,
14559 selection.head(),
14560 workspace::searchable::Direction::Next,
14561 ),
14562 SelectionGoal::None,
14563 )
14564 });
14565 })
14566 }
14567
14568 pub fn move_to_end_of_excerpt(
14569 &mut self,
14570 _: &MoveToEndOfExcerpt,
14571 window: &mut Window,
14572 cx: &mut Context<Self>,
14573 ) {
14574 if matches!(self.mode, EditorMode::SingleLine) {
14575 cx.propagate();
14576 return;
14577 }
14578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14579 self.change_selections(Default::default(), window, cx, |s| {
14580 s.move_with(|map, selection| {
14581 selection.collapse_to(
14582 movement::end_of_excerpt(
14583 map,
14584 selection.head(),
14585 workspace::searchable::Direction::Next,
14586 ),
14587 SelectionGoal::None,
14588 )
14589 });
14590 })
14591 }
14592
14593 pub fn move_to_end_of_previous_excerpt(
14594 &mut self,
14595 _: &MoveToEndOfPreviousExcerpt,
14596 window: &mut Window,
14597 cx: &mut Context<Self>,
14598 ) {
14599 if matches!(self.mode, EditorMode::SingleLine) {
14600 cx.propagate();
14601 return;
14602 }
14603 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14604 self.change_selections(Default::default(), window, cx, |s| {
14605 s.move_with(|map, selection| {
14606 selection.collapse_to(
14607 movement::end_of_excerpt(
14608 map,
14609 selection.head(),
14610 workspace::searchable::Direction::Prev,
14611 ),
14612 SelectionGoal::None,
14613 )
14614 });
14615 })
14616 }
14617
14618 pub fn select_to_start_of_excerpt(
14619 &mut self,
14620 _: &SelectToStartOfExcerpt,
14621 window: &mut Window,
14622 cx: &mut Context<Self>,
14623 ) {
14624 if matches!(self.mode, EditorMode::SingleLine) {
14625 cx.propagate();
14626 return;
14627 }
14628 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14629 self.change_selections(Default::default(), window, cx, |s| {
14630 s.move_heads_with(|map, head, _| {
14631 (
14632 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14633 SelectionGoal::None,
14634 )
14635 });
14636 })
14637 }
14638
14639 pub fn select_to_start_of_next_excerpt(
14640 &mut self,
14641 _: &SelectToStartOfNextExcerpt,
14642 window: &mut Window,
14643 cx: &mut Context<Self>,
14644 ) {
14645 if matches!(self.mode, EditorMode::SingleLine) {
14646 cx.propagate();
14647 return;
14648 }
14649 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14650 self.change_selections(Default::default(), window, cx, |s| {
14651 s.move_heads_with(|map, head, _| {
14652 (
14653 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14654 SelectionGoal::None,
14655 )
14656 });
14657 })
14658 }
14659
14660 pub fn select_to_end_of_excerpt(
14661 &mut self,
14662 _: &SelectToEndOfExcerpt,
14663 window: &mut Window,
14664 cx: &mut Context<Self>,
14665 ) {
14666 if matches!(self.mode, EditorMode::SingleLine) {
14667 cx.propagate();
14668 return;
14669 }
14670 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14671 self.change_selections(Default::default(), window, cx, |s| {
14672 s.move_heads_with(|map, head, _| {
14673 (
14674 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14675 SelectionGoal::None,
14676 )
14677 });
14678 })
14679 }
14680
14681 pub fn select_to_end_of_previous_excerpt(
14682 &mut self,
14683 _: &SelectToEndOfPreviousExcerpt,
14684 window: &mut Window,
14685 cx: &mut Context<Self>,
14686 ) {
14687 if matches!(self.mode, EditorMode::SingleLine) {
14688 cx.propagate();
14689 return;
14690 }
14691 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14692 self.change_selections(Default::default(), window, cx, |s| {
14693 s.move_heads_with(|map, head, _| {
14694 (
14695 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14696 SelectionGoal::None,
14697 )
14698 });
14699 })
14700 }
14701
14702 pub fn move_to_beginning(
14703 &mut self,
14704 _: &MoveToBeginning,
14705 window: &mut Window,
14706 cx: &mut Context<Self>,
14707 ) {
14708 if matches!(self.mode, EditorMode::SingleLine) {
14709 cx.propagate();
14710 return;
14711 }
14712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14713 self.change_selections(Default::default(), window, cx, |s| {
14714 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14715 });
14716 }
14717
14718 pub fn select_to_beginning(
14719 &mut self,
14720 _: &SelectToBeginning,
14721 window: &mut Window,
14722 cx: &mut Context<Self>,
14723 ) {
14724 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14725 selection.set_head(Point::zero(), SelectionGoal::None);
14726 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14727 self.change_selections(Default::default(), window, cx, |s| {
14728 s.select(vec![selection]);
14729 });
14730 }
14731
14732 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14733 if matches!(self.mode, EditorMode::SingleLine) {
14734 cx.propagate();
14735 return;
14736 }
14737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14738 let cursor = self.buffer.read(cx).read(cx).len();
14739 self.change_selections(Default::default(), window, cx, |s| {
14740 s.select_ranges(vec![cursor..cursor])
14741 });
14742 }
14743
14744 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14745 self.nav_history = nav_history;
14746 }
14747
14748 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14749 self.nav_history.as_ref()
14750 }
14751
14752 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14753 self.push_to_nav_history(
14754 self.selections.newest_anchor().head(),
14755 None,
14756 false,
14757 true,
14758 cx,
14759 );
14760 }
14761
14762 fn push_to_nav_history(
14763 &mut self,
14764 cursor_anchor: Anchor,
14765 new_position: Option<Point>,
14766 is_deactivate: bool,
14767 always: bool,
14768 cx: &mut Context<Self>,
14769 ) {
14770 if let Some(nav_history) = self.nav_history.as_mut() {
14771 let buffer = self.buffer.read(cx).read(cx);
14772 let cursor_position = cursor_anchor.to_point(&buffer);
14773 let scroll_state = self.scroll_manager.anchor();
14774 let scroll_top_row = scroll_state.top_row(&buffer);
14775 drop(buffer);
14776
14777 if let Some(new_position) = new_position {
14778 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14779 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14780 return;
14781 }
14782 }
14783
14784 nav_history.push(
14785 Some(NavigationData {
14786 cursor_anchor,
14787 cursor_position,
14788 scroll_anchor: scroll_state,
14789 scroll_top_row,
14790 }),
14791 cx,
14792 );
14793 cx.emit(EditorEvent::PushedToNavHistory {
14794 anchor: cursor_anchor,
14795 is_deactivate,
14796 })
14797 }
14798 }
14799
14800 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14802 let buffer = self.buffer.read(cx).snapshot(cx);
14803 let mut selection = self
14804 .selections
14805 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14806 selection.set_head(buffer.len(), SelectionGoal::None);
14807 self.change_selections(Default::default(), window, cx, |s| {
14808 s.select(vec![selection]);
14809 });
14810 }
14811
14812 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14813 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14814 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14815 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14816 });
14817 }
14818
14819 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14821 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14822 let mut selections = self.selections.all::<Point>(&display_map);
14823 let max_point = display_map.buffer_snapshot().max_point();
14824 for selection in &mut selections {
14825 let rows = selection.spanned_rows(true, &display_map);
14826 selection.start = Point::new(rows.start.0, 0);
14827 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14828 selection.reversed = false;
14829 }
14830 self.change_selections(Default::default(), window, cx, |s| {
14831 s.select(selections);
14832 });
14833 }
14834
14835 pub fn split_selection_into_lines(
14836 &mut self,
14837 action: &SplitSelectionIntoLines,
14838 window: &mut Window,
14839 cx: &mut Context<Self>,
14840 ) {
14841 let selections = self
14842 .selections
14843 .all::<Point>(&self.display_snapshot(cx))
14844 .into_iter()
14845 .map(|selection| selection.start..selection.end)
14846 .collect::<Vec<_>>();
14847 self.unfold_ranges(&selections, true, true, cx);
14848
14849 let mut new_selection_ranges = Vec::new();
14850 {
14851 let buffer = self.buffer.read(cx).read(cx);
14852 for selection in selections {
14853 for row in selection.start.row..selection.end.row {
14854 let line_start = Point::new(row, 0);
14855 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14856
14857 if action.keep_selections {
14858 // Keep the selection range for each line
14859 let selection_start = if row == selection.start.row {
14860 selection.start
14861 } else {
14862 line_start
14863 };
14864 new_selection_ranges.push(selection_start..line_end);
14865 } else {
14866 // Collapse to cursor at end of line
14867 new_selection_ranges.push(line_end..line_end);
14868 }
14869 }
14870
14871 let is_multiline_selection = selection.start.row != selection.end.row;
14872 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14873 // so this action feels more ergonomic when paired with other selection operations
14874 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14875 if !should_skip_last {
14876 if action.keep_selections {
14877 if is_multiline_selection {
14878 let line_start = Point::new(selection.end.row, 0);
14879 new_selection_ranges.push(line_start..selection.end);
14880 } else {
14881 new_selection_ranges.push(selection.start..selection.end);
14882 }
14883 } else {
14884 new_selection_ranges.push(selection.end..selection.end);
14885 }
14886 }
14887 }
14888 }
14889 self.change_selections(Default::default(), window, cx, |s| {
14890 s.select_ranges(new_selection_ranges);
14891 });
14892 }
14893
14894 pub fn add_selection_above(
14895 &mut self,
14896 action: &AddSelectionAbove,
14897 window: &mut Window,
14898 cx: &mut Context<Self>,
14899 ) {
14900 self.add_selection(true, action.skip_soft_wrap, window, cx);
14901 }
14902
14903 pub fn add_selection_below(
14904 &mut self,
14905 action: &AddSelectionBelow,
14906 window: &mut Window,
14907 cx: &mut Context<Self>,
14908 ) {
14909 self.add_selection(false, action.skip_soft_wrap, window, cx);
14910 }
14911
14912 fn add_selection(
14913 &mut self,
14914 above: bool,
14915 skip_soft_wrap: bool,
14916 window: &mut Window,
14917 cx: &mut Context<Self>,
14918 ) {
14919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14920
14921 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14922 let all_selections = self.selections.all::<Point>(&display_map);
14923 let text_layout_details = self.text_layout_details(window);
14924
14925 let (mut columnar_selections, new_selections_to_columnarize) = {
14926 if let Some(state) = self.add_selections_state.as_ref() {
14927 let columnar_selection_ids: HashSet<_> = state
14928 .groups
14929 .iter()
14930 .flat_map(|group| group.stack.iter())
14931 .copied()
14932 .collect();
14933
14934 all_selections
14935 .into_iter()
14936 .partition(|s| columnar_selection_ids.contains(&s.id))
14937 } else {
14938 (Vec::new(), all_selections)
14939 }
14940 };
14941
14942 let mut state = self
14943 .add_selections_state
14944 .take()
14945 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14946
14947 for selection in new_selections_to_columnarize {
14948 let range = selection.display_range(&display_map).sorted();
14949 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14950 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14951 let positions = start_x.min(end_x)..start_x.max(end_x);
14952 let mut stack = Vec::new();
14953 for row in range.start.row().0..=range.end.row().0 {
14954 if let Some(selection) = self.selections.build_columnar_selection(
14955 &display_map,
14956 DisplayRow(row),
14957 &positions,
14958 selection.reversed,
14959 &text_layout_details,
14960 ) {
14961 stack.push(selection.id);
14962 columnar_selections.push(selection);
14963 }
14964 }
14965 if !stack.is_empty() {
14966 if above {
14967 stack.reverse();
14968 }
14969 state.groups.push(AddSelectionsGroup { above, stack });
14970 }
14971 }
14972
14973 let mut final_selections = Vec::new();
14974 let end_row = if above {
14975 DisplayRow(0)
14976 } else {
14977 display_map.max_point().row()
14978 };
14979
14980 let mut last_added_item_per_group = HashMap::default();
14981 for group in state.groups.iter_mut() {
14982 if let Some(last_id) = group.stack.last() {
14983 last_added_item_per_group.insert(*last_id, group);
14984 }
14985 }
14986
14987 for selection in columnar_selections {
14988 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14989 if above == group.above {
14990 let range = selection.display_range(&display_map).sorted();
14991 debug_assert_eq!(range.start.row(), range.end.row());
14992 let mut row = range.start.row();
14993 let positions =
14994 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14995 Pixels::from(start)..Pixels::from(end)
14996 } else {
14997 let start_x =
14998 display_map.x_for_display_point(range.start, &text_layout_details);
14999 let end_x =
15000 display_map.x_for_display_point(range.end, &text_layout_details);
15001 start_x.min(end_x)..start_x.max(end_x)
15002 };
15003
15004 let mut maybe_new_selection = None;
15005 let direction = if above { -1 } else { 1 };
15006
15007 while row != end_row {
15008 let new_buffer_row = if skip_soft_wrap {
15009 let new_row = display_map
15010 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction);
15011 row = new_row.row();
15012 Some(new_row.to_point(&display_map).row)
15013 } else {
15014 if above {
15015 row.0 -= 1;
15016 } else {
15017 row.0 += 1;
15018 }
15019 None
15020 };
15021
15022 let new_selection = if let Some(buffer_row) = new_buffer_row {
15023 let start_col = selection.start.column;
15024 let end_col = selection.end.column;
15025 let buffer_columns = start_col.min(end_col)..start_col.max(end_col);
15026
15027 self.selections
15028 .build_columnar_selection_from_buffer_columns(
15029 &display_map,
15030 buffer_row,
15031 &buffer_columns,
15032 selection.reversed,
15033 &text_layout_details,
15034 )
15035 } else {
15036 self.selections.build_columnar_selection(
15037 &display_map,
15038 row,
15039 &positions,
15040 selection.reversed,
15041 &text_layout_details,
15042 )
15043 };
15044
15045 if let Some(new_selection) = new_selection {
15046 maybe_new_selection = Some(new_selection);
15047 break;
15048 }
15049 }
15050
15051 if let Some(new_selection) = maybe_new_selection {
15052 group.stack.push(new_selection.id);
15053 if above {
15054 final_selections.push(new_selection);
15055 final_selections.push(selection);
15056 } else {
15057 final_selections.push(selection);
15058 final_selections.push(new_selection);
15059 }
15060 } else {
15061 final_selections.push(selection);
15062 }
15063 } else {
15064 group.stack.pop();
15065 }
15066 } else {
15067 final_selections.push(selection);
15068 }
15069 }
15070
15071 self.change_selections(Default::default(), window, cx, |s| {
15072 s.select(final_selections);
15073 });
15074
15075 let final_selection_ids: HashSet<_> = self
15076 .selections
15077 .all::<Point>(&display_map)
15078 .iter()
15079 .map(|s| s.id)
15080 .collect();
15081 state.groups.retain_mut(|group| {
15082 // selections might get merged above so we remove invalid items from stacks
15083 group.stack.retain(|id| final_selection_ids.contains(id));
15084
15085 // single selection in stack can be treated as initial state
15086 group.stack.len() > 1
15087 });
15088
15089 if !state.groups.is_empty() {
15090 self.add_selections_state = Some(state);
15091 }
15092 }
15093
15094 pub fn insert_snippet_at_selections(
15095 &mut self,
15096 action: &InsertSnippet,
15097 window: &mut Window,
15098 cx: &mut Context<Self>,
15099 ) {
15100 self.try_insert_snippet_at_selections(action, window, cx)
15101 .log_err();
15102 }
15103
15104 fn try_insert_snippet_at_selections(
15105 &mut self,
15106 action: &InsertSnippet,
15107 window: &mut Window,
15108 cx: &mut Context<Self>,
15109 ) -> Result<()> {
15110 let insertion_ranges = self
15111 .selections
15112 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15113 .into_iter()
15114 .map(|selection| selection.range())
15115 .collect_vec();
15116
15117 let snippet = if let Some(snippet_body) = &action.snippet {
15118 if action.language.is_none() && action.name.is_none() {
15119 Snippet::parse(snippet_body)?
15120 } else {
15121 bail!("`snippet` is mutually exclusive with `language` and `name`")
15122 }
15123 } else if let Some(name) = &action.name {
15124 let project = self.project().context("no project")?;
15125 let snippet_store = project.read(cx).snippets().read(cx);
15126 let snippet = snippet_store
15127 .snippets_for(action.language.clone(), cx)
15128 .into_iter()
15129 .find(|snippet| snippet.name == *name)
15130 .context("snippet not found")?;
15131 Snippet::parse(&snippet.body)?
15132 } else {
15133 // todo(andrew): open modal to select snippet
15134 bail!("`name` or `snippet` is required")
15135 };
15136
15137 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15138 }
15139
15140 fn select_match_ranges(
15141 &mut self,
15142 range: Range<MultiBufferOffset>,
15143 reversed: bool,
15144 replace_newest: bool,
15145 auto_scroll: Option<Autoscroll>,
15146 window: &mut Window,
15147 cx: &mut Context<Editor>,
15148 ) {
15149 self.unfold_ranges(
15150 std::slice::from_ref(&range),
15151 false,
15152 auto_scroll.is_some(),
15153 cx,
15154 );
15155 let effects = if let Some(scroll) = auto_scroll {
15156 SelectionEffects::scroll(scroll)
15157 } else {
15158 SelectionEffects::no_scroll()
15159 };
15160 self.change_selections(effects, window, cx, |s| {
15161 if replace_newest {
15162 s.delete(s.newest_anchor().id);
15163 }
15164 if reversed {
15165 s.insert_range(range.end..range.start);
15166 } else {
15167 s.insert_range(range);
15168 }
15169 });
15170 }
15171
15172 pub fn select_next_match_internal(
15173 &mut self,
15174 display_map: &DisplaySnapshot,
15175 replace_newest: bool,
15176 autoscroll: Option<Autoscroll>,
15177 window: &mut Window,
15178 cx: &mut Context<Self>,
15179 ) -> Result<()> {
15180 let buffer = display_map.buffer_snapshot();
15181 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15182 if let Some(mut select_next_state) = self.select_next_state.take() {
15183 let query = &select_next_state.query;
15184 if !select_next_state.done {
15185 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15186 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15187 let mut next_selected_range = None;
15188
15189 let bytes_after_last_selection =
15190 buffer.bytes_in_range(last_selection.end..buffer.len());
15191 let bytes_before_first_selection =
15192 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15193 let query_matches = query
15194 .stream_find_iter(bytes_after_last_selection)
15195 .map(|result| (last_selection.end, result))
15196 .chain(
15197 query
15198 .stream_find_iter(bytes_before_first_selection)
15199 .map(|result| (MultiBufferOffset(0), result)),
15200 );
15201
15202 for (start_offset, query_match) in query_matches {
15203 let query_match = query_match.unwrap(); // can only fail due to I/O
15204 let offset_range =
15205 start_offset + query_match.start()..start_offset + query_match.end();
15206
15207 if !select_next_state.wordwise
15208 || (!buffer.is_inside_word(offset_range.start, None)
15209 && !buffer.is_inside_word(offset_range.end, None))
15210 {
15211 let idx = selections
15212 .partition_point(|selection| selection.end <= offset_range.start);
15213 let overlaps = selections
15214 .get(idx)
15215 .map_or(false, |selection| selection.start < offset_range.end);
15216
15217 if !overlaps {
15218 next_selected_range = Some(offset_range);
15219 break;
15220 }
15221 }
15222 }
15223
15224 if let Some(next_selected_range) = next_selected_range {
15225 self.select_match_ranges(
15226 next_selected_range,
15227 last_selection.reversed,
15228 replace_newest,
15229 autoscroll,
15230 window,
15231 cx,
15232 );
15233 } else {
15234 select_next_state.done = true;
15235 }
15236 }
15237
15238 self.select_next_state = Some(select_next_state);
15239 } else {
15240 let mut only_carets = true;
15241 let mut same_text_selected = true;
15242 let mut selected_text = None;
15243
15244 let mut selections_iter = selections.iter().peekable();
15245 while let Some(selection) = selections_iter.next() {
15246 if selection.start != selection.end {
15247 only_carets = false;
15248 }
15249
15250 if same_text_selected {
15251 if selected_text.is_none() {
15252 selected_text =
15253 Some(buffer.text_for_range(selection.range()).collect::<String>());
15254 }
15255
15256 if let Some(next_selection) = selections_iter.peek() {
15257 if next_selection.len() == selection.len() {
15258 let next_selected_text = buffer
15259 .text_for_range(next_selection.range())
15260 .collect::<String>();
15261 if Some(next_selected_text) != selected_text {
15262 same_text_selected = false;
15263 selected_text = None;
15264 }
15265 } else {
15266 same_text_selected = false;
15267 selected_text = None;
15268 }
15269 }
15270 }
15271 }
15272
15273 if only_carets {
15274 for selection in &mut selections {
15275 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15276 selection.start = word_range.start;
15277 selection.end = word_range.end;
15278 selection.goal = SelectionGoal::None;
15279 selection.reversed = false;
15280 self.select_match_ranges(
15281 selection.start..selection.end,
15282 selection.reversed,
15283 replace_newest,
15284 autoscroll,
15285 window,
15286 cx,
15287 );
15288 }
15289
15290 if selections.len() == 1 {
15291 let selection = selections
15292 .last()
15293 .expect("ensured that there's only one selection");
15294 let query = buffer
15295 .text_for_range(selection.start..selection.end)
15296 .collect::<String>();
15297 let is_empty = query.is_empty();
15298 let select_state = SelectNextState {
15299 query: self.build_query(&[query], cx)?,
15300 wordwise: true,
15301 done: is_empty,
15302 };
15303 self.select_next_state = Some(select_state);
15304 } else {
15305 self.select_next_state = None;
15306 }
15307 } else if let Some(selected_text) = selected_text {
15308 self.select_next_state = Some(SelectNextState {
15309 query: self.build_query(&[selected_text], cx)?,
15310 wordwise: false,
15311 done: false,
15312 });
15313 self.select_next_match_internal(
15314 display_map,
15315 replace_newest,
15316 autoscroll,
15317 window,
15318 cx,
15319 )?;
15320 }
15321 }
15322 Ok(())
15323 }
15324
15325 pub fn select_all_matches(
15326 &mut self,
15327 _action: &SelectAllMatches,
15328 window: &mut Window,
15329 cx: &mut Context<Self>,
15330 ) -> Result<()> {
15331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15332
15333 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15334
15335 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15336 let Some(select_next_state) = self.select_next_state.as_mut() else {
15337 return Ok(());
15338 };
15339 if select_next_state.done {
15340 return Ok(());
15341 }
15342
15343 let mut new_selections = Vec::new();
15344
15345 let reversed = self
15346 .selections
15347 .oldest::<MultiBufferOffset>(&display_map)
15348 .reversed;
15349 let buffer = display_map.buffer_snapshot();
15350 let query_matches = select_next_state
15351 .query
15352 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15353
15354 for query_match in query_matches.into_iter() {
15355 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15356 let offset_range = if reversed {
15357 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15358 } else {
15359 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15360 };
15361
15362 if !select_next_state.wordwise
15363 || (!buffer.is_inside_word(offset_range.start, None)
15364 && !buffer.is_inside_word(offset_range.end, None))
15365 {
15366 new_selections.push(offset_range.start..offset_range.end);
15367 }
15368 }
15369
15370 select_next_state.done = true;
15371
15372 if new_selections.is_empty() {
15373 log::error!("bug: new_selections is empty in select_all_matches");
15374 return Ok(());
15375 }
15376
15377 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15378 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15379 selections.select_ranges(new_selections)
15380 });
15381
15382 Ok(())
15383 }
15384
15385 pub fn select_next(
15386 &mut self,
15387 action: &SelectNext,
15388 window: &mut Window,
15389 cx: &mut Context<Self>,
15390 ) -> Result<()> {
15391 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15392 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15393 self.select_next_match_internal(
15394 &display_map,
15395 action.replace_newest,
15396 Some(Autoscroll::newest()),
15397 window,
15398 cx,
15399 )?;
15400 Ok(())
15401 }
15402
15403 pub fn select_previous(
15404 &mut self,
15405 action: &SelectPrevious,
15406 window: &mut Window,
15407 cx: &mut Context<Self>,
15408 ) -> Result<()> {
15409 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15410 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15411 let buffer = display_map.buffer_snapshot();
15412 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15413 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15414 let query = &select_prev_state.query;
15415 if !select_prev_state.done {
15416 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15417 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15418 let mut next_selected_range = None;
15419 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15420 let bytes_before_last_selection =
15421 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15422 let bytes_after_first_selection =
15423 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15424 let query_matches = query
15425 .stream_find_iter(bytes_before_last_selection)
15426 .map(|result| (last_selection.start, result))
15427 .chain(
15428 query
15429 .stream_find_iter(bytes_after_first_selection)
15430 .map(|result| (buffer.len(), result)),
15431 );
15432 for (end_offset, query_match) in query_matches {
15433 let query_match = query_match.unwrap(); // can only fail due to I/O
15434 let offset_range =
15435 end_offset - query_match.end()..end_offset - query_match.start();
15436
15437 if !select_prev_state.wordwise
15438 || (!buffer.is_inside_word(offset_range.start, None)
15439 && !buffer.is_inside_word(offset_range.end, None))
15440 {
15441 next_selected_range = Some(offset_range);
15442 break;
15443 }
15444 }
15445
15446 if let Some(next_selected_range) = next_selected_range {
15447 self.select_match_ranges(
15448 next_selected_range,
15449 last_selection.reversed,
15450 action.replace_newest,
15451 Some(Autoscroll::newest()),
15452 window,
15453 cx,
15454 );
15455 } else {
15456 select_prev_state.done = true;
15457 }
15458 }
15459
15460 self.select_prev_state = Some(select_prev_state);
15461 } else {
15462 let mut only_carets = true;
15463 let mut same_text_selected = true;
15464 let mut selected_text = None;
15465
15466 let mut selections_iter = selections.iter().peekable();
15467 while let Some(selection) = selections_iter.next() {
15468 if selection.start != selection.end {
15469 only_carets = false;
15470 }
15471
15472 if same_text_selected {
15473 if selected_text.is_none() {
15474 selected_text =
15475 Some(buffer.text_for_range(selection.range()).collect::<String>());
15476 }
15477
15478 if let Some(next_selection) = selections_iter.peek() {
15479 if next_selection.len() == selection.len() {
15480 let next_selected_text = buffer
15481 .text_for_range(next_selection.range())
15482 .collect::<String>();
15483 if Some(next_selected_text) != selected_text {
15484 same_text_selected = false;
15485 selected_text = None;
15486 }
15487 } else {
15488 same_text_selected = false;
15489 selected_text = None;
15490 }
15491 }
15492 }
15493 }
15494
15495 if only_carets {
15496 for selection in &mut selections {
15497 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15498 selection.start = word_range.start;
15499 selection.end = word_range.end;
15500 selection.goal = SelectionGoal::None;
15501 selection.reversed = false;
15502 self.select_match_ranges(
15503 selection.start..selection.end,
15504 selection.reversed,
15505 action.replace_newest,
15506 Some(Autoscroll::newest()),
15507 window,
15508 cx,
15509 );
15510 }
15511 if selections.len() == 1 {
15512 let selection = selections
15513 .last()
15514 .expect("ensured that there's only one selection");
15515 let query = buffer
15516 .text_for_range(selection.start..selection.end)
15517 .collect::<String>();
15518 let is_empty = query.is_empty();
15519 let select_state = SelectNextState {
15520 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15521 wordwise: true,
15522 done: is_empty,
15523 };
15524 self.select_prev_state = Some(select_state);
15525 } else {
15526 self.select_prev_state = None;
15527 }
15528 } else if let Some(selected_text) = selected_text {
15529 self.select_prev_state = Some(SelectNextState {
15530 query: self
15531 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15532 wordwise: false,
15533 done: false,
15534 });
15535 self.select_previous(action, window, cx)?;
15536 }
15537 }
15538 Ok(())
15539 }
15540
15541 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15542 /// setting the case sensitivity based on the global
15543 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15544 /// editor's settings.
15545 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15546 where
15547 I: IntoIterator<Item = P>,
15548 P: AsRef<[u8]>,
15549 {
15550 let case_sensitive = self
15551 .select_next_is_case_sensitive
15552 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15553
15554 let mut builder = AhoCorasickBuilder::new();
15555 builder.ascii_case_insensitive(!case_sensitive);
15556 builder.build(patterns)
15557 }
15558
15559 pub fn find_next_match(
15560 &mut self,
15561 _: &FindNextMatch,
15562 window: &mut Window,
15563 cx: &mut Context<Self>,
15564 ) -> Result<()> {
15565 let selections = self.selections.disjoint_anchors_arc();
15566 match selections.first() {
15567 Some(first) if selections.len() >= 2 => {
15568 self.change_selections(Default::default(), window, cx, |s| {
15569 s.select_ranges([first.range()]);
15570 });
15571 }
15572 _ => self.select_next(
15573 &SelectNext {
15574 replace_newest: true,
15575 },
15576 window,
15577 cx,
15578 )?,
15579 }
15580 Ok(())
15581 }
15582
15583 pub fn find_previous_match(
15584 &mut self,
15585 _: &FindPreviousMatch,
15586 window: &mut Window,
15587 cx: &mut Context<Self>,
15588 ) -> Result<()> {
15589 let selections = self.selections.disjoint_anchors_arc();
15590 match selections.last() {
15591 Some(last) if selections.len() >= 2 => {
15592 self.change_selections(Default::default(), window, cx, |s| {
15593 s.select_ranges([last.range()]);
15594 });
15595 }
15596 _ => self.select_previous(
15597 &SelectPrevious {
15598 replace_newest: true,
15599 },
15600 window,
15601 cx,
15602 )?,
15603 }
15604 Ok(())
15605 }
15606
15607 pub fn toggle_comments(
15608 &mut self,
15609 action: &ToggleComments,
15610 window: &mut Window,
15611 cx: &mut Context<Self>,
15612 ) {
15613 if self.read_only(cx) {
15614 return;
15615 }
15616 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15617 let text_layout_details = &self.text_layout_details(window);
15618 self.transact(window, cx, |this, window, cx| {
15619 let mut selections = this
15620 .selections
15621 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15622 let mut edits = Vec::new();
15623 let mut selection_edit_ranges = Vec::new();
15624 let mut last_toggled_row = None;
15625 let snapshot = this.buffer.read(cx).read(cx);
15626 let empty_str: Arc<str> = Arc::default();
15627 let mut suffixes_inserted = Vec::new();
15628 let ignore_indent = action.ignore_indent;
15629
15630 fn comment_prefix_range(
15631 snapshot: &MultiBufferSnapshot,
15632 row: MultiBufferRow,
15633 comment_prefix: &str,
15634 comment_prefix_whitespace: &str,
15635 ignore_indent: bool,
15636 ) -> Range<Point> {
15637 let indent_size = if ignore_indent {
15638 0
15639 } else {
15640 snapshot.indent_size_for_line(row).len
15641 };
15642
15643 let start = Point::new(row.0, indent_size);
15644
15645 let mut line_bytes = snapshot
15646 .bytes_in_range(start..snapshot.max_point())
15647 .flatten()
15648 .copied();
15649
15650 // If this line currently begins with the line comment prefix, then record
15651 // the range containing the prefix.
15652 if line_bytes
15653 .by_ref()
15654 .take(comment_prefix.len())
15655 .eq(comment_prefix.bytes())
15656 {
15657 // Include any whitespace that matches the comment prefix.
15658 let matching_whitespace_len = line_bytes
15659 .zip(comment_prefix_whitespace.bytes())
15660 .take_while(|(a, b)| a == b)
15661 .count() as u32;
15662 let end = Point::new(
15663 start.row,
15664 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15665 );
15666 start..end
15667 } else {
15668 start..start
15669 }
15670 }
15671
15672 fn comment_suffix_range(
15673 snapshot: &MultiBufferSnapshot,
15674 row: MultiBufferRow,
15675 comment_suffix: &str,
15676 comment_suffix_has_leading_space: bool,
15677 ) -> Range<Point> {
15678 let end = Point::new(row.0, snapshot.line_len(row));
15679 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15680
15681 let mut line_end_bytes = snapshot
15682 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15683 .flatten()
15684 .copied();
15685
15686 let leading_space_len = if suffix_start_column > 0
15687 && line_end_bytes.next() == Some(b' ')
15688 && comment_suffix_has_leading_space
15689 {
15690 1
15691 } else {
15692 0
15693 };
15694
15695 // If this line currently begins with the line comment prefix, then record
15696 // the range containing the prefix.
15697 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15698 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15699 start..end
15700 } else {
15701 end..end
15702 }
15703 }
15704
15705 // TODO: Handle selections that cross excerpts
15706 for selection in &mut selections {
15707 let start_column = snapshot
15708 .indent_size_for_line(MultiBufferRow(selection.start.row))
15709 .len;
15710 let language = if let Some(language) =
15711 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15712 {
15713 language
15714 } else {
15715 continue;
15716 };
15717
15718 selection_edit_ranges.clear();
15719
15720 // If multiple selections contain a given row, avoid processing that
15721 // row more than once.
15722 let mut start_row = MultiBufferRow(selection.start.row);
15723 if last_toggled_row == Some(start_row) {
15724 start_row = start_row.next_row();
15725 }
15726 let end_row =
15727 if selection.end.row > selection.start.row && selection.end.column == 0 {
15728 MultiBufferRow(selection.end.row - 1)
15729 } else {
15730 MultiBufferRow(selection.end.row)
15731 };
15732 last_toggled_row = Some(end_row);
15733
15734 if start_row > end_row {
15735 continue;
15736 }
15737
15738 // If the language has line comments, toggle those.
15739 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15740
15741 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15742 if ignore_indent {
15743 full_comment_prefixes = full_comment_prefixes
15744 .into_iter()
15745 .map(|s| Arc::from(s.trim_end()))
15746 .collect();
15747 }
15748
15749 if !full_comment_prefixes.is_empty() {
15750 let first_prefix = full_comment_prefixes
15751 .first()
15752 .expect("prefixes is non-empty");
15753 let prefix_trimmed_lengths = full_comment_prefixes
15754 .iter()
15755 .map(|p| p.trim_end_matches(' ').len())
15756 .collect::<SmallVec<[usize; 4]>>();
15757
15758 let mut all_selection_lines_are_comments = true;
15759
15760 for row in start_row.0..=end_row.0 {
15761 let row = MultiBufferRow(row);
15762 if start_row < end_row && snapshot.is_line_blank(row) {
15763 continue;
15764 }
15765
15766 let prefix_range = full_comment_prefixes
15767 .iter()
15768 .zip(prefix_trimmed_lengths.iter().copied())
15769 .map(|(prefix, trimmed_prefix_len)| {
15770 comment_prefix_range(
15771 snapshot.deref(),
15772 row,
15773 &prefix[..trimmed_prefix_len],
15774 &prefix[trimmed_prefix_len..],
15775 ignore_indent,
15776 )
15777 })
15778 .max_by_key(|range| range.end.column - range.start.column)
15779 .expect("prefixes is non-empty");
15780
15781 if prefix_range.is_empty() {
15782 all_selection_lines_are_comments = false;
15783 }
15784
15785 selection_edit_ranges.push(prefix_range);
15786 }
15787
15788 if all_selection_lines_are_comments {
15789 edits.extend(
15790 selection_edit_ranges
15791 .iter()
15792 .cloned()
15793 .map(|range| (range, empty_str.clone())),
15794 );
15795 } else {
15796 let min_column = selection_edit_ranges
15797 .iter()
15798 .map(|range| range.start.column)
15799 .min()
15800 .unwrap_or(0);
15801 edits.extend(selection_edit_ranges.iter().map(|range| {
15802 let position = Point::new(range.start.row, min_column);
15803 (position..position, first_prefix.clone())
15804 }));
15805 }
15806 } else if let Some(BlockCommentConfig {
15807 start: full_comment_prefix,
15808 end: comment_suffix,
15809 ..
15810 }) = language.block_comment()
15811 {
15812 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15813 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15814 let prefix_range = comment_prefix_range(
15815 snapshot.deref(),
15816 start_row,
15817 comment_prefix,
15818 comment_prefix_whitespace,
15819 ignore_indent,
15820 );
15821 let suffix_range = comment_suffix_range(
15822 snapshot.deref(),
15823 end_row,
15824 comment_suffix.trim_start_matches(' '),
15825 comment_suffix.starts_with(' '),
15826 );
15827
15828 if prefix_range.is_empty() || suffix_range.is_empty() {
15829 edits.push((
15830 prefix_range.start..prefix_range.start,
15831 full_comment_prefix.clone(),
15832 ));
15833 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15834 suffixes_inserted.push((end_row, comment_suffix.len()));
15835 } else {
15836 edits.push((prefix_range, empty_str.clone()));
15837 edits.push((suffix_range, empty_str.clone()));
15838 }
15839 } else {
15840 continue;
15841 }
15842 }
15843
15844 drop(snapshot);
15845 this.buffer.update(cx, |buffer, cx| {
15846 buffer.edit(edits, None, cx);
15847 });
15848
15849 // Adjust selections so that they end before any comment suffixes that
15850 // were inserted.
15851 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15852 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15853 let snapshot = this.buffer.read(cx).read(cx);
15854 for selection in &mut selections {
15855 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15856 match row.cmp(&MultiBufferRow(selection.end.row)) {
15857 Ordering::Less => {
15858 suffixes_inserted.next();
15859 continue;
15860 }
15861 Ordering::Greater => break,
15862 Ordering::Equal => {
15863 if selection.end.column == snapshot.line_len(row) {
15864 if selection.is_empty() {
15865 selection.start.column -= suffix_len as u32;
15866 }
15867 selection.end.column -= suffix_len as u32;
15868 }
15869 break;
15870 }
15871 }
15872 }
15873 }
15874
15875 drop(snapshot);
15876 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15877
15878 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15879 let selections_on_single_row = selections.windows(2).all(|selections| {
15880 selections[0].start.row == selections[1].start.row
15881 && selections[0].end.row == selections[1].end.row
15882 && selections[0].start.row == selections[0].end.row
15883 });
15884 let selections_selecting = selections
15885 .iter()
15886 .any(|selection| selection.start != selection.end);
15887 let advance_downwards = action.advance_downwards
15888 && selections_on_single_row
15889 && !selections_selecting
15890 && !matches!(this.mode, EditorMode::SingleLine);
15891
15892 if advance_downwards {
15893 let snapshot = this.buffer.read(cx).snapshot(cx);
15894
15895 this.change_selections(Default::default(), window, cx, |s| {
15896 s.move_cursors_with(|display_snapshot, display_point, _| {
15897 let mut point = display_point.to_point(display_snapshot);
15898 point.row += 1;
15899 point = snapshot.clip_point(point, Bias::Left);
15900 let display_point = point.to_display_point(display_snapshot);
15901 let goal = SelectionGoal::HorizontalPosition(
15902 display_snapshot
15903 .x_for_display_point(display_point, text_layout_details)
15904 .into(),
15905 );
15906 (display_point, goal)
15907 })
15908 });
15909 }
15910 });
15911 }
15912
15913 pub fn select_enclosing_symbol(
15914 &mut self,
15915 _: &SelectEnclosingSymbol,
15916 window: &mut Window,
15917 cx: &mut Context<Self>,
15918 ) {
15919 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15920
15921 let buffer = self.buffer.read(cx).snapshot(cx);
15922 let old_selections = self
15923 .selections
15924 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15925 .into_boxed_slice();
15926
15927 fn update_selection(
15928 selection: &Selection<MultiBufferOffset>,
15929 buffer_snap: &MultiBufferSnapshot,
15930 ) -> Option<Selection<MultiBufferOffset>> {
15931 let cursor = selection.head();
15932 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15933 for symbol in symbols.iter().rev() {
15934 let start = symbol.range.start.to_offset(buffer_snap);
15935 let end = symbol.range.end.to_offset(buffer_snap);
15936 let new_range = start..end;
15937 if start < selection.start || end > selection.end {
15938 return Some(Selection {
15939 id: selection.id,
15940 start: new_range.start,
15941 end: new_range.end,
15942 goal: SelectionGoal::None,
15943 reversed: selection.reversed,
15944 });
15945 }
15946 }
15947 None
15948 }
15949
15950 let mut selected_larger_symbol = false;
15951 let new_selections = old_selections
15952 .iter()
15953 .map(|selection| match update_selection(selection, &buffer) {
15954 Some(new_selection) => {
15955 if new_selection.range() != selection.range() {
15956 selected_larger_symbol = true;
15957 }
15958 new_selection
15959 }
15960 None => selection.clone(),
15961 })
15962 .collect::<Vec<_>>();
15963
15964 if selected_larger_symbol {
15965 self.change_selections(Default::default(), window, cx, |s| {
15966 s.select(new_selections);
15967 });
15968 }
15969 }
15970
15971 pub fn select_larger_syntax_node(
15972 &mut self,
15973 _: &SelectLargerSyntaxNode,
15974 window: &mut Window,
15975 cx: &mut Context<Self>,
15976 ) {
15977 let Some(visible_row_count) = self.visible_row_count() else {
15978 return;
15979 };
15980 let old_selections: Box<[_]> = self
15981 .selections
15982 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15983 .into();
15984 if old_selections.is_empty() {
15985 return;
15986 }
15987
15988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15989
15990 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15991 let buffer = self.buffer.read(cx).snapshot(cx);
15992
15993 let mut selected_larger_node = false;
15994 let mut new_selections = old_selections
15995 .iter()
15996 .map(|selection| {
15997 let old_range = selection.start..selection.end;
15998
15999 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
16000 // manually select word at selection
16001 if ["string_content", "inline"].contains(&node.kind()) {
16002 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
16003 // ignore if word is already selected
16004 if !word_range.is_empty() && old_range != word_range {
16005 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
16006 // only select word if start and end point belongs to same word
16007 if word_range == last_word_range {
16008 selected_larger_node = true;
16009 return Selection {
16010 id: selection.id,
16011 start: word_range.start,
16012 end: word_range.end,
16013 goal: SelectionGoal::None,
16014 reversed: selection.reversed,
16015 };
16016 }
16017 }
16018 }
16019 }
16020
16021 let mut new_range = old_range.clone();
16022 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16023 new_range = range;
16024 if !node.is_named() {
16025 continue;
16026 }
16027 if !display_map.intersects_fold(new_range.start)
16028 && !display_map.intersects_fold(new_range.end)
16029 {
16030 break;
16031 }
16032 }
16033
16034 selected_larger_node |= new_range != old_range;
16035 Selection {
16036 id: selection.id,
16037 start: new_range.start,
16038 end: new_range.end,
16039 goal: SelectionGoal::None,
16040 reversed: selection.reversed,
16041 }
16042 })
16043 .collect::<Vec<_>>();
16044
16045 if !selected_larger_node {
16046 return; // don't put this call in the history
16047 }
16048
16049 // scroll based on transformation done to the last selection created by the user
16050 let (last_old, last_new) = old_selections
16051 .last()
16052 .zip(new_selections.last().cloned())
16053 .expect("old_selections isn't empty");
16054
16055 // revert selection
16056 let is_selection_reversed = {
16057 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16058 new_selections.last_mut().expect("checked above").reversed =
16059 should_newest_selection_be_reversed;
16060 should_newest_selection_be_reversed
16061 };
16062
16063 if selected_larger_node {
16064 self.select_syntax_node_history.disable_clearing = true;
16065 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16066 s.select(new_selections.clone());
16067 });
16068 self.select_syntax_node_history.disable_clearing = false;
16069 }
16070
16071 let start_row = last_new.start.to_display_point(&display_map).row().0;
16072 let end_row = last_new.end.to_display_point(&display_map).row().0;
16073 let selection_height = end_row - start_row + 1;
16074 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16075
16076 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16077 let scroll_behavior = if fits_on_the_screen {
16078 self.request_autoscroll(Autoscroll::fit(), cx);
16079 SelectSyntaxNodeScrollBehavior::FitSelection
16080 } else if is_selection_reversed {
16081 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16082 SelectSyntaxNodeScrollBehavior::CursorTop
16083 } else {
16084 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16085 SelectSyntaxNodeScrollBehavior::CursorBottom
16086 };
16087
16088 self.select_syntax_node_history.push((
16089 old_selections,
16090 scroll_behavior,
16091 is_selection_reversed,
16092 ));
16093 }
16094
16095 pub fn select_smaller_syntax_node(
16096 &mut self,
16097 _: &SelectSmallerSyntaxNode,
16098 window: &mut Window,
16099 cx: &mut Context<Self>,
16100 ) {
16101 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16102
16103 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16104 self.select_syntax_node_history.pop()
16105 {
16106 if let Some(selection) = selections.last_mut() {
16107 selection.reversed = is_selection_reversed;
16108 }
16109
16110 self.select_syntax_node_history.disable_clearing = true;
16111 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16112 s.select(selections.to_vec());
16113 });
16114 self.select_syntax_node_history.disable_clearing = false;
16115
16116 match scroll_behavior {
16117 SelectSyntaxNodeScrollBehavior::CursorTop => {
16118 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16119 }
16120 SelectSyntaxNodeScrollBehavior::FitSelection => {
16121 self.request_autoscroll(Autoscroll::fit(), cx);
16122 }
16123 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16124 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16125 }
16126 }
16127 }
16128 }
16129
16130 pub fn unwrap_syntax_node(
16131 &mut self,
16132 _: &UnwrapSyntaxNode,
16133 window: &mut Window,
16134 cx: &mut Context<Self>,
16135 ) {
16136 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16137
16138 let buffer = self.buffer.read(cx).snapshot(cx);
16139 let selections = self
16140 .selections
16141 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16142 .into_iter()
16143 // subtracting the offset requires sorting
16144 .sorted_by_key(|i| i.start);
16145
16146 let full_edits = selections
16147 .into_iter()
16148 .filter_map(|selection| {
16149 let child = if selection.is_empty()
16150 && let Some((_, ancestor_range)) =
16151 buffer.syntax_ancestor(selection.start..selection.end)
16152 {
16153 ancestor_range
16154 } else {
16155 selection.range()
16156 };
16157
16158 let mut parent = child.clone();
16159 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16160 parent = ancestor_range;
16161 if parent.start < child.start || parent.end > child.end {
16162 break;
16163 }
16164 }
16165
16166 if parent == child {
16167 return None;
16168 }
16169 let text = buffer.text_for_range(child).collect::<String>();
16170 Some((selection.id, parent, text))
16171 })
16172 .collect::<Vec<_>>();
16173 if full_edits.is_empty() {
16174 return;
16175 }
16176
16177 self.transact(window, cx, |this, window, cx| {
16178 this.buffer.update(cx, |buffer, cx| {
16179 buffer.edit(
16180 full_edits
16181 .iter()
16182 .map(|(_, p, t)| (p.clone(), t.clone()))
16183 .collect::<Vec<_>>(),
16184 None,
16185 cx,
16186 );
16187 });
16188 this.change_selections(Default::default(), window, cx, |s| {
16189 let mut offset = 0;
16190 let mut selections = vec![];
16191 for (id, parent, text) in full_edits {
16192 let start = parent.start - offset;
16193 offset += (parent.end - parent.start) - text.len();
16194 selections.push(Selection {
16195 id,
16196 start,
16197 end: start + text.len(),
16198 reversed: false,
16199 goal: Default::default(),
16200 });
16201 }
16202 s.select(selections);
16203 });
16204 });
16205 }
16206
16207 pub fn select_next_syntax_node(
16208 &mut self,
16209 _: &SelectNextSyntaxNode,
16210 window: &mut Window,
16211 cx: &mut Context<Self>,
16212 ) {
16213 let old_selections: Box<[_]> = self
16214 .selections
16215 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16216 .into();
16217 if old_selections.is_empty() {
16218 return;
16219 }
16220
16221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16222
16223 let buffer = self.buffer.read(cx).snapshot(cx);
16224 let mut selected_sibling = false;
16225
16226 let new_selections = old_selections
16227 .iter()
16228 .map(|selection| {
16229 let old_range = selection.start..selection.end;
16230
16231 let old_range =
16232 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16233 let excerpt = buffer.excerpt_containing(old_range.clone());
16234
16235 if let Some(mut excerpt) = excerpt
16236 && let Some(node) = excerpt
16237 .buffer()
16238 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16239 {
16240 let new_range = excerpt.map_range_from_buffer(
16241 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16242 );
16243 selected_sibling = true;
16244 Selection {
16245 id: selection.id,
16246 start: new_range.start,
16247 end: new_range.end,
16248 goal: SelectionGoal::None,
16249 reversed: selection.reversed,
16250 }
16251 } else {
16252 selection.clone()
16253 }
16254 })
16255 .collect::<Vec<_>>();
16256
16257 if selected_sibling {
16258 self.change_selections(
16259 SelectionEffects::scroll(Autoscroll::fit()),
16260 window,
16261 cx,
16262 |s| {
16263 s.select(new_selections);
16264 },
16265 );
16266 }
16267 }
16268
16269 pub fn select_prev_syntax_node(
16270 &mut self,
16271 _: &SelectPreviousSyntaxNode,
16272 window: &mut Window,
16273 cx: &mut Context<Self>,
16274 ) {
16275 let old_selections: Box<[_]> = self
16276 .selections
16277 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16278 .into();
16279 if old_selections.is_empty() {
16280 return;
16281 }
16282
16283 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16284
16285 let buffer = self.buffer.read(cx).snapshot(cx);
16286 let mut selected_sibling = false;
16287
16288 let new_selections = old_selections
16289 .iter()
16290 .map(|selection| {
16291 let old_range = selection.start..selection.end;
16292 let old_range =
16293 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16294 let excerpt = buffer.excerpt_containing(old_range.clone());
16295
16296 if let Some(mut excerpt) = excerpt
16297 && let Some(node) = excerpt
16298 .buffer()
16299 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16300 {
16301 let new_range = excerpt.map_range_from_buffer(
16302 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16303 );
16304 selected_sibling = true;
16305 Selection {
16306 id: selection.id,
16307 start: new_range.start,
16308 end: new_range.end,
16309 goal: SelectionGoal::None,
16310 reversed: selection.reversed,
16311 }
16312 } else {
16313 selection.clone()
16314 }
16315 })
16316 .collect::<Vec<_>>();
16317
16318 if selected_sibling {
16319 self.change_selections(
16320 SelectionEffects::scroll(Autoscroll::fit()),
16321 window,
16322 cx,
16323 |s| {
16324 s.select(new_selections);
16325 },
16326 );
16327 }
16328 }
16329
16330 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16331 if !EditorSettings::get_global(cx).gutter.runnables {
16332 self.clear_tasks();
16333 return Task::ready(());
16334 }
16335 let project = self.project().map(Entity::downgrade);
16336 let task_sources = self.lsp_task_sources(cx);
16337 let multi_buffer = self.buffer.downgrade();
16338 cx.spawn_in(window, async move |editor, cx| {
16339 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16340 let Some(project) = project.and_then(|p| p.upgrade()) else {
16341 return;
16342 };
16343 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16344 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16345 }) else {
16346 return;
16347 };
16348
16349 let hide_runnables = project
16350 .update(cx, |project, _| project.is_via_collab())
16351 .unwrap_or(true);
16352 if hide_runnables {
16353 return;
16354 }
16355 let new_rows =
16356 cx.background_spawn({
16357 let snapshot = display_snapshot.clone();
16358 async move {
16359 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16360 }
16361 })
16362 .await;
16363 let Ok(lsp_tasks) =
16364 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16365 else {
16366 return;
16367 };
16368 let lsp_tasks = lsp_tasks.await;
16369
16370 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16371 lsp_tasks
16372 .into_iter()
16373 .flat_map(|(kind, tasks)| {
16374 tasks.into_iter().filter_map(move |(location, task)| {
16375 Some((kind.clone(), location?, task))
16376 })
16377 })
16378 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16379 let buffer = location.target.buffer;
16380 let buffer_snapshot = buffer.read(cx).snapshot();
16381 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16382 |(excerpt_id, snapshot, _)| {
16383 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16384 display_snapshot
16385 .buffer_snapshot()
16386 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16387 } else {
16388 None
16389 }
16390 },
16391 );
16392 if let Some(offset) = offset {
16393 let task_buffer_range =
16394 location.target.range.to_point(&buffer_snapshot);
16395 let context_buffer_range =
16396 task_buffer_range.to_offset(&buffer_snapshot);
16397 let context_range = BufferOffset(context_buffer_range.start)
16398 ..BufferOffset(context_buffer_range.end);
16399
16400 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16401 .or_insert_with(|| RunnableTasks {
16402 templates: Vec::new(),
16403 offset,
16404 column: task_buffer_range.start.column,
16405 extra_variables: HashMap::default(),
16406 context_range,
16407 })
16408 .templates
16409 .push((kind, task.original_task().clone()));
16410 }
16411
16412 acc
16413 })
16414 }) else {
16415 return;
16416 };
16417
16418 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16419 buffer.language_settings(cx).tasks.prefer_lsp
16420 }) else {
16421 return;
16422 };
16423
16424 let rows = Self::runnable_rows(
16425 project,
16426 display_snapshot,
16427 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16428 new_rows,
16429 cx.clone(),
16430 )
16431 .await;
16432 editor
16433 .update(cx, |editor, _| {
16434 editor.clear_tasks();
16435 for (key, mut value) in rows {
16436 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16437 value.templates.extend(lsp_tasks.templates);
16438 }
16439
16440 editor.insert_tasks(key, value);
16441 }
16442 for (key, value) in lsp_tasks_by_rows {
16443 editor.insert_tasks(key, value);
16444 }
16445 })
16446 .ok();
16447 })
16448 }
16449 fn fetch_runnable_ranges(
16450 snapshot: &DisplaySnapshot,
16451 range: Range<Anchor>,
16452 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16453 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16454 }
16455
16456 fn runnable_rows(
16457 project: Entity<Project>,
16458 snapshot: DisplaySnapshot,
16459 prefer_lsp: bool,
16460 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16461 cx: AsyncWindowContext,
16462 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16463 cx.spawn(async move |cx| {
16464 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16465 for (run_range, mut runnable) in runnable_ranges {
16466 let Some(tasks) = cx
16467 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16468 .ok()
16469 else {
16470 continue;
16471 };
16472 let mut tasks = tasks.await;
16473
16474 if prefer_lsp {
16475 tasks.retain(|(task_kind, _)| {
16476 !matches!(task_kind, TaskSourceKind::Language { .. })
16477 });
16478 }
16479 if tasks.is_empty() {
16480 continue;
16481 }
16482
16483 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16484 let Some(row) = snapshot
16485 .buffer_snapshot()
16486 .buffer_line_for_row(MultiBufferRow(point.row))
16487 .map(|(_, range)| range.start.row)
16488 else {
16489 continue;
16490 };
16491
16492 let context_range =
16493 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16494 runnable_rows.push((
16495 (runnable.buffer_id, row),
16496 RunnableTasks {
16497 templates: tasks,
16498 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16499 context_range,
16500 column: point.column,
16501 extra_variables: runnable.extra_captures,
16502 },
16503 ));
16504 }
16505 runnable_rows
16506 })
16507 }
16508
16509 fn templates_with_tags(
16510 project: &Entity<Project>,
16511 runnable: &mut Runnable,
16512 cx: &mut App,
16513 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16514 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16515 let (worktree_id, file) = project
16516 .buffer_for_id(runnable.buffer, cx)
16517 .and_then(|buffer| buffer.read(cx).file())
16518 .map(|file| (file.worktree_id(cx), file.clone()))
16519 .unzip();
16520
16521 (
16522 project.task_store().read(cx).task_inventory().cloned(),
16523 worktree_id,
16524 file,
16525 )
16526 });
16527
16528 let tags = mem::take(&mut runnable.tags);
16529 let language = runnable.language.clone();
16530 cx.spawn(async move |cx| {
16531 let mut templates_with_tags = Vec::new();
16532 if let Some(inventory) = inventory {
16533 for RunnableTag(tag) in tags {
16534 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16535 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16536 }) else {
16537 return templates_with_tags;
16538 };
16539 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16540 move |(_, template)| {
16541 template.tags.iter().any(|source_tag| source_tag == &tag)
16542 },
16543 ));
16544 }
16545 }
16546 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16547
16548 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16549 // Strongest source wins; if we have worktree tag binding, prefer that to
16550 // global and language bindings;
16551 // if we have a global binding, prefer that to language binding.
16552 let first_mismatch = templates_with_tags
16553 .iter()
16554 .position(|(tag_source, _)| tag_source != leading_tag_source);
16555 if let Some(index) = first_mismatch {
16556 templates_with_tags.truncate(index);
16557 }
16558 }
16559
16560 templates_with_tags
16561 })
16562 }
16563
16564 pub fn move_to_enclosing_bracket(
16565 &mut self,
16566 _: &MoveToEnclosingBracket,
16567 window: &mut Window,
16568 cx: &mut Context<Self>,
16569 ) {
16570 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16571 self.change_selections(Default::default(), window, cx, |s| {
16572 s.move_offsets_with(|snapshot, selection| {
16573 let Some(enclosing_bracket_ranges) =
16574 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16575 else {
16576 return;
16577 };
16578
16579 let mut best_length = usize::MAX;
16580 let mut best_inside = false;
16581 let mut best_in_bracket_range = false;
16582 let mut best_destination = None;
16583 for (open, close) in enclosing_bracket_ranges {
16584 let close = close.to_inclusive();
16585 let length = *close.end() - open.start;
16586 let inside = selection.start >= open.end && selection.end <= *close.start();
16587 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16588 || close.contains(&selection.head());
16589
16590 // If best is next to a bracket and current isn't, skip
16591 if !in_bracket_range && best_in_bracket_range {
16592 continue;
16593 }
16594
16595 // Prefer smaller lengths unless best is inside and current isn't
16596 if length > best_length && (best_inside || !inside) {
16597 continue;
16598 }
16599
16600 best_length = length;
16601 best_inside = inside;
16602 best_in_bracket_range = in_bracket_range;
16603 best_destination = Some(
16604 if close.contains(&selection.start) && close.contains(&selection.end) {
16605 if inside { open.end } else { open.start }
16606 } else if inside {
16607 *close.start()
16608 } else {
16609 *close.end()
16610 },
16611 );
16612 }
16613
16614 if let Some(destination) = best_destination {
16615 selection.collapse_to(destination, SelectionGoal::None);
16616 }
16617 })
16618 });
16619 }
16620
16621 pub fn undo_selection(
16622 &mut self,
16623 _: &UndoSelection,
16624 window: &mut Window,
16625 cx: &mut Context<Self>,
16626 ) {
16627 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16628 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16629 self.selection_history.mode = SelectionHistoryMode::Undoing;
16630 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16631 this.end_selection(window, cx);
16632 this.change_selections(
16633 SelectionEffects::scroll(Autoscroll::newest()),
16634 window,
16635 cx,
16636 |s| s.select_anchors(entry.selections.to_vec()),
16637 );
16638 });
16639 self.selection_history.mode = SelectionHistoryMode::Normal;
16640
16641 self.select_next_state = entry.select_next_state;
16642 self.select_prev_state = entry.select_prev_state;
16643 self.add_selections_state = entry.add_selections_state;
16644 }
16645 }
16646
16647 pub fn redo_selection(
16648 &mut self,
16649 _: &RedoSelection,
16650 window: &mut Window,
16651 cx: &mut Context<Self>,
16652 ) {
16653 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16654 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16655 self.selection_history.mode = SelectionHistoryMode::Redoing;
16656 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16657 this.end_selection(window, cx);
16658 this.change_selections(
16659 SelectionEffects::scroll(Autoscroll::newest()),
16660 window,
16661 cx,
16662 |s| s.select_anchors(entry.selections.to_vec()),
16663 );
16664 });
16665 self.selection_history.mode = SelectionHistoryMode::Normal;
16666
16667 self.select_next_state = entry.select_next_state;
16668 self.select_prev_state = entry.select_prev_state;
16669 self.add_selections_state = entry.add_selections_state;
16670 }
16671 }
16672
16673 pub fn expand_excerpts(
16674 &mut self,
16675 action: &ExpandExcerpts,
16676 _: &mut Window,
16677 cx: &mut Context<Self>,
16678 ) {
16679 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16680 }
16681
16682 pub fn expand_excerpts_down(
16683 &mut self,
16684 action: &ExpandExcerptsDown,
16685 _: &mut Window,
16686 cx: &mut Context<Self>,
16687 ) {
16688 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16689 }
16690
16691 pub fn expand_excerpts_up(
16692 &mut self,
16693 action: &ExpandExcerptsUp,
16694 _: &mut Window,
16695 cx: &mut Context<Self>,
16696 ) {
16697 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16698 }
16699
16700 pub fn expand_excerpts_for_direction(
16701 &mut self,
16702 lines: u32,
16703 direction: ExpandExcerptDirection,
16704 cx: &mut Context<Self>,
16705 ) {
16706 let selections = self.selections.disjoint_anchors_arc();
16707
16708 let lines = if lines == 0 {
16709 EditorSettings::get_global(cx).expand_excerpt_lines
16710 } else {
16711 lines
16712 };
16713
16714 let snapshot = self.buffer.read(cx).snapshot(cx);
16715 let mut excerpt_ids = selections
16716 .iter()
16717 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16718 .collect::<Vec<_>>();
16719 excerpt_ids.sort();
16720 excerpt_ids.dedup();
16721
16722 if self.delegate_expand_excerpts {
16723 cx.emit(EditorEvent::ExpandExcerptsRequested {
16724 excerpt_ids,
16725 lines,
16726 direction,
16727 });
16728 return;
16729 }
16730
16731 self.buffer.update(cx, |buffer, cx| {
16732 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16733 })
16734 }
16735
16736 pub fn expand_excerpt(
16737 &mut self,
16738 excerpt: ExcerptId,
16739 direction: ExpandExcerptDirection,
16740 window: &mut Window,
16741 cx: &mut Context<Self>,
16742 ) {
16743 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16744
16745 if self.delegate_expand_excerpts {
16746 cx.emit(EditorEvent::ExpandExcerptsRequested {
16747 excerpt_ids: vec![excerpt],
16748 lines: lines_to_expand,
16749 direction,
16750 });
16751 return;
16752 }
16753
16754 let current_scroll_position = self.scroll_position(cx);
16755 let mut scroll = None;
16756
16757 if direction == ExpandExcerptDirection::Down {
16758 let multi_buffer = self.buffer.read(cx);
16759 let snapshot = multi_buffer.snapshot(cx);
16760 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16761 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16762 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16763 {
16764 let buffer_snapshot = buffer.read(cx).snapshot();
16765 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16766 let last_row = buffer_snapshot.max_point().row;
16767 let lines_below = last_row.saturating_sub(excerpt_end_row);
16768 if lines_below >= lines_to_expand {
16769 scroll = Some(
16770 current_scroll_position
16771 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16772 );
16773 }
16774 }
16775 }
16776 if direction == ExpandExcerptDirection::Up
16777 && self
16778 .buffer
16779 .read(cx)
16780 .snapshot(cx)
16781 .excerpt_before(excerpt)
16782 .is_none()
16783 {
16784 scroll = Some(current_scroll_position);
16785 }
16786
16787 self.buffer.update(cx, |buffer, cx| {
16788 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16789 });
16790
16791 if let Some(new_scroll_position) = scroll {
16792 self.set_scroll_position(new_scroll_position, window, cx);
16793 }
16794 }
16795
16796 pub fn go_to_singleton_buffer_point(
16797 &mut self,
16798 point: Point,
16799 window: &mut Window,
16800 cx: &mut Context<Self>,
16801 ) {
16802 self.go_to_singleton_buffer_range(point..point, window, cx);
16803 }
16804
16805 pub fn go_to_singleton_buffer_range(
16806 &mut self,
16807 range: Range<Point>,
16808 window: &mut Window,
16809 cx: &mut Context<Self>,
16810 ) {
16811 let multibuffer = self.buffer().read(cx);
16812 let Some(buffer) = multibuffer.as_singleton() else {
16813 return;
16814 };
16815 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16816 return;
16817 };
16818 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16819 return;
16820 };
16821 self.change_selections(
16822 SelectionEffects::default().nav_history(true),
16823 window,
16824 cx,
16825 |s| s.select_anchor_ranges([start..end]),
16826 );
16827 }
16828
16829 pub fn go_to_diagnostic(
16830 &mut self,
16831 action: &GoToDiagnostic,
16832 window: &mut Window,
16833 cx: &mut Context<Self>,
16834 ) {
16835 if !self.diagnostics_enabled() {
16836 return;
16837 }
16838 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16839 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16840 }
16841
16842 pub fn go_to_prev_diagnostic(
16843 &mut self,
16844 action: &GoToPreviousDiagnostic,
16845 window: &mut Window,
16846 cx: &mut Context<Self>,
16847 ) {
16848 if !self.diagnostics_enabled() {
16849 return;
16850 }
16851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16852 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16853 }
16854
16855 pub fn go_to_diagnostic_impl(
16856 &mut self,
16857 direction: Direction,
16858 severity: GoToDiagnosticSeverityFilter,
16859 window: &mut Window,
16860 cx: &mut Context<Self>,
16861 ) {
16862 let buffer = self.buffer.read(cx).snapshot(cx);
16863 let selection = self
16864 .selections
16865 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16866
16867 let mut active_group_id = None;
16868 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16869 && active_group.active_range.start.to_offset(&buffer) == selection.start
16870 {
16871 active_group_id = Some(active_group.group_id);
16872 }
16873
16874 fn filtered<'a>(
16875 severity: GoToDiagnosticSeverityFilter,
16876 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16877 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16878 diagnostics
16879 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16880 .filter(|entry| entry.range.start != entry.range.end)
16881 .filter(|entry| !entry.diagnostic.is_unnecessary)
16882 }
16883
16884 let before = filtered(
16885 severity,
16886 buffer
16887 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16888 .filter(|entry| entry.range.start <= selection.start),
16889 );
16890 let after = filtered(
16891 severity,
16892 buffer
16893 .diagnostics_in_range(selection.start..buffer.len())
16894 .filter(|entry| entry.range.start >= selection.start),
16895 );
16896
16897 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16898 if direction == Direction::Prev {
16899 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16900 {
16901 for diagnostic in prev_diagnostics.into_iter().rev() {
16902 if diagnostic.range.start != selection.start
16903 || active_group_id
16904 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16905 {
16906 found = Some(diagnostic);
16907 break 'outer;
16908 }
16909 }
16910 }
16911 } else {
16912 for diagnostic in after.chain(before) {
16913 if diagnostic.range.start != selection.start
16914 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16915 {
16916 found = Some(diagnostic);
16917 break;
16918 }
16919 }
16920 }
16921 let Some(next_diagnostic) = found else {
16922 return;
16923 };
16924
16925 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16926 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16927 return;
16928 };
16929 let snapshot = self.snapshot(window, cx);
16930 if snapshot.intersects_fold(next_diagnostic.range.start) {
16931 self.unfold_ranges(
16932 std::slice::from_ref(&next_diagnostic.range),
16933 true,
16934 false,
16935 cx,
16936 );
16937 }
16938 self.change_selections(Default::default(), window, cx, |s| {
16939 s.select_ranges(vec![
16940 next_diagnostic.range.start..next_diagnostic.range.start,
16941 ])
16942 });
16943 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16944 self.refresh_edit_prediction(false, true, window, cx);
16945 }
16946
16947 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16948 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16949 let snapshot = self.snapshot(window, cx);
16950 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16951 self.go_to_hunk_before_or_after_position(
16952 &snapshot,
16953 selection.head(),
16954 Direction::Next,
16955 window,
16956 cx,
16957 );
16958 }
16959
16960 pub fn go_to_hunk_before_or_after_position(
16961 &mut self,
16962 snapshot: &EditorSnapshot,
16963 position: Point,
16964 direction: Direction,
16965 window: &mut Window,
16966 cx: &mut Context<Editor>,
16967 ) {
16968 let row = if direction == Direction::Next {
16969 self.hunk_after_position(snapshot, position)
16970 .map(|hunk| hunk.row_range.start)
16971 } else {
16972 self.hunk_before_position(snapshot, position)
16973 };
16974
16975 if let Some(row) = row {
16976 let destination = Point::new(row.0, 0);
16977 let autoscroll = Autoscroll::center();
16978
16979 self.unfold_ranges(&[destination..destination], false, false, cx);
16980 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16981 s.select_ranges([destination..destination]);
16982 });
16983 }
16984 }
16985
16986 fn hunk_after_position(
16987 &mut self,
16988 snapshot: &EditorSnapshot,
16989 position: Point,
16990 ) -> Option<MultiBufferDiffHunk> {
16991 snapshot
16992 .buffer_snapshot()
16993 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16994 .find(|hunk| hunk.row_range.start.0 > position.row)
16995 .or_else(|| {
16996 snapshot
16997 .buffer_snapshot()
16998 .diff_hunks_in_range(Point::zero()..position)
16999 .find(|hunk| hunk.row_range.end.0 < position.row)
17000 })
17001 }
17002
17003 fn go_to_prev_hunk(
17004 &mut self,
17005 _: &GoToPreviousHunk,
17006 window: &mut Window,
17007 cx: &mut Context<Self>,
17008 ) {
17009 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17010 let snapshot = self.snapshot(window, cx);
17011 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17012 self.go_to_hunk_before_or_after_position(
17013 &snapshot,
17014 selection.head(),
17015 Direction::Prev,
17016 window,
17017 cx,
17018 );
17019 }
17020
17021 fn hunk_before_position(
17022 &mut self,
17023 snapshot: &EditorSnapshot,
17024 position: Point,
17025 ) -> Option<MultiBufferRow> {
17026 snapshot
17027 .buffer_snapshot()
17028 .diff_hunk_before(position)
17029 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17030 }
17031
17032 fn go_to_next_change(
17033 &mut self,
17034 _: &GoToNextChange,
17035 window: &mut Window,
17036 cx: &mut Context<Self>,
17037 ) {
17038 if let Some(selections) = self
17039 .change_list
17040 .next_change(1, Direction::Next)
17041 .map(|s| s.to_vec())
17042 {
17043 self.change_selections(Default::default(), window, cx, |s| {
17044 let map = s.display_snapshot();
17045 s.select_display_ranges(selections.iter().map(|a| {
17046 let point = a.to_display_point(&map);
17047 point..point
17048 }))
17049 })
17050 }
17051 }
17052
17053 fn go_to_previous_change(
17054 &mut self,
17055 _: &GoToPreviousChange,
17056 window: &mut Window,
17057 cx: &mut Context<Self>,
17058 ) {
17059 if let Some(selections) = self
17060 .change_list
17061 .next_change(1, Direction::Prev)
17062 .map(|s| s.to_vec())
17063 {
17064 self.change_selections(Default::default(), window, cx, |s| {
17065 let map = s.display_snapshot();
17066 s.select_display_ranges(selections.iter().map(|a| {
17067 let point = a.to_display_point(&map);
17068 point..point
17069 }))
17070 })
17071 }
17072 }
17073
17074 pub fn go_to_next_document_highlight(
17075 &mut self,
17076 _: &GoToNextDocumentHighlight,
17077 window: &mut Window,
17078 cx: &mut Context<Self>,
17079 ) {
17080 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17081 }
17082
17083 pub fn go_to_prev_document_highlight(
17084 &mut self,
17085 _: &GoToPreviousDocumentHighlight,
17086 window: &mut Window,
17087 cx: &mut Context<Self>,
17088 ) {
17089 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17090 }
17091
17092 pub fn go_to_document_highlight_before_or_after_position(
17093 &mut self,
17094 direction: Direction,
17095 window: &mut Window,
17096 cx: &mut Context<Editor>,
17097 ) {
17098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17099 let snapshot = self.snapshot(window, cx);
17100 let buffer = &snapshot.buffer_snapshot();
17101 let position = self
17102 .selections
17103 .newest::<Point>(&snapshot.display_snapshot)
17104 .head();
17105 let anchor_position = buffer.anchor_after(position);
17106
17107 // Get all document highlights (both read and write)
17108 let mut all_highlights = Vec::new();
17109
17110 if let Some((_, read_highlights)) = self
17111 .background_highlights
17112 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17113 {
17114 all_highlights.extend(read_highlights.iter());
17115 }
17116
17117 if let Some((_, write_highlights)) = self
17118 .background_highlights
17119 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17120 {
17121 all_highlights.extend(write_highlights.iter());
17122 }
17123
17124 if all_highlights.is_empty() {
17125 return;
17126 }
17127
17128 // Sort highlights by position
17129 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17130
17131 let target_highlight = match direction {
17132 Direction::Next => {
17133 // Find the first highlight after the current position
17134 all_highlights
17135 .iter()
17136 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17137 }
17138 Direction::Prev => {
17139 // Find the last highlight before the current position
17140 all_highlights
17141 .iter()
17142 .rev()
17143 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17144 }
17145 };
17146
17147 if let Some(highlight) = target_highlight {
17148 let destination = highlight.start.to_point(buffer);
17149 let autoscroll = Autoscroll::center();
17150
17151 self.unfold_ranges(&[destination..destination], false, false, cx);
17152 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17153 s.select_ranges([destination..destination]);
17154 });
17155 }
17156 }
17157
17158 fn go_to_line<T: 'static>(
17159 &mut self,
17160 position: Anchor,
17161 highlight_color: Option<Hsla>,
17162 window: &mut Window,
17163 cx: &mut Context<Self>,
17164 ) {
17165 let snapshot = self.snapshot(window, cx).display_snapshot;
17166 let position = position.to_point(&snapshot.buffer_snapshot());
17167 let start = snapshot
17168 .buffer_snapshot()
17169 .clip_point(Point::new(position.row, 0), Bias::Left);
17170 let end = start + Point::new(1, 0);
17171 let start = snapshot.buffer_snapshot().anchor_before(start);
17172 let end = snapshot.buffer_snapshot().anchor_before(end);
17173
17174 self.highlight_rows::<T>(
17175 start..end,
17176 highlight_color
17177 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17178 Default::default(),
17179 cx,
17180 );
17181
17182 if self.buffer.read(cx).is_singleton() {
17183 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17184 }
17185 }
17186
17187 pub fn go_to_definition(
17188 &mut self,
17189 _: &GoToDefinition,
17190 window: &mut Window,
17191 cx: &mut Context<Self>,
17192 ) -> Task<Result<Navigated>> {
17193 let definition =
17194 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17195 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17196 cx.spawn_in(window, async move |editor, cx| {
17197 if definition.await? == Navigated::Yes {
17198 return Ok(Navigated::Yes);
17199 }
17200 match fallback_strategy {
17201 GoToDefinitionFallback::None => Ok(Navigated::No),
17202 GoToDefinitionFallback::FindAllReferences => {
17203 match editor.update_in(cx, |editor, window, cx| {
17204 editor.find_all_references(&FindAllReferences::default(), window, cx)
17205 })? {
17206 Some(references) => references.await,
17207 None => Ok(Navigated::No),
17208 }
17209 }
17210 }
17211 })
17212 }
17213
17214 pub fn go_to_declaration(
17215 &mut self,
17216 _: &GoToDeclaration,
17217 window: &mut Window,
17218 cx: &mut Context<Self>,
17219 ) -> Task<Result<Navigated>> {
17220 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17221 }
17222
17223 pub fn go_to_declaration_split(
17224 &mut self,
17225 _: &GoToDeclaration,
17226 window: &mut Window,
17227 cx: &mut Context<Self>,
17228 ) -> Task<Result<Navigated>> {
17229 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17230 }
17231
17232 pub fn go_to_implementation(
17233 &mut self,
17234 _: &GoToImplementation,
17235 window: &mut Window,
17236 cx: &mut Context<Self>,
17237 ) -> Task<Result<Navigated>> {
17238 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17239 }
17240
17241 pub fn go_to_implementation_split(
17242 &mut self,
17243 _: &GoToImplementationSplit,
17244 window: &mut Window,
17245 cx: &mut Context<Self>,
17246 ) -> Task<Result<Navigated>> {
17247 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17248 }
17249
17250 pub fn go_to_type_definition(
17251 &mut self,
17252 _: &GoToTypeDefinition,
17253 window: &mut Window,
17254 cx: &mut Context<Self>,
17255 ) -> Task<Result<Navigated>> {
17256 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17257 }
17258
17259 pub fn go_to_definition_split(
17260 &mut self,
17261 _: &GoToDefinitionSplit,
17262 window: &mut Window,
17263 cx: &mut Context<Self>,
17264 ) -> Task<Result<Navigated>> {
17265 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17266 }
17267
17268 pub fn go_to_type_definition_split(
17269 &mut self,
17270 _: &GoToTypeDefinitionSplit,
17271 window: &mut Window,
17272 cx: &mut Context<Self>,
17273 ) -> Task<Result<Navigated>> {
17274 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17275 }
17276
17277 fn go_to_definition_of_kind(
17278 &mut self,
17279 kind: GotoDefinitionKind,
17280 split: bool,
17281 window: &mut Window,
17282 cx: &mut Context<Self>,
17283 ) -> Task<Result<Navigated>> {
17284 let Some(provider) = self.semantics_provider.clone() else {
17285 return Task::ready(Ok(Navigated::No));
17286 };
17287 let head = self
17288 .selections
17289 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17290 .head();
17291 let buffer = self.buffer.read(cx);
17292 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17293 return Task::ready(Ok(Navigated::No));
17294 };
17295 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17296 return Task::ready(Ok(Navigated::No));
17297 };
17298
17299 cx.spawn_in(window, async move |editor, cx| {
17300 let Some(definitions) = definitions.await? else {
17301 return Ok(Navigated::No);
17302 };
17303 let navigated = editor
17304 .update_in(cx, |editor, window, cx| {
17305 editor.navigate_to_hover_links(
17306 Some(kind),
17307 definitions
17308 .into_iter()
17309 .filter(|location| {
17310 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17311 })
17312 .map(HoverLink::Text)
17313 .collect::<Vec<_>>(),
17314 split,
17315 window,
17316 cx,
17317 )
17318 })?
17319 .await?;
17320 anyhow::Ok(navigated)
17321 })
17322 }
17323
17324 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17325 let selection = self.selections.newest_anchor();
17326 let head = selection.head();
17327 let tail = selection.tail();
17328
17329 let Some((buffer, start_position)) =
17330 self.buffer.read(cx).text_anchor_for_position(head, cx)
17331 else {
17332 return;
17333 };
17334
17335 let end_position = if head != tail {
17336 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17337 return;
17338 };
17339 Some(pos)
17340 } else {
17341 None
17342 };
17343
17344 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17345 let url = if let Some(end_pos) = end_position {
17346 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17347 } else {
17348 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17349 };
17350
17351 if let Some(url) = url {
17352 cx.update(|window, cx| {
17353 if parse_zed_link(&url, cx).is_some() {
17354 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17355 } else {
17356 cx.open_url(&url);
17357 }
17358 })?;
17359 }
17360
17361 anyhow::Ok(())
17362 });
17363
17364 url_finder.detach();
17365 }
17366
17367 pub fn open_selected_filename(
17368 &mut self,
17369 _: &OpenSelectedFilename,
17370 window: &mut Window,
17371 cx: &mut Context<Self>,
17372 ) {
17373 let Some(workspace) = self.workspace() else {
17374 return;
17375 };
17376
17377 let position = self.selections.newest_anchor().head();
17378
17379 let Some((buffer, buffer_position)) =
17380 self.buffer.read(cx).text_anchor_for_position(position, cx)
17381 else {
17382 return;
17383 };
17384
17385 let project = self.project.clone();
17386
17387 cx.spawn_in(window, async move |_, cx| {
17388 let result = find_file(&buffer, project, buffer_position, cx).await;
17389
17390 if let Some((_, path)) = result {
17391 workspace
17392 .update_in(cx, |workspace, window, cx| {
17393 workspace.open_resolved_path(path, window, cx)
17394 })?
17395 .await?;
17396 }
17397 anyhow::Ok(())
17398 })
17399 .detach();
17400 }
17401
17402 pub(crate) fn navigate_to_hover_links(
17403 &mut self,
17404 kind: Option<GotoDefinitionKind>,
17405 definitions: Vec<HoverLink>,
17406 split: bool,
17407 window: &mut Window,
17408 cx: &mut Context<Editor>,
17409 ) -> Task<Result<Navigated>> {
17410 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17411 let mut first_url_or_file = None;
17412 let definitions: Vec<_> = definitions
17413 .into_iter()
17414 .filter_map(|def| match def {
17415 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17416 HoverLink::InlayHint(lsp_location, server_id) => {
17417 let computation =
17418 self.compute_target_location(lsp_location, server_id, window, cx);
17419 Some(cx.background_spawn(computation))
17420 }
17421 HoverLink::Url(url) => {
17422 first_url_or_file = Some(Either::Left(url));
17423 None
17424 }
17425 HoverLink::File(path) => {
17426 first_url_or_file = Some(Either::Right(path));
17427 None
17428 }
17429 })
17430 .collect();
17431
17432 let workspace = self.workspace();
17433
17434 cx.spawn_in(window, async move |editor, cx| {
17435 let locations: Vec<Location> = future::join_all(definitions)
17436 .await
17437 .into_iter()
17438 .filter_map(|location| location.transpose())
17439 .collect::<Result<_>>()
17440 .context("location tasks")?;
17441 let mut locations = cx.update(|_, cx| {
17442 locations
17443 .into_iter()
17444 .map(|location| {
17445 let buffer = location.buffer.read(cx);
17446 (location.buffer, location.range.to_point(buffer))
17447 })
17448 .into_group_map()
17449 })?;
17450 let mut num_locations = 0;
17451 for ranges in locations.values_mut() {
17452 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17453 ranges.dedup();
17454 num_locations += ranges.len();
17455 }
17456
17457 if num_locations > 1 {
17458 let tab_kind = match kind {
17459 Some(GotoDefinitionKind::Implementation) => "Implementations",
17460 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17461 Some(GotoDefinitionKind::Declaration) => "Declarations",
17462 Some(GotoDefinitionKind::Type) => "Types",
17463 };
17464 let title = editor
17465 .update_in(cx, |_, _, cx| {
17466 let target = locations
17467 .iter()
17468 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17469 .map(|(buffer, location)| {
17470 buffer
17471 .read(cx)
17472 .text_for_range(location.clone())
17473 .collect::<String>()
17474 })
17475 .filter(|text| !text.contains('\n'))
17476 .unique()
17477 .take(3)
17478 .join(", ");
17479 if target.is_empty() {
17480 tab_kind.to_owned()
17481 } else {
17482 format!("{tab_kind} for {target}")
17483 }
17484 })
17485 .context("buffer title")?;
17486
17487 let Some(workspace) = workspace else {
17488 return Ok(Navigated::No);
17489 };
17490
17491 let opened = workspace
17492 .update_in(cx, |workspace, window, cx| {
17493 let allow_preview = PreviewTabsSettings::get_global(cx)
17494 .enable_preview_multibuffer_from_code_navigation;
17495 Self::open_locations_in_multibuffer(
17496 workspace,
17497 locations,
17498 title,
17499 split,
17500 allow_preview,
17501 MultibufferSelectionMode::First,
17502 window,
17503 cx,
17504 )
17505 })
17506 .is_ok();
17507
17508 anyhow::Ok(Navigated::from_bool(opened))
17509 } else if num_locations == 0 {
17510 // If there is one url or file, open it directly
17511 match first_url_or_file {
17512 Some(Either::Left(url)) => {
17513 cx.update(|window, cx| {
17514 if parse_zed_link(&url, cx).is_some() {
17515 window
17516 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17517 } else {
17518 cx.open_url(&url);
17519 }
17520 })?;
17521 Ok(Navigated::Yes)
17522 }
17523 Some(Either::Right(path)) => {
17524 // TODO(andrew): respect preview tab settings
17525 // `enable_keep_preview_on_code_navigation` and
17526 // `enable_preview_file_from_code_navigation`
17527 let Some(workspace) = workspace else {
17528 return Ok(Navigated::No);
17529 };
17530 workspace
17531 .update_in(cx, |workspace, window, cx| {
17532 workspace.open_resolved_path(path, window, cx)
17533 })?
17534 .await?;
17535 Ok(Navigated::Yes)
17536 }
17537 None => Ok(Navigated::No),
17538 }
17539 } else {
17540 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17541 let target_range = target_ranges.first().unwrap().clone();
17542
17543 editor.update_in(cx, |editor, window, cx| {
17544 let range = target_range.to_point(target_buffer.read(cx));
17545 let range = editor.range_for_match(&range);
17546 let range = collapse_multiline_range(range);
17547
17548 if !split
17549 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17550 {
17551 editor.go_to_singleton_buffer_range(range, window, cx);
17552 } else {
17553 let Some(workspace) = workspace else {
17554 return Navigated::No;
17555 };
17556 let pane = workspace.read(cx).active_pane().clone();
17557 window.defer(cx, move |window, cx| {
17558 let target_editor: Entity<Self> =
17559 workspace.update(cx, |workspace, cx| {
17560 let pane = if split {
17561 workspace.adjacent_pane(window, cx)
17562 } else {
17563 workspace.active_pane().clone()
17564 };
17565
17566 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17567 let keep_old_preview = preview_tabs_settings
17568 .enable_keep_preview_on_code_navigation;
17569 let allow_new_preview = preview_tabs_settings
17570 .enable_preview_file_from_code_navigation;
17571
17572 workspace.open_project_item(
17573 pane,
17574 target_buffer.clone(),
17575 true,
17576 true,
17577 keep_old_preview,
17578 allow_new_preview,
17579 window,
17580 cx,
17581 )
17582 });
17583 target_editor.update(cx, |target_editor, cx| {
17584 // When selecting a definition in a different buffer, disable the nav history
17585 // to avoid creating a history entry at the previous cursor location.
17586 pane.update(cx, |pane, _| pane.disable_history());
17587 target_editor.go_to_singleton_buffer_range(range, window, cx);
17588 pane.update(cx, |pane, _| pane.enable_history());
17589 });
17590 });
17591 }
17592 Navigated::Yes
17593 })
17594 }
17595 })
17596 }
17597
17598 fn compute_target_location(
17599 &self,
17600 lsp_location: lsp::Location,
17601 server_id: LanguageServerId,
17602 window: &mut Window,
17603 cx: &mut Context<Self>,
17604 ) -> Task<anyhow::Result<Option<Location>>> {
17605 let Some(project) = self.project.clone() else {
17606 return Task::ready(Ok(None));
17607 };
17608
17609 cx.spawn_in(window, async move |editor, cx| {
17610 let location_task = editor.update(cx, |_, cx| {
17611 project.update(cx, |project, cx| {
17612 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17613 })
17614 })?;
17615 let location = Some({
17616 let target_buffer_handle = location_task.await.context("open local buffer")?;
17617 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17618 let target_start = target_buffer
17619 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17620 let target_end = target_buffer
17621 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17622 target_buffer.anchor_after(target_start)
17623 ..target_buffer.anchor_before(target_end)
17624 })?;
17625 Location {
17626 buffer: target_buffer_handle,
17627 range,
17628 }
17629 });
17630 Ok(location)
17631 })
17632 }
17633
17634 fn go_to_next_reference(
17635 &mut self,
17636 _: &GoToNextReference,
17637 window: &mut Window,
17638 cx: &mut Context<Self>,
17639 ) {
17640 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17641 if let Some(task) = task {
17642 task.detach();
17643 };
17644 }
17645
17646 fn go_to_prev_reference(
17647 &mut self,
17648 _: &GoToPreviousReference,
17649 window: &mut Window,
17650 cx: &mut Context<Self>,
17651 ) {
17652 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17653 if let Some(task) = task {
17654 task.detach();
17655 };
17656 }
17657
17658 pub fn go_to_reference_before_or_after_position(
17659 &mut self,
17660 direction: Direction,
17661 count: usize,
17662 window: &mut Window,
17663 cx: &mut Context<Self>,
17664 ) -> Option<Task<Result<()>>> {
17665 let selection = self.selections.newest_anchor();
17666 let head = selection.head();
17667
17668 let multi_buffer = self.buffer.read(cx);
17669
17670 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17671 let workspace = self.workspace()?;
17672 let project = workspace.read(cx).project().clone();
17673 let references =
17674 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17675 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17676 let Some(locations) = references.await? else {
17677 return Ok(());
17678 };
17679
17680 if locations.is_empty() {
17681 // totally normal - the cursor may be on something which is not
17682 // a symbol (e.g. a keyword)
17683 log::info!("no references found under cursor");
17684 return Ok(());
17685 }
17686
17687 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17688
17689 let (locations, current_location_index) =
17690 multi_buffer.update(cx, |multi_buffer, cx| {
17691 let mut locations = locations
17692 .into_iter()
17693 .filter_map(|loc| {
17694 let start = multi_buffer.buffer_anchor_to_anchor(
17695 &loc.buffer,
17696 loc.range.start,
17697 cx,
17698 )?;
17699 let end = multi_buffer.buffer_anchor_to_anchor(
17700 &loc.buffer,
17701 loc.range.end,
17702 cx,
17703 )?;
17704 Some(start..end)
17705 })
17706 .collect::<Vec<_>>();
17707
17708 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17709 // There is an O(n) implementation, but given this list will be
17710 // small (usually <100 items), the extra O(log(n)) factor isn't
17711 // worth the (surprisingly large amount of) extra complexity.
17712 locations
17713 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17714
17715 let head_offset = head.to_offset(&multi_buffer_snapshot);
17716
17717 let current_location_index = locations.iter().position(|loc| {
17718 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17719 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17720 });
17721
17722 (locations, current_location_index)
17723 })?;
17724
17725 let Some(current_location_index) = current_location_index else {
17726 // This indicates something has gone wrong, because we already
17727 // handle the "no references" case above
17728 log::error!(
17729 "failed to find current reference under cursor. Total references: {}",
17730 locations.len()
17731 );
17732 return Ok(());
17733 };
17734
17735 let destination_location_index = match direction {
17736 Direction::Next => (current_location_index + count) % locations.len(),
17737 Direction::Prev => {
17738 (current_location_index + locations.len() - count % locations.len())
17739 % locations.len()
17740 }
17741 };
17742
17743 // TODO(cameron): is this needed?
17744 // the thinking is to avoid "jumping to the current location" (avoid
17745 // polluting "jumplist" in vim terms)
17746 if current_location_index == destination_location_index {
17747 return Ok(());
17748 }
17749
17750 let Range { start, end } = locations[destination_location_index];
17751
17752 editor.update_in(cx, |editor, window, cx| {
17753 let effects = SelectionEffects::default();
17754
17755 editor.unfold_ranges(&[start..end], false, false, cx);
17756 editor.change_selections(effects, window, cx, |s| {
17757 s.select_ranges([start..start]);
17758 });
17759 })?;
17760
17761 Ok(())
17762 }))
17763 }
17764
17765 pub fn find_all_references(
17766 &mut self,
17767 action: &FindAllReferences,
17768 window: &mut Window,
17769 cx: &mut Context<Self>,
17770 ) -> Option<Task<Result<Navigated>>> {
17771 let always_open_multibuffer = action.always_open_multibuffer;
17772 let selection = self.selections.newest_anchor();
17773 let multi_buffer = self.buffer.read(cx);
17774 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17775 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17776 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17777 let head = selection_offset.head();
17778
17779 let head_anchor = multi_buffer_snapshot.anchor_at(
17780 head,
17781 if head < selection_offset.tail() {
17782 Bias::Right
17783 } else {
17784 Bias::Left
17785 },
17786 );
17787
17788 match self
17789 .find_all_references_task_sources
17790 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17791 {
17792 Ok(_) => {
17793 log::info!(
17794 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17795 );
17796 return None;
17797 }
17798 Err(i) => {
17799 self.find_all_references_task_sources.insert(i, head_anchor);
17800 }
17801 }
17802
17803 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17804 let workspace = self.workspace()?;
17805 let project = workspace.read(cx).project().clone();
17806 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17807 Some(cx.spawn_in(window, async move |editor, cx| {
17808 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17809 if let Ok(i) = editor
17810 .find_all_references_task_sources
17811 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17812 {
17813 editor.find_all_references_task_sources.remove(i);
17814 }
17815 });
17816
17817 let Some(locations) = references.await? else {
17818 return anyhow::Ok(Navigated::No);
17819 };
17820 let mut locations = cx.update(|_, cx| {
17821 locations
17822 .into_iter()
17823 .map(|location| {
17824 let buffer = location.buffer.read(cx);
17825 (location.buffer, location.range.to_point(buffer))
17826 })
17827 // if special-casing the single-match case, remove ranges
17828 // that intersect current selection
17829 .filter(|(location_buffer, location)| {
17830 if always_open_multibuffer || &buffer != location_buffer {
17831 return true;
17832 }
17833
17834 !location.contains_inclusive(&selection_point.range())
17835 })
17836 .into_group_map()
17837 })?;
17838 if locations.is_empty() {
17839 return anyhow::Ok(Navigated::No);
17840 }
17841 for ranges in locations.values_mut() {
17842 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17843 ranges.dedup();
17844 }
17845 let mut num_locations = 0;
17846 for ranges in locations.values_mut() {
17847 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17848 ranges.dedup();
17849 num_locations += ranges.len();
17850 }
17851
17852 if num_locations == 1 && !always_open_multibuffer {
17853 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17854 let target_range = target_ranges.first().unwrap().clone();
17855
17856 return editor.update_in(cx, |editor, window, cx| {
17857 let range = target_range.to_point(target_buffer.read(cx));
17858 let range = editor.range_for_match(&range);
17859 let range = range.start..range.start;
17860
17861 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17862 editor.go_to_singleton_buffer_range(range, window, cx);
17863 } else {
17864 let pane = workspace.read(cx).active_pane().clone();
17865 window.defer(cx, move |window, cx| {
17866 let target_editor: Entity<Self> =
17867 workspace.update(cx, |workspace, cx| {
17868 let pane = workspace.active_pane().clone();
17869
17870 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17871 let keep_old_preview = preview_tabs_settings
17872 .enable_keep_preview_on_code_navigation;
17873 let allow_new_preview = preview_tabs_settings
17874 .enable_preview_file_from_code_navigation;
17875
17876 workspace.open_project_item(
17877 pane,
17878 target_buffer.clone(),
17879 true,
17880 true,
17881 keep_old_preview,
17882 allow_new_preview,
17883 window,
17884 cx,
17885 )
17886 });
17887 target_editor.update(cx, |target_editor, cx| {
17888 // When selecting a definition in a different buffer, disable the nav history
17889 // to avoid creating a history entry at the previous cursor location.
17890 pane.update(cx, |pane, _| pane.disable_history());
17891 target_editor.go_to_singleton_buffer_range(range, window, cx);
17892 pane.update(cx, |pane, _| pane.enable_history());
17893 });
17894 });
17895 }
17896 Navigated::No
17897 });
17898 }
17899
17900 workspace.update_in(cx, |workspace, window, cx| {
17901 let target = locations
17902 .iter()
17903 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17904 .map(|(buffer, location)| {
17905 buffer
17906 .read(cx)
17907 .text_for_range(location.clone())
17908 .collect::<String>()
17909 })
17910 .filter(|text| !text.contains('\n'))
17911 .unique()
17912 .take(3)
17913 .join(", ");
17914 let title = if target.is_empty() {
17915 "References".to_owned()
17916 } else {
17917 format!("References to {target}")
17918 };
17919 let allow_preview = PreviewTabsSettings::get_global(cx)
17920 .enable_preview_multibuffer_from_code_navigation;
17921 Self::open_locations_in_multibuffer(
17922 workspace,
17923 locations,
17924 title,
17925 false,
17926 allow_preview,
17927 MultibufferSelectionMode::First,
17928 window,
17929 cx,
17930 );
17931 Navigated::Yes
17932 })
17933 }))
17934 }
17935
17936 /// Opens a multibuffer with the given project locations in it.
17937 pub fn open_locations_in_multibuffer(
17938 workspace: &mut Workspace,
17939 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17940 title: String,
17941 split: bool,
17942 allow_preview: bool,
17943 multibuffer_selection_mode: MultibufferSelectionMode,
17944 window: &mut Window,
17945 cx: &mut Context<Workspace>,
17946 ) {
17947 if locations.is_empty() {
17948 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17949 return;
17950 }
17951
17952 let capability = workspace.project().read(cx).capability();
17953 let mut ranges = <Vec<Range<Anchor>>>::new();
17954
17955 // a key to find existing multibuffer editors with the same set of locations
17956 // to prevent us from opening more and more multibuffer tabs for searches and the like
17957 let mut key = (title.clone(), vec![]);
17958 let excerpt_buffer = cx.new(|cx| {
17959 let key = &mut key.1;
17960 let mut multibuffer = MultiBuffer::new(capability);
17961 for (buffer, mut ranges_for_buffer) in locations {
17962 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17963 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17964 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17965 PathKey::for_buffer(&buffer, cx),
17966 buffer.clone(),
17967 ranges_for_buffer,
17968 multibuffer_context_lines(cx),
17969 cx,
17970 );
17971 ranges.extend(new_ranges)
17972 }
17973
17974 multibuffer.with_title(title)
17975 });
17976 let existing = workspace.active_pane().update(cx, |pane, cx| {
17977 pane.items()
17978 .filter_map(|item| item.downcast::<Editor>())
17979 .find(|editor| {
17980 editor
17981 .read(cx)
17982 .lookup_key
17983 .as_ref()
17984 .and_then(|it| {
17985 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17986 })
17987 .is_some_and(|it| *it == key)
17988 })
17989 });
17990 let was_existing = existing.is_some();
17991 let editor = existing.unwrap_or_else(|| {
17992 cx.new(|cx| {
17993 let mut editor = Editor::for_multibuffer(
17994 excerpt_buffer,
17995 Some(workspace.project().clone()),
17996 window,
17997 cx,
17998 );
17999 editor.lookup_key = Some(Box::new(key));
18000 editor
18001 })
18002 });
18003 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
18004 MultibufferSelectionMode::First => {
18005 if let Some(first_range) = ranges.first() {
18006 editor.change_selections(
18007 SelectionEffects::no_scroll(),
18008 window,
18009 cx,
18010 |selections| {
18011 selections.clear_disjoint();
18012 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18013 },
18014 );
18015 }
18016 editor.highlight_background::<Self>(
18017 &ranges,
18018 |_, theme| theme.colors().editor_highlighted_line_background,
18019 cx,
18020 );
18021 }
18022 MultibufferSelectionMode::All => {
18023 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18024 selections.clear_disjoint();
18025 selections.select_anchor_ranges(ranges);
18026 });
18027 }
18028 });
18029
18030 let item = Box::new(editor);
18031
18032 let pane = if split {
18033 workspace.adjacent_pane(window, cx)
18034 } else {
18035 workspace.active_pane().clone()
18036 };
18037 let activate_pane = split;
18038
18039 let mut destination_index = None;
18040 pane.update(cx, |pane, cx| {
18041 if allow_preview && !was_existing {
18042 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18043 }
18044 if was_existing && !allow_preview {
18045 pane.unpreview_item_if_preview(item.item_id());
18046 }
18047 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18048 });
18049 }
18050
18051 pub fn rename(
18052 &mut self,
18053 _: &Rename,
18054 window: &mut Window,
18055 cx: &mut Context<Self>,
18056 ) -> Option<Task<Result<()>>> {
18057 use language::ToOffset as _;
18058
18059 let provider = self.semantics_provider.clone()?;
18060 let selection = self.selections.newest_anchor().clone();
18061 let (cursor_buffer, cursor_buffer_position) = self
18062 .buffer
18063 .read(cx)
18064 .text_anchor_for_position(selection.head(), cx)?;
18065 let (tail_buffer, cursor_buffer_position_end) = self
18066 .buffer
18067 .read(cx)
18068 .text_anchor_for_position(selection.tail(), cx)?;
18069 if tail_buffer != cursor_buffer {
18070 return None;
18071 }
18072
18073 let snapshot = cursor_buffer.read(cx).snapshot();
18074 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18075 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18076 let prepare_rename = provider
18077 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18078 .unwrap_or_else(|| Task::ready(Ok(None)));
18079 drop(snapshot);
18080
18081 Some(cx.spawn_in(window, async move |this, cx| {
18082 let rename_range = if let Some(range) = prepare_rename.await? {
18083 Some(range)
18084 } else {
18085 this.update(cx, |this, cx| {
18086 let buffer = this.buffer.read(cx).snapshot(cx);
18087 let mut buffer_highlights = this
18088 .document_highlights_for_position(selection.head(), &buffer)
18089 .filter(|highlight| {
18090 highlight.start.excerpt_id == selection.head().excerpt_id
18091 && highlight.end.excerpt_id == selection.head().excerpt_id
18092 });
18093 buffer_highlights
18094 .next()
18095 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18096 })?
18097 };
18098 if let Some(rename_range) = rename_range {
18099 this.update_in(cx, |this, window, cx| {
18100 let snapshot = cursor_buffer.read(cx).snapshot();
18101 let rename_buffer_range = rename_range.to_offset(&snapshot);
18102 let cursor_offset_in_rename_range =
18103 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18104 let cursor_offset_in_rename_range_end =
18105 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18106
18107 this.take_rename(false, window, cx);
18108 let buffer = this.buffer.read(cx).read(cx);
18109 let cursor_offset = selection.head().to_offset(&buffer);
18110 let rename_start =
18111 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18112 let rename_end = rename_start + rename_buffer_range.len();
18113 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18114 let mut old_highlight_id = None;
18115 let old_name: Arc<str> = buffer
18116 .chunks(rename_start..rename_end, true)
18117 .map(|chunk| {
18118 if old_highlight_id.is_none() {
18119 old_highlight_id = chunk.syntax_highlight_id;
18120 }
18121 chunk.text
18122 })
18123 .collect::<String>()
18124 .into();
18125
18126 drop(buffer);
18127
18128 // Position the selection in the rename editor so that it matches the current selection.
18129 this.show_local_selections = false;
18130 let rename_editor = cx.new(|cx| {
18131 let mut editor = Editor::single_line(window, cx);
18132 editor.buffer.update(cx, |buffer, cx| {
18133 buffer.edit(
18134 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18135 None,
18136 cx,
18137 )
18138 });
18139 let cursor_offset_in_rename_range =
18140 MultiBufferOffset(cursor_offset_in_rename_range);
18141 let cursor_offset_in_rename_range_end =
18142 MultiBufferOffset(cursor_offset_in_rename_range_end);
18143 let rename_selection_range = match cursor_offset_in_rename_range
18144 .cmp(&cursor_offset_in_rename_range_end)
18145 {
18146 Ordering::Equal => {
18147 editor.select_all(&SelectAll, window, cx);
18148 return editor;
18149 }
18150 Ordering::Less => {
18151 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18152 }
18153 Ordering::Greater => {
18154 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18155 }
18156 };
18157 if rename_selection_range.end.0 > old_name.len() {
18158 editor.select_all(&SelectAll, window, cx);
18159 } else {
18160 editor.change_selections(Default::default(), window, cx, |s| {
18161 s.select_ranges([rename_selection_range]);
18162 });
18163 }
18164 editor
18165 });
18166 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18167 if e == &EditorEvent::Focused {
18168 cx.emit(EditorEvent::FocusedIn)
18169 }
18170 })
18171 .detach();
18172
18173 let write_highlights =
18174 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18175 let read_highlights =
18176 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18177 let ranges = write_highlights
18178 .iter()
18179 .flat_map(|(_, ranges)| ranges.iter())
18180 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18181 .cloned()
18182 .collect();
18183
18184 this.highlight_text::<Rename>(
18185 ranges,
18186 HighlightStyle {
18187 fade_out: Some(0.6),
18188 ..Default::default()
18189 },
18190 cx,
18191 );
18192 let rename_focus_handle = rename_editor.focus_handle(cx);
18193 window.focus(&rename_focus_handle, cx);
18194 let block_id = this.insert_blocks(
18195 [BlockProperties {
18196 style: BlockStyle::Flex,
18197 placement: BlockPlacement::Below(range.start),
18198 height: Some(1),
18199 render: Arc::new({
18200 let rename_editor = rename_editor.clone();
18201 move |cx: &mut BlockContext| {
18202 let mut text_style = cx.editor_style.text.clone();
18203 if let Some(highlight_style) = old_highlight_id
18204 .and_then(|h| h.style(&cx.editor_style.syntax))
18205 {
18206 text_style = text_style.highlight(highlight_style);
18207 }
18208 div()
18209 .block_mouse_except_scroll()
18210 .pl(cx.anchor_x)
18211 .child(EditorElement::new(
18212 &rename_editor,
18213 EditorStyle {
18214 background: cx.theme().system().transparent,
18215 local_player: cx.editor_style.local_player,
18216 text: text_style,
18217 scrollbar_width: cx.editor_style.scrollbar_width,
18218 syntax: cx.editor_style.syntax.clone(),
18219 status: cx.editor_style.status.clone(),
18220 inlay_hints_style: HighlightStyle {
18221 font_weight: Some(FontWeight::BOLD),
18222 ..make_inlay_hints_style(cx.app)
18223 },
18224 edit_prediction_styles: make_suggestion_styles(
18225 cx.app,
18226 ),
18227 ..EditorStyle::default()
18228 },
18229 ))
18230 .into_any_element()
18231 }
18232 }),
18233 priority: 0,
18234 }],
18235 Some(Autoscroll::fit()),
18236 cx,
18237 )[0];
18238 this.pending_rename = Some(RenameState {
18239 range,
18240 old_name,
18241 editor: rename_editor,
18242 block_id,
18243 });
18244 })?;
18245 }
18246
18247 Ok(())
18248 }))
18249 }
18250
18251 pub fn confirm_rename(
18252 &mut self,
18253 _: &ConfirmRename,
18254 window: &mut Window,
18255 cx: &mut Context<Self>,
18256 ) -> Option<Task<Result<()>>> {
18257 let rename = self.take_rename(false, window, cx)?;
18258 let workspace = self.workspace()?.downgrade();
18259 let (buffer, start) = self
18260 .buffer
18261 .read(cx)
18262 .text_anchor_for_position(rename.range.start, cx)?;
18263 let (end_buffer, _) = self
18264 .buffer
18265 .read(cx)
18266 .text_anchor_for_position(rename.range.end, cx)?;
18267 if buffer != end_buffer {
18268 return None;
18269 }
18270
18271 let old_name = rename.old_name;
18272 let new_name = rename.editor.read(cx).text(cx);
18273
18274 let rename = self.semantics_provider.as_ref()?.perform_rename(
18275 &buffer,
18276 start,
18277 new_name.clone(),
18278 cx,
18279 )?;
18280
18281 Some(cx.spawn_in(window, async move |editor, cx| {
18282 let project_transaction = rename.await?;
18283 Self::open_project_transaction(
18284 &editor,
18285 workspace,
18286 project_transaction,
18287 format!("Rename: {} → {}", old_name, new_name),
18288 cx,
18289 )
18290 .await?;
18291
18292 editor.update(cx, |editor, cx| {
18293 editor.refresh_document_highlights(cx);
18294 })?;
18295 Ok(())
18296 }))
18297 }
18298
18299 fn take_rename(
18300 &mut self,
18301 moving_cursor: bool,
18302 window: &mut Window,
18303 cx: &mut Context<Self>,
18304 ) -> Option<RenameState> {
18305 let rename = self.pending_rename.take()?;
18306 if rename.editor.focus_handle(cx).is_focused(window) {
18307 window.focus(&self.focus_handle, cx);
18308 }
18309
18310 self.remove_blocks(
18311 [rename.block_id].into_iter().collect(),
18312 Some(Autoscroll::fit()),
18313 cx,
18314 );
18315 self.clear_highlights::<Rename>(cx);
18316 self.show_local_selections = true;
18317
18318 if moving_cursor {
18319 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18320 editor
18321 .selections
18322 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18323 .head()
18324 });
18325
18326 // Update the selection to match the position of the selection inside
18327 // the rename editor.
18328 let snapshot = self.buffer.read(cx).read(cx);
18329 let rename_range = rename.range.to_offset(&snapshot);
18330 let cursor_in_editor = snapshot
18331 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18332 .min(rename_range.end);
18333 drop(snapshot);
18334
18335 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18336 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18337 });
18338 } else {
18339 self.refresh_document_highlights(cx);
18340 }
18341
18342 Some(rename)
18343 }
18344
18345 pub fn pending_rename(&self) -> Option<&RenameState> {
18346 self.pending_rename.as_ref()
18347 }
18348
18349 fn format(
18350 &mut self,
18351 _: &Format,
18352 window: &mut Window,
18353 cx: &mut Context<Self>,
18354 ) -> Option<Task<Result<()>>> {
18355 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18356
18357 let project = match &self.project {
18358 Some(project) => project.clone(),
18359 None => return None,
18360 };
18361
18362 Some(self.perform_format(
18363 project,
18364 FormatTrigger::Manual,
18365 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18366 window,
18367 cx,
18368 ))
18369 }
18370
18371 fn format_selections(
18372 &mut self,
18373 _: &FormatSelections,
18374 window: &mut Window,
18375 cx: &mut Context<Self>,
18376 ) -> Option<Task<Result<()>>> {
18377 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18378
18379 let project = match &self.project {
18380 Some(project) => project.clone(),
18381 None => return None,
18382 };
18383
18384 let ranges = self
18385 .selections
18386 .all_adjusted(&self.display_snapshot(cx))
18387 .into_iter()
18388 .map(|selection| selection.range())
18389 .collect_vec();
18390
18391 Some(self.perform_format(
18392 project,
18393 FormatTrigger::Manual,
18394 FormatTarget::Ranges(ranges),
18395 window,
18396 cx,
18397 ))
18398 }
18399
18400 fn perform_format(
18401 &mut self,
18402 project: Entity<Project>,
18403 trigger: FormatTrigger,
18404 target: FormatTarget,
18405 window: &mut Window,
18406 cx: &mut Context<Self>,
18407 ) -> Task<Result<()>> {
18408 let buffer = self.buffer.clone();
18409 let (buffers, target) = match target {
18410 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18411 FormatTarget::Ranges(selection_ranges) => {
18412 let multi_buffer = buffer.read(cx);
18413 let snapshot = multi_buffer.read(cx);
18414 let mut buffers = HashSet::default();
18415 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18416 BTreeMap::new();
18417 for selection_range in selection_ranges {
18418 for (buffer, buffer_range, _) in
18419 snapshot.range_to_buffer_ranges(selection_range)
18420 {
18421 let buffer_id = buffer.remote_id();
18422 let start = buffer.anchor_before(buffer_range.start);
18423 let end = buffer.anchor_after(buffer_range.end);
18424 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18425 buffer_id_to_ranges
18426 .entry(buffer_id)
18427 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18428 .or_insert_with(|| vec![start..end]);
18429 }
18430 }
18431 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18432 }
18433 };
18434
18435 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18436 let selections_prev = transaction_id_prev
18437 .and_then(|transaction_id_prev| {
18438 // default to selections as they were after the last edit, if we have them,
18439 // instead of how they are now.
18440 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18441 // will take you back to where you made the last edit, instead of staying where you scrolled
18442 self.selection_history
18443 .transaction(transaction_id_prev)
18444 .map(|t| t.0.clone())
18445 })
18446 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18447
18448 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18449 let format = project.update(cx, |project, cx| {
18450 project.format(buffers, target, true, trigger, cx)
18451 });
18452
18453 cx.spawn_in(window, async move |editor, cx| {
18454 let transaction = futures::select_biased! {
18455 transaction = format.log_err().fuse() => transaction,
18456 () = timeout => {
18457 log::warn!("timed out waiting for formatting");
18458 None
18459 }
18460 };
18461
18462 buffer
18463 .update(cx, |buffer, cx| {
18464 if let Some(transaction) = transaction
18465 && !buffer.is_singleton()
18466 {
18467 buffer.push_transaction(&transaction.0, cx);
18468 }
18469 cx.notify();
18470 })
18471 .ok();
18472
18473 if let Some(transaction_id_now) =
18474 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18475 {
18476 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18477 if has_new_transaction {
18478 _ = editor.update(cx, |editor, _| {
18479 editor
18480 .selection_history
18481 .insert_transaction(transaction_id_now, selections_prev);
18482 });
18483 }
18484 }
18485
18486 Ok(())
18487 })
18488 }
18489
18490 fn organize_imports(
18491 &mut self,
18492 _: &OrganizeImports,
18493 window: &mut Window,
18494 cx: &mut Context<Self>,
18495 ) -> Option<Task<Result<()>>> {
18496 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18497 let project = match &self.project {
18498 Some(project) => project.clone(),
18499 None => return None,
18500 };
18501 Some(self.perform_code_action_kind(
18502 project,
18503 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18504 window,
18505 cx,
18506 ))
18507 }
18508
18509 fn perform_code_action_kind(
18510 &mut self,
18511 project: Entity<Project>,
18512 kind: CodeActionKind,
18513 window: &mut Window,
18514 cx: &mut Context<Self>,
18515 ) -> Task<Result<()>> {
18516 let buffer = self.buffer.clone();
18517 let buffers = buffer.read(cx).all_buffers();
18518 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18519 let apply_action = project.update(cx, |project, cx| {
18520 project.apply_code_action_kind(buffers, kind, true, cx)
18521 });
18522 cx.spawn_in(window, async move |_, cx| {
18523 let transaction = futures::select_biased! {
18524 () = timeout => {
18525 log::warn!("timed out waiting for executing code action");
18526 None
18527 }
18528 transaction = apply_action.log_err().fuse() => transaction,
18529 };
18530 buffer
18531 .update(cx, |buffer, cx| {
18532 // check if we need this
18533 if let Some(transaction) = transaction
18534 && !buffer.is_singleton()
18535 {
18536 buffer.push_transaction(&transaction.0, cx);
18537 }
18538 cx.notify();
18539 })
18540 .ok();
18541 Ok(())
18542 })
18543 }
18544
18545 pub fn restart_language_server(
18546 &mut self,
18547 _: &RestartLanguageServer,
18548 _: &mut Window,
18549 cx: &mut Context<Self>,
18550 ) {
18551 if let Some(project) = self.project.clone() {
18552 self.buffer.update(cx, |multi_buffer, cx| {
18553 project.update(cx, |project, cx| {
18554 project.restart_language_servers_for_buffers(
18555 multi_buffer.all_buffers().into_iter().collect(),
18556 HashSet::default(),
18557 cx,
18558 );
18559 });
18560 })
18561 }
18562 }
18563
18564 pub fn stop_language_server(
18565 &mut self,
18566 _: &StopLanguageServer,
18567 _: &mut Window,
18568 cx: &mut Context<Self>,
18569 ) {
18570 if let Some(project) = self.project.clone() {
18571 self.buffer.update(cx, |multi_buffer, cx| {
18572 project.update(cx, |project, cx| {
18573 project.stop_language_servers_for_buffers(
18574 multi_buffer.all_buffers().into_iter().collect(),
18575 HashSet::default(),
18576 cx,
18577 );
18578 });
18579 });
18580 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18581 }
18582 }
18583
18584 fn cancel_language_server_work(
18585 workspace: &mut Workspace,
18586 _: &actions::CancelLanguageServerWork,
18587 _: &mut Window,
18588 cx: &mut Context<Workspace>,
18589 ) {
18590 let project = workspace.project();
18591 let buffers = workspace
18592 .active_item(cx)
18593 .and_then(|item| item.act_as::<Editor>(cx))
18594 .map_or(HashSet::default(), |editor| {
18595 editor.read(cx).buffer.read(cx).all_buffers()
18596 });
18597 project.update(cx, |project, cx| {
18598 project.cancel_language_server_work_for_buffers(buffers, cx);
18599 });
18600 }
18601
18602 fn show_character_palette(
18603 &mut self,
18604 _: &ShowCharacterPalette,
18605 window: &mut Window,
18606 _: &mut Context<Self>,
18607 ) {
18608 window.show_character_palette();
18609 }
18610
18611 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18612 if !self.diagnostics_enabled() {
18613 return;
18614 }
18615
18616 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18617 let buffer = self.buffer.read(cx).snapshot(cx);
18618 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18619 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18620 let is_valid = buffer
18621 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18622 .any(|entry| {
18623 entry.diagnostic.is_primary
18624 && !entry.range.is_empty()
18625 && entry.range.start == primary_range_start
18626 && entry.diagnostic.message == active_diagnostics.active_message
18627 });
18628
18629 if !is_valid {
18630 self.dismiss_diagnostics(cx);
18631 }
18632 }
18633 }
18634
18635 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18636 match &self.active_diagnostics {
18637 ActiveDiagnostic::Group(group) => Some(group),
18638 _ => None,
18639 }
18640 }
18641
18642 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18643 if !self.diagnostics_enabled() {
18644 return;
18645 }
18646 self.dismiss_diagnostics(cx);
18647 self.active_diagnostics = ActiveDiagnostic::All;
18648 }
18649
18650 fn activate_diagnostics(
18651 &mut self,
18652 buffer_id: BufferId,
18653 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18654 window: &mut Window,
18655 cx: &mut Context<Self>,
18656 ) {
18657 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18658 return;
18659 }
18660 self.dismiss_diagnostics(cx);
18661 let snapshot = self.snapshot(window, cx);
18662 let buffer = self.buffer.read(cx).snapshot(cx);
18663 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18664 return;
18665 };
18666
18667 let diagnostic_group = buffer
18668 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18669 .collect::<Vec<_>>();
18670
18671 let language_registry = self
18672 .project()
18673 .map(|project| project.read(cx).languages().clone());
18674
18675 let blocks = renderer.render_group(
18676 diagnostic_group,
18677 buffer_id,
18678 snapshot,
18679 cx.weak_entity(),
18680 language_registry,
18681 cx,
18682 );
18683
18684 let blocks = self.display_map.update(cx, |display_map, cx| {
18685 display_map.insert_blocks(blocks, cx).into_iter().collect()
18686 });
18687 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18688 active_range: buffer.anchor_before(diagnostic.range.start)
18689 ..buffer.anchor_after(diagnostic.range.end),
18690 active_message: diagnostic.diagnostic.message.clone(),
18691 group_id: diagnostic.diagnostic.group_id,
18692 blocks,
18693 });
18694 cx.notify();
18695 }
18696
18697 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18698 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18699 return;
18700 };
18701
18702 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18703 if let ActiveDiagnostic::Group(group) = prev {
18704 self.display_map.update(cx, |display_map, cx| {
18705 display_map.remove_blocks(group.blocks, cx);
18706 });
18707 cx.notify();
18708 }
18709 }
18710
18711 /// Disable inline diagnostics rendering for this editor.
18712 pub fn disable_inline_diagnostics(&mut self) {
18713 self.inline_diagnostics_enabled = false;
18714 self.inline_diagnostics_update = Task::ready(());
18715 self.inline_diagnostics.clear();
18716 }
18717
18718 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18719 self.diagnostics_enabled = false;
18720 self.dismiss_diagnostics(cx);
18721 self.inline_diagnostics_update = Task::ready(());
18722 self.inline_diagnostics.clear();
18723 }
18724
18725 pub fn disable_word_completions(&mut self) {
18726 self.word_completions_enabled = false;
18727 }
18728
18729 pub fn diagnostics_enabled(&self) -> bool {
18730 self.diagnostics_enabled && self.mode.is_full()
18731 }
18732
18733 pub fn inline_diagnostics_enabled(&self) -> bool {
18734 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18735 }
18736
18737 pub fn show_inline_diagnostics(&self) -> bool {
18738 self.show_inline_diagnostics
18739 }
18740
18741 pub fn toggle_inline_diagnostics(
18742 &mut self,
18743 _: &ToggleInlineDiagnostics,
18744 window: &mut Window,
18745 cx: &mut Context<Editor>,
18746 ) {
18747 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18748 self.refresh_inline_diagnostics(false, window, cx);
18749 }
18750
18751 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18752 self.diagnostics_max_severity = severity;
18753 self.display_map.update(cx, |display_map, _| {
18754 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18755 });
18756 }
18757
18758 pub fn toggle_diagnostics(
18759 &mut self,
18760 _: &ToggleDiagnostics,
18761 window: &mut Window,
18762 cx: &mut Context<Editor>,
18763 ) {
18764 if !self.diagnostics_enabled() {
18765 return;
18766 }
18767
18768 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18769 EditorSettings::get_global(cx)
18770 .diagnostics_max_severity
18771 .filter(|severity| severity != &DiagnosticSeverity::Off)
18772 .unwrap_or(DiagnosticSeverity::Hint)
18773 } else {
18774 DiagnosticSeverity::Off
18775 };
18776 self.set_max_diagnostics_severity(new_severity, cx);
18777 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18778 self.active_diagnostics = ActiveDiagnostic::None;
18779 self.inline_diagnostics_update = Task::ready(());
18780 self.inline_diagnostics.clear();
18781 } else {
18782 self.refresh_inline_diagnostics(false, window, cx);
18783 }
18784
18785 cx.notify();
18786 }
18787
18788 pub fn toggle_minimap(
18789 &mut self,
18790 _: &ToggleMinimap,
18791 window: &mut Window,
18792 cx: &mut Context<Editor>,
18793 ) {
18794 if self.supports_minimap(cx) {
18795 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18796 }
18797 }
18798
18799 fn refresh_inline_diagnostics(
18800 &mut self,
18801 debounce: bool,
18802 window: &mut Window,
18803 cx: &mut Context<Self>,
18804 ) {
18805 let max_severity = ProjectSettings::get_global(cx)
18806 .diagnostics
18807 .inline
18808 .max_severity
18809 .unwrap_or(self.diagnostics_max_severity);
18810
18811 if !self.inline_diagnostics_enabled()
18812 || !self.diagnostics_enabled()
18813 || !self.show_inline_diagnostics
18814 || max_severity == DiagnosticSeverity::Off
18815 {
18816 self.inline_diagnostics_update = Task::ready(());
18817 self.inline_diagnostics.clear();
18818 return;
18819 }
18820
18821 let debounce_ms = ProjectSettings::get_global(cx)
18822 .diagnostics
18823 .inline
18824 .update_debounce_ms;
18825 let debounce = if debounce && debounce_ms > 0 {
18826 Some(Duration::from_millis(debounce_ms))
18827 } else {
18828 None
18829 };
18830 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18831 if let Some(debounce) = debounce {
18832 cx.background_executor().timer(debounce).await;
18833 }
18834 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18835 editor
18836 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18837 .ok()
18838 }) else {
18839 return;
18840 };
18841
18842 let new_inline_diagnostics = cx
18843 .background_spawn(async move {
18844 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18845 for diagnostic_entry in
18846 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18847 {
18848 let message = diagnostic_entry
18849 .diagnostic
18850 .message
18851 .split_once('\n')
18852 .map(|(line, _)| line)
18853 .map(SharedString::new)
18854 .unwrap_or_else(|| {
18855 SharedString::new(&*diagnostic_entry.diagnostic.message)
18856 });
18857 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18858 let (Ok(i) | Err(i)) = inline_diagnostics
18859 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18860 inline_diagnostics.insert(
18861 i,
18862 (
18863 start_anchor,
18864 InlineDiagnostic {
18865 message,
18866 group_id: diagnostic_entry.diagnostic.group_id,
18867 start: diagnostic_entry.range.start.to_point(&snapshot),
18868 is_primary: diagnostic_entry.diagnostic.is_primary,
18869 severity: diagnostic_entry.diagnostic.severity,
18870 },
18871 ),
18872 );
18873 }
18874 inline_diagnostics
18875 })
18876 .await;
18877
18878 editor
18879 .update(cx, |editor, cx| {
18880 editor.inline_diagnostics = new_inline_diagnostics;
18881 cx.notify();
18882 })
18883 .ok();
18884 });
18885 }
18886
18887 fn pull_diagnostics(
18888 &mut self,
18889 buffer_id: Option<BufferId>,
18890 window: &Window,
18891 cx: &mut Context<Self>,
18892 ) -> Option<()> {
18893 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18894 return None;
18895 }
18896 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18897 .diagnostics
18898 .lsp_pull_diagnostics;
18899 if !pull_diagnostics_settings.enabled {
18900 return None;
18901 }
18902 let project = self.project()?.downgrade();
18903
18904 let mut edited_buffer_ids = HashSet::default();
18905 let mut edited_worktree_ids = HashSet::default();
18906 let edited_buffers = match buffer_id {
18907 Some(buffer_id) => {
18908 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18909 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18910 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18911 edited_worktree_ids.insert(worktree_id);
18912 vec![buffer]
18913 }
18914 None => self
18915 .buffer()
18916 .read(cx)
18917 .all_buffers()
18918 .into_iter()
18919 .filter(|buffer| {
18920 let buffer = buffer.read(cx);
18921 match buffer.file().map(|f| f.worktree_id(cx)) {
18922 Some(worktree_id) => {
18923 edited_buffer_ids.insert(buffer.remote_id());
18924 edited_worktree_ids.insert(worktree_id);
18925 true
18926 }
18927 None => false,
18928 }
18929 })
18930 .collect::<Vec<_>>(),
18931 };
18932
18933 if edited_buffers.is_empty() {
18934 self.pull_diagnostics_task = Task::ready(());
18935 self.pull_diagnostics_background_task = Task::ready(());
18936 return None;
18937 }
18938
18939 let mut already_used_buffers = HashSet::default();
18940 let related_open_buffers = self
18941 .workspace
18942 .as_ref()
18943 .and_then(|(workspace, _)| workspace.upgrade())
18944 .into_iter()
18945 .flat_map(|workspace| workspace.read(cx).panes())
18946 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18947 .filter(|editor| editor != &cx.entity())
18948 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18949 .filter(|buffer| {
18950 let buffer = buffer.read(cx);
18951 let buffer_id = buffer.remote_id();
18952 if already_used_buffers.insert(buffer_id) {
18953 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18954 return !edited_buffer_ids.contains(&buffer_id)
18955 && edited_worktree_ids.contains(&worktree_id);
18956 }
18957 }
18958 false
18959 })
18960 .collect::<Vec<_>>();
18961
18962 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18963 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18964 if buffers.is_empty() {
18965 return Task::ready(());
18966 }
18967 let project_weak = project.clone();
18968 cx.spawn_in(window, async move |_, cx| {
18969 cx.background_executor().timer(delay).await;
18970
18971 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18972 buffers
18973 .into_iter()
18974 .filter_map(|buffer| {
18975 project_weak
18976 .update(cx, |project, cx| {
18977 project.lsp_store().update(cx, |lsp_store, cx| {
18978 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18979 })
18980 })
18981 .ok()
18982 })
18983 .collect::<FuturesUnordered<_>>()
18984 }) else {
18985 return;
18986 };
18987
18988 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18989 if let Err(e) = pull_task {
18990 log::error!("Failed to update project diagnostics: {e:#}");
18991 }
18992 }
18993 })
18994 };
18995
18996 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18997 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18998
18999 Some(())
19000 }
19001
19002 pub fn set_selections_from_remote(
19003 &mut self,
19004 selections: Vec<Selection<Anchor>>,
19005 pending_selection: Option<Selection<Anchor>>,
19006 window: &mut Window,
19007 cx: &mut Context<Self>,
19008 ) {
19009 let old_cursor_position = self.selections.newest_anchor().head();
19010 self.selections
19011 .change_with(&self.display_snapshot(cx), |s| {
19012 s.select_anchors(selections);
19013 if let Some(pending_selection) = pending_selection {
19014 s.set_pending(pending_selection, SelectMode::Character);
19015 } else {
19016 s.clear_pending();
19017 }
19018 });
19019 self.selections_did_change(
19020 false,
19021 &old_cursor_position,
19022 SelectionEffects::default(),
19023 window,
19024 cx,
19025 );
19026 }
19027
19028 pub fn transact(
19029 &mut self,
19030 window: &mut Window,
19031 cx: &mut Context<Self>,
19032 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19033 ) -> Option<TransactionId> {
19034 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19035 this.start_transaction_at(Instant::now(), window, cx);
19036 update(this, window, cx);
19037 this.end_transaction_at(Instant::now(), cx)
19038 })
19039 }
19040
19041 pub fn start_transaction_at(
19042 &mut self,
19043 now: Instant,
19044 window: &mut Window,
19045 cx: &mut Context<Self>,
19046 ) -> Option<TransactionId> {
19047 self.end_selection(window, cx);
19048 if let Some(tx_id) = self
19049 .buffer
19050 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19051 {
19052 self.selection_history
19053 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19054 cx.emit(EditorEvent::TransactionBegun {
19055 transaction_id: tx_id,
19056 });
19057 Some(tx_id)
19058 } else {
19059 None
19060 }
19061 }
19062
19063 pub fn end_transaction_at(
19064 &mut self,
19065 now: Instant,
19066 cx: &mut Context<Self>,
19067 ) -> Option<TransactionId> {
19068 if let Some(transaction_id) = self
19069 .buffer
19070 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19071 {
19072 if let Some((_, end_selections)) =
19073 self.selection_history.transaction_mut(transaction_id)
19074 {
19075 *end_selections = Some(self.selections.disjoint_anchors_arc());
19076 } else {
19077 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19078 }
19079
19080 cx.emit(EditorEvent::Edited { transaction_id });
19081 Some(transaction_id)
19082 } else {
19083 None
19084 }
19085 }
19086
19087 pub fn modify_transaction_selection_history(
19088 &mut self,
19089 transaction_id: TransactionId,
19090 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19091 ) -> bool {
19092 self.selection_history
19093 .transaction_mut(transaction_id)
19094 .map(modify)
19095 .is_some()
19096 }
19097
19098 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19099 if self.selection_mark_mode {
19100 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19101 s.move_with(|_, sel| {
19102 sel.collapse_to(sel.head(), SelectionGoal::None);
19103 });
19104 })
19105 }
19106 self.selection_mark_mode = true;
19107 cx.notify();
19108 }
19109
19110 pub fn swap_selection_ends(
19111 &mut self,
19112 _: &actions::SwapSelectionEnds,
19113 window: &mut Window,
19114 cx: &mut Context<Self>,
19115 ) {
19116 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19117 s.move_with(|_, sel| {
19118 if sel.start != sel.end {
19119 sel.reversed = !sel.reversed
19120 }
19121 });
19122 });
19123 self.request_autoscroll(Autoscroll::newest(), cx);
19124 cx.notify();
19125 }
19126
19127 pub fn toggle_focus(
19128 workspace: &mut Workspace,
19129 _: &actions::ToggleFocus,
19130 window: &mut Window,
19131 cx: &mut Context<Workspace>,
19132 ) {
19133 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19134 return;
19135 };
19136 workspace.activate_item(&item, true, true, window, cx);
19137 }
19138
19139 pub fn toggle_fold(
19140 &mut self,
19141 _: &actions::ToggleFold,
19142 window: &mut Window,
19143 cx: &mut Context<Self>,
19144 ) {
19145 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19146 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19147 let selection = self.selections.newest::<Point>(&display_map);
19148
19149 let range = if selection.is_empty() {
19150 let point = selection.head().to_display_point(&display_map);
19151 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19152 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19153 .to_point(&display_map);
19154 start..end
19155 } else {
19156 selection.range()
19157 };
19158 if display_map.folds_in_range(range).next().is_some() {
19159 self.unfold_lines(&Default::default(), window, cx)
19160 } else {
19161 self.fold(&Default::default(), window, cx)
19162 }
19163 } else {
19164 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19165 let buffer_ids: HashSet<_> = self
19166 .selections
19167 .disjoint_anchor_ranges()
19168 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19169 .collect();
19170
19171 let should_unfold = buffer_ids
19172 .iter()
19173 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19174
19175 for buffer_id in buffer_ids {
19176 if should_unfold {
19177 self.unfold_buffer(buffer_id, cx);
19178 } else {
19179 self.fold_buffer(buffer_id, cx);
19180 }
19181 }
19182 }
19183 }
19184
19185 pub fn toggle_fold_recursive(
19186 &mut self,
19187 _: &actions::ToggleFoldRecursive,
19188 window: &mut Window,
19189 cx: &mut Context<Self>,
19190 ) {
19191 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19192
19193 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19194 let range = if selection.is_empty() {
19195 let point = selection.head().to_display_point(&display_map);
19196 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19197 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19198 .to_point(&display_map);
19199 start..end
19200 } else {
19201 selection.range()
19202 };
19203 if display_map.folds_in_range(range).next().is_some() {
19204 self.unfold_recursive(&Default::default(), window, cx)
19205 } else {
19206 self.fold_recursive(&Default::default(), window, cx)
19207 }
19208 }
19209
19210 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19211 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19212 let mut to_fold = Vec::new();
19213 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19214 let selections = self.selections.all_adjusted(&display_map);
19215
19216 for selection in selections {
19217 let range = selection.range().sorted();
19218 let buffer_start_row = range.start.row;
19219
19220 if range.start.row != range.end.row {
19221 let mut found = false;
19222 let mut row = range.start.row;
19223 while row <= range.end.row {
19224 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19225 {
19226 found = true;
19227 row = crease.range().end.row + 1;
19228 to_fold.push(crease);
19229 } else {
19230 row += 1
19231 }
19232 }
19233 if found {
19234 continue;
19235 }
19236 }
19237
19238 for row in (0..=range.start.row).rev() {
19239 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19240 && crease.range().end.row >= buffer_start_row
19241 {
19242 to_fold.push(crease);
19243 if row <= range.start.row {
19244 break;
19245 }
19246 }
19247 }
19248 }
19249
19250 self.fold_creases(to_fold, true, window, cx);
19251 } else {
19252 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19253 let buffer_ids = self
19254 .selections
19255 .disjoint_anchor_ranges()
19256 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19257 .collect::<HashSet<_>>();
19258 for buffer_id in buffer_ids {
19259 self.fold_buffer(buffer_id, cx);
19260 }
19261 }
19262 }
19263
19264 pub fn toggle_fold_all(
19265 &mut self,
19266 _: &actions::ToggleFoldAll,
19267 window: &mut Window,
19268 cx: &mut Context<Self>,
19269 ) {
19270 let has_folds = if self.buffer.read(cx).is_singleton() {
19271 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19272 let has_folds = display_map
19273 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19274 .next()
19275 .is_some();
19276 has_folds
19277 } else {
19278 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19279 let has_folds = buffer_ids
19280 .iter()
19281 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19282 has_folds
19283 };
19284
19285 if has_folds {
19286 self.unfold_all(&actions::UnfoldAll, window, cx);
19287 } else {
19288 self.fold_all(&actions::FoldAll, window, cx);
19289 }
19290 }
19291
19292 fn fold_at_level(
19293 &mut self,
19294 fold_at: &FoldAtLevel,
19295 window: &mut Window,
19296 cx: &mut Context<Self>,
19297 ) {
19298 if !self.buffer.read(cx).is_singleton() {
19299 return;
19300 }
19301
19302 let fold_at_level = fold_at.0;
19303 let snapshot = self.buffer.read(cx).snapshot(cx);
19304 let mut to_fold = Vec::new();
19305 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19306
19307 let row_ranges_to_keep: Vec<Range<u32>> = self
19308 .selections
19309 .all::<Point>(&self.display_snapshot(cx))
19310 .into_iter()
19311 .map(|sel| sel.start.row..sel.end.row)
19312 .collect();
19313
19314 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19315 while start_row < end_row {
19316 match self
19317 .snapshot(window, cx)
19318 .crease_for_buffer_row(MultiBufferRow(start_row))
19319 {
19320 Some(crease) => {
19321 let nested_start_row = crease.range().start.row + 1;
19322 let nested_end_row = crease.range().end.row;
19323
19324 if current_level < fold_at_level {
19325 stack.push((nested_start_row, nested_end_row, current_level + 1));
19326 } else if current_level == fold_at_level {
19327 // Fold iff there is no selection completely contained within the fold region
19328 if !row_ranges_to_keep.iter().any(|selection| {
19329 selection.end >= nested_start_row
19330 && selection.start <= nested_end_row
19331 }) {
19332 to_fold.push(crease);
19333 }
19334 }
19335
19336 start_row = nested_end_row + 1;
19337 }
19338 None => start_row += 1,
19339 }
19340 }
19341 }
19342
19343 self.fold_creases(to_fold, true, window, cx);
19344 }
19345
19346 pub fn fold_at_level_1(
19347 &mut self,
19348 _: &actions::FoldAtLevel1,
19349 window: &mut Window,
19350 cx: &mut Context<Self>,
19351 ) {
19352 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19353 }
19354
19355 pub fn fold_at_level_2(
19356 &mut self,
19357 _: &actions::FoldAtLevel2,
19358 window: &mut Window,
19359 cx: &mut Context<Self>,
19360 ) {
19361 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19362 }
19363
19364 pub fn fold_at_level_3(
19365 &mut self,
19366 _: &actions::FoldAtLevel3,
19367 window: &mut Window,
19368 cx: &mut Context<Self>,
19369 ) {
19370 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19371 }
19372
19373 pub fn fold_at_level_4(
19374 &mut self,
19375 _: &actions::FoldAtLevel4,
19376 window: &mut Window,
19377 cx: &mut Context<Self>,
19378 ) {
19379 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19380 }
19381
19382 pub fn fold_at_level_5(
19383 &mut self,
19384 _: &actions::FoldAtLevel5,
19385 window: &mut Window,
19386 cx: &mut Context<Self>,
19387 ) {
19388 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19389 }
19390
19391 pub fn fold_at_level_6(
19392 &mut self,
19393 _: &actions::FoldAtLevel6,
19394 window: &mut Window,
19395 cx: &mut Context<Self>,
19396 ) {
19397 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19398 }
19399
19400 pub fn fold_at_level_7(
19401 &mut self,
19402 _: &actions::FoldAtLevel7,
19403 window: &mut Window,
19404 cx: &mut Context<Self>,
19405 ) {
19406 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19407 }
19408
19409 pub fn fold_at_level_8(
19410 &mut self,
19411 _: &actions::FoldAtLevel8,
19412 window: &mut Window,
19413 cx: &mut Context<Self>,
19414 ) {
19415 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19416 }
19417
19418 pub fn fold_at_level_9(
19419 &mut self,
19420 _: &actions::FoldAtLevel9,
19421 window: &mut Window,
19422 cx: &mut Context<Self>,
19423 ) {
19424 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19425 }
19426
19427 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19428 if self.buffer.read(cx).is_singleton() {
19429 let mut fold_ranges = Vec::new();
19430 let snapshot = self.buffer.read(cx).snapshot(cx);
19431
19432 for row in 0..snapshot.max_row().0 {
19433 if let Some(foldable_range) = self
19434 .snapshot(window, cx)
19435 .crease_for_buffer_row(MultiBufferRow(row))
19436 {
19437 fold_ranges.push(foldable_range);
19438 }
19439 }
19440
19441 self.fold_creases(fold_ranges, true, window, cx);
19442 } else {
19443 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19444 editor
19445 .update_in(cx, |editor, _, cx| {
19446 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19447 editor.fold_buffer(buffer_id, cx);
19448 }
19449 })
19450 .ok();
19451 });
19452 }
19453 cx.emit(SearchEvent::ResultsCollapsedChanged(
19454 CollapseDirection::Collapsed,
19455 ));
19456 }
19457
19458 pub fn fold_function_bodies(
19459 &mut self,
19460 _: &actions::FoldFunctionBodies,
19461 window: &mut Window,
19462 cx: &mut Context<Self>,
19463 ) {
19464 let snapshot = self.buffer.read(cx).snapshot(cx);
19465
19466 let ranges = snapshot
19467 .text_object_ranges(
19468 MultiBufferOffset(0)..snapshot.len(),
19469 TreeSitterOptions::default(),
19470 )
19471 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19472 .collect::<Vec<_>>();
19473
19474 let creases = ranges
19475 .into_iter()
19476 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19477 .collect();
19478
19479 self.fold_creases(creases, true, window, cx);
19480 }
19481
19482 pub fn fold_recursive(
19483 &mut self,
19484 _: &actions::FoldRecursive,
19485 window: &mut Window,
19486 cx: &mut Context<Self>,
19487 ) {
19488 let mut to_fold = Vec::new();
19489 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19490 let selections = self.selections.all_adjusted(&display_map);
19491
19492 for selection in selections {
19493 let range = selection.range().sorted();
19494 let buffer_start_row = range.start.row;
19495
19496 if range.start.row != range.end.row {
19497 let mut found = false;
19498 for row in range.start.row..=range.end.row {
19499 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19500 found = true;
19501 to_fold.push(crease);
19502 }
19503 }
19504 if found {
19505 continue;
19506 }
19507 }
19508
19509 for row in (0..=range.start.row).rev() {
19510 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19511 if crease.range().end.row >= buffer_start_row {
19512 to_fold.push(crease);
19513 } else {
19514 break;
19515 }
19516 }
19517 }
19518 }
19519
19520 self.fold_creases(to_fold, true, window, cx);
19521 }
19522
19523 pub fn fold_at(
19524 &mut self,
19525 buffer_row: MultiBufferRow,
19526 window: &mut Window,
19527 cx: &mut Context<Self>,
19528 ) {
19529 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19530
19531 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19532 let autoscroll = self
19533 .selections
19534 .all::<Point>(&display_map)
19535 .iter()
19536 .any(|selection| crease.range().overlaps(&selection.range()));
19537
19538 self.fold_creases(vec![crease], autoscroll, window, cx);
19539 }
19540 }
19541
19542 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19543 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19544 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19545 let buffer = display_map.buffer_snapshot();
19546 let selections = self.selections.all::<Point>(&display_map);
19547 let ranges = selections
19548 .iter()
19549 .map(|s| {
19550 let range = s.display_range(&display_map).sorted();
19551 let mut start = range.start.to_point(&display_map);
19552 let mut end = range.end.to_point(&display_map);
19553 start.column = 0;
19554 end.column = buffer.line_len(MultiBufferRow(end.row));
19555 start..end
19556 })
19557 .collect::<Vec<_>>();
19558
19559 self.unfold_ranges(&ranges, true, true, cx);
19560 } else {
19561 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19562 let buffer_ids = self
19563 .selections
19564 .disjoint_anchor_ranges()
19565 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19566 .collect::<HashSet<_>>();
19567 for buffer_id in buffer_ids {
19568 self.unfold_buffer(buffer_id, cx);
19569 }
19570 }
19571 }
19572
19573 pub fn unfold_recursive(
19574 &mut self,
19575 _: &UnfoldRecursive,
19576 _window: &mut Window,
19577 cx: &mut Context<Self>,
19578 ) {
19579 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19580 let selections = self.selections.all::<Point>(&display_map);
19581 let ranges = selections
19582 .iter()
19583 .map(|s| {
19584 let mut range = s.display_range(&display_map).sorted();
19585 *range.start.column_mut() = 0;
19586 *range.end.column_mut() = display_map.line_len(range.end.row());
19587 let start = range.start.to_point(&display_map);
19588 let end = range.end.to_point(&display_map);
19589 start..end
19590 })
19591 .collect::<Vec<_>>();
19592
19593 self.unfold_ranges(&ranges, true, true, cx);
19594 }
19595
19596 pub fn unfold_at(
19597 &mut self,
19598 buffer_row: MultiBufferRow,
19599 _window: &mut Window,
19600 cx: &mut Context<Self>,
19601 ) {
19602 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19603
19604 let intersection_range = Point::new(buffer_row.0, 0)
19605 ..Point::new(
19606 buffer_row.0,
19607 display_map.buffer_snapshot().line_len(buffer_row),
19608 );
19609
19610 let autoscroll = self
19611 .selections
19612 .all::<Point>(&display_map)
19613 .iter()
19614 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19615
19616 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19617 }
19618
19619 pub fn unfold_all(
19620 &mut self,
19621 _: &actions::UnfoldAll,
19622 _window: &mut Window,
19623 cx: &mut Context<Self>,
19624 ) {
19625 if self.buffer.read(cx).is_singleton() {
19626 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19627 self.unfold_ranges(
19628 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19629 true,
19630 true,
19631 cx,
19632 );
19633 } else {
19634 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19635 editor
19636 .update(cx, |editor, cx| {
19637 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19638 editor.unfold_buffer(buffer_id, cx);
19639 }
19640 })
19641 .ok();
19642 });
19643 }
19644 cx.emit(SearchEvent::ResultsCollapsedChanged(
19645 CollapseDirection::Expanded,
19646 ));
19647 }
19648
19649 pub fn fold_selected_ranges(
19650 &mut self,
19651 _: &FoldSelectedRanges,
19652 window: &mut Window,
19653 cx: &mut Context<Self>,
19654 ) {
19655 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19656 let selections = self.selections.all_adjusted(&display_map);
19657 let ranges = selections
19658 .into_iter()
19659 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19660 .collect::<Vec<_>>();
19661 self.fold_creases(ranges, true, window, cx);
19662 }
19663
19664 pub fn fold_ranges<T: ToOffset + Clone>(
19665 &mut self,
19666 ranges: Vec<Range<T>>,
19667 auto_scroll: bool,
19668 window: &mut Window,
19669 cx: &mut Context<Self>,
19670 ) {
19671 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19672 let ranges = ranges
19673 .into_iter()
19674 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19675 .collect::<Vec<_>>();
19676 self.fold_creases(ranges, auto_scroll, window, cx);
19677 }
19678
19679 pub fn fold_creases<T: ToOffset + Clone>(
19680 &mut self,
19681 creases: Vec<Crease<T>>,
19682 auto_scroll: bool,
19683 _window: &mut Window,
19684 cx: &mut Context<Self>,
19685 ) {
19686 if creases.is_empty() {
19687 return;
19688 }
19689
19690 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19691
19692 if auto_scroll {
19693 self.request_autoscroll(Autoscroll::fit(), cx);
19694 }
19695
19696 cx.notify();
19697
19698 self.scrollbar_marker_state.dirty = true;
19699 self.folds_did_change(cx);
19700 }
19701
19702 /// Removes any folds whose ranges intersect any of the given ranges.
19703 pub fn unfold_ranges<T: ToOffset + Clone>(
19704 &mut self,
19705 ranges: &[Range<T>],
19706 inclusive: bool,
19707 auto_scroll: bool,
19708 cx: &mut Context<Self>,
19709 ) {
19710 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19711 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19712 });
19713 self.folds_did_change(cx);
19714 }
19715
19716 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19717 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19718 return;
19719 }
19720
19721 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19722 self.display_map.update(cx, |display_map, cx| {
19723 display_map.fold_buffers([buffer_id], cx)
19724 });
19725
19726 let snapshot = self.display_snapshot(cx);
19727 self.selections.change_with(&snapshot, |selections| {
19728 selections.remove_selections_from_buffer(buffer_id);
19729 });
19730
19731 cx.emit(EditorEvent::BufferFoldToggled {
19732 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19733 folded: true,
19734 });
19735 cx.notify();
19736 }
19737
19738 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19739 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19740 return;
19741 }
19742 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19743 self.display_map.update(cx, |display_map, cx| {
19744 display_map.unfold_buffers([buffer_id], cx);
19745 });
19746 cx.emit(EditorEvent::BufferFoldToggled {
19747 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19748 folded: false,
19749 });
19750 cx.notify();
19751 }
19752
19753 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19754 self.display_map.read(cx).is_buffer_folded(buffer)
19755 }
19756
19757 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19758 self.display_map.read(cx).folded_buffers()
19759 }
19760
19761 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19762 self.display_map.update(cx, |display_map, cx| {
19763 display_map.disable_header_for_buffer(buffer_id, cx);
19764 });
19765 cx.notify();
19766 }
19767
19768 /// Removes any folds with the given ranges.
19769 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19770 &mut self,
19771 ranges: &[Range<T>],
19772 type_id: TypeId,
19773 auto_scroll: bool,
19774 cx: &mut Context<Self>,
19775 ) {
19776 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19777 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19778 });
19779 self.folds_did_change(cx);
19780 }
19781
19782 fn remove_folds_with<T: ToOffset + Clone>(
19783 &mut self,
19784 ranges: &[Range<T>],
19785 auto_scroll: bool,
19786 cx: &mut Context<Self>,
19787 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19788 ) {
19789 if ranges.is_empty() {
19790 return;
19791 }
19792
19793 let mut buffers_affected = HashSet::default();
19794 let multi_buffer = self.buffer().read(cx);
19795 for range in ranges {
19796 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19797 buffers_affected.insert(buffer.read(cx).remote_id());
19798 };
19799 }
19800
19801 self.display_map.update(cx, update);
19802
19803 if auto_scroll {
19804 self.request_autoscroll(Autoscroll::fit(), cx);
19805 }
19806
19807 cx.notify();
19808 self.scrollbar_marker_state.dirty = true;
19809 self.active_indent_guides_state.dirty = true;
19810 }
19811
19812 pub fn update_renderer_widths(
19813 &mut self,
19814 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19815 cx: &mut Context<Self>,
19816 ) -> bool {
19817 self.display_map
19818 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19819 }
19820
19821 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19822 self.display_map.read(cx).fold_placeholder.clone()
19823 }
19824
19825 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19826 self.buffer.update(cx, |buffer, cx| {
19827 buffer.set_all_diff_hunks_expanded(cx);
19828 });
19829 }
19830
19831 pub fn expand_all_diff_hunks(
19832 &mut self,
19833 _: &ExpandAllDiffHunks,
19834 _window: &mut Window,
19835 cx: &mut Context<Self>,
19836 ) {
19837 self.buffer.update(cx, |buffer, cx| {
19838 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19839 });
19840 }
19841
19842 pub fn collapse_all_diff_hunks(
19843 &mut self,
19844 _: &CollapseAllDiffHunks,
19845 _window: &mut Window,
19846 cx: &mut Context<Self>,
19847 ) {
19848 self.buffer.update(cx, |buffer, cx| {
19849 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19850 });
19851 }
19852
19853 pub fn toggle_selected_diff_hunks(
19854 &mut self,
19855 _: &ToggleSelectedDiffHunks,
19856 _window: &mut Window,
19857 cx: &mut Context<Self>,
19858 ) {
19859 let ranges: Vec<_> = self
19860 .selections
19861 .disjoint_anchors()
19862 .iter()
19863 .map(|s| s.range())
19864 .collect();
19865 self.toggle_diff_hunks_in_ranges(ranges, cx);
19866 }
19867
19868 pub fn diff_hunks_in_ranges<'a>(
19869 &'a self,
19870 ranges: &'a [Range<Anchor>],
19871 buffer: &'a MultiBufferSnapshot,
19872 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19873 ranges.iter().flat_map(move |range| {
19874 let end_excerpt_id = range.end.excerpt_id;
19875 let range = range.to_point(buffer);
19876 let mut peek_end = range.end;
19877 if range.end.row < buffer.max_row().0 {
19878 peek_end = Point::new(range.end.row + 1, 0);
19879 }
19880 buffer
19881 .diff_hunks_in_range(range.start..peek_end)
19882 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19883 })
19884 }
19885
19886 pub fn has_stageable_diff_hunks_in_ranges(
19887 &self,
19888 ranges: &[Range<Anchor>],
19889 snapshot: &MultiBufferSnapshot,
19890 ) -> bool {
19891 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19892 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19893 }
19894
19895 pub fn toggle_staged_selected_diff_hunks(
19896 &mut self,
19897 _: &::git::ToggleStaged,
19898 _: &mut Window,
19899 cx: &mut Context<Self>,
19900 ) {
19901 let snapshot = self.buffer.read(cx).snapshot(cx);
19902 let ranges: Vec<_> = self
19903 .selections
19904 .disjoint_anchors()
19905 .iter()
19906 .map(|s| s.range())
19907 .collect();
19908 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19909 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19910 }
19911
19912 pub fn set_render_diff_hunk_controls(
19913 &mut self,
19914 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19915 cx: &mut Context<Self>,
19916 ) {
19917 self.render_diff_hunk_controls = render_diff_hunk_controls;
19918 cx.notify();
19919 }
19920
19921 pub fn stage_and_next(
19922 &mut self,
19923 _: &::git::StageAndNext,
19924 window: &mut Window,
19925 cx: &mut Context<Self>,
19926 ) {
19927 self.do_stage_or_unstage_and_next(true, window, cx);
19928 }
19929
19930 pub fn unstage_and_next(
19931 &mut self,
19932 _: &::git::UnstageAndNext,
19933 window: &mut Window,
19934 cx: &mut Context<Self>,
19935 ) {
19936 self.do_stage_or_unstage_and_next(false, window, cx);
19937 }
19938
19939 pub fn stage_or_unstage_diff_hunks(
19940 &mut self,
19941 stage: bool,
19942 ranges: Vec<Range<Anchor>>,
19943 cx: &mut Context<Self>,
19944 ) {
19945 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19946 cx.spawn(async move |this, cx| {
19947 task.await?;
19948 this.update(cx, |this, cx| {
19949 let snapshot = this.buffer.read(cx).snapshot(cx);
19950 let chunk_by = this
19951 .diff_hunks_in_ranges(&ranges, &snapshot)
19952 .chunk_by(|hunk| hunk.buffer_id);
19953 for (buffer_id, hunks) in &chunk_by {
19954 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19955 }
19956 })
19957 })
19958 .detach_and_log_err(cx);
19959 }
19960
19961 fn save_buffers_for_ranges_if_needed(
19962 &mut self,
19963 ranges: &[Range<Anchor>],
19964 cx: &mut Context<Editor>,
19965 ) -> Task<Result<()>> {
19966 let multibuffer = self.buffer.read(cx);
19967 let snapshot = multibuffer.read(cx);
19968 let buffer_ids: HashSet<_> = ranges
19969 .iter()
19970 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19971 .collect();
19972 drop(snapshot);
19973
19974 let mut buffers = HashSet::default();
19975 for buffer_id in buffer_ids {
19976 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19977 let buffer = buffer_entity.read(cx);
19978 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19979 {
19980 buffers.insert(buffer_entity);
19981 }
19982 }
19983 }
19984
19985 if let Some(project) = &self.project {
19986 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19987 } else {
19988 Task::ready(Ok(()))
19989 }
19990 }
19991
19992 fn do_stage_or_unstage_and_next(
19993 &mut self,
19994 stage: bool,
19995 window: &mut Window,
19996 cx: &mut Context<Self>,
19997 ) {
19998 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19999
20000 if ranges.iter().any(|range| range.start != range.end) {
20001 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20002 return;
20003 }
20004
20005 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20006 let snapshot = self.snapshot(window, cx);
20007 let position = self
20008 .selections
20009 .newest::<Point>(&snapshot.display_snapshot)
20010 .head();
20011 let mut row = snapshot
20012 .buffer_snapshot()
20013 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20014 .find(|hunk| hunk.row_range.start.0 > position.row)
20015 .map(|hunk| hunk.row_range.start);
20016
20017 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20018 // Outside of the project diff editor, wrap around to the beginning.
20019 if !all_diff_hunks_expanded {
20020 row = row.or_else(|| {
20021 snapshot
20022 .buffer_snapshot()
20023 .diff_hunks_in_range(Point::zero()..position)
20024 .find(|hunk| hunk.row_range.end.0 < position.row)
20025 .map(|hunk| hunk.row_range.start)
20026 });
20027 }
20028
20029 if let Some(row) = row {
20030 let destination = Point::new(row.0, 0);
20031 let autoscroll = Autoscroll::center();
20032
20033 self.unfold_ranges(&[destination..destination], false, false, cx);
20034 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20035 s.select_ranges([destination..destination]);
20036 });
20037 }
20038 }
20039
20040 fn do_stage_or_unstage(
20041 &self,
20042 stage: bool,
20043 buffer_id: BufferId,
20044 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20045 cx: &mut App,
20046 ) -> Option<()> {
20047 let project = self.project()?;
20048 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20049 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20050 let buffer_snapshot = buffer.read(cx).snapshot();
20051 let file_exists = buffer_snapshot
20052 .file()
20053 .is_some_and(|file| file.disk_state().exists());
20054 diff.update(cx, |diff, cx| {
20055 diff.stage_or_unstage_hunks(
20056 stage,
20057 &hunks
20058 .map(|hunk| buffer_diff::DiffHunk {
20059 buffer_range: hunk.buffer_range,
20060 // We don't need to pass in word diffs here because they're only used for rendering and
20061 // this function changes internal state
20062 base_word_diffs: Vec::default(),
20063 buffer_word_diffs: Vec::default(),
20064 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20065 ..hunk.diff_base_byte_range.end.0,
20066 secondary_status: hunk.status.secondary,
20067 range: Point::zero()..Point::zero(), // unused
20068 })
20069 .collect::<Vec<_>>(),
20070 &buffer_snapshot,
20071 file_exists,
20072 cx,
20073 )
20074 });
20075 None
20076 }
20077
20078 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20079 let ranges: Vec<_> = self
20080 .selections
20081 .disjoint_anchors()
20082 .iter()
20083 .map(|s| s.range())
20084 .collect();
20085 self.buffer
20086 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20087 }
20088
20089 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20090 self.buffer.update(cx, |buffer, cx| {
20091 let ranges = vec![Anchor::min()..Anchor::max()];
20092 if !buffer.all_diff_hunks_expanded()
20093 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20094 {
20095 buffer.collapse_diff_hunks(ranges, cx);
20096 true
20097 } else {
20098 false
20099 }
20100 })
20101 }
20102
20103 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20104 if self.buffer.read(cx).all_diff_hunks_expanded() {
20105 return true;
20106 }
20107 let ranges = vec![Anchor::min()..Anchor::max()];
20108 self.buffer
20109 .read(cx)
20110 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20111 }
20112
20113 fn toggle_diff_hunks_in_ranges(
20114 &mut self,
20115 ranges: Vec<Range<Anchor>>,
20116 cx: &mut Context<Editor>,
20117 ) {
20118 self.buffer.update(cx, |buffer, cx| {
20119 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20120 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20121 })
20122 }
20123
20124 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20125 self.buffer.update(cx, |buffer, cx| {
20126 let snapshot = buffer.snapshot(cx);
20127 let excerpt_id = range.end.excerpt_id;
20128 let point_range = range.to_point(&snapshot);
20129 let expand = !buffer.single_hunk_is_expanded(range, cx);
20130 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20131 })
20132 }
20133
20134 pub(crate) fn apply_all_diff_hunks(
20135 &mut self,
20136 _: &ApplyAllDiffHunks,
20137 window: &mut Window,
20138 cx: &mut Context<Self>,
20139 ) {
20140 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20141
20142 let buffers = self.buffer.read(cx).all_buffers();
20143 for branch_buffer in buffers {
20144 branch_buffer.update(cx, |branch_buffer, cx| {
20145 branch_buffer.merge_into_base(Vec::new(), cx);
20146 });
20147 }
20148
20149 if let Some(project) = self.project.clone() {
20150 self.save(
20151 SaveOptions {
20152 format: true,
20153 autosave: false,
20154 },
20155 project,
20156 window,
20157 cx,
20158 )
20159 .detach_and_log_err(cx);
20160 }
20161 }
20162
20163 pub(crate) fn apply_selected_diff_hunks(
20164 &mut self,
20165 _: &ApplyDiffHunk,
20166 window: &mut Window,
20167 cx: &mut Context<Self>,
20168 ) {
20169 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20170 let snapshot = self.snapshot(window, cx);
20171 let hunks = snapshot.hunks_for_ranges(
20172 self.selections
20173 .all(&snapshot.display_snapshot)
20174 .into_iter()
20175 .map(|selection| selection.range()),
20176 );
20177 let mut ranges_by_buffer = HashMap::default();
20178 self.transact(window, cx, |editor, _window, cx| {
20179 for hunk in hunks {
20180 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20181 ranges_by_buffer
20182 .entry(buffer.clone())
20183 .or_insert_with(Vec::new)
20184 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20185 }
20186 }
20187
20188 for (buffer, ranges) in ranges_by_buffer {
20189 buffer.update(cx, |buffer, cx| {
20190 buffer.merge_into_base(ranges, cx);
20191 });
20192 }
20193 });
20194
20195 if let Some(project) = self.project.clone() {
20196 self.save(
20197 SaveOptions {
20198 format: true,
20199 autosave: false,
20200 },
20201 project,
20202 window,
20203 cx,
20204 )
20205 .detach_and_log_err(cx);
20206 }
20207 }
20208
20209 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20210 if hovered != self.gutter_hovered {
20211 self.gutter_hovered = hovered;
20212 cx.notify();
20213 }
20214 }
20215
20216 pub fn insert_blocks(
20217 &mut self,
20218 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20219 autoscroll: Option<Autoscroll>,
20220 cx: &mut Context<Self>,
20221 ) -> Vec<CustomBlockId> {
20222 let blocks = self
20223 .display_map
20224 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20225 if let Some(autoscroll) = autoscroll {
20226 self.request_autoscroll(autoscroll, cx);
20227 }
20228 cx.notify();
20229 blocks
20230 }
20231
20232 pub fn resize_blocks(
20233 &mut self,
20234 heights: HashMap<CustomBlockId, u32>,
20235 autoscroll: Option<Autoscroll>,
20236 cx: &mut Context<Self>,
20237 ) {
20238 self.display_map
20239 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20240 if let Some(autoscroll) = autoscroll {
20241 self.request_autoscroll(autoscroll, cx);
20242 }
20243 cx.notify();
20244 }
20245
20246 pub fn replace_blocks(
20247 &mut self,
20248 renderers: HashMap<CustomBlockId, RenderBlock>,
20249 autoscroll: Option<Autoscroll>,
20250 cx: &mut Context<Self>,
20251 ) {
20252 self.display_map
20253 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20254 if let Some(autoscroll) = autoscroll {
20255 self.request_autoscroll(autoscroll, cx);
20256 }
20257 cx.notify();
20258 }
20259
20260 pub fn remove_blocks(
20261 &mut self,
20262 block_ids: HashSet<CustomBlockId>,
20263 autoscroll: Option<Autoscroll>,
20264 cx: &mut Context<Self>,
20265 ) {
20266 self.display_map.update(cx, |display_map, cx| {
20267 display_map.remove_blocks(block_ids, cx)
20268 });
20269 if let Some(autoscroll) = autoscroll {
20270 self.request_autoscroll(autoscroll, cx);
20271 }
20272 cx.notify();
20273 }
20274
20275 pub fn row_for_block(
20276 &self,
20277 block_id: CustomBlockId,
20278 cx: &mut Context<Self>,
20279 ) -> Option<DisplayRow> {
20280 self.display_map
20281 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20282 }
20283
20284 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20285 self.focused_block = Some(focused_block);
20286 }
20287
20288 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20289 self.focused_block.take()
20290 }
20291
20292 pub fn insert_creases(
20293 &mut self,
20294 creases: impl IntoIterator<Item = Crease<Anchor>>,
20295 cx: &mut Context<Self>,
20296 ) -> Vec<CreaseId> {
20297 self.display_map
20298 .update(cx, |map, cx| map.insert_creases(creases, cx))
20299 }
20300
20301 pub fn remove_creases(
20302 &mut self,
20303 ids: impl IntoIterator<Item = CreaseId>,
20304 cx: &mut Context<Self>,
20305 ) -> Vec<(CreaseId, Range<Anchor>)> {
20306 self.display_map
20307 .update(cx, |map, cx| map.remove_creases(ids, cx))
20308 }
20309
20310 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20311 self.display_map
20312 .update(cx, |map, cx| map.snapshot(cx))
20313 .longest_row()
20314 }
20315
20316 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20317 self.display_map
20318 .update(cx, |map, cx| map.snapshot(cx))
20319 .max_point()
20320 }
20321
20322 pub fn text(&self, cx: &App) -> String {
20323 self.buffer.read(cx).read(cx).text()
20324 }
20325
20326 pub fn is_empty(&self, cx: &App) -> bool {
20327 self.buffer.read(cx).read(cx).is_empty()
20328 }
20329
20330 pub fn text_option(&self, cx: &App) -> Option<String> {
20331 let text = self.text(cx);
20332 let text = text.trim();
20333
20334 if text.is_empty() {
20335 return None;
20336 }
20337
20338 Some(text.to_string())
20339 }
20340
20341 pub fn set_text(
20342 &mut self,
20343 text: impl Into<Arc<str>>,
20344 window: &mut Window,
20345 cx: &mut Context<Self>,
20346 ) {
20347 self.transact(window, cx, |this, _, cx| {
20348 this.buffer
20349 .read(cx)
20350 .as_singleton()
20351 .expect("you can only call set_text on editors for singleton buffers")
20352 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20353 });
20354 }
20355
20356 pub fn display_text(&self, cx: &mut App) -> String {
20357 self.display_map
20358 .update(cx, |map, cx| map.snapshot(cx))
20359 .text()
20360 }
20361
20362 fn create_minimap(
20363 &self,
20364 minimap_settings: MinimapSettings,
20365 window: &mut Window,
20366 cx: &mut Context<Self>,
20367 ) -> Option<Entity<Self>> {
20368 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20369 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20370 }
20371
20372 fn initialize_new_minimap(
20373 &self,
20374 minimap_settings: MinimapSettings,
20375 window: &mut Window,
20376 cx: &mut Context<Self>,
20377 ) -> Entity<Self> {
20378 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20379 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20380
20381 let mut minimap = Editor::new_internal(
20382 EditorMode::Minimap {
20383 parent: cx.weak_entity(),
20384 },
20385 self.buffer.clone(),
20386 None,
20387 Some(self.display_map.clone()),
20388 window,
20389 cx,
20390 );
20391 minimap.scroll_manager.clone_state(&self.scroll_manager);
20392 minimap.set_text_style_refinement(TextStyleRefinement {
20393 font_size: Some(MINIMAP_FONT_SIZE),
20394 font_weight: Some(MINIMAP_FONT_WEIGHT),
20395 font_family: Some(MINIMAP_FONT_FAMILY),
20396 ..Default::default()
20397 });
20398 minimap.update_minimap_configuration(minimap_settings, cx);
20399 cx.new(|_| minimap)
20400 }
20401
20402 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20403 let current_line_highlight = minimap_settings
20404 .current_line_highlight
20405 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20406 self.set_current_line_highlight(Some(current_line_highlight));
20407 }
20408
20409 pub fn minimap(&self) -> Option<&Entity<Self>> {
20410 self.minimap
20411 .as_ref()
20412 .filter(|_| self.minimap_visibility.visible())
20413 }
20414
20415 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20416 let mut wrap_guides = smallvec![];
20417
20418 if self.show_wrap_guides == Some(false) {
20419 return wrap_guides;
20420 }
20421
20422 let settings = self.buffer.read(cx).language_settings(cx);
20423 if settings.show_wrap_guides {
20424 match self.soft_wrap_mode(cx) {
20425 SoftWrap::Column(soft_wrap) => {
20426 wrap_guides.push((soft_wrap as usize, true));
20427 }
20428 SoftWrap::Bounded(soft_wrap) => {
20429 wrap_guides.push((soft_wrap as usize, true));
20430 }
20431 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20432 }
20433 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20434 }
20435
20436 wrap_guides
20437 }
20438
20439 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20440 let settings = self.buffer.read(cx).language_settings(cx);
20441 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20442 match mode {
20443 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20444 SoftWrap::None
20445 }
20446 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20447 language_settings::SoftWrap::PreferredLineLength => {
20448 SoftWrap::Column(settings.preferred_line_length)
20449 }
20450 language_settings::SoftWrap::Bounded => {
20451 SoftWrap::Bounded(settings.preferred_line_length)
20452 }
20453 }
20454 }
20455
20456 pub fn set_soft_wrap_mode(
20457 &mut self,
20458 mode: language_settings::SoftWrap,
20459
20460 cx: &mut Context<Self>,
20461 ) {
20462 self.soft_wrap_mode_override = Some(mode);
20463 cx.notify();
20464 }
20465
20466 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20467 self.hard_wrap = hard_wrap;
20468 cx.notify();
20469 }
20470
20471 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20472 self.text_style_refinement = Some(style);
20473 }
20474
20475 /// called by the Element so we know what style we were most recently rendered with.
20476 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20477 // We intentionally do not inform the display map about the minimap style
20478 // so that wrapping is not recalculated and stays consistent for the editor
20479 // and its linked minimap.
20480 if !self.mode.is_minimap() {
20481 let font = style.text.font();
20482 let font_size = style.text.font_size.to_pixels(window.rem_size());
20483 let display_map = self
20484 .placeholder_display_map
20485 .as_ref()
20486 .filter(|_| self.is_empty(cx))
20487 .unwrap_or(&self.display_map);
20488
20489 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20490 }
20491 self.style = Some(style);
20492 }
20493
20494 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20495 if self.style.is_none() {
20496 self.style = Some(self.create_style(cx));
20497 }
20498 self.style.as_ref().unwrap()
20499 }
20500
20501 // Called by the element. This method is not designed to be called outside of the editor
20502 // element's layout code because it does not notify when rewrapping is computed synchronously.
20503 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20504 if self.is_empty(cx) {
20505 self.placeholder_display_map
20506 .as_ref()
20507 .map_or(false, |display_map| {
20508 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20509 })
20510 } else {
20511 self.display_map
20512 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20513 }
20514 }
20515
20516 pub fn set_soft_wrap(&mut self) {
20517 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20518 }
20519
20520 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20521 if self.soft_wrap_mode_override.is_some() {
20522 self.soft_wrap_mode_override.take();
20523 } else {
20524 let soft_wrap = match self.soft_wrap_mode(cx) {
20525 SoftWrap::GitDiff => return,
20526 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20527 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20528 language_settings::SoftWrap::None
20529 }
20530 };
20531 self.soft_wrap_mode_override = Some(soft_wrap);
20532 }
20533 cx.notify();
20534 }
20535
20536 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20537 let Some(workspace) = self.workspace() else {
20538 return;
20539 };
20540 let fs = workspace.read(cx).app_state().fs.clone();
20541 let current_show = TabBarSettings::get_global(cx).show;
20542 update_settings_file(fs, cx, move |setting, _| {
20543 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20544 });
20545 }
20546
20547 pub fn toggle_indent_guides(
20548 &mut self,
20549 _: &ToggleIndentGuides,
20550 _: &mut Window,
20551 cx: &mut Context<Self>,
20552 ) {
20553 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20554 self.buffer
20555 .read(cx)
20556 .language_settings(cx)
20557 .indent_guides
20558 .enabled
20559 });
20560 self.show_indent_guides = Some(!currently_enabled);
20561 cx.notify();
20562 }
20563
20564 fn should_show_indent_guides(&self) -> Option<bool> {
20565 self.show_indent_guides
20566 }
20567
20568 pub fn disable_indent_guides_for_buffer(
20569 &mut self,
20570 buffer_id: BufferId,
20571 cx: &mut Context<Self>,
20572 ) {
20573 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20574 cx.notify();
20575 }
20576
20577 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20578 self.buffers_with_disabled_indent_guides
20579 .contains(&buffer_id)
20580 }
20581
20582 pub fn toggle_line_numbers(
20583 &mut self,
20584 _: &ToggleLineNumbers,
20585 _: &mut Window,
20586 cx: &mut Context<Self>,
20587 ) {
20588 let mut editor_settings = EditorSettings::get_global(cx).clone();
20589 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20590 EditorSettings::override_global(editor_settings, cx);
20591 }
20592
20593 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20594 if let Some(show_line_numbers) = self.show_line_numbers {
20595 return show_line_numbers;
20596 }
20597 EditorSettings::get_global(cx).gutter.line_numbers
20598 }
20599
20600 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
20601 match (
20602 self.use_relative_line_numbers,
20603 EditorSettings::get_global(cx).relative_line_numbers,
20604 ) {
20605 (None, setting) => setting,
20606 (Some(false), _) => RelativeLineNumbers::Disabled,
20607 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20608 (Some(true), _) => RelativeLineNumbers::Enabled,
20609 }
20610 }
20611
20612 pub fn toggle_relative_line_numbers(
20613 &mut self,
20614 _: &ToggleRelativeLineNumbers,
20615 _: &mut Window,
20616 cx: &mut Context<Self>,
20617 ) {
20618 let is_relative = self.relative_line_numbers(cx);
20619 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20620 }
20621
20622 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20623 self.use_relative_line_numbers = is_relative;
20624 cx.notify();
20625 }
20626
20627 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20628 self.show_gutter = show_gutter;
20629 cx.notify();
20630 }
20631
20632 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20633 self.show_scrollbars = ScrollbarAxes {
20634 horizontal: show,
20635 vertical: show,
20636 };
20637 cx.notify();
20638 }
20639
20640 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20641 self.show_scrollbars.vertical = show;
20642 cx.notify();
20643 }
20644
20645 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20646 self.show_scrollbars.horizontal = show;
20647 cx.notify();
20648 }
20649
20650 pub fn set_minimap_visibility(
20651 &mut self,
20652 minimap_visibility: MinimapVisibility,
20653 window: &mut Window,
20654 cx: &mut Context<Self>,
20655 ) {
20656 if self.minimap_visibility != minimap_visibility {
20657 if minimap_visibility.visible() && self.minimap.is_none() {
20658 let minimap_settings = EditorSettings::get_global(cx).minimap;
20659 self.minimap =
20660 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20661 }
20662 self.minimap_visibility = minimap_visibility;
20663 cx.notify();
20664 }
20665 }
20666
20667 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20668 self.set_show_scrollbars(false, cx);
20669 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20670 }
20671
20672 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20673 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20674 }
20675
20676 /// Normally the text in full mode and auto height editors is padded on the
20677 /// left side by roughly half a character width for improved hit testing.
20678 ///
20679 /// Use this method to disable this for cases where this is not wanted (e.g.
20680 /// if you want to align the editor text with some other text above or below)
20681 /// or if you want to add this padding to single-line editors.
20682 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20683 self.offset_content = offset_content;
20684 cx.notify();
20685 }
20686
20687 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20688 self.show_line_numbers = Some(show_line_numbers);
20689 cx.notify();
20690 }
20691
20692 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20693 self.disable_expand_excerpt_buttons = true;
20694 cx.notify();
20695 }
20696
20697 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
20698 self.delegate_expand_excerpts = delegate;
20699 }
20700
20701 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20702 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20703 cx.notify();
20704 }
20705
20706 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20707 self.show_code_actions = Some(show_code_actions);
20708 cx.notify();
20709 }
20710
20711 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20712 self.show_runnables = Some(show_runnables);
20713 cx.notify();
20714 }
20715
20716 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20717 self.show_breakpoints = Some(show_breakpoints);
20718 cx.notify();
20719 }
20720
20721 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20722 if self.display_map.read(cx).masked != masked {
20723 self.display_map.update(cx, |map, _| map.masked = masked);
20724 }
20725 cx.notify()
20726 }
20727
20728 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20729 self.show_wrap_guides = Some(show_wrap_guides);
20730 cx.notify();
20731 }
20732
20733 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20734 self.show_indent_guides = Some(show_indent_guides);
20735 cx.notify();
20736 }
20737
20738 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20739 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20740 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20741 && let Some(dir) = file.abs_path(cx).parent()
20742 {
20743 return Some(dir.to_owned());
20744 }
20745 }
20746
20747 None
20748 }
20749
20750 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20751 self.active_excerpt(cx)?
20752 .1
20753 .read(cx)
20754 .file()
20755 .and_then(|f| f.as_local())
20756 }
20757
20758 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20759 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20760 let buffer = buffer.read(cx);
20761 if let Some(project_path) = buffer.project_path(cx) {
20762 let project = self.project()?.read(cx);
20763 project.absolute_path(&project_path, cx)
20764 } else {
20765 buffer
20766 .file()
20767 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20768 }
20769 })
20770 }
20771
20772 pub fn reveal_in_finder(
20773 &mut self,
20774 _: &RevealInFileManager,
20775 _window: &mut Window,
20776 cx: &mut Context<Self>,
20777 ) {
20778 if let Some(target) = self.target_file(cx) {
20779 cx.reveal_path(&target.abs_path(cx));
20780 }
20781 }
20782
20783 pub fn copy_path(
20784 &mut self,
20785 _: &zed_actions::workspace::CopyPath,
20786 _window: &mut Window,
20787 cx: &mut Context<Self>,
20788 ) {
20789 if let Some(path) = self.target_file_abs_path(cx)
20790 && let Some(path) = path.to_str()
20791 {
20792 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20793 } else {
20794 cx.propagate();
20795 }
20796 }
20797
20798 pub fn copy_relative_path(
20799 &mut self,
20800 _: &zed_actions::workspace::CopyRelativePath,
20801 _window: &mut Window,
20802 cx: &mut Context<Self>,
20803 ) {
20804 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20805 let project = self.project()?.read(cx);
20806 let path = buffer.read(cx).file()?.path();
20807 let path = path.display(project.path_style(cx));
20808 Some(path)
20809 }) {
20810 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20811 } else {
20812 cx.propagate();
20813 }
20814 }
20815
20816 /// Returns the project path for the editor's buffer, if any buffer is
20817 /// opened in the editor.
20818 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20819 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20820 buffer.read(cx).project_path(cx)
20821 } else {
20822 None
20823 }
20824 }
20825
20826 // Returns true if the editor handled a go-to-line request
20827 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20828 maybe!({
20829 let breakpoint_store = self.breakpoint_store.as_ref()?;
20830
20831 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20832 else {
20833 self.clear_row_highlights::<ActiveDebugLine>();
20834 return None;
20835 };
20836
20837 let position = active_stack_frame.position;
20838 let buffer_id = position.buffer_id?;
20839 let snapshot = self
20840 .project
20841 .as_ref()?
20842 .read(cx)
20843 .buffer_for_id(buffer_id, cx)?
20844 .read(cx)
20845 .snapshot();
20846
20847 let mut handled = false;
20848 for (id, ExcerptRange { context, .. }) in
20849 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20850 {
20851 if context.start.cmp(&position, &snapshot).is_ge()
20852 || context.end.cmp(&position, &snapshot).is_lt()
20853 {
20854 continue;
20855 }
20856 let snapshot = self.buffer.read(cx).snapshot(cx);
20857 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20858
20859 handled = true;
20860 self.clear_row_highlights::<ActiveDebugLine>();
20861
20862 self.go_to_line::<ActiveDebugLine>(
20863 multibuffer_anchor,
20864 Some(cx.theme().colors().editor_debugger_active_line_background),
20865 window,
20866 cx,
20867 );
20868
20869 cx.notify();
20870 }
20871
20872 handled.then_some(())
20873 })
20874 .is_some()
20875 }
20876
20877 pub fn copy_file_name_without_extension(
20878 &mut self,
20879 _: &CopyFileNameWithoutExtension,
20880 _: &mut Window,
20881 cx: &mut Context<Self>,
20882 ) {
20883 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20884 let file = buffer.read(cx).file()?;
20885 file.path().file_stem()
20886 }) {
20887 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20888 }
20889 }
20890
20891 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20892 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20893 let file = buffer.read(cx).file()?;
20894 Some(file.file_name(cx))
20895 }) {
20896 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20897 }
20898 }
20899
20900 pub fn toggle_git_blame(
20901 &mut self,
20902 _: &::git::Blame,
20903 window: &mut Window,
20904 cx: &mut Context<Self>,
20905 ) {
20906 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20907
20908 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20909 self.start_git_blame(true, window, cx);
20910 }
20911
20912 cx.notify();
20913 }
20914
20915 pub fn toggle_git_blame_inline(
20916 &mut self,
20917 _: &ToggleGitBlameInline,
20918 window: &mut Window,
20919 cx: &mut Context<Self>,
20920 ) {
20921 self.toggle_git_blame_inline_internal(true, window, cx);
20922 cx.notify();
20923 }
20924
20925 pub fn open_git_blame_commit(
20926 &mut self,
20927 _: &OpenGitBlameCommit,
20928 window: &mut Window,
20929 cx: &mut Context<Self>,
20930 ) {
20931 self.open_git_blame_commit_internal(window, cx);
20932 }
20933
20934 fn open_git_blame_commit_internal(
20935 &mut self,
20936 window: &mut Window,
20937 cx: &mut Context<Self>,
20938 ) -> Option<()> {
20939 let blame = self.blame.as_ref()?;
20940 let snapshot = self.snapshot(window, cx);
20941 let cursor = self
20942 .selections
20943 .newest::<Point>(&snapshot.display_snapshot)
20944 .head();
20945 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20946 let (_, blame_entry) = blame
20947 .update(cx, |blame, cx| {
20948 blame
20949 .blame_for_rows(
20950 &[RowInfo {
20951 buffer_id: Some(buffer.remote_id()),
20952 buffer_row: Some(point.row),
20953 ..Default::default()
20954 }],
20955 cx,
20956 )
20957 .next()
20958 })
20959 .flatten()?;
20960 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20961 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20962 let workspace = self.workspace()?.downgrade();
20963 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20964 None
20965 }
20966
20967 pub fn git_blame_inline_enabled(&self) -> bool {
20968 self.git_blame_inline_enabled
20969 }
20970
20971 pub fn toggle_selection_menu(
20972 &mut self,
20973 _: &ToggleSelectionMenu,
20974 _: &mut Window,
20975 cx: &mut Context<Self>,
20976 ) {
20977 self.show_selection_menu = self
20978 .show_selection_menu
20979 .map(|show_selections_menu| !show_selections_menu)
20980 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20981
20982 cx.notify();
20983 }
20984
20985 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20986 self.show_selection_menu
20987 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20988 }
20989
20990 fn start_git_blame(
20991 &mut self,
20992 user_triggered: bool,
20993 window: &mut Window,
20994 cx: &mut Context<Self>,
20995 ) {
20996 if let Some(project) = self.project() {
20997 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20998 && buffer.read(cx).file().is_none()
20999 {
21000 return;
21001 }
21002
21003 let focused = self.focus_handle(cx).contains_focused(window, cx);
21004
21005 let project = project.clone();
21006 let blame = cx
21007 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
21008 self.blame_subscription =
21009 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
21010 self.blame = Some(blame);
21011 }
21012 }
21013
21014 fn toggle_git_blame_inline_internal(
21015 &mut self,
21016 user_triggered: bool,
21017 window: &mut Window,
21018 cx: &mut Context<Self>,
21019 ) {
21020 if self.git_blame_inline_enabled {
21021 self.git_blame_inline_enabled = false;
21022 self.show_git_blame_inline = false;
21023 self.show_git_blame_inline_delay_task.take();
21024 } else {
21025 self.git_blame_inline_enabled = true;
21026 self.start_git_blame_inline(user_triggered, window, cx);
21027 }
21028
21029 cx.notify();
21030 }
21031
21032 fn start_git_blame_inline(
21033 &mut self,
21034 user_triggered: bool,
21035 window: &mut Window,
21036 cx: &mut Context<Self>,
21037 ) {
21038 self.start_git_blame(user_triggered, window, cx);
21039
21040 if ProjectSettings::get_global(cx)
21041 .git
21042 .inline_blame_delay()
21043 .is_some()
21044 {
21045 self.start_inline_blame_timer(window, cx);
21046 } else {
21047 self.show_git_blame_inline = true
21048 }
21049 }
21050
21051 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
21052 self.blame.as_ref()
21053 }
21054
21055 pub fn show_git_blame_gutter(&self) -> bool {
21056 self.show_git_blame_gutter
21057 }
21058
21059 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
21060 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
21061 }
21062
21063 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
21064 self.show_git_blame_inline
21065 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
21066 && !self.newest_selection_head_on_empty_line(cx)
21067 && self.has_blame_entries(cx)
21068 }
21069
21070 fn has_blame_entries(&self, cx: &App) -> bool {
21071 self.blame()
21072 .is_some_and(|blame| blame.read(cx).has_generated_entries())
21073 }
21074
21075 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
21076 let cursor_anchor = self.selections.newest_anchor().head();
21077
21078 let snapshot = self.buffer.read(cx).snapshot(cx);
21079 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
21080
21081 snapshot.line_len(buffer_row) == 0
21082 }
21083
21084 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
21085 let buffer_and_selection = maybe!({
21086 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
21087 let selection_range = selection.range();
21088
21089 let multi_buffer = self.buffer().read(cx);
21090 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21091 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
21092
21093 let (buffer, range, _) = if selection.reversed {
21094 buffer_ranges.first()
21095 } else {
21096 buffer_ranges.last()
21097 }?;
21098
21099 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
21100 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
21101
21102 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
21103 let selection = start_row_in_buffer..end_row_in_buffer;
21104
21105 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
21106 };
21107
21108 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
21109
21110 Some((
21111 multi_buffer.buffer(buffer.remote_id()).unwrap(),
21112 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, Bias::Left, buffer)
21113 ..buffer_diff_snapshot.row_to_base_text_row(
21114 end_row_in_buffer,
21115 Bias::Left,
21116 buffer,
21117 ),
21118 ))
21119 });
21120
21121 let Some((buffer, selection)) = buffer_and_selection else {
21122 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
21123 };
21124
21125 let Some(project) = self.project() else {
21126 return Task::ready(Err(anyhow!("editor does not have project")));
21127 };
21128
21129 project.update(cx, |project, cx| {
21130 project.get_permalink_to_line(&buffer, selection, cx)
21131 })
21132 }
21133
21134 pub fn copy_permalink_to_line(
21135 &mut self,
21136 _: &CopyPermalinkToLine,
21137 window: &mut Window,
21138 cx: &mut Context<Self>,
21139 ) {
21140 let permalink_task = self.get_permalink_to_line(cx);
21141 let workspace = self.workspace();
21142
21143 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21144 Ok(permalink) => {
21145 cx.update(|_, cx| {
21146 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21147 })
21148 .ok();
21149 }
21150 Err(err) => {
21151 let message = format!("Failed to copy permalink: {err}");
21152
21153 anyhow::Result::<()>::Err(err).log_err();
21154
21155 if let Some(workspace) = workspace {
21156 workspace
21157 .update_in(cx, |workspace, _, cx| {
21158 struct CopyPermalinkToLine;
21159
21160 workspace.show_toast(
21161 Toast::new(
21162 NotificationId::unique::<CopyPermalinkToLine>(),
21163 message,
21164 ),
21165 cx,
21166 )
21167 })
21168 .ok();
21169 }
21170 }
21171 })
21172 .detach();
21173 }
21174
21175 pub fn copy_file_location(
21176 &mut self,
21177 _: &CopyFileLocation,
21178 _: &mut Window,
21179 cx: &mut Context<Self>,
21180 ) {
21181 let selection = self
21182 .selections
21183 .newest::<Point>(&self.display_snapshot(cx))
21184 .start
21185 .row
21186 + 1;
21187 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21188 let project = self.project()?.read(cx);
21189 let file = buffer.read(cx).file()?;
21190 let path = file.path().display(project.path_style(cx));
21191
21192 Some(format!("{path}:{selection}"))
21193 }) {
21194 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21195 }
21196 }
21197
21198 pub fn open_permalink_to_line(
21199 &mut self,
21200 _: &OpenPermalinkToLine,
21201 window: &mut Window,
21202 cx: &mut Context<Self>,
21203 ) {
21204 let permalink_task = self.get_permalink_to_line(cx);
21205 let workspace = self.workspace();
21206
21207 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21208 Ok(permalink) => {
21209 cx.update(|_, cx| {
21210 cx.open_url(permalink.as_ref());
21211 })
21212 .ok();
21213 }
21214 Err(err) => {
21215 let message = format!("Failed to open permalink: {err}");
21216
21217 anyhow::Result::<()>::Err(err).log_err();
21218
21219 if let Some(workspace) = workspace {
21220 workspace
21221 .update(cx, |workspace, cx| {
21222 struct OpenPermalinkToLine;
21223
21224 workspace.show_toast(
21225 Toast::new(
21226 NotificationId::unique::<OpenPermalinkToLine>(),
21227 message,
21228 ),
21229 cx,
21230 )
21231 })
21232 .ok();
21233 }
21234 }
21235 })
21236 .detach();
21237 }
21238
21239 pub fn insert_uuid_v4(
21240 &mut self,
21241 _: &InsertUuidV4,
21242 window: &mut Window,
21243 cx: &mut Context<Self>,
21244 ) {
21245 self.insert_uuid(UuidVersion::V4, window, cx);
21246 }
21247
21248 pub fn insert_uuid_v7(
21249 &mut self,
21250 _: &InsertUuidV7,
21251 window: &mut Window,
21252 cx: &mut Context<Self>,
21253 ) {
21254 self.insert_uuid(UuidVersion::V7, window, cx);
21255 }
21256
21257 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21258 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21259 self.transact(window, cx, |this, window, cx| {
21260 let edits = this
21261 .selections
21262 .all::<Point>(&this.display_snapshot(cx))
21263 .into_iter()
21264 .map(|selection| {
21265 let uuid = match version {
21266 UuidVersion::V4 => uuid::Uuid::new_v4(),
21267 UuidVersion::V7 => uuid::Uuid::now_v7(),
21268 };
21269
21270 (selection.range(), uuid.to_string())
21271 });
21272 this.edit(edits, cx);
21273 this.refresh_edit_prediction(true, false, window, cx);
21274 });
21275 }
21276
21277 pub fn open_selections_in_multibuffer(
21278 &mut self,
21279 _: &OpenSelectionsInMultibuffer,
21280 window: &mut Window,
21281 cx: &mut Context<Self>,
21282 ) {
21283 let multibuffer = self.buffer.read(cx);
21284
21285 let Some(buffer) = multibuffer.as_singleton() else {
21286 return;
21287 };
21288
21289 let Some(workspace) = self.workspace() else {
21290 return;
21291 };
21292
21293 let title = multibuffer.title(cx).to_string();
21294
21295 let locations = self
21296 .selections
21297 .all_anchors(&self.display_snapshot(cx))
21298 .iter()
21299 .map(|selection| {
21300 (
21301 buffer.clone(),
21302 (selection.start.text_anchor..selection.end.text_anchor)
21303 .to_point(buffer.read(cx)),
21304 )
21305 })
21306 .into_group_map();
21307
21308 cx.spawn_in(window, async move |_, cx| {
21309 workspace.update_in(cx, |workspace, window, cx| {
21310 Self::open_locations_in_multibuffer(
21311 workspace,
21312 locations,
21313 format!("Selections for '{title}'"),
21314 false,
21315 false,
21316 MultibufferSelectionMode::All,
21317 window,
21318 cx,
21319 );
21320 })
21321 })
21322 .detach();
21323 }
21324
21325 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21326 /// last highlight added will be used.
21327 ///
21328 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21329 pub fn highlight_rows<T: 'static>(
21330 &mut self,
21331 range: Range<Anchor>,
21332 color: Hsla,
21333 options: RowHighlightOptions,
21334 cx: &mut Context<Self>,
21335 ) {
21336 let snapshot = self.buffer().read(cx).snapshot(cx);
21337 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21338 let ix = row_highlights.binary_search_by(|highlight| {
21339 Ordering::Equal
21340 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21341 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21342 });
21343
21344 if let Err(mut ix) = ix {
21345 let index = post_inc(&mut self.highlight_order);
21346
21347 // If this range intersects with the preceding highlight, then merge it with
21348 // the preceding highlight. Otherwise insert a new highlight.
21349 let mut merged = false;
21350 if ix > 0 {
21351 let prev_highlight = &mut row_highlights[ix - 1];
21352 if prev_highlight
21353 .range
21354 .end
21355 .cmp(&range.start, &snapshot)
21356 .is_ge()
21357 {
21358 ix -= 1;
21359 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21360 prev_highlight.range.end = range.end;
21361 }
21362 merged = true;
21363 prev_highlight.index = index;
21364 prev_highlight.color = color;
21365 prev_highlight.options = options;
21366 }
21367 }
21368
21369 if !merged {
21370 row_highlights.insert(
21371 ix,
21372 RowHighlight {
21373 range,
21374 index,
21375 color,
21376 options,
21377 type_id: TypeId::of::<T>(),
21378 },
21379 );
21380 }
21381
21382 // If any of the following highlights intersect with this one, merge them.
21383 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21384 let highlight = &row_highlights[ix];
21385 if next_highlight
21386 .range
21387 .start
21388 .cmp(&highlight.range.end, &snapshot)
21389 .is_le()
21390 {
21391 if next_highlight
21392 .range
21393 .end
21394 .cmp(&highlight.range.end, &snapshot)
21395 .is_gt()
21396 {
21397 row_highlights[ix].range.end = next_highlight.range.end;
21398 }
21399 row_highlights.remove(ix + 1);
21400 } else {
21401 break;
21402 }
21403 }
21404 }
21405 }
21406
21407 /// Remove any highlighted row ranges of the given type that intersect the
21408 /// given ranges.
21409 pub fn remove_highlighted_rows<T: 'static>(
21410 &mut self,
21411 ranges_to_remove: Vec<Range<Anchor>>,
21412 cx: &mut Context<Self>,
21413 ) {
21414 let snapshot = self.buffer().read(cx).snapshot(cx);
21415 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21416 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21417 row_highlights.retain(|highlight| {
21418 while let Some(range_to_remove) = ranges_to_remove.peek() {
21419 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21420 Ordering::Less | Ordering::Equal => {
21421 ranges_to_remove.next();
21422 }
21423 Ordering::Greater => {
21424 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21425 Ordering::Less | Ordering::Equal => {
21426 return false;
21427 }
21428 Ordering::Greater => break,
21429 }
21430 }
21431 }
21432 }
21433
21434 true
21435 })
21436 }
21437
21438 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21439 pub fn clear_row_highlights<T: 'static>(&mut self) {
21440 self.highlighted_rows.remove(&TypeId::of::<T>());
21441 }
21442
21443 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21444 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21445 self.highlighted_rows
21446 .get(&TypeId::of::<T>())
21447 .map_or(&[] as &[_], |vec| vec.as_slice())
21448 .iter()
21449 .map(|highlight| (highlight.range.clone(), highlight.color))
21450 }
21451
21452 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21453 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21454 /// Allows to ignore certain kinds of highlights.
21455 pub fn highlighted_display_rows(
21456 &self,
21457 window: &mut Window,
21458 cx: &mut App,
21459 ) -> BTreeMap<DisplayRow, LineHighlight> {
21460 let snapshot = self.snapshot(window, cx);
21461 let mut used_highlight_orders = HashMap::default();
21462 self.highlighted_rows
21463 .iter()
21464 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21465 .fold(
21466 BTreeMap::<DisplayRow, LineHighlight>::new(),
21467 |mut unique_rows, highlight| {
21468 let start = highlight.range.start.to_display_point(&snapshot);
21469 let end = highlight.range.end.to_display_point(&snapshot);
21470 let start_row = start.row().0;
21471 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21472 {
21473 end.row().0.saturating_sub(1)
21474 } else {
21475 end.row().0
21476 };
21477 for row in start_row..=end_row {
21478 let used_index =
21479 used_highlight_orders.entry(row).or_insert(highlight.index);
21480 if highlight.index >= *used_index {
21481 *used_index = highlight.index;
21482 unique_rows.insert(
21483 DisplayRow(row),
21484 LineHighlight {
21485 include_gutter: highlight.options.include_gutter,
21486 border: None,
21487 background: highlight.color.into(),
21488 type_id: Some(highlight.type_id),
21489 },
21490 );
21491 }
21492 }
21493 unique_rows
21494 },
21495 )
21496 }
21497
21498 pub fn highlighted_display_row_for_autoscroll(
21499 &self,
21500 snapshot: &DisplaySnapshot,
21501 ) -> Option<DisplayRow> {
21502 self.highlighted_rows
21503 .values()
21504 .flat_map(|highlighted_rows| highlighted_rows.iter())
21505 .filter_map(|highlight| {
21506 if highlight.options.autoscroll {
21507 Some(highlight.range.start.to_display_point(snapshot).row())
21508 } else {
21509 None
21510 }
21511 })
21512 .min()
21513 }
21514
21515 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21516 self.highlight_background::<SearchWithinRange>(
21517 ranges,
21518 |_, colors| colors.colors().editor_document_highlight_read_background,
21519 cx,
21520 )
21521 }
21522
21523 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21524 self.breadcrumb_header = Some(new_header);
21525 }
21526
21527 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21528 self.clear_background_highlights::<SearchWithinRange>(cx);
21529 }
21530
21531 pub fn highlight_background<T: 'static>(
21532 &mut self,
21533 ranges: &[Range<Anchor>],
21534 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21535 cx: &mut Context<Self>,
21536 ) {
21537 self.background_highlights.insert(
21538 HighlightKey::Type(TypeId::of::<T>()),
21539 (Arc::new(color_fetcher), Arc::from(ranges)),
21540 );
21541 self.scrollbar_marker_state.dirty = true;
21542 cx.notify();
21543 }
21544
21545 pub fn highlight_background_key<T: 'static>(
21546 &mut self,
21547 key: usize,
21548 ranges: &[Range<Anchor>],
21549 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21550 cx: &mut Context<Self>,
21551 ) {
21552 self.background_highlights.insert(
21553 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21554 (Arc::new(color_fetcher), Arc::from(ranges)),
21555 );
21556 self.scrollbar_marker_state.dirty = true;
21557 cx.notify();
21558 }
21559
21560 pub fn clear_background_highlights<T: 'static>(
21561 &mut self,
21562 cx: &mut Context<Self>,
21563 ) -> Option<BackgroundHighlight> {
21564 let text_highlights = self
21565 .background_highlights
21566 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21567 if !text_highlights.1.is_empty() {
21568 self.scrollbar_marker_state.dirty = true;
21569 cx.notify();
21570 }
21571 Some(text_highlights)
21572 }
21573
21574 pub fn highlight_gutter<T: 'static>(
21575 &mut self,
21576 ranges: impl Into<Vec<Range<Anchor>>>,
21577 color_fetcher: fn(&App) -> Hsla,
21578 cx: &mut Context<Self>,
21579 ) {
21580 self.gutter_highlights
21581 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21582 cx.notify();
21583 }
21584
21585 pub fn clear_gutter_highlights<T: 'static>(
21586 &mut self,
21587 cx: &mut Context<Self>,
21588 ) -> Option<GutterHighlight> {
21589 cx.notify();
21590 self.gutter_highlights.remove(&TypeId::of::<T>())
21591 }
21592
21593 pub fn insert_gutter_highlight<T: 'static>(
21594 &mut self,
21595 range: Range<Anchor>,
21596 color_fetcher: fn(&App) -> Hsla,
21597 cx: &mut Context<Self>,
21598 ) {
21599 let snapshot = self.buffer().read(cx).snapshot(cx);
21600 let mut highlights = self
21601 .gutter_highlights
21602 .remove(&TypeId::of::<T>())
21603 .map(|(_, highlights)| highlights)
21604 .unwrap_or_default();
21605 let ix = highlights.binary_search_by(|highlight| {
21606 Ordering::Equal
21607 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21608 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21609 });
21610 if let Err(ix) = ix {
21611 highlights.insert(ix, range);
21612 }
21613 self.gutter_highlights
21614 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21615 }
21616
21617 pub fn remove_gutter_highlights<T: 'static>(
21618 &mut self,
21619 ranges_to_remove: Vec<Range<Anchor>>,
21620 cx: &mut Context<Self>,
21621 ) {
21622 let snapshot = self.buffer().read(cx).snapshot(cx);
21623 let Some((color_fetcher, mut gutter_highlights)) =
21624 self.gutter_highlights.remove(&TypeId::of::<T>())
21625 else {
21626 return;
21627 };
21628 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21629 gutter_highlights.retain(|highlight| {
21630 while let Some(range_to_remove) = ranges_to_remove.peek() {
21631 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21632 Ordering::Less | Ordering::Equal => {
21633 ranges_to_remove.next();
21634 }
21635 Ordering::Greater => {
21636 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21637 Ordering::Less | Ordering::Equal => {
21638 return false;
21639 }
21640 Ordering::Greater => break,
21641 }
21642 }
21643 }
21644 }
21645
21646 true
21647 });
21648 self.gutter_highlights
21649 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21650 }
21651
21652 #[cfg(feature = "test-support")]
21653 pub fn all_text_highlights(
21654 &self,
21655 window: &mut Window,
21656 cx: &mut Context<Self>,
21657 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21658 let snapshot = self.snapshot(window, cx);
21659 self.display_map.update(cx, |display_map, _| {
21660 display_map
21661 .all_text_highlights()
21662 .map(|highlight| {
21663 let (style, ranges) = highlight.as_ref();
21664 (
21665 *style,
21666 ranges
21667 .iter()
21668 .map(|range| range.clone().to_display_points(&snapshot))
21669 .collect(),
21670 )
21671 })
21672 .collect()
21673 })
21674 }
21675
21676 #[cfg(feature = "test-support")]
21677 pub fn all_text_background_highlights(
21678 &self,
21679 window: &mut Window,
21680 cx: &mut Context<Self>,
21681 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21682 let snapshot = self.snapshot(window, cx);
21683 let buffer = &snapshot.buffer_snapshot();
21684 let start = buffer.anchor_before(MultiBufferOffset(0));
21685 let end = buffer.anchor_after(buffer.len());
21686 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21687 }
21688
21689 #[cfg(any(test, feature = "test-support"))]
21690 pub fn sorted_background_highlights_in_range(
21691 &self,
21692 search_range: Range<Anchor>,
21693 display_snapshot: &DisplaySnapshot,
21694 theme: &Theme,
21695 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21696 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21697 res.sort_by(|a, b| {
21698 a.0.start
21699 .cmp(&b.0.start)
21700 .then_with(|| a.0.end.cmp(&b.0.end))
21701 .then_with(|| a.1.cmp(&b.1))
21702 });
21703 res
21704 }
21705
21706 #[cfg(feature = "test-support")]
21707 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21708 let snapshot = self.buffer().read(cx).snapshot(cx);
21709
21710 let highlights = self
21711 .background_highlights
21712 .get(&HighlightKey::Type(TypeId::of::<
21713 items::BufferSearchHighlights,
21714 >()));
21715
21716 if let Some((_color, ranges)) = highlights {
21717 ranges
21718 .iter()
21719 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21720 .collect_vec()
21721 } else {
21722 vec![]
21723 }
21724 }
21725
21726 fn document_highlights_for_position<'a>(
21727 &'a self,
21728 position: Anchor,
21729 buffer: &'a MultiBufferSnapshot,
21730 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21731 let read_highlights = self
21732 .background_highlights
21733 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21734 .map(|h| &h.1);
21735 let write_highlights = self
21736 .background_highlights
21737 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21738 .map(|h| &h.1);
21739 let left_position = position.bias_left(buffer);
21740 let right_position = position.bias_right(buffer);
21741 read_highlights
21742 .into_iter()
21743 .chain(write_highlights)
21744 .flat_map(move |ranges| {
21745 let start_ix = match ranges.binary_search_by(|probe| {
21746 let cmp = probe.end.cmp(&left_position, buffer);
21747 if cmp.is_ge() {
21748 Ordering::Greater
21749 } else {
21750 Ordering::Less
21751 }
21752 }) {
21753 Ok(i) | Err(i) => i,
21754 };
21755
21756 ranges[start_ix..]
21757 .iter()
21758 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21759 })
21760 }
21761
21762 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21763 self.background_highlights
21764 .get(&HighlightKey::Type(TypeId::of::<T>()))
21765 .is_some_and(|(_, highlights)| !highlights.is_empty())
21766 }
21767
21768 /// Returns all background highlights for a given range.
21769 ///
21770 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21771 pub fn background_highlights_in_range(
21772 &self,
21773 search_range: Range<Anchor>,
21774 display_snapshot: &DisplaySnapshot,
21775 theme: &Theme,
21776 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21777 let mut results = Vec::new();
21778 for (color_fetcher, ranges) in self.background_highlights.values() {
21779 let start_ix = match ranges.binary_search_by(|probe| {
21780 let cmp = probe
21781 .end
21782 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21783 if cmp.is_gt() {
21784 Ordering::Greater
21785 } else {
21786 Ordering::Less
21787 }
21788 }) {
21789 Ok(i) | Err(i) => i,
21790 };
21791 for (index, range) in ranges[start_ix..].iter().enumerate() {
21792 if range
21793 .start
21794 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21795 .is_ge()
21796 {
21797 break;
21798 }
21799
21800 let color = color_fetcher(&(start_ix + index), theme);
21801 let start = range.start.to_display_point(display_snapshot);
21802 let end = range.end.to_display_point(display_snapshot);
21803 results.push((start..end, color))
21804 }
21805 }
21806 results
21807 }
21808
21809 pub fn gutter_highlights_in_range(
21810 &self,
21811 search_range: Range<Anchor>,
21812 display_snapshot: &DisplaySnapshot,
21813 cx: &App,
21814 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21815 let mut results = Vec::new();
21816 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21817 let color = color_fetcher(cx);
21818 let start_ix = match ranges.binary_search_by(|probe| {
21819 let cmp = probe
21820 .end
21821 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21822 if cmp.is_gt() {
21823 Ordering::Greater
21824 } else {
21825 Ordering::Less
21826 }
21827 }) {
21828 Ok(i) | Err(i) => i,
21829 };
21830 for range in &ranges[start_ix..] {
21831 if range
21832 .start
21833 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21834 .is_ge()
21835 {
21836 break;
21837 }
21838
21839 let start = range.start.to_display_point(display_snapshot);
21840 let end = range.end.to_display_point(display_snapshot);
21841 results.push((start..end, color))
21842 }
21843 }
21844 results
21845 }
21846
21847 /// Get the text ranges corresponding to the redaction query
21848 pub fn redacted_ranges(
21849 &self,
21850 search_range: Range<Anchor>,
21851 display_snapshot: &DisplaySnapshot,
21852 cx: &App,
21853 ) -> Vec<Range<DisplayPoint>> {
21854 display_snapshot
21855 .buffer_snapshot()
21856 .redacted_ranges(search_range, |file| {
21857 if let Some(file) = file {
21858 file.is_private()
21859 && EditorSettings::get(
21860 Some(SettingsLocation {
21861 worktree_id: file.worktree_id(cx),
21862 path: file.path().as_ref(),
21863 }),
21864 cx,
21865 )
21866 .redact_private_values
21867 } else {
21868 false
21869 }
21870 })
21871 .map(|range| {
21872 range.start.to_display_point(display_snapshot)
21873 ..range.end.to_display_point(display_snapshot)
21874 })
21875 .collect()
21876 }
21877
21878 pub fn highlight_text_key<T: 'static>(
21879 &mut self,
21880 key: usize,
21881 ranges: Vec<Range<Anchor>>,
21882 style: HighlightStyle,
21883 merge: bool,
21884 cx: &mut Context<Self>,
21885 ) {
21886 self.display_map.update(cx, |map, cx| {
21887 map.highlight_text(
21888 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21889 ranges,
21890 style,
21891 merge,
21892 cx,
21893 );
21894 });
21895 cx.notify();
21896 }
21897
21898 pub fn highlight_text<T: 'static>(
21899 &mut self,
21900 ranges: Vec<Range<Anchor>>,
21901 style: HighlightStyle,
21902 cx: &mut Context<Self>,
21903 ) {
21904 self.display_map.update(cx, |map, cx| {
21905 map.highlight_text(
21906 HighlightKey::Type(TypeId::of::<T>()),
21907 ranges,
21908 style,
21909 false,
21910 cx,
21911 )
21912 });
21913 cx.notify();
21914 }
21915
21916 pub fn text_highlights<'a, T: 'static>(
21917 &'a self,
21918 cx: &'a App,
21919 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21920 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21921 }
21922
21923 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21924 let cleared = self
21925 .display_map
21926 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21927 if cleared {
21928 cx.notify();
21929 }
21930 }
21931
21932 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21933 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21934 && self.focus_handle.is_focused(window)
21935 }
21936
21937 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21938 self.show_cursor_when_unfocused = is_enabled;
21939 cx.notify();
21940 }
21941
21942 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21943 cx.notify();
21944 }
21945
21946 fn on_debug_session_event(
21947 &mut self,
21948 _session: Entity<Session>,
21949 event: &SessionEvent,
21950 cx: &mut Context<Self>,
21951 ) {
21952 if let SessionEvent::InvalidateInlineValue = event {
21953 self.refresh_inline_values(cx);
21954 }
21955 }
21956
21957 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21958 let Some(project) = self.project.clone() else {
21959 return;
21960 };
21961
21962 if !self.inline_value_cache.enabled {
21963 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21964 self.splice_inlays(&inlays, Vec::new(), cx);
21965 return;
21966 }
21967
21968 let current_execution_position = self
21969 .highlighted_rows
21970 .get(&TypeId::of::<ActiveDebugLine>())
21971 .and_then(|lines| lines.last().map(|line| line.range.end));
21972
21973 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21974 let inline_values = editor
21975 .update(cx, |editor, cx| {
21976 let Some(current_execution_position) = current_execution_position else {
21977 return Some(Task::ready(Ok(Vec::new())));
21978 };
21979
21980 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21981 let snapshot = buffer.snapshot(cx);
21982
21983 let excerpt = snapshot.excerpt_containing(
21984 current_execution_position..current_execution_position,
21985 )?;
21986
21987 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21988 })?;
21989
21990 let range =
21991 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21992
21993 project.inline_values(buffer, range, cx)
21994 })
21995 .ok()
21996 .flatten()?
21997 .await
21998 .context("refreshing debugger inlays")
21999 .log_err()?;
22000
22001 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
22002
22003 for (buffer_id, inline_value) in inline_values
22004 .into_iter()
22005 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
22006 {
22007 buffer_inline_values
22008 .entry(buffer_id)
22009 .or_default()
22010 .push(inline_value);
22011 }
22012
22013 editor
22014 .update(cx, |editor, cx| {
22015 let snapshot = editor.buffer.read(cx).snapshot(cx);
22016 let mut new_inlays = Vec::default();
22017
22018 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
22019 let buffer_id = buffer_snapshot.remote_id();
22020 buffer_inline_values
22021 .get(&buffer_id)
22022 .into_iter()
22023 .flatten()
22024 .for_each(|hint| {
22025 let inlay = Inlay::debugger(
22026 post_inc(&mut editor.next_inlay_id),
22027 Anchor::in_buffer(excerpt_id, hint.position),
22028 hint.text(),
22029 );
22030 if !inlay.text().chars().contains(&'\n') {
22031 new_inlays.push(inlay);
22032 }
22033 });
22034 }
22035
22036 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
22037 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
22038
22039 editor.splice_inlays(&inlay_ids, new_inlays, cx);
22040 })
22041 .ok()?;
22042 Some(())
22043 });
22044 }
22045
22046 fn on_buffer_event(
22047 &mut self,
22048 multibuffer: &Entity<MultiBuffer>,
22049 event: &multi_buffer::Event,
22050 window: &mut Window,
22051 cx: &mut Context<Self>,
22052 ) {
22053 match event {
22054 multi_buffer::Event::Edited { edited_buffer } => {
22055 self.scrollbar_marker_state.dirty = true;
22056 self.active_indent_guides_state.dirty = true;
22057 self.refresh_active_diagnostics(cx);
22058 self.refresh_code_actions(window, cx);
22059 self.refresh_single_line_folds(window, cx);
22060 self.refresh_matching_bracket_highlights(window, cx);
22061 if self.has_active_edit_prediction() {
22062 self.update_visible_edit_prediction(window, cx);
22063 }
22064
22065 if let Some(buffer) = edited_buffer {
22066 if buffer.read(cx).file().is_none() {
22067 cx.emit(EditorEvent::TitleChanged);
22068 }
22069
22070 if self.project.is_some() {
22071 let buffer_id = buffer.read(cx).remote_id();
22072 self.register_buffer(buffer_id, cx);
22073 self.update_lsp_data(Some(buffer_id), window, cx);
22074 self.refresh_inlay_hints(
22075 InlayHintRefreshReason::BufferEdited(buffer_id),
22076 cx,
22077 );
22078 }
22079 }
22080
22081 cx.emit(EditorEvent::BufferEdited);
22082 cx.emit(SearchEvent::MatchesInvalidated);
22083
22084 let Some(project) = &self.project else { return };
22085 let (telemetry, is_via_ssh) = {
22086 let project = project.read(cx);
22087 let telemetry = project.client().telemetry().clone();
22088 let is_via_ssh = project.is_via_remote_server();
22089 (telemetry, is_via_ssh)
22090 };
22091 telemetry.log_edit_event("editor", is_via_ssh);
22092 }
22093 multi_buffer::Event::ExcerptsAdded {
22094 buffer,
22095 predecessor,
22096 excerpts,
22097 } => {
22098 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22099 let buffer_id = buffer.read(cx).remote_id();
22100 if self.buffer.read(cx).diff_for(buffer_id).is_none()
22101 && let Some(project) = &self.project
22102 {
22103 update_uncommitted_diff_for_buffer(
22104 cx.entity(),
22105 project,
22106 [buffer.clone()],
22107 self.buffer.clone(),
22108 cx,
22109 )
22110 .detach();
22111 }
22112 self.update_lsp_data(Some(buffer_id), window, cx);
22113 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22114 self.colorize_brackets(false, cx);
22115 cx.emit(EditorEvent::ExcerptsAdded {
22116 buffer: buffer.clone(),
22117 predecessor: *predecessor,
22118 excerpts: excerpts.clone(),
22119 });
22120 }
22121 multi_buffer::Event::ExcerptsRemoved {
22122 ids,
22123 removed_buffer_ids,
22124 } => {
22125 if let Some(inlay_hints) = &mut self.inlay_hints {
22126 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
22127 }
22128 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
22129 for buffer_id in removed_buffer_ids {
22130 self.registered_buffers.remove(buffer_id);
22131 }
22132 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22133 cx.emit(EditorEvent::ExcerptsRemoved {
22134 ids: ids.clone(),
22135 removed_buffer_ids: removed_buffer_ids.clone(),
22136 });
22137 }
22138 multi_buffer::Event::ExcerptsEdited {
22139 excerpt_ids,
22140 buffer_ids,
22141 } => {
22142 self.display_map.update(cx, |map, cx| {
22143 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22144 });
22145 cx.emit(EditorEvent::ExcerptsEdited {
22146 ids: excerpt_ids.clone(),
22147 });
22148 }
22149 multi_buffer::Event::ExcerptsExpanded { ids } => {
22150 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22151 self.refresh_document_highlights(cx);
22152 for id in ids {
22153 self.fetched_tree_sitter_chunks.remove(id);
22154 }
22155 self.colorize_brackets(false, cx);
22156 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22157 }
22158 multi_buffer::Event::Reparsed(buffer_id) => {
22159 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22160 self.refresh_selected_text_highlights(true, window, cx);
22161 self.colorize_brackets(true, cx);
22162 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22163
22164 cx.emit(EditorEvent::Reparsed(*buffer_id));
22165 }
22166 multi_buffer::Event::DiffHunksToggled => {
22167 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22168 }
22169 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22170 if !is_fresh_language {
22171 self.registered_buffers.remove(&buffer_id);
22172 }
22173 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22174 cx.emit(EditorEvent::Reparsed(*buffer_id));
22175 cx.notify();
22176 }
22177 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22178 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22179 multi_buffer::Event::FileHandleChanged
22180 | multi_buffer::Event::Reloaded
22181 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22182 multi_buffer::Event::DiagnosticsUpdated => {
22183 self.update_diagnostics_state(window, cx);
22184 }
22185 _ => {}
22186 };
22187 }
22188
22189 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22190 if !self.diagnostics_enabled() {
22191 return;
22192 }
22193 self.refresh_active_diagnostics(cx);
22194 self.refresh_inline_diagnostics(true, window, cx);
22195 self.scrollbar_marker_state.dirty = true;
22196 cx.notify();
22197 }
22198
22199 pub fn start_temporary_diff_override(&mut self) {
22200 self.load_diff_task.take();
22201 self.temporary_diff_override = true;
22202 }
22203
22204 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22205 self.temporary_diff_override = false;
22206 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22207 self.buffer.update(cx, |buffer, cx| {
22208 buffer.set_all_diff_hunks_collapsed(cx);
22209 });
22210
22211 if let Some(project) = self.project.clone() {
22212 self.load_diff_task = Some(
22213 update_uncommitted_diff_for_buffer(
22214 cx.entity(),
22215 &project,
22216 self.buffer.read(cx).all_buffers(),
22217 self.buffer.clone(),
22218 cx,
22219 )
22220 .shared(),
22221 );
22222 }
22223 }
22224
22225 fn on_display_map_changed(
22226 &mut self,
22227 _: Entity<DisplayMap>,
22228 _: &mut Window,
22229 cx: &mut Context<Self>,
22230 ) {
22231 cx.notify();
22232 }
22233
22234 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22235 if !self.mode.is_full() {
22236 return None;
22237 }
22238
22239 let theme_settings = theme::ThemeSettings::get_global(cx);
22240 let theme = cx.theme();
22241 let accent_colors = theme.accents().clone();
22242
22243 let accent_overrides = theme_settings
22244 .theme_overrides
22245 .get(theme.name.as_ref())
22246 .map(|theme_style| &theme_style.accents)
22247 .into_iter()
22248 .flatten()
22249 .chain(
22250 theme_settings
22251 .experimental_theme_overrides
22252 .as_ref()
22253 .map(|overrides| &overrides.accents)
22254 .into_iter()
22255 .flatten(),
22256 )
22257 .flat_map(|accent| accent.0.clone())
22258 .collect();
22259
22260 Some(AccentData {
22261 colors: accent_colors,
22262 overrides: accent_overrides,
22263 })
22264 }
22265
22266 fn fetch_applicable_language_settings(
22267 &self,
22268 cx: &App,
22269 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22270 if !self.mode.is_full() {
22271 return HashMap::default();
22272 }
22273
22274 self.buffer().read(cx).all_buffers().into_iter().fold(
22275 HashMap::default(),
22276 |mut acc, buffer| {
22277 let buffer = buffer.read(cx);
22278 let language = buffer.language().map(|language| language.name());
22279 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22280 let file = buffer.file();
22281 v.insert(language_settings(language, file, cx).into_owned());
22282 }
22283 acc
22284 },
22285 )
22286 }
22287
22288 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22289 let new_language_settings = self.fetch_applicable_language_settings(cx);
22290 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22291 self.applicable_language_settings = new_language_settings;
22292
22293 let new_accents = self.fetch_accent_data(cx);
22294 let accents_changed = new_accents != self.accent_data;
22295 self.accent_data = new_accents;
22296
22297 if self.diagnostics_enabled() {
22298 let new_severity = EditorSettings::get_global(cx)
22299 .diagnostics_max_severity
22300 .unwrap_or(DiagnosticSeverity::Hint);
22301 self.set_max_diagnostics_severity(new_severity, cx);
22302 }
22303 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22304 self.update_edit_prediction_settings(cx);
22305 self.refresh_edit_prediction(true, false, window, cx);
22306 self.refresh_inline_values(cx);
22307 self.refresh_inlay_hints(
22308 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22309 self.selections.newest_anchor().head(),
22310 &self.buffer.read(cx).snapshot(cx),
22311 cx,
22312 )),
22313 cx,
22314 );
22315
22316 let old_cursor_shape = self.cursor_shape;
22317 let old_show_breadcrumbs = self.show_breadcrumbs;
22318
22319 {
22320 let editor_settings = EditorSettings::get_global(cx);
22321 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22322 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22323 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22324 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22325 }
22326
22327 if old_cursor_shape != self.cursor_shape {
22328 cx.emit(EditorEvent::CursorShapeChanged);
22329 }
22330
22331 if old_show_breadcrumbs != self.show_breadcrumbs {
22332 cx.emit(EditorEvent::BreadcrumbsChanged);
22333 }
22334
22335 let project_settings = ProjectSettings::get_global(cx);
22336 self.buffer_serialization = self
22337 .should_serialize_buffer()
22338 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22339
22340 if self.mode.is_full() {
22341 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22342 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22343 if self.show_inline_diagnostics != show_inline_diagnostics {
22344 self.show_inline_diagnostics = show_inline_diagnostics;
22345 self.refresh_inline_diagnostics(false, window, cx);
22346 }
22347
22348 if self.git_blame_inline_enabled != inline_blame_enabled {
22349 self.toggle_git_blame_inline_internal(false, window, cx);
22350 }
22351
22352 let minimap_settings = EditorSettings::get_global(cx).minimap;
22353 if self.minimap_visibility != MinimapVisibility::Disabled {
22354 if self.minimap_visibility.settings_visibility()
22355 != minimap_settings.minimap_enabled()
22356 {
22357 self.set_minimap_visibility(
22358 MinimapVisibility::for_mode(self.mode(), cx),
22359 window,
22360 cx,
22361 );
22362 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22363 minimap_entity.update(cx, |minimap_editor, cx| {
22364 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22365 })
22366 }
22367 }
22368
22369 if language_settings_changed || accents_changed {
22370 self.colorize_brackets(true, cx);
22371 }
22372
22373 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22374 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22375 }) {
22376 if !inlay_splice.is_empty() {
22377 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22378 }
22379 self.refresh_colors_for_visible_range(None, window, cx);
22380 }
22381 }
22382
22383 cx.notify();
22384 }
22385
22386 pub fn set_searchable(&mut self, searchable: bool) {
22387 self.searchable = searchable;
22388 }
22389
22390 pub fn searchable(&self) -> bool {
22391 self.searchable
22392 }
22393
22394 pub fn open_excerpts_in_split(
22395 &mut self,
22396 _: &OpenExcerptsSplit,
22397 window: &mut Window,
22398 cx: &mut Context<Self>,
22399 ) {
22400 self.open_excerpts_common(None, true, window, cx)
22401 }
22402
22403 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22404 self.open_excerpts_common(None, false, window, cx)
22405 }
22406
22407 fn open_excerpts_common(
22408 &mut self,
22409 jump_data: Option<JumpData>,
22410 split: bool,
22411 window: &mut Window,
22412 cx: &mut Context<Self>,
22413 ) {
22414 let Some(workspace) = self.workspace() else {
22415 cx.propagate();
22416 return;
22417 };
22418
22419 if self.buffer.read(cx).is_singleton() {
22420 cx.propagate();
22421 return;
22422 }
22423
22424 let mut new_selections_by_buffer = HashMap::default();
22425 match &jump_data {
22426 Some(JumpData::MultiBufferPoint {
22427 excerpt_id,
22428 position,
22429 anchor,
22430 line_offset_from_top,
22431 }) => {
22432 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22433 if let Some(buffer) = multi_buffer_snapshot
22434 .buffer_id_for_excerpt(*excerpt_id)
22435 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22436 {
22437 let buffer_snapshot = buffer.read(cx).snapshot();
22438 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22439 language::ToPoint::to_point(anchor, &buffer_snapshot)
22440 } else {
22441 buffer_snapshot.clip_point(*position, Bias::Left)
22442 };
22443 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22444 new_selections_by_buffer.insert(
22445 buffer,
22446 (
22447 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22448 Some(*line_offset_from_top),
22449 ),
22450 );
22451 }
22452 }
22453 Some(JumpData::MultiBufferRow {
22454 row,
22455 line_offset_from_top,
22456 }) => {
22457 let point = MultiBufferPoint::new(row.0, 0);
22458 if let Some((buffer, buffer_point, _)) =
22459 self.buffer.read(cx).point_to_buffer_point(point, cx)
22460 {
22461 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22462 new_selections_by_buffer
22463 .entry(buffer)
22464 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22465 .0
22466 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22467 }
22468 }
22469 None => {
22470 let selections = self
22471 .selections
22472 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22473 let multi_buffer = self.buffer.read(cx);
22474 for selection in selections {
22475 for (snapshot, range, _, anchor) in multi_buffer
22476 .snapshot(cx)
22477 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22478 {
22479 if let Some(anchor) = anchor {
22480 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22481 else {
22482 continue;
22483 };
22484 let offset = text::ToOffset::to_offset(
22485 &anchor.text_anchor,
22486 &buffer_handle.read(cx).snapshot(),
22487 );
22488 let range = BufferOffset(offset)..BufferOffset(offset);
22489 new_selections_by_buffer
22490 .entry(buffer_handle)
22491 .or_insert((Vec::new(), None))
22492 .0
22493 .push(range)
22494 } else {
22495 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22496 else {
22497 continue;
22498 };
22499 new_selections_by_buffer
22500 .entry(buffer_handle)
22501 .or_insert((Vec::new(), None))
22502 .0
22503 .push(range)
22504 }
22505 }
22506 }
22507 }
22508 }
22509
22510 new_selections_by_buffer
22511 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22512
22513 if new_selections_by_buffer.is_empty() {
22514 return;
22515 }
22516
22517 // We defer the pane interaction because we ourselves are a workspace item
22518 // and activating a new item causes the pane to call a method on us reentrantly,
22519 // which panics if we're on the stack.
22520 window.defer(cx, move |window, cx| {
22521 workspace.update(cx, |workspace, cx| {
22522 let pane = if split {
22523 workspace.adjacent_pane(window, cx)
22524 } else {
22525 workspace.active_pane().clone()
22526 };
22527
22528 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22529 let buffer_read = buffer.read(cx);
22530 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22531 (true, project::File::from_dyn(Some(file)).is_some())
22532 } else {
22533 (false, false)
22534 };
22535
22536 // If project file is none workspace.open_project_item will fail to open the excerpt
22537 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22538 // so we check if there's a tab match in that case first
22539 let editor = (!has_file || !is_project_file)
22540 .then(|| {
22541 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22542 // so `workspace.open_project_item` will never find them, always opening a new editor.
22543 // Instead, we try to activate the existing editor in the pane first.
22544 let (editor, pane_item_index, pane_item_id) =
22545 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22546 let editor = item.downcast::<Editor>()?;
22547 let singleton_buffer =
22548 editor.read(cx).buffer().read(cx).as_singleton()?;
22549 if singleton_buffer == buffer {
22550 Some((editor, i, item.item_id()))
22551 } else {
22552 None
22553 }
22554 })?;
22555 pane.update(cx, |pane, cx| {
22556 pane.activate_item(pane_item_index, true, true, window, cx);
22557 if !PreviewTabsSettings::get_global(cx)
22558 .enable_preview_from_multibuffer
22559 {
22560 pane.unpreview_item_if_preview(pane_item_id);
22561 }
22562 });
22563 Some(editor)
22564 })
22565 .flatten()
22566 .unwrap_or_else(|| {
22567 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22568 .enable_keep_preview_on_code_navigation;
22569 let allow_new_preview =
22570 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22571 workspace.open_project_item::<Self>(
22572 pane.clone(),
22573 buffer,
22574 true,
22575 true,
22576 keep_old_preview,
22577 allow_new_preview,
22578 window,
22579 cx,
22580 )
22581 });
22582
22583 editor.update(cx, |editor, cx| {
22584 if has_file && !is_project_file {
22585 editor.set_read_only(true);
22586 }
22587 let autoscroll = match scroll_offset {
22588 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22589 None => Autoscroll::newest(),
22590 };
22591 let nav_history = editor.nav_history.take();
22592 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22593 let Some((&excerpt_id, _, buffer_snapshot)) =
22594 multibuffer_snapshot.as_singleton()
22595 else {
22596 return;
22597 };
22598 editor.change_selections(
22599 SelectionEffects::scroll(autoscroll),
22600 window,
22601 cx,
22602 |s| {
22603 s.select_ranges(ranges.into_iter().map(|range| {
22604 let range = buffer_snapshot.anchor_before(range.start)
22605 ..buffer_snapshot.anchor_after(range.end);
22606 multibuffer_snapshot
22607 .anchor_range_in_excerpt(excerpt_id, range)
22608 .unwrap()
22609 }));
22610 },
22611 );
22612 editor.nav_history = nav_history;
22613 });
22614 }
22615 })
22616 });
22617 }
22618
22619 // Allow opening excerpts for buffers that either belong to the current project
22620 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22621 // are also supported so tests and other in-memory views keep working.
22622 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22623 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22624 }
22625
22626 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22627 let snapshot = self.buffer.read(cx).read(cx);
22628 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22629 Some(
22630 ranges
22631 .iter()
22632 .map(move |range| {
22633 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22634 })
22635 .collect(),
22636 )
22637 }
22638
22639 fn selection_replacement_ranges(
22640 &self,
22641 range: Range<MultiBufferOffsetUtf16>,
22642 cx: &mut App,
22643 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22644 let selections = self
22645 .selections
22646 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22647 let newest_selection = selections
22648 .iter()
22649 .max_by_key(|selection| selection.id)
22650 .unwrap();
22651 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22652 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22653 let snapshot = self.buffer.read(cx).read(cx);
22654 selections
22655 .into_iter()
22656 .map(|mut selection| {
22657 selection.start.0.0 =
22658 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22659 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22660 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22661 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22662 })
22663 .collect()
22664 }
22665
22666 fn report_editor_event(
22667 &self,
22668 reported_event: ReportEditorEvent,
22669 file_extension: Option<String>,
22670 cx: &App,
22671 ) {
22672 if cfg!(any(test, feature = "test-support")) {
22673 return;
22674 }
22675
22676 let Some(project) = &self.project else { return };
22677
22678 // If None, we are in a file without an extension
22679 let file = self
22680 .buffer
22681 .read(cx)
22682 .as_singleton()
22683 .and_then(|b| b.read(cx).file());
22684 let file_extension = file_extension.or(file
22685 .as_ref()
22686 .and_then(|file| Path::new(file.file_name(cx)).extension())
22687 .and_then(|e| e.to_str())
22688 .map(|a| a.to_string()));
22689
22690 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22691 .map(|vim_mode| vim_mode.0)
22692 .unwrap_or(false);
22693
22694 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22695 let copilot_enabled = edit_predictions_provider
22696 == language::language_settings::EditPredictionProvider::Copilot;
22697 let copilot_enabled_for_language = self
22698 .buffer
22699 .read(cx)
22700 .language_settings(cx)
22701 .show_edit_predictions;
22702
22703 let project = project.read(cx);
22704 let event_type = reported_event.event_type();
22705
22706 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22707 telemetry::event!(
22708 event_type,
22709 type = if auto_saved {"autosave"} else {"manual"},
22710 file_extension,
22711 vim_mode,
22712 copilot_enabled,
22713 copilot_enabled_for_language,
22714 edit_predictions_provider,
22715 is_via_ssh = project.is_via_remote_server(),
22716 );
22717 } else {
22718 telemetry::event!(
22719 event_type,
22720 file_extension,
22721 vim_mode,
22722 copilot_enabled,
22723 copilot_enabled_for_language,
22724 edit_predictions_provider,
22725 is_via_ssh = project.is_via_remote_server(),
22726 );
22727 };
22728 }
22729
22730 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22731 /// with each line being an array of {text, highlight} objects.
22732 fn copy_highlight_json(
22733 &mut self,
22734 _: &CopyHighlightJson,
22735 window: &mut Window,
22736 cx: &mut Context<Self>,
22737 ) {
22738 #[derive(Serialize)]
22739 struct Chunk<'a> {
22740 text: String,
22741 highlight: Option<&'a str>,
22742 }
22743
22744 let snapshot = self.buffer.read(cx).snapshot(cx);
22745 let range = self
22746 .selected_text_range(false, window, cx)
22747 .and_then(|selection| {
22748 if selection.range.is_empty() {
22749 None
22750 } else {
22751 Some(
22752 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22753 selection.range.start,
22754 )))
22755 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22756 selection.range.end,
22757 ))),
22758 )
22759 }
22760 })
22761 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22762
22763 let chunks = snapshot.chunks(range, true);
22764 let mut lines = Vec::new();
22765 let mut line: VecDeque<Chunk> = VecDeque::new();
22766
22767 let Some(style) = self.style.as_ref() else {
22768 return;
22769 };
22770
22771 for chunk in chunks {
22772 let highlight = chunk
22773 .syntax_highlight_id
22774 .and_then(|id| id.name(&style.syntax));
22775 let mut chunk_lines = chunk.text.split('\n').peekable();
22776 while let Some(text) = chunk_lines.next() {
22777 let mut merged_with_last_token = false;
22778 if let Some(last_token) = line.back_mut()
22779 && last_token.highlight == highlight
22780 {
22781 last_token.text.push_str(text);
22782 merged_with_last_token = true;
22783 }
22784
22785 if !merged_with_last_token {
22786 line.push_back(Chunk {
22787 text: text.into(),
22788 highlight,
22789 });
22790 }
22791
22792 if chunk_lines.peek().is_some() {
22793 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22794 line.pop_front();
22795 }
22796 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22797 line.pop_back();
22798 }
22799
22800 lines.push(mem::take(&mut line));
22801 }
22802 }
22803 }
22804
22805 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22806 return;
22807 };
22808 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22809 }
22810
22811 pub fn open_context_menu(
22812 &mut self,
22813 _: &OpenContextMenu,
22814 window: &mut Window,
22815 cx: &mut Context<Self>,
22816 ) {
22817 self.request_autoscroll(Autoscroll::newest(), cx);
22818 let position = self
22819 .selections
22820 .newest_display(&self.display_snapshot(cx))
22821 .start;
22822 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22823 }
22824
22825 pub fn replay_insert_event(
22826 &mut self,
22827 text: &str,
22828 relative_utf16_range: Option<Range<isize>>,
22829 window: &mut Window,
22830 cx: &mut Context<Self>,
22831 ) {
22832 if !self.input_enabled {
22833 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22834 return;
22835 }
22836 if let Some(relative_utf16_range) = relative_utf16_range {
22837 let selections = self
22838 .selections
22839 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22840 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22841 let new_ranges = selections.into_iter().map(|range| {
22842 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22843 range
22844 .head()
22845 .0
22846 .0
22847 .saturating_add_signed(relative_utf16_range.start),
22848 ));
22849 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22850 range
22851 .head()
22852 .0
22853 .0
22854 .saturating_add_signed(relative_utf16_range.end),
22855 ));
22856 start..end
22857 });
22858 s.select_ranges(new_ranges);
22859 });
22860 }
22861
22862 self.handle_input(text, window, cx);
22863 }
22864
22865 pub fn is_focused(&self, window: &Window) -> bool {
22866 self.focus_handle.is_focused(window)
22867 }
22868
22869 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22870 cx.emit(EditorEvent::Focused);
22871
22872 if let Some(descendant) = self
22873 .last_focused_descendant
22874 .take()
22875 .and_then(|descendant| descendant.upgrade())
22876 {
22877 window.focus(&descendant, cx);
22878 } else {
22879 if let Some(blame) = self.blame.as_ref() {
22880 blame.update(cx, GitBlame::focus)
22881 }
22882
22883 self.blink_manager.update(cx, BlinkManager::enable);
22884 self.show_cursor_names(window, cx);
22885 self.buffer.update(cx, |buffer, cx| {
22886 buffer.finalize_last_transaction(cx);
22887 if self.leader_id.is_none() {
22888 buffer.set_active_selections(
22889 &self.selections.disjoint_anchors_arc(),
22890 self.selections.line_mode(),
22891 self.cursor_shape,
22892 cx,
22893 );
22894 }
22895 });
22896
22897 if let Some(position_map) = self.last_position_map.clone() {
22898 EditorElement::mouse_moved(
22899 self,
22900 &MouseMoveEvent {
22901 position: window.mouse_position(),
22902 pressed_button: None,
22903 modifiers: window.modifiers(),
22904 },
22905 &position_map,
22906 window,
22907 cx,
22908 );
22909 }
22910 }
22911 }
22912
22913 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22914 cx.emit(EditorEvent::FocusedIn)
22915 }
22916
22917 fn handle_focus_out(
22918 &mut self,
22919 event: FocusOutEvent,
22920 _window: &mut Window,
22921 cx: &mut Context<Self>,
22922 ) {
22923 if event.blurred != self.focus_handle {
22924 self.last_focused_descendant = Some(event.blurred);
22925 }
22926 self.selection_drag_state = SelectionDragState::None;
22927 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22928 }
22929
22930 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22931 self.blink_manager.update(cx, BlinkManager::disable);
22932 self.buffer
22933 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22934
22935 if let Some(blame) = self.blame.as_ref() {
22936 blame.update(cx, GitBlame::blur)
22937 }
22938 if !self.hover_state.focused(window, cx) {
22939 hide_hover(self, cx);
22940 }
22941 if !self
22942 .context_menu
22943 .borrow()
22944 .as_ref()
22945 .is_some_and(|context_menu| context_menu.focused(window, cx))
22946 {
22947 self.hide_context_menu(window, cx);
22948 }
22949 self.take_active_edit_prediction(cx);
22950 cx.emit(EditorEvent::Blurred);
22951 cx.notify();
22952 }
22953
22954 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22955 let mut pending: String = window
22956 .pending_input_keystrokes()
22957 .into_iter()
22958 .flatten()
22959 .filter_map(|keystroke| keystroke.key_char.clone())
22960 .collect();
22961
22962 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22963 pending = "".to_string();
22964 }
22965
22966 let existing_pending = self
22967 .text_highlights::<PendingInput>(cx)
22968 .map(|(_, ranges)| ranges.to_vec());
22969 if existing_pending.is_none() && pending.is_empty() {
22970 return;
22971 }
22972 let transaction =
22973 self.transact(window, cx, |this, window, cx| {
22974 let selections = this
22975 .selections
22976 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22977 let edits = selections
22978 .iter()
22979 .map(|selection| (selection.end..selection.end, pending.clone()));
22980 this.edit(edits, cx);
22981 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22982 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22983 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22984 }));
22985 });
22986 if let Some(existing_ranges) = existing_pending {
22987 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22988 this.edit(edits, cx);
22989 }
22990 });
22991
22992 let snapshot = self.snapshot(window, cx);
22993 let ranges = self
22994 .selections
22995 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22996 .into_iter()
22997 .map(|selection| {
22998 snapshot.buffer_snapshot().anchor_after(selection.end)
22999 ..snapshot
23000 .buffer_snapshot()
23001 .anchor_before(selection.end + pending.len())
23002 })
23003 .collect();
23004
23005 if pending.is_empty() {
23006 self.clear_highlights::<PendingInput>(cx);
23007 } else {
23008 self.highlight_text::<PendingInput>(
23009 ranges,
23010 HighlightStyle {
23011 underline: Some(UnderlineStyle {
23012 thickness: px(1.),
23013 color: None,
23014 wavy: false,
23015 }),
23016 ..Default::default()
23017 },
23018 cx,
23019 );
23020 }
23021
23022 self.ime_transaction = self.ime_transaction.or(transaction);
23023 if let Some(transaction) = self.ime_transaction {
23024 self.buffer.update(cx, |buffer, cx| {
23025 buffer.group_until_transaction(transaction, cx);
23026 });
23027 }
23028
23029 if self.text_highlights::<PendingInput>(cx).is_none() {
23030 self.ime_transaction.take();
23031 }
23032 }
23033
23034 pub fn register_action_renderer(
23035 &mut self,
23036 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
23037 ) -> Subscription {
23038 let id = self.next_editor_action_id.post_inc();
23039 self.editor_actions
23040 .borrow_mut()
23041 .insert(id, Box::new(listener));
23042
23043 let editor_actions = self.editor_actions.clone();
23044 Subscription::new(move || {
23045 editor_actions.borrow_mut().remove(&id);
23046 })
23047 }
23048
23049 pub fn register_action<A: Action>(
23050 &mut self,
23051 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
23052 ) -> Subscription {
23053 let id = self.next_editor_action_id.post_inc();
23054 let listener = Arc::new(listener);
23055 self.editor_actions.borrow_mut().insert(
23056 id,
23057 Box::new(move |_, window, _| {
23058 let listener = listener.clone();
23059 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
23060 let action = action.downcast_ref().unwrap();
23061 if phase == DispatchPhase::Bubble {
23062 listener(action, window, cx)
23063 }
23064 })
23065 }),
23066 );
23067
23068 let editor_actions = self.editor_actions.clone();
23069 Subscription::new(move || {
23070 editor_actions.borrow_mut().remove(&id);
23071 })
23072 }
23073
23074 pub fn file_header_size(&self) -> u32 {
23075 FILE_HEADER_HEIGHT
23076 }
23077
23078 pub fn restore(
23079 &mut self,
23080 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
23081 window: &mut Window,
23082 cx: &mut Context<Self>,
23083 ) {
23084 self.buffer().update(cx, |multi_buffer, cx| {
23085 for (buffer_id, changes) in revert_changes {
23086 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
23087 buffer.update(cx, |buffer, cx| {
23088 buffer.edit(
23089 changes
23090 .into_iter()
23091 .map(|(range, text)| (range, text.to_string())),
23092 None,
23093 cx,
23094 );
23095 });
23096 }
23097 }
23098 });
23099 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23100 selections.refresh()
23101 });
23102 }
23103
23104 pub fn to_pixel_point(
23105 &mut self,
23106 source: multi_buffer::Anchor,
23107 editor_snapshot: &EditorSnapshot,
23108 window: &mut Window,
23109 cx: &App,
23110 ) -> Option<gpui::Point<Pixels>> {
23111 let source_point = source.to_display_point(editor_snapshot);
23112 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23113 }
23114
23115 pub fn display_to_pixel_point(
23116 &mut self,
23117 source: DisplayPoint,
23118 editor_snapshot: &EditorSnapshot,
23119 window: &mut Window,
23120 cx: &App,
23121 ) -> Option<gpui::Point<Pixels>> {
23122 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23123 let text_layout_details = self.text_layout_details(window);
23124 let scroll_top = text_layout_details
23125 .scroll_anchor
23126 .scroll_position(editor_snapshot)
23127 .y;
23128
23129 if source.row().as_f64() < scroll_top.floor() {
23130 return None;
23131 }
23132 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23133 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23134 Some(gpui::Point::new(source_x, source_y))
23135 }
23136
23137 pub fn has_visible_completions_menu(&self) -> bool {
23138 !self.edit_prediction_preview_is_active()
23139 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23140 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23141 })
23142 }
23143
23144 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23145 if self.mode.is_minimap() {
23146 return;
23147 }
23148 self.addons
23149 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23150 }
23151
23152 pub fn unregister_addon<T: Addon>(&mut self) {
23153 self.addons.remove(&std::any::TypeId::of::<T>());
23154 }
23155
23156 pub fn addon<T: Addon>(&self) -> Option<&T> {
23157 let type_id = std::any::TypeId::of::<T>();
23158 self.addons
23159 .get(&type_id)
23160 .and_then(|item| item.to_any().downcast_ref::<T>())
23161 }
23162
23163 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23164 let type_id = std::any::TypeId::of::<T>();
23165 self.addons
23166 .get_mut(&type_id)
23167 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23168 }
23169
23170 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23171 let text_layout_details = self.text_layout_details(window);
23172 let style = &text_layout_details.editor_style;
23173 let font_id = window.text_system().resolve_font(&style.text.font());
23174 let font_size = style.text.font_size.to_pixels(window.rem_size());
23175 let line_height = style.text.line_height_in_pixels(window.rem_size());
23176 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23177 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23178
23179 CharacterDimensions {
23180 em_width,
23181 em_advance,
23182 line_height,
23183 }
23184 }
23185
23186 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23187 self.load_diff_task.clone()
23188 }
23189
23190 fn read_metadata_from_db(
23191 &mut self,
23192 item_id: u64,
23193 workspace_id: WorkspaceId,
23194 window: &mut Window,
23195 cx: &mut Context<Editor>,
23196 ) {
23197 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23198 && !self.mode.is_minimap()
23199 && WorkspaceSettings::get(None, cx).restore_on_startup
23200 != RestoreOnStartupBehavior::EmptyTab
23201 {
23202 let buffer_snapshot = OnceCell::new();
23203
23204 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23205 && !folds.is_empty()
23206 {
23207 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23208 self.fold_ranges(
23209 folds
23210 .into_iter()
23211 .map(|(start, end)| {
23212 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23213 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23214 })
23215 .collect(),
23216 false,
23217 window,
23218 cx,
23219 );
23220 }
23221
23222 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23223 && !selections.is_empty()
23224 {
23225 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23226 // skip adding the initial selection to selection history
23227 self.selection_history.mode = SelectionHistoryMode::Skipping;
23228 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23229 s.select_ranges(selections.into_iter().map(|(start, end)| {
23230 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23231 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23232 }));
23233 });
23234 self.selection_history.mode = SelectionHistoryMode::Normal;
23235 };
23236 }
23237
23238 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23239 }
23240
23241 fn update_lsp_data(
23242 &mut self,
23243 for_buffer: Option<BufferId>,
23244 window: &mut Window,
23245 cx: &mut Context<'_, Self>,
23246 ) {
23247 self.pull_diagnostics(for_buffer, window, cx);
23248 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23249 }
23250
23251 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23252 if self.ignore_lsp_data() {
23253 return;
23254 }
23255 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23256 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23257 }
23258 }
23259
23260 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23261 if self.ignore_lsp_data() {
23262 return;
23263 }
23264
23265 if !self.registered_buffers.contains_key(&buffer_id)
23266 && let Some(project) = self.project.as_ref()
23267 {
23268 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23269 project.update(cx, |project, cx| {
23270 self.registered_buffers.insert(
23271 buffer_id,
23272 project.register_buffer_with_language_servers(&buffer, cx),
23273 );
23274 });
23275 } else {
23276 self.registered_buffers.remove(&buffer_id);
23277 }
23278 }
23279 }
23280
23281 fn ignore_lsp_data(&self) -> bool {
23282 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23283 // skip any LSP updates for it.
23284 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23285 }
23286
23287 fn create_style(&self, cx: &App) -> EditorStyle {
23288 let settings = ThemeSettings::get_global(cx);
23289
23290 let mut text_style = match self.mode {
23291 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23292 color: cx.theme().colors().editor_foreground,
23293 font_family: settings.ui_font.family.clone(),
23294 font_features: settings.ui_font.features.clone(),
23295 font_fallbacks: settings.ui_font.fallbacks.clone(),
23296 font_size: rems(0.875).into(),
23297 font_weight: settings.ui_font.weight,
23298 line_height: relative(settings.buffer_line_height.value()),
23299 ..Default::default()
23300 },
23301 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23302 color: cx.theme().colors().editor_foreground,
23303 font_family: settings.buffer_font.family.clone(),
23304 font_features: settings.buffer_font.features.clone(),
23305 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23306 font_size: settings.buffer_font_size(cx).into(),
23307 font_weight: settings.buffer_font.weight,
23308 line_height: relative(settings.buffer_line_height.value()),
23309 ..Default::default()
23310 },
23311 };
23312 if let Some(text_style_refinement) = &self.text_style_refinement {
23313 text_style.refine(text_style_refinement)
23314 }
23315
23316 let background = match self.mode {
23317 EditorMode::SingleLine => cx.theme().system().transparent,
23318 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23319 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23320 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23321 };
23322
23323 EditorStyle {
23324 background,
23325 border: cx.theme().colors().border,
23326 local_player: cx.theme().players().local(),
23327 text: text_style,
23328 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23329 syntax: cx.theme().syntax().clone(),
23330 status: cx.theme().status().clone(),
23331 inlay_hints_style: make_inlay_hints_style(cx),
23332 edit_prediction_styles: make_suggestion_styles(cx),
23333 unnecessary_code_fade: settings.unnecessary_code_fade,
23334 show_underlines: self.diagnostics_enabled(),
23335 }
23336 }
23337 fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
23338 let cursor = self.selections.newest_anchor().head();
23339 let multibuffer = self.buffer().read(cx);
23340 let is_singleton = multibuffer.is_singleton();
23341 let (buffer_id, symbols) = multibuffer
23342 .read(cx)
23343 .symbols_containing(cursor, Some(variant.syntax()))?;
23344 let buffer = multibuffer.buffer(buffer_id)?;
23345
23346 let buffer = buffer.read(cx);
23347 let settings = ThemeSettings::get_global(cx);
23348 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
23349 let mut breadcrumbs = if is_singleton {
23350 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
23351 buffer
23352 .snapshot()
23353 .resolve_file_path(
23354 self.project
23355 .as_ref()
23356 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
23357 .unwrap_or_default(),
23358 cx,
23359 )
23360 .unwrap_or_else(|| {
23361 if multibuffer.is_singleton() {
23362 multibuffer.title(cx).to_string()
23363 } else {
23364 "untitled".to_string()
23365 }
23366 })
23367 });
23368 vec![BreadcrumbText {
23369 text,
23370 highlights: None,
23371 font: Some(settings.buffer_font.clone()),
23372 }]
23373 } else {
23374 vec![]
23375 };
23376
23377 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
23378 text: symbol.text,
23379 highlights: Some(symbol.highlight_ranges),
23380 font: Some(settings.buffer_font.clone()),
23381 }));
23382 Some(breadcrumbs)
23383 }
23384}
23385
23386fn edit_for_markdown_paste<'a>(
23387 buffer: &MultiBufferSnapshot,
23388 range: Range<MultiBufferOffset>,
23389 to_insert: &'a str,
23390 url: Option<url::Url>,
23391) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23392 if url.is_none() {
23393 return (range, Cow::Borrowed(to_insert));
23394 };
23395
23396 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23397
23398 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23399 Cow::Borrowed(to_insert)
23400 } else {
23401 Cow::Owned(format!("[{old_text}]({to_insert})"))
23402 };
23403 (range, new_text)
23404}
23405
23406fn process_completion_for_edit(
23407 completion: &Completion,
23408 intent: CompletionIntent,
23409 buffer: &Entity<Buffer>,
23410 cursor_position: &text::Anchor,
23411 cx: &mut Context<Editor>,
23412) -> CompletionEdit {
23413 let buffer = buffer.read(cx);
23414 let buffer_snapshot = buffer.snapshot();
23415 let (snippet, new_text) = if completion.is_snippet() {
23416 let mut snippet_source = completion.new_text.clone();
23417 // Workaround for typescript language server issues so that methods don't expand within
23418 // strings and functions with type expressions. The previous point is used because the query
23419 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23420 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23421 let previous_point = if previous_point.column > 0 {
23422 cursor_position.to_previous_offset(&buffer_snapshot)
23423 } else {
23424 cursor_position.to_offset(&buffer_snapshot)
23425 };
23426 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23427 && scope.prefers_label_for_snippet_in_completion()
23428 && let Some(label) = completion.label()
23429 && matches!(
23430 completion.kind(),
23431 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23432 )
23433 {
23434 snippet_source = label;
23435 }
23436 match Snippet::parse(&snippet_source).log_err() {
23437 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23438 None => (None, completion.new_text.clone()),
23439 }
23440 } else {
23441 (None, completion.new_text.clone())
23442 };
23443
23444 let mut range_to_replace = {
23445 let replace_range = &completion.replace_range;
23446 if let CompletionSource::Lsp {
23447 insert_range: Some(insert_range),
23448 ..
23449 } = &completion.source
23450 {
23451 debug_assert_eq!(
23452 insert_range.start, replace_range.start,
23453 "insert_range and replace_range should start at the same position"
23454 );
23455 debug_assert!(
23456 insert_range
23457 .start
23458 .cmp(cursor_position, &buffer_snapshot)
23459 .is_le(),
23460 "insert_range should start before or at cursor position"
23461 );
23462 debug_assert!(
23463 replace_range
23464 .start
23465 .cmp(cursor_position, &buffer_snapshot)
23466 .is_le(),
23467 "replace_range should start before or at cursor position"
23468 );
23469
23470 let should_replace = match intent {
23471 CompletionIntent::CompleteWithInsert => false,
23472 CompletionIntent::CompleteWithReplace => true,
23473 CompletionIntent::Complete | CompletionIntent::Compose => {
23474 let insert_mode =
23475 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23476 .completions
23477 .lsp_insert_mode;
23478 match insert_mode {
23479 LspInsertMode::Insert => false,
23480 LspInsertMode::Replace => true,
23481 LspInsertMode::ReplaceSubsequence => {
23482 let mut text_to_replace = buffer.chars_for_range(
23483 buffer.anchor_before(replace_range.start)
23484 ..buffer.anchor_after(replace_range.end),
23485 );
23486 let mut current_needle = text_to_replace.next();
23487 for haystack_ch in completion.label.text.chars() {
23488 if let Some(needle_ch) = current_needle
23489 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23490 {
23491 current_needle = text_to_replace.next();
23492 }
23493 }
23494 current_needle.is_none()
23495 }
23496 LspInsertMode::ReplaceSuffix => {
23497 if replace_range
23498 .end
23499 .cmp(cursor_position, &buffer_snapshot)
23500 .is_gt()
23501 {
23502 let range_after_cursor = *cursor_position..replace_range.end;
23503 let text_after_cursor = buffer
23504 .text_for_range(
23505 buffer.anchor_before(range_after_cursor.start)
23506 ..buffer.anchor_after(range_after_cursor.end),
23507 )
23508 .collect::<String>()
23509 .to_ascii_lowercase();
23510 completion
23511 .label
23512 .text
23513 .to_ascii_lowercase()
23514 .ends_with(&text_after_cursor)
23515 } else {
23516 true
23517 }
23518 }
23519 }
23520 }
23521 };
23522
23523 if should_replace {
23524 replace_range.clone()
23525 } else {
23526 insert_range.clone()
23527 }
23528 } else {
23529 replace_range.clone()
23530 }
23531 };
23532
23533 if range_to_replace
23534 .end
23535 .cmp(cursor_position, &buffer_snapshot)
23536 .is_lt()
23537 {
23538 range_to_replace.end = *cursor_position;
23539 }
23540
23541 let replace_range = range_to_replace.to_offset(buffer);
23542 CompletionEdit {
23543 new_text,
23544 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23545 snippet,
23546 }
23547}
23548
23549struct CompletionEdit {
23550 new_text: String,
23551 replace_range: Range<BufferOffset>,
23552 snippet: Option<Snippet>,
23553}
23554
23555fn comment_delimiter_for_newline(
23556 start_point: &Point,
23557 buffer: &MultiBufferSnapshot,
23558 language: &LanguageScope,
23559) -> Option<Arc<str>> {
23560 let delimiters = language.line_comment_prefixes();
23561 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
23562 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23563
23564 let num_of_whitespaces = snapshot
23565 .chars_for_range(range.clone())
23566 .take_while(|c| c.is_whitespace())
23567 .count();
23568 let comment_candidate = snapshot
23569 .chars_for_range(range.clone())
23570 .skip(num_of_whitespaces)
23571 .take(max_len_of_delimiter)
23572 .collect::<String>();
23573 let (delimiter, trimmed_len) = delimiters
23574 .iter()
23575 .filter_map(|delimiter| {
23576 let prefix = delimiter.trim_end();
23577 if comment_candidate.starts_with(prefix) {
23578 Some((delimiter, prefix.len()))
23579 } else {
23580 None
23581 }
23582 })
23583 .max_by_key(|(_, len)| *len)?;
23584
23585 if let Some(BlockCommentConfig {
23586 start: block_start, ..
23587 }) = language.block_comment()
23588 {
23589 let block_start_trimmed = block_start.trim_end();
23590 if block_start_trimmed.starts_with(delimiter.trim_end()) {
23591 let line_content = snapshot
23592 .chars_for_range(range)
23593 .skip(num_of_whitespaces)
23594 .take(block_start_trimmed.len())
23595 .collect::<String>();
23596
23597 if line_content.starts_with(block_start_trimmed) {
23598 return None;
23599 }
23600 }
23601 }
23602
23603 let cursor_is_placed_after_comment_marker =
23604 num_of_whitespaces + trimmed_len <= start_point.column as usize;
23605 if cursor_is_placed_after_comment_marker {
23606 Some(delimiter.clone())
23607 } else {
23608 None
23609 }
23610}
23611
23612fn documentation_delimiter_for_newline(
23613 start_point: &Point,
23614 buffer: &MultiBufferSnapshot,
23615 language: &LanguageScope,
23616 newline_config: &mut NewlineConfig,
23617) -> Option<Arc<str>> {
23618 let BlockCommentConfig {
23619 start: start_tag,
23620 end: end_tag,
23621 prefix: delimiter,
23622 tab_size: len,
23623 } = language.documentation_comment()?;
23624 let is_within_block_comment = buffer
23625 .language_scope_at(*start_point)
23626 .is_some_and(|scope| scope.override_name() == Some("comment"));
23627 if !is_within_block_comment {
23628 return None;
23629 }
23630
23631 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23632
23633 let num_of_whitespaces = snapshot
23634 .chars_for_range(range.clone())
23635 .take_while(|c| c.is_whitespace())
23636 .count();
23637
23638 // 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.
23639 let column = start_point.column;
23640 let cursor_is_after_start_tag = {
23641 let start_tag_len = start_tag.len();
23642 let start_tag_line = snapshot
23643 .chars_for_range(range.clone())
23644 .skip(num_of_whitespaces)
23645 .take(start_tag_len)
23646 .collect::<String>();
23647 if start_tag_line.starts_with(start_tag.as_ref()) {
23648 num_of_whitespaces + start_tag_len <= column as usize
23649 } else {
23650 false
23651 }
23652 };
23653
23654 let cursor_is_after_delimiter = {
23655 let delimiter_trim = delimiter.trim_end();
23656 let delimiter_line = snapshot
23657 .chars_for_range(range.clone())
23658 .skip(num_of_whitespaces)
23659 .take(delimiter_trim.len())
23660 .collect::<String>();
23661 if delimiter_line.starts_with(delimiter_trim) {
23662 num_of_whitespaces + delimiter_trim.len() <= column as usize
23663 } else {
23664 false
23665 }
23666 };
23667
23668 let mut needs_extra_line = false;
23669 let mut extra_line_additional_indent = IndentSize::spaces(0);
23670
23671 let cursor_is_before_end_tag_if_exists = {
23672 let mut char_position = 0u32;
23673 let mut end_tag_offset = None;
23674
23675 'outer: for chunk in snapshot.text_for_range(range) {
23676 if let Some(byte_pos) = chunk.find(&**end_tag) {
23677 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
23678 end_tag_offset = Some(char_position + chars_before_match);
23679 break 'outer;
23680 }
23681 char_position += chunk.chars().count() as u32;
23682 }
23683
23684 if let Some(end_tag_offset) = end_tag_offset {
23685 let cursor_is_before_end_tag = column <= end_tag_offset;
23686 if cursor_is_after_start_tag {
23687 if cursor_is_before_end_tag {
23688 needs_extra_line = true;
23689 }
23690 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
23691 if cursor_is_at_start_of_end_tag {
23692 extra_line_additional_indent.len = *len;
23693 }
23694 }
23695 cursor_is_before_end_tag
23696 } else {
23697 true
23698 }
23699 };
23700
23701 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
23702 && cursor_is_before_end_tag_if_exists
23703 {
23704 let additional_indent = if cursor_is_after_start_tag {
23705 IndentSize::spaces(*len)
23706 } else {
23707 IndentSize::spaces(0)
23708 };
23709
23710 *newline_config = NewlineConfig::Newline {
23711 additional_indent,
23712 extra_line_additional_indent: if needs_extra_line {
23713 Some(extra_line_additional_indent)
23714 } else {
23715 None
23716 },
23717 prevent_auto_indent: true,
23718 };
23719 Some(delimiter.clone())
23720 } else {
23721 None
23722 }
23723}
23724
23725const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
23726
23727fn list_delimiter_for_newline(
23728 start_point: &Point,
23729 buffer: &MultiBufferSnapshot,
23730 language: &LanguageScope,
23731 newline_config: &mut NewlineConfig,
23732) -> Option<Arc<str>> {
23733 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23734
23735 let num_of_whitespaces = snapshot
23736 .chars_for_range(range.clone())
23737 .take_while(|c| c.is_whitespace())
23738 .count();
23739
23740 let task_list_entries: Vec<_> = language
23741 .task_list()
23742 .into_iter()
23743 .flat_map(|config| {
23744 config
23745 .prefixes
23746 .iter()
23747 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
23748 })
23749 .collect();
23750 let unordered_list_entries: Vec<_> = language
23751 .unordered_list()
23752 .iter()
23753 .map(|marker| (marker.as_ref(), marker.as_ref()))
23754 .collect();
23755
23756 let all_entries: Vec<_> = task_list_entries
23757 .into_iter()
23758 .chain(unordered_list_entries)
23759 .collect();
23760
23761 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
23762 let candidate: String = snapshot
23763 .chars_for_range(range.clone())
23764 .skip(num_of_whitespaces)
23765 .take(max_prefix_len)
23766 .collect();
23767
23768 if let Some((prefix, continuation)) = all_entries
23769 .iter()
23770 .filter(|(prefix, _)| candidate.starts_with(*prefix))
23771 .max_by_key(|(prefix, _)| prefix.len())
23772 {
23773 let end_of_prefix = num_of_whitespaces + prefix.len();
23774 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
23775 let has_content_after_marker = snapshot
23776 .chars_for_range(range)
23777 .skip(end_of_prefix)
23778 .any(|c| !c.is_whitespace());
23779
23780 if has_content_after_marker && cursor_is_after_prefix {
23781 return Some((*continuation).into());
23782 }
23783
23784 if start_point.column as usize == end_of_prefix {
23785 if num_of_whitespaces == 0 {
23786 *newline_config = NewlineConfig::ClearCurrentLine;
23787 } else {
23788 *newline_config = NewlineConfig::UnindentCurrentLine {
23789 continuation: (*continuation).into(),
23790 };
23791 }
23792 }
23793
23794 return None;
23795 }
23796 }
23797
23798 let candidate: String = snapshot
23799 .chars_for_range(range.clone())
23800 .skip(num_of_whitespaces)
23801 .take(ORDERED_LIST_MAX_MARKER_LEN)
23802 .collect();
23803
23804 for ordered_config in language.ordered_list() {
23805 let regex = match Regex::new(&ordered_config.pattern) {
23806 Ok(r) => r,
23807 Err(_) => continue,
23808 };
23809
23810 if let Some(captures) = regex.captures(&candidate) {
23811 let full_match = captures.get(0)?;
23812 let marker_len = full_match.len();
23813 let end_of_prefix = num_of_whitespaces + marker_len;
23814 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
23815
23816 let has_content_after_marker = snapshot
23817 .chars_for_range(range)
23818 .skip(end_of_prefix)
23819 .any(|c| !c.is_whitespace());
23820
23821 if has_content_after_marker && cursor_is_after_prefix {
23822 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
23823 let continuation = ordered_config
23824 .format
23825 .replace("{1}", &(number + 1).to_string());
23826 return Some(continuation.into());
23827 }
23828
23829 if start_point.column as usize == end_of_prefix {
23830 let continuation = ordered_config.format.replace("{1}", "1");
23831 if num_of_whitespaces == 0 {
23832 *newline_config = NewlineConfig::ClearCurrentLine;
23833 } else {
23834 *newline_config = NewlineConfig::UnindentCurrentLine {
23835 continuation: continuation.into(),
23836 };
23837 }
23838 }
23839
23840 return None;
23841 }
23842 }
23843
23844 None
23845}
23846
23847fn is_list_prefix_row(
23848 row: MultiBufferRow,
23849 buffer: &MultiBufferSnapshot,
23850 language: &LanguageScope,
23851) -> bool {
23852 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
23853 return false;
23854 };
23855
23856 let num_of_whitespaces = snapshot
23857 .chars_for_range(range.clone())
23858 .take_while(|c| c.is_whitespace())
23859 .count();
23860
23861 let task_list_prefixes: Vec<_> = language
23862 .task_list()
23863 .into_iter()
23864 .flat_map(|config| {
23865 config
23866 .prefixes
23867 .iter()
23868 .map(|p| p.as_ref())
23869 .collect::<Vec<_>>()
23870 })
23871 .collect();
23872 let unordered_list_markers: Vec<_> = language
23873 .unordered_list()
23874 .iter()
23875 .map(|marker| marker.as_ref())
23876 .collect();
23877 let all_prefixes: Vec<_> = task_list_prefixes
23878 .into_iter()
23879 .chain(unordered_list_markers)
23880 .collect();
23881 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
23882 let candidate: String = snapshot
23883 .chars_for_range(range.clone())
23884 .skip(num_of_whitespaces)
23885 .take(max_prefix_len)
23886 .collect();
23887 if all_prefixes
23888 .iter()
23889 .any(|prefix| candidate.starts_with(*prefix))
23890 {
23891 return true;
23892 }
23893 }
23894
23895 let ordered_list_candidate: String = snapshot
23896 .chars_for_range(range)
23897 .skip(num_of_whitespaces)
23898 .take(ORDERED_LIST_MAX_MARKER_LEN)
23899 .collect();
23900 for ordered_config in language.ordered_list() {
23901 let regex = match Regex::new(&ordered_config.pattern) {
23902 Ok(r) => r,
23903 Err(_) => continue,
23904 };
23905 if let Some(captures) = regex.captures(&ordered_list_candidate) {
23906 return captures.get(0).is_some();
23907 }
23908 }
23909
23910 false
23911}
23912
23913#[derive(Debug)]
23914enum NewlineConfig {
23915 /// Insert newline with optional additional indent and optional extra blank line
23916 Newline {
23917 additional_indent: IndentSize,
23918 extra_line_additional_indent: Option<IndentSize>,
23919 prevent_auto_indent: bool,
23920 },
23921 /// Clear the current line
23922 ClearCurrentLine,
23923 /// Unindent the current line and add continuation
23924 UnindentCurrentLine { continuation: Arc<str> },
23925}
23926
23927impl NewlineConfig {
23928 fn has_extra_line(&self) -> bool {
23929 matches!(
23930 self,
23931 Self::Newline {
23932 extra_line_additional_indent: Some(_),
23933 ..
23934 }
23935 )
23936 }
23937
23938 fn insert_extra_newline_brackets(
23939 buffer: &MultiBufferSnapshot,
23940 range: Range<MultiBufferOffset>,
23941 language: &language::LanguageScope,
23942 ) -> bool {
23943 let leading_whitespace_len = buffer
23944 .reversed_chars_at(range.start)
23945 .take_while(|c| c.is_whitespace() && *c != '\n')
23946 .map(|c| c.len_utf8())
23947 .sum::<usize>();
23948 let trailing_whitespace_len = buffer
23949 .chars_at(range.end)
23950 .take_while(|c| c.is_whitespace() && *c != '\n')
23951 .map(|c| c.len_utf8())
23952 .sum::<usize>();
23953 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23954
23955 language.brackets().any(|(pair, enabled)| {
23956 let pair_start = pair.start.trim_end();
23957 let pair_end = pair.end.trim_start();
23958
23959 enabled
23960 && pair.newline
23961 && buffer.contains_str_at(range.end, pair_end)
23962 && buffer.contains_str_at(
23963 range.start.saturating_sub_usize(pair_start.len()),
23964 pair_start,
23965 )
23966 })
23967 }
23968
23969 fn insert_extra_newline_tree_sitter(
23970 buffer: &MultiBufferSnapshot,
23971 range: Range<MultiBufferOffset>,
23972 ) -> bool {
23973 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23974 [(buffer, range, _)] => (*buffer, range.clone()),
23975 _ => return false,
23976 };
23977 let pair = {
23978 let mut result: Option<BracketMatch<usize>> = None;
23979
23980 for pair in buffer
23981 .all_bracket_ranges(range.start.0..range.end.0)
23982 .filter(move |pair| {
23983 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23984 })
23985 {
23986 let len = pair.close_range.end - pair.open_range.start;
23987
23988 if let Some(existing) = &result {
23989 let existing_len = existing.close_range.end - existing.open_range.start;
23990 if len > existing_len {
23991 continue;
23992 }
23993 }
23994
23995 result = Some(pair);
23996 }
23997
23998 result
23999 };
24000 let Some(pair) = pair else {
24001 return false;
24002 };
24003 pair.newline_only
24004 && buffer
24005 .chars_for_range(pair.open_range.end..range.start.0)
24006 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
24007 .all(|c| c.is_whitespace() && c != '\n')
24008 }
24009}
24010
24011fn update_uncommitted_diff_for_buffer(
24012 editor: Entity<Editor>,
24013 project: &Entity<Project>,
24014 buffers: impl IntoIterator<Item = Entity<Buffer>>,
24015 buffer: Entity<MultiBuffer>,
24016 cx: &mut App,
24017) -> Task<()> {
24018 let mut tasks = Vec::new();
24019 project.update(cx, |project, cx| {
24020 for buffer in buffers {
24021 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
24022 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
24023 }
24024 }
24025 });
24026 cx.spawn(async move |cx| {
24027 let diffs = future::join_all(tasks).await;
24028 if editor
24029 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
24030 .unwrap_or(false)
24031 {
24032 return;
24033 }
24034
24035 buffer
24036 .update(cx, |buffer, cx| {
24037 for diff in diffs.into_iter().flatten() {
24038 buffer.add_diff(diff, cx);
24039 }
24040 })
24041 .ok();
24042 })
24043}
24044
24045fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
24046 let tab_size = tab_size.get() as usize;
24047 let mut width = offset;
24048
24049 for ch in text.chars() {
24050 width += if ch == '\t' {
24051 tab_size - (width % tab_size)
24052 } else {
24053 1
24054 };
24055 }
24056
24057 width - offset
24058}
24059
24060#[cfg(test)]
24061mod tests {
24062 use super::*;
24063
24064 #[test]
24065 fn test_string_size_with_expanded_tabs() {
24066 let nz = |val| NonZeroU32::new(val).unwrap();
24067 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
24068 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
24069 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
24070 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
24071 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
24072 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
24073 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
24074 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
24075 }
24076}
24077
24078/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
24079struct WordBreakingTokenizer<'a> {
24080 input: &'a str,
24081}
24082
24083impl<'a> WordBreakingTokenizer<'a> {
24084 fn new(input: &'a str) -> Self {
24085 Self { input }
24086 }
24087}
24088
24089fn is_char_ideographic(ch: char) -> bool {
24090 use unicode_script::Script::*;
24091 use unicode_script::UnicodeScript;
24092 matches!(ch.script(), Han | Tangut | Yi)
24093}
24094
24095fn is_grapheme_ideographic(text: &str) -> bool {
24096 text.chars().any(is_char_ideographic)
24097}
24098
24099fn is_grapheme_whitespace(text: &str) -> bool {
24100 text.chars().any(|x| x.is_whitespace())
24101}
24102
24103fn should_stay_with_preceding_ideograph(text: &str) -> bool {
24104 text.chars()
24105 .next()
24106 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
24107}
24108
24109#[derive(PartialEq, Eq, Debug, Clone, Copy)]
24110enum WordBreakToken<'a> {
24111 Word { token: &'a str, grapheme_len: usize },
24112 InlineWhitespace { token: &'a str, grapheme_len: usize },
24113 Newline,
24114}
24115
24116impl<'a> Iterator for WordBreakingTokenizer<'a> {
24117 /// Yields a span, the count of graphemes in the token, and whether it was
24118 /// whitespace. Note that it also breaks at word boundaries.
24119 type Item = WordBreakToken<'a>;
24120
24121 fn next(&mut self) -> Option<Self::Item> {
24122 use unicode_segmentation::UnicodeSegmentation;
24123 if self.input.is_empty() {
24124 return None;
24125 }
24126
24127 let mut iter = self.input.graphemes(true).peekable();
24128 let mut offset = 0;
24129 let mut grapheme_len = 0;
24130 if let Some(first_grapheme) = iter.next() {
24131 let is_newline = first_grapheme == "\n";
24132 let is_whitespace = is_grapheme_whitespace(first_grapheme);
24133 offset += first_grapheme.len();
24134 grapheme_len += 1;
24135 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
24136 if let Some(grapheme) = iter.peek().copied()
24137 && should_stay_with_preceding_ideograph(grapheme)
24138 {
24139 offset += grapheme.len();
24140 grapheme_len += 1;
24141 }
24142 } else {
24143 let mut words = self.input[offset..].split_word_bound_indices().peekable();
24144 let mut next_word_bound = words.peek().copied();
24145 if next_word_bound.is_some_and(|(i, _)| i == 0) {
24146 next_word_bound = words.next();
24147 }
24148 while let Some(grapheme) = iter.peek().copied() {
24149 if next_word_bound.is_some_and(|(i, _)| i == offset) {
24150 break;
24151 };
24152 if is_grapheme_whitespace(grapheme) != is_whitespace
24153 || (grapheme == "\n") != is_newline
24154 {
24155 break;
24156 };
24157 offset += grapheme.len();
24158 grapheme_len += 1;
24159 iter.next();
24160 }
24161 }
24162 let token = &self.input[..offset];
24163 self.input = &self.input[offset..];
24164 if token == "\n" {
24165 Some(WordBreakToken::Newline)
24166 } else if is_whitespace {
24167 Some(WordBreakToken::InlineWhitespace {
24168 token,
24169 grapheme_len,
24170 })
24171 } else {
24172 Some(WordBreakToken::Word {
24173 token,
24174 grapheme_len,
24175 })
24176 }
24177 } else {
24178 None
24179 }
24180 }
24181}
24182
24183#[test]
24184fn test_word_breaking_tokenizer() {
24185 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
24186 ("", &[]),
24187 (" ", &[whitespace(" ", 2)]),
24188 ("Ʒ", &[word("Ʒ", 1)]),
24189 ("Ǽ", &[word("Ǽ", 1)]),
24190 ("⋑", &[word("⋑", 1)]),
24191 ("⋑⋑", &[word("⋑⋑", 2)]),
24192 (
24193 "原理,进而",
24194 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
24195 ),
24196 (
24197 "hello world",
24198 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
24199 ),
24200 (
24201 "hello, world",
24202 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
24203 ),
24204 (
24205 " hello world",
24206 &[
24207 whitespace(" ", 2),
24208 word("hello", 5),
24209 whitespace(" ", 1),
24210 word("world", 5),
24211 ],
24212 ),
24213 (
24214 "这是什么 \n 钢笔",
24215 &[
24216 word("这", 1),
24217 word("是", 1),
24218 word("什", 1),
24219 word("么", 1),
24220 whitespace(" ", 1),
24221 newline(),
24222 whitespace(" ", 1),
24223 word("钢", 1),
24224 word("笔", 1),
24225 ],
24226 ),
24227 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
24228 ];
24229
24230 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24231 WordBreakToken::Word {
24232 token,
24233 grapheme_len,
24234 }
24235 }
24236
24237 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24238 WordBreakToken::InlineWhitespace {
24239 token,
24240 grapheme_len,
24241 }
24242 }
24243
24244 fn newline() -> WordBreakToken<'static> {
24245 WordBreakToken::Newline
24246 }
24247
24248 for (input, result) in tests {
24249 assert_eq!(
24250 WordBreakingTokenizer::new(input)
24251 .collect::<Vec<_>>()
24252 .as_slice(),
24253 *result,
24254 );
24255 }
24256}
24257
24258fn wrap_with_prefix(
24259 first_line_prefix: String,
24260 subsequent_lines_prefix: String,
24261 unwrapped_text: String,
24262 wrap_column: usize,
24263 tab_size: NonZeroU32,
24264 preserve_existing_whitespace: bool,
24265) -> String {
24266 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
24267 let subsequent_lines_prefix_len =
24268 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
24269 let mut wrapped_text = String::new();
24270 let mut current_line = first_line_prefix;
24271 let mut is_first_line = true;
24272
24273 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
24274 let mut current_line_len = first_line_prefix_len;
24275 let mut in_whitespace = false;
24276 for token in tokenizer {
24277 let have_preceding_whitespace = in_whitespace;
24278 match token {
24279 WordBreakToken::Word {
24280 token,
24281 grapheme_len,
24282 } => {
24283 in_whitespace = false;
24284 let current_prefix_len = if is_first_line {
24285 first_line_prefix_len
24286 } else {
24287 subsequent_lines_prefix_len
24288 };
24289 if current_line_len + grapheme_len > wrap_column
24290 && current_line_len != current_prefix_len
24291 {
24292 wrapped_text.push_str(current_line.trim_end());
24293 wrapped_text.push('\n');
24294 is_first_line = false;
24295 current_line = subsequent_lines_prefix.clone();
24296 current_line_len = subsequent_lines_prefix_len;
24297 }
24298 current_line.push_str(token);
24299 current_line_len += grapheme_len;
24300 }
24301 WordBreakToken::InlineWhitespace {
24302 mut token,
24303 mut grapheme_len,
24304 } => {
24305 in_whitespace = true;
24306 if have_preceding_whitespace && !preserve_existing_whitespace {
24307 continue;
24308 }
24309 if !preserve_existing_whitespace {
24310 // Keep a single whitespace grapheme as-is
24311 if let Some(first) =
24312 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
24313 {
24314 token = first;
24315 } else {
24316 token = " ";
24317 }
24318 grapheme_len = 1;
24319 }
24320 let current_prefix_len = if is_first_line {
24321 first_line_prefix_len
24322 } else {
24323 subsequent_lines_prefix_len
24324 };
24325 if current_line_len + grapheme_len > wrap_column {
24326 wrapped_text.push_str(current_line.trim_end());
24327 wrapped_text.push('\n');
24328 is_first_line = false;
24329 current_line = subsequent_lines_prefix.clone();
24330 current_line_len = subsequent_lines_prefix_len;
24331 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
24332 current_line.push_str(token);
24333 current_line_len += grapheme_len;
24334 }
24335 }
24336 WordBreakToken::Newline => {
24337 in_whitespace = true;
24338 let current_prefix_len = if is_first_line {
24339 first_line_prefix_len
24340 } else {
24341 subsequent_lines_prefix_len
24342 };
24343 if preserve_existing_whitespace {
24344 wrapped_text.push_str(current_line.trim_end());
24345 wrapped_text.push('\n');
24346 is_first_line = false;
24347 current_line = subsequent_lines_prefix.clone();
24348 current_line_len = subsequent_lines_prefix_len;
24349 } else if have_preceding_whitespace {
24350 continue;
24351 } else if current_line_len + 1 > wrap_column
24352 && current_line_len != current_prefix_len
24353 {
24354 wrapped_text.push_str(current_line.trim_end());
24355 wrapped_text.push('\n');
24356 is_first_line = false;
24357 current_line = subsequent_lines_prefix.clone();
24358 current_line_len = subsequent_lines_prefix_len;
24359 } else if current_line_len != current_prefix_len {
24360 current_line.push(' ');
24361 current_line_len += 1;
24362 }
24363 }
24364 }
24365 }
24366
24367 if !current_line.is_empty() {
24368 wrapped_text.push_str(¤t_line);
24369 }
24370 wrapped_text
24371}
24372
24373#[test]
24374fn test_wrap_with_prefix() {
24375 assert_eq!(
24376 wrap_with_prefix(
24377 "# ".to_string(),
24378 "# ".to_string(),
24379 "abcdefg".to_string(),
24380 4,
24381 NonZeroU32::new(4).unwrap(),
24382 false,
24383 ),
24384 "# abcdefg"
24385 );
24386 assert_eq!(
24387 wrap_with_prefix(
24388 "".to_string(),
24389 "".to_string(),
24390 "\thello world".to_string(),
24391 8,
24392 NonZeroU32::new(4).unwrap(),
24393 false,
24394 ),
24395 "hello\nworld"
24396 );
24397 assert_eq!(
24398 wrap_with_prefix(
24399 "// ".to_string(),
24400 "// ".to_string(),
24401 "xx \nyy zz aa bb cc".to_string(),
24402 12,
24403 NonZeroU32::new(4).unwrap(),
24404 false,
24405 ),
24406 "// xx yy zz\n// aa bb cc"
24407 );
24408 assert_eq!(
24409 wrap_with_prefix(
24410 String::new(),
24411 String::new(),
24412 "这是什么 \n 钢笔".to_string(),
24413 3,
24414 NonZeroU32::new(4).unwrap(),
24415 false,
24416 ),
24417 "这是什\n么 钢\n笔"
24418 );
24419 assert_eq!(
24420 wrap_with_prefix(
24421 String::new(),
24422 String::new(),
24423 format!("foo{}bar", '\u{2009}'), // thin space
24424 80,
24425 NonZeroU32::new(4).unwrap(),
24426 false,
24427 ),
24428 format!("foo{}bar", '\u{2009}')
24429 );
24430}
24431
24432pub trait CollaborationHub {
24433 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
24434 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
24435 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
24436}
24437
24438impl CollaborationHub for Entity<Project> {
24439 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
24440 self.read(cx).collaborators()
24441 }
24442
24443 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
24444 self.read(cx).user_store().read(cx).participant_indices()
24445 }
24446
24447 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
24448 let this = self.read(cx);
24449 let user_ids = this.collaborators().values().map(|c| c.user_id);
24450 this.user_store().read(cx).participant_names(user_ids, cx)
24451 }
24452}
24453
24454pub trait SemanticsProvider {
24455 fn hover(
24456 &self,
24457 buffer: &Entity<Buffer>,
24458 position: text::Anchor,
24459 cx: &mut App,
24460 ) -> Option<Task<Option<Vec<project::Hover>>>>;
24461
24462 fn inline_values(
24463 &self,
24464 buffer_handle: Entity<Buffer>,
24465 range: Range<text::Anchor>,
24466 cx: &mut App,
24467 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24468
24469 fn applicable_inlay_chunks(
24470 &self,
24471 buffer: &Entity<Buffer>,
24472 ranges: &[Range<text::Anchor>],
24473 cx: &mut App,
24474 ) -> Vec<Range<BufferRow>>;
24475
24476 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24477
24478 fn inlay_hints(
24479 &self,
24480 invalidate: InvalidationStrategy,
24481 buffer: Entity<Buffer>,
24482 ranges: Vec<Range<text::Anchor>>,
24483 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24484 cx: &mut App,
24485 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24486
24487 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24488
24489 fn document_highlights(
24490 &self,
24491 buffer: &Entity<Buffer>,
24492 position: text::Anchor,
24493 cx: &mut App,
24494 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24495
24496 fn definitions(
24497 &self,
24498 buffer: &Entity<Buffer>,
24499 position: text::Anchor,
24500 kind: GotoDefinitionKind,
24501 cx: &mut App,
24502 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24503
24504 fn range_for_rename(
24505 &self,
24506 buffer: &Entity<Buffer>,
24507 position: text::Anchor,
24508 cx: &mut App,
24509 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24510
24511 fn perform_rename(
24512 &self,
24513 buffer: &Entity<Buffer>,
24514 position: text::Anchor,
24515 new_name: String,
24516 cx: &mut App,
24517 ) -> Option<Task<Result<ProjectTransaction>>>;
24518}
24519
24520pub trait CompletionProvider {
24521 fn completions(
24522 &self,
24523 excerpt_id: ExcerptId,
24524 buffer: &Entity<Buffer>,
24525 buffer_position: text::Anchor,
24526 trigger: CompletionContext,
24527 window: &mut Window,
24528 cx: &mut Context<Editor>,
24529 ) -> Task<Result<Vec<CompletionResponse>>>;
24530
24531 fn resolve_completions(
24532 &self,
24533 _buffer: Entity<Buffer>,
24534 _completion_indices: Vec<usize>,
24535 _completions: Rc<RefCell<Box<[Completion]>>>,
24536 _cx: &mut Context<Editor>,
24537 ) -> Task<Result<bool>> {
24538 Task::ready(Ok(false))
24539 }
24540
24541 fn apply_additional_edits_for_completion(
24542 &self,
24543 _buffer: Entity<Buffer>,
24544 _completions: Rc<RefCell<Box<[Completion]>>>,
24545 _completion_index: usize,
24546 _push_to_history: bool,
24547 _cx: &mut Context<Editor>,
24548 ) -> Task<Result<Option<language::Transaction>>> {
24549 Task::ready(Ok(None))
24550 }
24551
24552 fn is_completion_trigger(
24553 &self,
24554 buffer: &Entity<Buffer>,
24555 position: language::Anchor,
24556 text: &str,
24557 trigger_in_words: bool,
24558 cx: &mut Context<Editor>,
24559 ) -> bool;
24560
24561 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24562
24563 fn sort_completions(&self) -> bool {
24564 true
24565 }
24566
24567 fn filter_completions(&self) -> bool {
24568 true
24569 }
24570
24571 fn show_snippets(&self) -> bool {
24572 false
24573 }
24574}
24575
24576pub trait CodeActionProvider {
24577 fn id(&self) -> Arc<str>;
24578
24579 fn code_actions(
24580 &self,
24581 buffer: &Entity<Buffer>,
24582 range: Range<text::Anchor>,
24583 window: &mut Window,
24584 cx: &mut App,
24585 ) -> Task<Result<Vec<CodeAction>>>;
24586
24587 fn apply_code_action(
24588 &self,
24589 buffer_handle: Entity<Buffer>,
24590 action: CodeAction,
24591 excerpt_id: ExcerptId,
24592 push_to_history: bool,
24593 window: &mut Window,
24594 cx: &mut App,
24595 ) -> Task<Result<ProjectTransaction>>;
24596}
24597
24598impl CodeActionProvider for Entity<Project> {
24599 fn id(&self) -> Arc<str> {
24600 "project".into()
24601 }
24602
24603 fn code_actions(
24604 &self,
24605 buffer: &Entity<Buffer>,
24606 range: Range<text::Anchor>,
24607 _window: &mut Window,
24608 cx: &mut App,
24609 ) -> Task<Result<Vec<CodeAction>>> {
24610 self.update(cx, |project, cx| {
24611 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24612 let code_actions = project.code_actions(buffer, range, None, cx);
24613 cx.background_spawn(async move {
24614 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24615 Ok(code_lens_actions
24616 .context("code lens fetch")?
24617 .into_iter()
24618 .flatten()
24619 .chain(
24620 code_actions
24621 .context("code action fetch")?
24622 .into_iter()
24623 .flatten(),
24624 )
24625 .collect())
24626 })
24627 })
24628 }
24629
24630 fn apply_code_action(
24631 &self,
24632 buffer_handle: Entity<Buffer>,
24633 action: CodeAction,
24634 _excerpt_id: ExcerptId,
24635 push_to_history: bool,
24636 _window: &mut Window,
24637 cx: &mut App,
24638 ) -> Task<Result<ProjectTransaction>> {
24639 self.update(cx, |project, cx| {
24640 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24641 })
24642 }
24643}
24644
24645fn snippet_completions(
24646 project: &Project,
24647 buffer: &Entity<Buffer>,
24648 buffer_anchor: text::Anchor,
24649 classifier: CharClassifier,
24650 cx: &mut App,
24651) -> Task<Result<CompletionResponse>> {
24652 let languages = buffer.read(cx).languages_at(buffer_anchor);
24653 let snippet_store = project.snippets().read(cx);
24654
24655 let scopes: Vec<_> = languages
24656 .iter()
24657 .filter_map(|language| {
24658 let language_name = language.lsp_id();
24659 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24660
24661 if snippets.is_empty() {
24662 None
24663 } else {
24664 Some((language.default_scope(), snippets))
24665 }
24666 })
24667 .collect();
24668
24669 if scopes.is_empty() {
24670 return Task::ready(Ok(CompletionResponse {
24671 completions: vec![],
24672 display_options: CompletionDisplayOptions::default(),
24673 is_incomplete: false,
24674 }));
24675 }
24676
24677 let snapshot = buffer.read(cx).text_snapshot();
24678 let executor = cx.background_executor().clone();
24679
24680 cx.background_spawn(async move {
24681 let is_word_char = |c| classifier.is_word(c);
24682
24683 let mut is_incomplete = false;
24684 let mut completions: Vec<Completion> = Vec::new();
24685
24686 const MAX_PREFIX_LEN: usize = 128;
24687 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24688 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24689 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24690
24691 let max_buffer_window: String = snapshot
24692 .text_for_range(window_start..buffer_offset)
24693 .collect();
24694
24695 if max_buffer_window.is_empty() {
24696 return Ok(CompletionResponse {
24697 completions: vec![],
24698 display_options: CompletionDisplayOptions::default(),
24699 is_incomplete: true,
24700 });
24701 }
24702
24703 for (_scope, snippets) in scopes.into_iter() {
24704 // Sort snippets by word count to match longer snippet prefixes first.
24705 let mut sorted_snippet_candidates = snippets
24706 .iter()
24707 .enumerate()
24708 .flat_map(|(snippet_ix, snippet)| {
24709 snippet
24710 .prefix
24711 .iter()
24712 .enumerate()
24713 .map(move |(prefix_ix, prefix)| {
24714 let word_count =
24715 snippet_candidate_suffixes(prefix, is_word_char).count();
24716 ((snippet_ix, prefix_ix), prefix, word_count)
24717 })
24718 })
24719 .collect_vec();
24720 sorted_snippet_candidates
24721 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24722
24723 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24724
24725 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24726 .take(
24727 sorted_snippet_candidates
24728 .first()
24729 .map(|(_, _, word_count)| *word_count)
24730 .unwrap_or_default(),
24731 )
24732 .collect_vec();
24733
24734 const MAX_RESULTS: usize = 100;
24735 // Each match also remembers how many characters from the buffer it consumed
24736 let mut matches: Vec<(StringMatch, usize)> = vec![];
24737
24738 let mut snippet_list_cutoff_index = 0;
24739 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24740 let word_count = buffer_index + 1;
24741 // Increase `snippet_list_cutoff_index` until we have all of the
24742 // snippets with sufficiently many words.
24743 while sorted_snippet_candidates
24744 .get(snippet_list_cutoff_index)
24745 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24746 *snippet_word_count >= word_count
24747 })
24748 {
24749 snippet_list_cutoff_index += 1;
24750 }
24751
24752 // Take only the candidates with at least `word_count` many words
24753 let snippet_candidates_at_word_len =
24754 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24755
24756 let candidates = snippet_candidates_at_word_len
24757 .iter()
24758 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24759 .enumerate() // index in `sorted_snippet_candidates`
24760 // First char must match
24761 .filter(|(_ix, prefix)| {
24762 itertools::equal(
24763 prefix
24764 .chars()
24765 .next()
24766 .into_iter()
24767 .flat_map(|c| c.to_lowercase()),
24768 buffer_window
24769 .chars()
24770 .next()
24771 .into_iter()
24772 .flat_map(|c| c.to_lowercase()),
24773 )
24774 })
24775 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24776 .collect::<Vec<StringMatchCandidate>>();
24777
24778 matches.extend(
24779 fuzzy::match_strings(
24780 &candidates,
24781 &buffer_window,
24782 buffer_window.chars().any(|c| c.is_uppercase()),
24783 true,
24784 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24785 &Default::default(),
24786 executor.clone(),
24787 )
24788 .await
24789 .into_iter()
24790 .map(|string_match| (string_match, buffer_window.len())),
24791 );
24792
24793 if matches.len() >= MAX_RESULTS {
24794 break;
24795 }
24796 }
24797
24798 let to_lsp = |point: &text::Anchor| {
24799 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24800 point_to_lsp(end)
24801 };
24802 let lsp_end = to_lsp(&buffer_anchor);
24803
24804 if matches.len() >= MAX_RESULTS {
24805 is_incomplete = true;
24806 }
24807
24808 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24809 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24810 sorted_snippet_candidates[string_match.candidate_id];
24811 let snippet = &snippets[snippet_index];
24812 let start = buffer_offset - buffer_window_len;
24813 let start = snapshot.anchor_before(start);
24814 let range = start..buffer_anchor;
24815 let lsp_start = to_lsp(&start);
24816 let lsp_range = lsp::Range {
24817 start: lsp_start,
24818 end: lsp_end,
24819 };
24820 Completion {
24821 replace_range: range,
24822 new_text: snippet.body.clone(),
24823 source: CompletionSource::Lsp {
24824 insert_range: None,
24825 server_id: LanguageServerId(usize::MAX),
24826 resolved: true,
24827 lsp_completion: Box::new(lsp::CompletionItem {
24828 label: snippet.prefix.first().unwrap().clone(),
24829 kind: Some(CompletionItemKind::SNIPPET),
24830 label_details: snippet.description.as_ref().map(|description| {
24831 lsp::CompletionItemLabelDetails {
24832 detail: Some(description.clone()),
24833 description: None,
24834 }
24835 }),
24836 insert_text_format: Some(InsertTextFormat::SNIPPET),
24837 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24838 lsp::InsertReplaceEdit {
24839 new_text: snippet.body.clone(),
24840 insert: lsp_range,
24841 replace: lsp_range,
24842 },
24843 )),
24844 filter_text: Some(snippet.body.clone()),
24845 sort_text: Some(char::MAX.to_string()),
24846 ..lsp::CompletionItem::default()
24847 }),
24848 lsp_defaults: None,
24849 },
24850 label: CodeLabel {
24851 text: matching_prefix.clone(),
24852 runs: Vec::new(),
24853 filter_range: 0..matching_prefix.len(),
24854 },
24855 icon_path: None,
24856 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24857 single_line: snippet.name.clone().into(),
24858 plain_text: snippet
24859 .description
24860 .clone()
24861 .map(|description| description.into()),
24862 }),
24863 insert_text_mode: None,
24864 confirm: None,
24865 match_start: Some(start),
24866 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24867 }
24868 }));
24869 }
24870
24871 Ok(CompletionResponse {
24872 completions,
24873 display_options: CompletionDisplayOptions::default(),
24874 is_incomplete,
24875 })
24876 })
24877}
24878
24879impl CompletionProvider for Entity<Project> {
24880 fn completions(
24881 &self,
24882 _excerpt_id: ExcerptId,
24883 buffer: &Entity<Buffer>,
24884 buffer_position: text::Anchor,
24885 options: CompletionContext,
24886 _window: &mut Window,
24887 cx: &mut Context<Editor>,
24888 ) -> Task<Result<Vec<CompletionResponse>>> {
24889 self.update(cx, |project, cx| {
24890 let task = project.completions(buffer, buffer_position, options, cx);
24891 cx.background_spawn(task)
24892 })
24893 }
24894
24895 fn resolve_completions(
24896 &self,
24897 buffer: Entity<Buffer>,
24898 completion_indices: Vec<usize>,
24899 completions: Rc<RefCell<Box<[Completion]>>>,
24900 cx: &mut Context<Editor>,
24901 ) -> Task<Result<bool>> {
24902 self.update(cx, |project, cx| {
24903 project.lsp_store().update(cx, |lsp_store, cx| {
24904 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24905 })
24906 })
24907 }
24908
24909 fn apply_additional_edits_for_completion(
24910 &self,
24911 buffer: Entity<Buffer>,
24912 completions: Rc<RefCell<Box<[Completion]>>>,
24913 completion_index: usize,
24914 push_to_history: bool,
24915 cx: &mut Context<Editor>,
24916 ) -> Task<Result<Option<language::Transaction>>> {
24917 self.update(cx, |project, cx| {
24918 project.lsp_store().update(cx, |lsp_store, cx| {
24919 lsp_store.apply_additional_edits_for_completion(
24920 buffer,
24921 completions,
24922 completion_index,
24923 push_to_history,
24924 cx,
24925 )
24926 })
24927 })
24928 }
24929
24930 fn is_completion_trigger(
24931 &self,
24932 buffer: &Entity<Buffer>,
24933 position: language::Anchor,
24934 text: &str,
24935 trigger_in_words: bool,
24936 cx: &mut Context<Editor>,
24937 ) -> bool {
24938 let mut chars = text.chars();
24939 let char = if let Some(char) = chars.next() {
24940 char
24941 } else {
24942 return false;
24943 };
24944 if chars.next().is_some() {
24945 return false;
24946 }
24947
24948 let buffer = buffer.read(cx);
24949 let snapshot = buffer.snapshot();
24950 let classifier = snapshot
24951 .char_classifier_at(position)
24952 .scope_context(Some(CharScopeContext::Completion));
24953 if trigger_in_words && classifier.is_word(char) {
24954 return true;
24955 }
24956
24957 buffer.completion_triggers().contains(text)
24958 }
24959
24960 fn show_snippets(&self) -> bool {
24961 true
24962 }
24963}
24964
24965impl SemanticsProvider for Entity<Project> {
24966 fn hover(
24967 &self,
24968 buffer: &Entity<Buffer>,
24969 position: text::Anchor,
24970 cx: &mut App,
24971 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24972 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24973 }
24974
24975 fn document_highlights(
24976 &self,
24977 buffer: &Entity<Buffer>,
24978 position: text::Anchor,
24979 cx: &mut App,
24980 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24981 Some(self.update(cx, |project, cx| {
24982 project.document_highlights(buffer, position, cx)
24983 }))
24984 }
24985
24986 fn definitions(
24987 &self,
24988 buffer: &Entity<Buffer>,
24989 position: text::Anchor,
24990 kind: GotoDefinitionKind,
24991 cx: &mut App,
24992 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24993 Some(self.update(cx, |project, cx| match kind {
24994 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24995 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24996 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24997 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24998 }))
24999 }
25000
25001 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
25002 self.update(cx, |project, cx| {
25003 if project
25004 .active_debug_session(cx)
25005 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
25006 {
25007 return true;
25008 }
25009
25010 buffer.update(cx, |buffer, cx| {
25011 project.any_language_server_supports_inlay_hints(buffer, cx)
25012 })
25013 })
25014 }
25015
25016 fn inline_values(
25017 &self,
25018 buffer_handle: Entity<Buffer>,
25019 range: Range<text::Anchor>,
25020 cx: &mut App,
25021 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
25022 self.update(cx, |project, cx| {
25023 let (session, active_stack_frame) = project.active_debug_session(cx)?;
25024
25025 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
25026 })
25027 }
25028
25029 fn applicable_inlay_chunks(
25030 &self,
25031 buffer: &Entity<Buffer>,
25032 ranges: &[Range<text::Anchor>],
25033 cx: &mut App,
25034 ) -> Vec<Range<BufferRow>> {
25035 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
25036 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
25037 })
25038 }
25039
25040 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
25041 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
25042 lsp_store.invalidate_inlay_hints(for_buffers)
25043 });
25044 }
25045
25046 fn inlay_hints(
25047 &self,
25048 invalidate: InvalidationStrategy,
25049 buffer: Entity<Buffer>,
25050 ranges: Vec<Range<text::Anchor>>,
25051 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
25052 cx: &mut App,
25053 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
25054 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
25055 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
25056 }))
25057 }
25058
25059 fn range_for_rename(
25060 &self,
25061 buffer: &Entity<Buffer>,
25062 position: text::Anchor,
25063 cx: &mut App,
25064 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
25065 Some(self.update(cx, |project, cx| {
25066 let buffer = buffer.clone();
25067 let task = project.prepare_rename(buffer.clone(), position, cx);
25068 cx.spawn(async move |_, cx| {
25069 Ok(match task.await? {
25070 PrepareRenameResponse::Success(range) => Some(range),
25071 PrepareRenameResponse::InvalidPosition => None,
25072 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
25073 // Fallback on using TreeSitter info to determine identifier range
25074 buffer.read_with(cx, |buffer, _| {
25075 let snapshot = buffer.snapshot();
25076 let (range, kind) = snapshot.surrounding_word(position, None);
25077 if kind != Some(CharKind::Word) {
25078 return None;
25079 }
25080 Some(
25081 snapshot.anchor_before(range.start)
25082 ..snapshot.anchor_after(range.end),
25083 )
25084 })?
25085 }
25086 })
25087 })
25088 }))
25089 }
25090
25091 fn perform_rename(
25092 &self,
25093 buffer: &Entity<Buffer>,
25094 position: text::Anchor,
25095 new_name: String,
25096 cx: &mut App,
25097 ) -> Option<Task<Result<ProjectTransaction>>> {
25098 Some(self.update(cx, |project, cx| {
25099 project.perform_rename(buffer.clone(), position, new_name, cx)
25100 }))
25101 }
25102}
25103
25104fn consume_contiguous_rows(
25105 contiguous_row_selections: &mut Vec<Selection<Point>>,
25106 selection: &Selection<Point>,
25107 display_map: &DisplaySnapshot,
25108 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
25109) -> (MultiBufferRow, MultiBufferRow) {
25110 contiguous_row_selections.push(selection.clone());
25111 let start_row = starting_row(selection, display_map);
25112 let mut end_row = ending_row(selection, display_map);
25113
25114 while let Some(next_selection) = selections.peek() {
25115 if next_selection.start.row <= end_row.0 {
25116 end_row = ending_row(next_selection, display_map);
25117 contiguous_row_selections.push(selections.next().unwrap().clone());
25118 } else {
25119 break;
25120 }
25121 }
25122 (start_row, end_row)
25123}
25124
25125fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
25126 if selection.start.column > 0 {
25127 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
25128 } else {
25129 MultiBufferRow(selection.start.row)
25130 }
25131}
25132
25133fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
25134 if next_selection.end.column > 0 || next_selection.is_empty() {
25135 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
25136 } else {
25137 MultiBufferRow(next_selection.end.row)
25138 }
25139}
25140
25141impl EditorSnapshot {
25142 pub fn remote_selections_in_range<'a>(
25143 &'a self,
25144 range: &'a Range<Anchor>,
25145 collaboration_hub: &dyn CollaborationHub,
25146 cx: &'a App,
25147 ) -> impl 'a + Iterator<Item = RemoteSelection> {
25148 let participant_names = collaboration_hub.user_names(cx);
25149 let participant_indices = collaboration_hub.user_participant_indices(cx);
25150 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
25151 let collaborators_by_replica_id = collaborators_by_peer_id
25152 .values()
25153 .map(|collaborator| (collaborator.replica_id, collaborator))
25154 .collect::<HashMap<_, _>>();
25155 self.buffer_snapshot()
25156 .selections_in_range(range, false)
25157 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
25158 if replica_id == ReplicaId::AGENT {
25159 Some(RemoteSelection {
25160 replica_id,
25161 selection,
25162 cursor_shape,
25163 line_mode,
25164 collaborator_id: CollaboratorId::Agent,
25165 user_name: Some("Agent".into()),
25166 color: cx.theme().players().agent(),
25167 })
25168 } else {
25169 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
25170 let participant_index = participant_indices.get(&collaborator.user_id).copied();
25171 let user_name = participant_names.get(&collaborator.user_id).cloned();
25172 Some(RemoteSelection {
25173 replica_id,
25174 selection,
25175 cursor_shape,
25176 line_mode,
25177 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
25178 user_name,
25179 color: if let Some(index) = participant_index {
25180 cx.theme().players().color_for_participant(index.0)
25181 } else {
25182 cx.theme().players().absent()
25183 },
25184 })
25185 }
25186 })
25187 }
25188
25189 pub fn hunks_for_ranges(
25190 &self,
25191 ranges: impl IntoIterator<Item = Range<Point>>,
25192 ) -> Vec<MultiBufferDiffHunk> {
25193 let mut hunks = Vec::new();
25194 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
25195 HashMap::default();
25196 for query_range in ranges {
25197 let query_rows =
25198 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
25199 for hunk in self.buffer_snapshot().diff_hunks_in_range(
25200 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
25201 ) {
25202 // Include deleted hunks that are adjacent to the query range, because
25203 // otherwise they would be missed.
25204 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
25205 if hunk.status().is_deleted() {
25206 intersects_range |= hunk.row_range.start == query_rows.end;
25207 intersects_range |= hunk.row_range.end == query_rows.start;
25208 }
25209 if intersects_range {
25210 if !processed_buffer_rows
25211 .entry(hunk.buffer_id)
25212 .or_default()
25213 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
25214 {
25215 continue;
25216 }
25217 hunks.push(hunk);
25218 }
25219 }
25220 }
25221
25222 hunks
25223 }
25224
25225 fn display_diff_hunks_for_rows<'a>(
25226 &'a self,
25227 display_rows: Range<DisplayRow>,
25228 folded_buffers: &'a HashSet<BufferId>,
25229 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
25230 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
25231 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
25232
25233 self.buffer_snapshot()
25234 .diff_hunks_in_range(buffer_start..buffer_end)
25235 .filter_map(|hunk| {
25236 if folded_buffers.contains(&hunk.buffer_id) {
25237 return None;
25238 }
25239
25240 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
25241 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
25242
25243 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
25244 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
25245
25246 let display_hunk = if hunk_display_start.column() != 0 {
25247 DisplayDiffHunk::Folded {
25248 display_row: hunk_display_start.row(),
25249 }
25250 } else {
25251 let mut end_row = hunk_display_end.row();
25252 if hunk_display_end.column() > 0 {
25253 end_row.0 += 1;
25254 }
25255 let is_created_file = hunk.is_created_file();
25256
25257 DisplayDiffHunk::Unfolded {
25258 status: hunk.status(),
25259 diff_base_byte_range: hunk.diff_base_byte_range.start.0
25260 ..hunk.diff_base_byte_range.end.0,
25261 word_diffs: hunk.word_diffs,
25262 display_row_range: hunk_display_start.row()..end_row,
25263 multi_buffer_range: Anchor::range_in_buffer(
25264 hunk.excerpt_id,
25265 hunk.buffer_range,
25266 ),
25267 is_created_file,
25268 }
25269 };
25270
25271 Some(display_hunk)
25272 })
25273 }
25274
25275 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
25276 self.display_snapshot
25277 .buffer_snapshot()
25278 .language_at(position)
25279 }
25280
25281 pub fn is_focused(&self) -> bool {
25282 self.is_focused
25283 }
25284
25285 pub fn placeholder_text(&self) -> Option<String> {
25286 self.placeholder_display_snapshot
25287 .as_ref()
25288 .map(|display_map| display_map.text())
25289 }
25290
25291 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
25292 self.scroll_anchor.scroll_position(&self.display_snapshot)
25293 }
25294
25295 pub fn gutter_dimensions(
25296 &self,
25297 font_id: FontId,
25298 font_size: Pixels,
25299 style: &EditorStyle,
25300 window: &mut Window,
25301 cx: &App,
25302 ) -> GutterDimensions {
25303 if self.show_gutter
25304 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
25305 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
25306 {
25307 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
25308 matches!(
25309 ProjectSettings::get_global(cx).git.git_gutter,
25310 GitGutterSetting::TrackedFiles
25311 )
25312 });
25313 let gutter_settings = EditorSettings::get_global(cx).gutter;
25314 let show_line_numbers = self
25315 .show_line_numbers
25316 .unwrap_or(gutter_settings.line_numbers);
25317 let line_gutter_width = if show_line_numbers {
25318 // Avoid flicker-like gutter resizes when the line number gains another digit by
25319 // only resizing the gutter on files with > 10**min_line_number_digits lines.
25320 let min_width_for_number_on_gutter =
25321 ch_advance * gutter_settings.min_line_number_digits as f32;
25322 self.max_line_number_width(style, window)
25323 .max(min_width_for_number_on_gutter)
25324 } else {
25325 0.0.into()
25326 };
25327
25328 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
25329 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
25330
25331 let git_blame_entries_width =
25332 self.git_blame_gutter_max_author_length
25333 .map(|max_author_length| {
25334 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
25335 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
25336
25337 /// The number of characters to dedicate to gaps and margins.
25338 const SPACING_WIDTH: usize = 4;
25339
25340 let max_char_count = max_author_length.min(renderer.max_author_length())
25341 + ::git::SHORT_SHA_LENGTH
25342 + MAX_RELATIVE_TIMESTAMP.len()
25343 + SPACING_WIDTH;
25344
25345 ch_advance * max_char_count
25346 });
25347
25348 let is_singleton = self.buffer_snapshot().is_singleton();
25349
25350 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
25351 left_padding += if !is_singleton {
25352 ch_width * 4.0
25353 } else if show_runnables || show_breakpoints {
25354 ch_width * 3.0
25355 } else if show_git_gutter && show_line_numbers {
25356 ch_width * 2.0
25357 } else if show_git_gutter || show_line_numbers {
25358 ch_width
25359 } else {
25360 px(0.)
25361 };
25362
25363 let shows_folds = is_singleton && gutter_settings.folds;
25364
25365 let right_padding = if shows_folds && show_line_numbers {
25366 ch_width * 4.0
25367 } else if shows_folds || (!is_singleton && show_line_numbers) {
25368 ch_width * 3.0
25369 } else if show_line_numbers {
25370 ch_width
25371 } else {
25372 px(0.)
25373 };
25374
25375 GutterDimensions {
25376 left_padding,
25377 right_padding,
25378 width: line_gutter_width + left_padding + right_padding,
25379 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
25380 git_blame_entries_width,
25381 }
25382 } else if self.offset_content {
25383 GutterDimensions::default_with_margin(font_id, font_size, cx)
25384 } else {
25385 GutterDimensions::default()
25386 }
25387 }
25388
25389 pub fn render_crease_toggle(
25390 &self,
25391 buffer_row: MultiBufferRow,
25392 row_contains_cursor: bool,
25393 editor: Entity<Editor>,
25394 window: &mut Window,
25395 cx: &mut App,
25396 ) -> Option<AnyElement> {
25397 let folded = self.is_line_folded(buffer_row);
25398 let mut is_foldable = false;
25399
25400 if let Some(crease) = self
25401 .crease_snapshot
25402 .query_row(buffer_row, self.buffer_snapshot())
25403 {
25404 is_foldable = true;
25405 match crease {
25406 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
25407 if let Some(render_toggle) = render_toggle {
25408 let toggle_callback =
25409 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
25410 if folded {
25411 editor.update(cx, |editor, cx| {
25412 editor.fold_at(buffer_row, window, cx)
25413 });
25414 } else {
25415 editor.update(cx, |editor, cx| {
25416 editor.unfold_at(buffer_row, window, cx)
25417 });
25418 }
25419 });
25420 return Some((render_toggle)(
25421 buffer_row,
25422 folded,
25423 toggle_callback,
25424 window,
25425 cx,
25426 ));
25427 }
25428 }
25429 }
25430 }
25431
25432 is_foldable |= self.starts_indent(buffer_row);
25433
25434 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
25435 Some(
25436 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
25437 .toggle_state(folded)
25438 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
25439 if folded {
25440 this.unfold_at(buffer_row, window, cx);
25441 } else {
25442 this.fold_at(buffer_row, window, cx);
25443 }
25444 }))
25445 .into_any_element(),
25446 )
25447 } else {
25448 None
25449 }
25450 }
25451
25452 pub fn render_crease_trailer(
25453 &self,
25454 buffer_row: MultiBufferRow,
25455 window: &mut Window,
25456 cx: &mut App,
25457 ) -> Option<AnyElement> {
25458 let folded = self.is_line_folded(buffer_row);
25459 if let Crease::Inline { render_trailer, .. } = self
25460 .crease_snapshot
25461 .query_row(buffer_row, self.buffer_snapshot())?
25462 {
25463 let render_trailer = render_trailer.as_ref()?;
25464 Some(render_trailer(buffer_row, folded, window, cx))
25465 } else {
25466 None
25467 }
25468 }
25469
25470 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25471 let digit_count = self.widest_line_number().ilog10() + 1;
25472 column_pixels(style, digit_count as usize, window)
25473 }
25474
25475 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
25476 ///
25477 /// This is positive if `base` is before `line`.
25478 fn relative_line_delta(
25479 &self,
25480 base: DisplayRow,
25481 line: DisplayRow,
25482 consider_wrapped_lines: bool,
25483 ) -> i64 {
25484 let point = DisplayPoint::new(line, 0).to_point(self);
25485 self.relative_line_delta_to_point(base, point, consider_wrapped_lines)
25486 }
25487
25488 /// Returns the line delta from `base` to `point` in the multibuffer.
25489 ///
25490 /// This is positive if `base` is before `point`.
25491 pub fn relative_line_delta_to_point(
25492 &self,
25493 base: DisplayRow,
25494 point: Point,
25495 consider_wrapped_lines: bool,
25496 ) -> i64 {
25497 let base_point = DisplayPoint::new(base, 0).to_point(self);
25498 if consider_wrapped_lines {
25499 let wrap_snapshot = self.wrap_snapshot();
25500 let base_wrap_row = wrap_snapshot.make_wrap_point(base_point, Bias::Left).row();
25501 let wrap_row = wrap_snapshot.make_wrap_point(point, Bias::Left).row();
25502 wrap_row.0 as i64 - base_wrap_row.0 as i64
25503 } else {
25504 point.row as i64 - base_point.row as i64
25505 }
25506 }
25507
25508 /// Returns the unsigned relative line number to display for each row in `rows`.
25509 ///
25510 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
25511 pub fn calculate_relative_line_numbers(
25512 &self,
25513 rows: &Range<DisplayRow>,
25514 relative_to: DisplayRow,
25515 count_wrapped_lines: bool,
25516 ) -> HashMap<DisplayRow, u32> {
25517 let initial_offset = self.relative_line_delta(relative_to, rows.start, count_wrapped_lines);
25518
25519 self.row_infos(rows.start)
25520 .take(rows.len())
25521 .enumerate()
25522 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
25523 .filter(|(_row, row_info)| {
25524 row_info.buffer_row.is_some()
25525 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
25526 })
25527 .enumerate()
25528 .flat_map(|(i, (row, _row_info))| {
25529 (row != relative_to)
25530 .then_some((row, (initial_offset + i as i64).unsigned_abs() as u32))
25531 })
25532 .collect()
25533 }
25534}
25535
25536pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25537 let font_size = style.text.font_size.to_pixels(window.rem_size());
25538 let layout = window.text_system().shape_line(
25539 SharedString::from(" ".repeat(column)),
25540 font_size,
25541 &[TextRun {
25542 len: column,
25543 font: style.text.font(),
25544 color: Hsla::default(),
25545 ..Default::default()
25546 }],
25547 None,
25548 );
25549
25550 layout.width
25551}
25552
25553impl Deref for EditorSnapshot {
25554 type Target = DisplaySnapshot;
25555
25556 fn deref(&self) -> &Self::Target {
25557 &self.display_snapshot
25558 }
25559}
25560
25561#[derive(Clone, Debug, PartialEq, Eq)]
25562pub enum EditorEvent {
25563 InputIgnored {
25564 text: Arc<str>,
25565 },
25566 InputHandled {
25567 utf16_range_to_replace: Option<Range<isize>>,
25568 text: Arc<str>,
25569 },
25570 ExcerptsAdded {
25571 buffer: Entity<Buffer>,
25572 predecessor: ExcerptId,
25573 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25574 },
25575 ExcerptsRemoved {
25576 ids: Vec<ExcerptId>,
25577 removed_buffer_ids: Vec<BufferId>,
25578 },
25579 BufferFoldToggled {
25580 ids: Vec<ExcerptId>,
25581 folded: bool,
25582 },
25583 ExcerptsEdited {
25584 ids: Vec<ExcerptId>,
25585 },
25586 ExcerptsExpanded {
25587 ids: Vec<ExcerptId>,
25588 },
25589 ExpandExcerptsRequested {
25590 excerpt_ids: Vec<ExcerptId>,
25591 lines: u32,
25592 direction: ExpandExcerptDirection,
25593 },
25594 BufferEdited,
25595 Edited {
25596 transaction_id: clock::Lamport,
25597 },
25598 Reparsed(BufferId),
25599 Focused,
25600 FocusedIn,
25601 Blurred,
25602 DirtyChanged,
25603 Saved,
25604 TitleChanged,
25605 SelectionsChanged {
25606 local: bool,
25607 },
25608 ScrollPositionChanged {
25609 local: bool,
25610 autoscroll: bool,
25611 },
25612 TransactionUndone {
25613 transaction_id: clock::Lamport,
25614 },
25615 TransactionBegun {
25616 transaction_id: clock::Lamport,
25617 },
25618 CursorShapeChanged,
25619 BreadcrumbsChanged,
25620 PushedToNavHistory {
25621 anchor: Anchor,
25622 is_deactivate: bool,
25623 },
25624}
25625
25626impl EventEmitter<EditorEvent> for Editor {}
25627
25628impl Focusable for Editor {
25629 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25630 self.focus_handle.clone()
25631 }
25632}
25633
25634impl Render for Editor {
25635 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25636 EditorElement::new(&cx.entity(), self.create_style(cx))
25637 }
25638}
25639
25640impl EntityInputHandler for Editor {
25641 fn text_for_range(
25642 &mut self,
25643 range_utf16: Range<usize>,
25644 adjusted_range: &mut Option<Range<usize>>,
25645 _: &mut Window,
25646 cx: &mut Context<Self>,
25647 ) -> Option<String> {
25648 let snapshot = self.buffer.read(cx).read(cx);
25649 let start = snapshot.clip_offset_utf16(
25650 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25651 Bias::Left,
25652 );
25653 let end = snapshot.clip_offset_utf16(
25654 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25655 Bias::Right,
25656 );
25657 if (start.0.0..end.0.0) != range_utf16 {
25658 adjusted_range.replace(start.0.0..end.0.0);
25659 }
25660 Some(snapshot.text_for_range(start..end).collect())
25661 }
25662
25663 fn selected_text_range(
25664 &mut self,
25665 ignore_disabled_input: bool,
25666 _: &mut Window,
25667 cx: &mut Context<Self>,
25668 ) -> Option<UTF16Selection> {
25669 // Prevent the IME menu from appearing when holding down an alphabetic key
25670 // while input is disabled.
25671 if !ignore_disabled_input && !self.input_enabled {
25672 return None;
25673 }
25674
25675 let selection = self
25676 .selections
25677 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25678 let range = selection.range();
25679
25680 Some(UTF16Selection {
25681 range: range.start.0.0..range.end.0.0,
25682 reversed: selection.reversed,
25683 })
25684 }
25685
25686 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25687 let snapshot = self.buffer.read(cx).read(cx);
25688 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25689 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25690 }
25691
25692 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25693 self.clear_highlights::<InputComposition>(cx);
25694 self.ime_transaction.take();
25695 }
25696
25697 fn replace_text_in_range(
25698 &mut self,
25699 range_utf16: Option<Range<usize>>,
25700 text: &str,
25701 window: &mut Window,
25702 cx: &mut Context<Self>,
25703 ) {
25704 if !self.input_enabled {
25705 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25706 return;
25707 }
25708
25709 self.transact(window, cx, |this, window, cx| {
25710 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25711 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25712 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25713 Some(this.selection_replacement_ranges(range_utf16, cx))
25714 } else {
25715 this.marked_text_ranges(cx)
25716 };
25717
25718 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25719 let newest_selection_id = this.selections.newest_anchor().id;
25720 this.selections
25721 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25722 .iter()
25723 .zip(ranges_to_replace.iter())
25724 .find_map(|(selection, range)| {
25725 if selection.id == newest_selection_id {
25726 Some(
25727 (range.start.0.0 as isize - selection.head().0.0 as isize)
25728 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25729 )
25730 } else {
25731 None
25732 }
25733 })
25734 });
25735
25736 cx.emit(EditorEvent::InputHandled {
25737 utf16_range_to_replace: range_to_replace,
25738 text: text.into(),
25739 });
25740
25741 if let Some(new_selected_ranges) = new_selected_ranges {
25742 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25743 selections.select_ranges(new_selected_ranges)
25744 });
25745 this.backspace(&Default::default(), window, cx);
25746 }
25747
25748 this.handle_input(text, window, cx);
25749 });
25750
25751 if let Some(transaction) = self.ime_transaction {
25752 self.buffer.update(cx, |buffer, cx| {
25753 buffer.group_until_transaction(transaction, cx);
25754 });
25755 }
25756
25757 self.unmark_text(window, cx);
25758 }
25759
25760 fn replace_and_mark_text_in_range(
25761 &mut self,
25762 range_utf16: Option<Range<usize>>,
25763 text: &str,
25764 new_selected_range_utf16: Option<Range<usize>>,
25765 window: &mut Window,
25766 cx: &mut Context<Self>,
25767 ) {
25768 if !self.input_enabled {
25769 return;
25770 }
25771
25772 let transaction = self.transact(window, cx, |this, window, cx| {
25773 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25774 let snapshot = this.buffer.read(cx).read(cx);
25775 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25776 for marked_range in &mut marked_ranges {
25777 marked_range.end = marked_range.start + relative_range_utf16.end;
25778 marked_range.start += relative_range_utf16.start;
25779 marked_range.start =
25780 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25781 marked_range.end =
25782 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25783 }
25784 }
25785 Some(marked_ranges)
25786 } else if let Some(range_utf16) = range_utf16 {
25787 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25788 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25789 Some(this.selection_replacement_ranges(range_utf16, cx))
25790 } else {
25791 None
25792 };
25793
25794 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25795 let newest_selection_id = this.selections.newest_anchor().id;
25796 this.selections
25797 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25798 .iter()
25799 .zip(ranges_to_replace.iter())
25800 .find_map(|(selection, range)| {
25801 if selection.id == newest_selection_id {
25802 Some(
25803 (range.start.0.0 as isize - selection.head().0.0 as isize)
25804 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25805 )
25806 } else {
25807 None
25808 }
25809 })
25810 });
25811
25812 cx.emit(EditorEvent::InputHandled {
25813 utf16_range_to_replace: range_to_replace,
25814 text: text.into(),
25815 });
25816
25817 if let Some(ranges) = ranges_to_replace {
25818 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25819 s.select_ranges(ranges)
25820 });
25821 }
25822
25823 let marked_ranges = {
25824 let snapshot = this.buffer.read(cx).read(cx);
25825 this.selections
25826 .disjoint_anchors_arc()
25827 .iter()
25828 .map(|selection| {
25829 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25830 })
25831 .collect::<Vec<_>>()
25832 };
25833
25834 if text.is_empty() {
25835 this.unmark_text(window, cx);
25836 } else {
25837 this.highlight_text::<InputComposition>(
25838 marked_ranges.clone(),
25839 HighlightStyle {
25840 underline: Some(UnderlineStyle {
25841 thickness: px(1.),
25842 color: None,
25843 wavy: false,
25844 }),
25845 ..Default::default()
25846 },
25847 cx,
25848 );
25849 }
25850
25851 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25852 let use_autoclose = this.use_autoclose;
25853 let use_auto_surround = this.use_auto_surround;
25854 this.set_use_autoclose(false);
25855 this.set_use_auto_surround(false);
25856 this.handle_input(text, window, cx);
25857 this.set_use_autoclose(use_autoclose);
25858 this.set_use_auto_surround(use_auto_surround);
25859
25860 if let Some(new_selected_range) = new_selected_range_utf16 {
25861 let snapshot = this.buffer.read(cx).read(cx);
25862 let new_selected_ranges = marked_ranges
25863 .into_iter()
25864 .map(|marked_range| {
25865 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25866 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25867 insertion_start.0 + new_selected_range.start,
25868 ));
25869 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25870 insertion_start.0 + new_selected_range.end,
25871 ));
25872 snapshot.clip_offset_utf16(new_start, Bias::Left)
25873 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25874 })
25875 .collect::<Vec<_>>();
25876
25877 drop(snapshot);
25878 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25879 selections.select_ranges(new_selected_ranges)
25880 });
25881 }
25882 });
25883
25884 self.ime_transaction = self.ime_transaction.or(transaction);
25885 if let Some(transaction) = self.ime_transaction {
25886 self.buffer.update(cx, |buffer, cx| {
25887 buffer.group_until_transaction(transaction, cx);
25888 });
25889 }
25890
25891 if self.text_highlights::<InputComposition>(cx).is_none() {
25892 self.ime_transaction.take();
25893 }
25894 }
25895
25896 fn bounds_for_range(
25897 &mut self,
25898 range_utf16: Range<usize>,
25899 element_bounds: gpui::Bounds<Pixels>,
25900 window: &mut Window,
25901 cx: &mut Context<Self>,
25902 ) -> Option<gpui::Bounds<Pixels>> {
25903 let text_layout_details = self.text_layout_details(window);
25904 let CharacterDimensions {
25905 em_width,
25906 em_advance,
25907 line_height,
25908 } = self.character_dimensions(window);
25909
25910 let snapshot = self.snapshot(window, cx);
25911 let scroll_position = snapshot.scroll_position();
25912 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25913
25914 let start =
25915 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25916 let x = Pixels::from(
25917 ScrollOffset::from(
25918 snapshot.x_for_display_point(start, &text_layout_details)
25919 + self.gutter_dimensions.full_width(),
25920 ) - scroll_left,
25921 );
25922 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25923
25924 Some(Bounds {
25925 origin: element_bounds.origin + point(x, y),
25926 size: size(em_width, line_height),
25927 })
25928 }
25929
25930 fn character_index_for_point(
25931 &mut self,
25932 point: gpui::Point<Pixels>,
25933 _window: &mut Window,
25934 _cx: &mut Context<Self>,
25935 ) -> Option<usize> {
25936 let position_map = self.last_position_map.as_ref()?;
25937 if !position_map.text_hitbox.contains(&point) {
25938 return None;
25939 }
25940 let display_point = position_map.point_for_position(point).previous_valid;
25941 let anchor = position_map
25942 .snapshot
25943 .display_point_to_anchor(display_point, Bias::Left);
25944 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25945 Some(utf16_offset.0.0)
25946 }
25947
25948 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25949 self.input_enabled
25950 }
25951}
25952
25953trait SelectionExt {
25954 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25955 fn spanned_rows(
25956 &self,
25957 include_end_if_at_line_start: bool,
25958 map: &DisplaySnapshot,
25959 ) -> Range<MultiBufferRow>;
25960}
25961
25962impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25963 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25964 let start = self
25965 .start
25966 .to_point(map.buffer_snapshot())
25967 .to_display_point(map);
25968 let end = self
25969 .end
25970 .to_point(map.buffer_snapshot())
25971 .to_display_point(map);
25972 if self.reversed {
25973 end..start
25974 } else {
25975 start..end
25976 }
25977 }
25978
25979 fn spanned_rows(
25980 &self,
25981 include_end_if_at_line_start: bool,
25982 map: &DisplaySnapshot,
25983 ) -> Range<MultiBufferRow> {
25984 let start = self.start.to_point(map.buffer_snapshot());
25985 let mut end = self.end.to_point(map.buffer_snapshot());
25986 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25987 end.row -= 1;
25988 }
25989
25990 let buffer_start = map.prev_line_boundary(start).0;
25991 let buffer_end = map.next_line_boundary(end).0;
25992 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25993 }
25994}
25995
25996impl<T: InvalidationRegion> InvalidationStack<T> {
25997 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25998 where
25999 S: Clone + ToOffset,
26000 {
26001 while let Some(region) = self.last() {
26002 let all_selections_inside_invalidation_ranges =
26003 if selections.len() == region.ranges().len() {
26004 selections
26005 .iter()
26006 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
26007 .all(|(selection, invalidation_range)| {
26008 let head = selection.head().to_offset(buffer);
26009 invalidation_range.start <= head && invalidation_range.end >= head
26010 })
26011 } else {
26012 false
26013 };
26014
26015 if all_selections_inside_invalidation_ranges {
26016 break;
26017 } else {
26018 self.pop();
26019 }
26020 }
26021 }
26022}
26023
26024impl<T> Default for InvalidationStack<T> {
26025 fn default() -> Self {
26026 Self(Default::default())
26027 }
26028}
26029
26030impl<T> Deref for InvalidationStack<T> {
26031 type Target = Vec<T>;
26032
26033 fn deref(&self) -> &Self::Target {
26034 &self.0
26035 }
26036}
26037
26038impl<T> DerefMut for InvalidationStack<T> {
26039 fn deref_mut(&mut self) -> &mut Self::Target {
26040 &mut self.0
26041 }
26042}
26043
26044impl InvalidationRegion for SnippetState {
26045 fn ranges(&self) -> &[Range<Anchor>] {
26046 &self.ranges[self.active_index]
26047 }
26048}
26049
26050fn edit_prediction_edit_text(
26051 current_snapshot: &BufferSnapshot,
26052 edits: &[(Range<Anchor>, impl AsRef<str>)],
26053 edit_preview: &EditPreview,
26054 include_deletions: bool,
26055 cx: &App,
26056) -> HighlightedText {
26057 let edits = edits
26058 .iter()
26059 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
26060 .collect::<Vec<_>>();
26061
26062 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
26063}
26064
26065fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
26066 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
26067 // Just show the raw edit text with basic styling
26068 let mut text = String::new();
26069 let mut highlights = Vec::new();
26070
26071 let insertion_highlight_style = HighlightStyle {
26072 color: Some(cx.theme().colors().text),
26073 ..Default::default()
26074 };
26075
26076 for (_, edit_text) in edits {
26077 let start_offset = text.len();
26078 text.push_str(edit_text);
26079 let end_offset = text.len();
26080
26081 if start_offset < end_offset {
26082 highlights.push((start_offset..end_offset, insertion_highlight_style));
26083 }
26084 }
26085
26086 HighlightedText {
26087 text: text.into(),
26088 highlights,
26089 }
26090}
26091
26092pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
26093 match severity {
26094 lsp::DiagnosticSeverity::ERROR => colors.error,
26095 lsp::DiagnosticSeverity::WARNING => colors.warning,
26096 lsp::DiagnosticSeverity::INFORMATION => colors.info,
26097 lsp::DiagnosticSeverity::HINT => colors.info,
26098 _ => colors.ignored,
26099 }
26100}
26101
26102pub fn styled_runs_for_code_label<'a>(
26103 label: &'a CodeLabel,
26104 syntax_theme: &'a theme::SyntaxTheme,
26105 local_player: &'a theme::PlayerColor,
26106) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
26107 let fade_out = HighlightStyle {
26108 fade_out: Some(0.35),
26109 ..Default::default()
26110 };
26111
26112 let mut prev_end = label.filter_range.end;
26113 label
26114 .runs
26115 .iter()
26116 .enumerate()
26117 .flat_map(move |(ix, (range, highlight_id))| {
26118 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
26119 HighlightStyle {
26120 color: Some(local_player.cursor),
26121 ..Default::default()
26122 }
26123 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
26124 HighlightStyle {
26125 background_color: Some(local_player.selection),
26126 ..Default::default()
26127 }
26128 } else if let Some(style) = highlight_id.style(syntax_theme) {
26129 style
26130 } else {
26131 return Default::default();
26132 };
26133 let muted_style = style.highlight(fade_out);
26134
26135 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
26136 if range.start >= label.filter_range.end {
26137 if range.start > prev_end {
26138 runs.push((prev_end..range.start, fade_out));
26139 }
26140 runs.push((range.clone(), muted_style));
26141 } else if range.end <= label.filter_range.end {
26142 runs.push((range.clone(), style));
26143 } else {
26144 runs.push((range.start..label.filter_range.end, style));
26145 runs.push((label.filter_range.end..range.end, muted_style));
26146 }
26147 prev_end = cmp::max(prev_end, range.end);
26148
26149 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
26150 runs.push((prev_end..label.text.len(), fade_out));
26151 }
26152
26153 runs
26154 })
26155}
26156
26157pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
26158 let mut prev_index = 0;
26159 let mut prev_codepoint: Option<char> = None;
26160 text.char_indices()
26161 .chain([(text.len(), '\0')])
26162 .filter_map(move |(index, codepoint)| {
26163 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26164 let is_boundary = index == text.len()
26165 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
26166 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
26167 if is_boundary {
26168 let chunk = &text[prev_index..index];
26169 prev_index = index;
26170 Some(chunk)
26171 } else {
26172 None
26173 }
26174 })
26175}
26176
26177/// Given a string of text immediately before the cursor, iterates over possible
26178/// strings a snippet could match to. More precisely: returns an iterator over
26179/// suffixes of `text` created by splitting at word boundaries (before & after
26180/// every non-word character).
26181///
26182/// Shorter suffixes are returned first.
26183pub(crate) fn snippet_candidate_suffixes(
26184 text: &str,
26185 is_word_char: impl Fn(char) -> bool,
26186) -> impl std::iter::Iterator<Item = &str> {
26187 let mut prev_index = text.len();
26188 let mut prev_codepoint = None;
26189 text.char_indices()
26190 .rev()
26191 .chain([(0, '\0')])
26192 .filter_map(move |(index, codepoint)| {
26193 let prev_index = std::mem::replace(&mut prev_index, index);
26194 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26195 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
26196 None
26197 } else {
26198 let chunk = &text[prev_index..]; // go to end of string
26199 Some(chunk)
26200 }
26201 })
26202}
26203
26204pub trait RangeToAnchorExt: Sized {
26205 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
26206
26207 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
26208 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
26209 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
26210 }
26211}
26212
26213impl<T: ToOffset> RangeToAnchorExt for Range<T> {
26214 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
26215 let start_offset = self.start.to_offset(snapshot);
26216 let end_offset = self.end.to_offset(snapshot);
26217 if start_offset == end_offset {
26218 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
26219 } else {
26220 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
26221 }
26222 }
26223}
26224
26225pub trait RowExt {
26226 fn as_f64(&self) -> f64;
26227
26228 fn next_row(&self) -> Self;
26229
26230 fn previous_row(&self) -> Self;
26231
26232 fn minus(&self, other: Self) -> u32;
26233}
26234
26235impl RowExt for DisplayRow {
26236 fn as_f64(&self) -> f64 {
26237 self.0 as _
26238 }
26239
26240 fn next_row(&self) -> Self {
26241 Self(self.0 + 1)
26242 }
26243
26244 fn previous_row(&self) -> Self {
26245 Self(self.0.saturating_sub(1))
26246 }
26247
26248 fn minus(&self, other: Self) -> u32 {
26249 self.0 - other.0
26250 }
26251}
26252
26253impl RowExt for MultiBufferRow {
26254 fn as_f64(&self) -> f64 {
26255 self.0 as _
26256 }
26257
26258 fn next_row(&self) -> Self {
26259 Self(self.0 + 1)
26260 }
26261
26262 fn previous_row(&self) -> Self {
26263 Self(self.0.saturating_sub(1))
26264 }
26265
26266 fn minus(&self, other: Self) -> u32 {
26267 self.0 - other.0
26268 }
26269}
26270
26271trait RowRangeExt {
26272 type Row;
26273
26274 fn len(&self) -> usize;
26275
26276 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
26277}
26278
26279impl RowRangeExt for Range<MultiBufferRow> {
26280 type Row = MultiBufferRow;
26281
26282 fn len(&self) -> usize {
26283 (self.end.0 - self.start.0) as usize
26284 }
26285
26286 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
26287 (self.start.0..self.end.0).map(MultiBufferRow)
26288 }
26289}
26290
26291impl RowRangeExt for Range<DisplayRow> {
26292 type Row = DisplayRow;
26293
26294 fn len(&self) -> usize {
26295 (self.end.0 - self.start.0) as usize
26296 }
26297
26298 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
26299 (self.start.0..self.end.0).map(DisplayRow)
26300 }
26301}
26302
26303/// If select range has more than one line, we
26304/// just point the cursor to range.start.
26305fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
26306 if range.start.row == range.end.row {
26307 range
26308 } else {
26309 range.start..range.start
26310 }
26311}
26312pub struct KillRing(ClipboardItem);
26313impl Global for KillRing {}
26314
26315const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
26316
26317enum BreakpointPromptEditAction {
26318 Log,
26319 Condition,
26320 HitCondition,
26321}
26322
26323struct BreakpointPromptEditor {
26324 pub(crate) prompt: Entity<Editor>,
26325 editor: WeakEntity<Editor>,
26326 breakpoint_anchor: Anchor,
26327 breakpoint: Breakpoint,
26328 edit_action: BreakpointPromptEditAction,
26329 block_ids: HashSet<CustomBlockId>,
26330 editor_margins: Arc<Mutex<EditorMargins>>,
26331 _subscriptions: Vec<Subscription>,
26332}
26333
26334impl BreakpointPromptEditor {
26335 const MAX_LINES: u8 = 4;
26336
26337 fn new(
26338 editor: WeakEntity<Editor>,
26339 breakpoint_anchor: Anchor,
26340 breakpoint: Breakpoint,
26341 edit_action: BreakpointPromptEditAction,
26342 window: &mut Window,
26343 cx: &mut Context<Self>,
26344 ) -> Self {
26345 let base_text = match edit_action {
26346 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
26347 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
26348 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
26349 }
26350 .map(|msg| msg.to_string())
26351 .unwrap_or_default();
26352
26353 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
26354 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26355
26356 let prompt = cx.new(|cx| {
26357 let mut prompt = Editor::new(
26358 EditorMode::AutoHeight {
26359 min_lines: 1,
26360 max_lines: Some(Self::MAX_LINES as usize),
26361 },
26362 buffer,
26363 None,
26364 window,
26365 cx,
26366 );
26367 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
26368 prompt.set_show_cursor_when_unfocused(false, cx);
26369 prompt.set_placeholder_text(
26370 match edit_action {
26371 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
26372 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
26373 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
26374 },
26375 window,
26376 cx,
26377 );
26378
26379 prompt
26380 });
26381
26382 Self {
26383 prompt,
26384 editor,
26385 breakpoint_anchor,
26386 breakpoint,
26387 edit_action,
26388 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
26389 block_ids: Default::default(),
26390 _subscriptions: vec![],
26391 }
26392 }
26393
26394 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
26395 self.block_ids.extend(block_ids)
26396 }
26397
26398 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
26399 if let Some(editor) = self.editor.upgrade() {
26400 let message = self
26401 .prompt
26402 .read(cx)
26403 .buffer
26404 .read(cx)
26405 .as_singleton()
26406 .expect("A multi buffer in breakpoint prompt isn't possible")
26407 .read(cx)
26408 .as_rope()
26409 .to_string();
26410
26411 editor.update(cx, |editor, cx| {
26412 editor.edit_breakpoint_at_anchor(
26413 self.breakpoint_anchor,
26414 self.breakpoint.clone(),
26415 match self.edit_action {
26416 BreakpointPromptEditAction::Log => {
26417 BreakpointEditAction::EditLogMessage(message.into())
26418 }
26419 BreakpointPromptEditAction::Condition => {
26420 BreakpointEditAction::EditCondition(message.into())
26421 }
26422 BreakpointPromptEditAction::HitCondition => {
26423 BreakpointEditAction::EditHitCondition(message.into())
26424 }
26425 },
26426 cx,
26427 );
26428
26429 editor.remove_blocks(self.block_ids.clone(), None, cx);
26430 cx.focus_self(window);
26431 });
26432 }
26433 }
26434
26435 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
26436 self.editor
26437 .update(cx, |editor, cx| {
26438 editor.remove_blocks(self.block_ids.clone(), None, cx);
26439 window.focus(&editor.focus_handle, cx);
26440 })
26441 .log_err();
26442 }
26443
26444 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
26445 let settings = ThemeSettings::get_global(cx);
26446 let text_style = TextStyle {
26447 color: if self.prompt.read(cx).read_only(cx) {
26448 cx.theme().colors().text_disabled
26449 } else {
26450 cx.theme().colors().text
26451 },
26452 font_family: settings.buffer_font.family.clone(),
26453 font_fallbacks: settings.buffer_font.fallbacks.clone(),
26454 font_size: settings.buffer_font_size(cx).into(),
26455 font_weight: settings.buffer_font.weight,
26456 line_height: relative(settings.buffer_line_height.value()),
26457 ..Default::default()
26458 };
26459 EditorElement::new(
26460 &self.prompt,
26461 EditorStyle {
26462 background: cx.theme().colors().editor_background,
26463 local_player: cx.theme().players().local(),
26464 text: text_style,
26465 ..Default::default()
26466 },
26467 )
26468 }
26469}
26470
26471impl Render for BreakpointPromptEditor {
26472 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26473 let editor_margins = *self.editor_margins.lock();
26474 let gutter_dimensions = editor_margins.gutter;
26475 h_flex()
26476 .key_context("Editor")
26477 .bg(cx.theme().colors().editor_background)
26478 .border_y_1()
26479 .border_color(cx.theme().status().info_border)
26480 .size_full()
26481 .py(window.line_height() / 2.5)
26482 .on_action(cx.listener(Self::confirm))
26483 .on_action(cx.listener(Self::cancel))
26484 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
26485 .child(div().flex_1().child(self.render_prompt_editor(cx)))
26486 }
26487}
26488
26489impl Focusable for BreakpointPromptEditor {
26490 fn focus_handle(&self, cx: &App) -> FocusHandle {
26491 self.prompt.focus_handle(cx)
26492 }
26493}
26494
26495fn all_edits_insertions_or_deletions(
26496 edits: &Vec<(Range<Anchor>, Arc<str>)>,
26497 snapshot: &MultiBufferSnapshot,
26498) -> bool {
26499 let mut all_insertions = true;
26500 let mut all_deletions = true;
26501
26502 for (range, new_text) in edits.iter() {
26503 let range_is_empty = range.to_offset(snapshot).is_empty();
26504 let text_is_empty = new_text.is_empty();
26505
26506 if range_is_empty != text_is_empty {
26507 if range_is_empty {
26508 all_deletions = false;
26509 } else {
26510 all_insertions = false;
26511 }
26512 } else {
26513 return false;
26514 }
26515
26516 if !all_insertions && !all_deletions {
26517 return false;
26518 }
26519 }
26520 all_insertions || all_deletions
26521}
26522
26523struct MissingEditPredictionKeybindingTooltip;
26524
26525impl Render for MissingEditPredictionKeybindingTooltip {
26526 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26527 ui::tooltip_container(cx, |container, cx| {
26528 container
26529 .flex_shrink_0()
26530 .max_w_80()
26531 .min_h(rems_from_px(124.))
26532 .justify_between()
26533 .child(
26534 v_flex()
26535 .flex_1()
26536 .text_ui_sm(cx)
26537 .child(Label::new("Conflict with Accept Keybinding"))
26538 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26539 )
26540 .child(
26541 h_flex()
26542 .pb_1()
26543 .gap_1()
26544 .items_end()
26545 .w_full()
26546 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26547 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26548 }))
26549 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26550 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26551 })),
26552 )
26553 })
26554 }
26555}
26556
26557#[derive(Debug, Clone, Copy, PartialEq)]
26558pub struct LineHighlight {
26559 pub background: Background,
26560 pub border: Option<gpui::Hsla>,
26561 pub include_gutter: bool,
26562 pub type_id: Option<TypeId>,
26563}
26564
26565struct LineManipulationResult {
26566 pub new_text: String,
26567 pub line_count_before: usize,
26568 pub line_count_after: usize,
26569}
26570
26571fn render_diff_hunk_controls(
26572 row: u32,
26573 status: &DiffHunkStatus,
26574 hunk_range: Range<Anchor>,
26575 is_created_file: bool,
26576 line_height: Pixels,
26577 editor: &Entity<Editor>,
26578 _window: &mut Window,
26579 cx: &mut App,
26580) -> AnyElement {
26581 h_flex()
26582 .h(line_height)
26583 .mr_1()
26584 .gap_1()
26585 .px_0p5()
26586 .pb_1()
26587 .border_x_1()
26588 .border_b_1()
26589 .border_color(cx.theme().colors().border_variant)
26590 .rounded_b_lg()
26591 .bg(cx.theme().colors().editor_background)
26592 .gap_1()
26593 .block_mouse_except_scroll()
26594 .shadow_md()
26595 .child(if status.has_secondary_hunk() {
26596 Button::new(("stage", row as u64), "Stage")
26597 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26598 .tooltip({
26599 let focus_handle = editor.focus_handle(cx);
26600 move |_window, cx| {
26601 Tooltip::for_action_in(
26602 "Stage Hunk",
26603 &::git::ToggleStaged,
26604 &focus_handle,
26605 cx,
26606 )
26607 }
26608 })
26609 .on_click({
26610 let editor = editor.clone();
26611 move |_event, _window, cx| {
26612 editor.update(cx, |editor, cx| {
26613 editor.stage_or_unstage_diff_hunks(
26614 true,
26615 vec![hunk_range.start..hunk_range.start],
26616 cx,
26617 );
26618 });
26619 }
26620 })
26621 } else {
26622 Button::new(("unstage", row as u64), "Unstage")
26623 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26624 .tooltip({
26625 let focus_handle = editor.focus_handle(cx);
26626 move |_window, cx| {
26627 Tooltip::for_action_in(
26628 "Unstage Hunk",
26629 &::git::ToggleStaged,
26630 &focus_handle,
26631 cx,
26632 )
26633 }
26634 })
26635 .on_click({
26636 let editor = editor.clone();
26637 move |_event, _window, cx| {
26638 editor.update(cx, |editor, cx| {
26639 editor.stage_or_unstage_diff_hunks(
26640 false,
26641 vec![hunk_range.start..hunk_range.start],
26642 cx,
26643 );
26644 });
26645 }
26646 })
26647 })
26648 .child(
26649 Button::new(("restore", row as u64), "Restore")
26650 .tooltip({
26651 let focus_handle = editor.focus_handle(cx);
26652 move |_window, cx| {
26653 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26654 }
26655 })
26656 .on_click({
26657 let editor = editor.clone();
26658 move |_event, window, cx| {
26659 editor.update(cx, |editor, cx| {
26660 let snapshot = editor.snapshot(window, cx);
26661 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26662 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26663 });
26664 }
26665 })
26666 .disabled(is_created_file),
26667 )
26668 .when(
26669 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26670 |el| {
26671 el.child(
26672 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26673 .shape(IconButtonShape::Square)
26674 .icon_size(IconSize::Small)
26675 // .disabled(!has_multiple_hunks)
26676 .tooltip({
26677 let focus_handle = editor.focus_handle(cx);
26678 move |_window, cx| {
26679 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26680 }
26681 })
26682 .on_click({
26683 let editor = editor.clone();
26684 move |_event, window, cx| {
26685 editor.update(cx, |editor, cx| {
26686 let snapshot = editor.snapshot(window, cx);
26687 let position =
26688 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26689 editor.go_to_hunk_before_or_after_position(
26690 &snapshot,
26691 position,
26692 Direction::Next,
26693 window,
26694 cx,
26695 );
26696 editor.expand_selected_diff_hunks(cx);
26697 });
26698 }
26699 }),
26700 )
26701 .child(
26702 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26703 .shape(IconButtonShape::Square)
26704 .icon_size(IconSize::Small)
26705 // .disabled(!has_multiple_hunks)
26706 .tooltip({
26707 let focus_handle = editor.focus_handle(cx);
26708 move |_window, cx| {
26709 Tooltip::for_action_in(
26710 "Previous Hunk",
26711 &GoToPreviousHunk,
26712 &focus_handle,
26713 cx,
26714 )
26715 }
26716 })
26717 .on_click({
26718 let editor = editor.clone();
26719 move |_event, window, cx| {
26720 editor.update(cx, |editor, cx| {
26721 let snapshot = editor.snapshot(window, cx);
26722 let point =
26723 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26724 editor.go_to_hunk_before_or_after_position(
26725 &snapshot,
26726 point,
26727 Direction::Prev,
26728 window,
26729 cx,
26730 );
26731 editor.expand_selected_diff_hunks(cx);
26732 });
26733 }
26734 }),
26735 )
26736 },
26737 )
26738 .into_any_element()
26739}
26740
26741pub fn multibuffer_context_lines(cx: &App) -> u32 {
26742 EditorSettings::try_get(cx)
26743 .map(|settings| settings.excerpt_context_lines)
26744 .unwrap_or(2)
26745 .min(32)
26746}