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};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use inlays::Inlay;
65pub use items::MAX_TAB_TITLE_LEN;
66pub use lsp::CompletionContext;
67pub use lsp_ext::lsp_tasks;
68pub use multi_buffer::{
69 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
70 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
71 ToPoint,
72};
73pub use split::SplittableEditor;
74pub use text::Bias;
75
76use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
77use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
78use anyhow::{Context as _, Result, anyhow, bail};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction_types::{
92 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionGranularity,
93};
94use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
95use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
96use futures::{
97 FutureExt, StreamExt as _,
98 future::{self, Shared, join},
99 stream::FuturesUnordered,
100};
101use fuzzy::{StringMatch, StringMatchCandidate};
102use git::blame::{GitBlame, GlobalBlameRenderer};
103use gpui::{
104 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
105 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
106 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
107 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
108 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
109 Render, ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextRun,
110 TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
111 WeakEntity, WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative,
112 size,
113};
114use hover_links::{HoverLink, HoveredLinkState, find_file};
115use hover_popover::{HoverState, hide_hover};
116use indent_guides::ActiveIndentGuidesState;
117use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
118use itertools::{Either, Itertools};
119use language::{
120 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
121 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
122 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
123 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
124 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
125 TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143};
144use parking_lot::Mutex;
145use persistence::DB;
146use project::{
147 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
148 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
149 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
150 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
151 debugger::{
152 breakpoint_store::{
153 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
154 BreakpointStore, BreakpointStoreEvent,
155 },
156 session::{Session, SessionEvent},
157 },
158 git_store::GitStoreEvent,
159 lsp_store::{
160 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
161 OpenLspBufferHandle,
162 },
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use regex::Regex;
167use rpc::{ErrorCode, ErrorExt, proto::PeerId};
168use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
169use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
170use serde::{Deserialize, Serialize};
171use settings::{
172 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
173 update_settings_file,
174};
175use smallvec::{SmallVec, smallvec};
176use snippet::Snippet;
177use std::{
178 any::{Any, TypeId},
179 borrow::Cow,
180 cell::{OnceCell, RefCell},
181 cmp::{self, Ordering, Reverse},
182 collections::hash_map,
183 iter::{self, Peekable},
184 mem,
185 num::NonZeroU32,
186 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
187 path::{Path, PathBuf},
188 rc::Rc,
189 sync::Arc,
190 time::{Duration, Instant},
191};
192use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
193use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
194use theme::{
195 AccentColors, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
196 observe_buffer_font_size_adjustment,
197};
198use ui::{
199 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
200 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
201};
202use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
203use workspace::{
204 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
205 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
206 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
207 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
208 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
209 searchable::SearchEvent,
210};
211
212use crate::{
213 code_context_menus::CompletionsMenuSource,
214 editor_settings::MultiCursorModifier,
215 hover_links::{find_url, find_url_from_range},
216 inlays::{
217 InlineValueCache,
218 inlay_hints::{LspInlayHintData, inlay_hint_settings},
219 },
220 scroll::{ScrollOffset, ScrollPixelOffset},
221 selections_collection::resolve_selections_wrapping_blocks,
222 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
223};
224
225pub const FILE_HEADER_HEIGHT: u32 = 2;
226pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
227const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
228const MAX_LINE_LEN: usize = 1024;
229const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
230const MAX_SELECTION_HISTORY_LEN: usize = 1024;
231pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
232#[doc(hidden)]
233pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
234pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
235
236pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
237pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
239pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
240
241pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
242pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
243pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
244
245pub type RenderDiffHunkControlsFn = Arc<
246 dyn Fn(
247 u32,
248 &DiffHunkStatus,
249 Range<Anchor>,
250 bool,
251 Pixels,
252 &Entity<Editor>,
253 &mut Window,
254 &mut App,
255 ) -> AnyElement,
256>;
257
258enum ReportEditorEvent {
259 Saved { auto_saved: bool },
260 EditorOpened,
261 Closed,
262}
263
264impl ReportEditorEvent {
265 pub fn event_type(&self) -> &'static str {
266 match self {
267 Self::Saved { .. } => "Editor Saved",
268 Self::EditorOpened => "Editor Opened",
269 Self::Closed => "Editor Closed",
270 }
271 }
272}
273
274pub enum ActiveDebugLine {}
275pub enum DebugStackFrameLine {}
276enum DocumentHighlightRead {}
277enum DocumentHighlightWrite {}
278enum InputComposition {}
279pub enum PendingInput {}
280enum SelectedTextHighlight {}
281
282pub enum ConflictsOuter {}
283pub enum ConflictsOurs {}
284pub enum ConflictsTheirs {}
285pub enum ConflictsOursMarker {}
286pub enum ConflictsTheirsMarker {}
287
288pub struct HunkAddedColor;
289pub struct HunkRemovedColor;
290
291#[derive(Debug, Copy, Clone, PartialEq, Eq)]
292pub enum Navigated {
293 Yes,
294 No,
295}
296
297impl Navigated {
298 pub fn from_bool(yes: bool) -> Navigated {
299 if yes { Navigated::Yes } else { Navigated::No }
300 }
301}
302
303#[derive(Debug, Clone, PartialEq, Eq)]
304enum DisplayDiffHunk {
305 Folded {
306 display_row: DisplayRow,
307 },
308 Unfolded {
309 is_created_file: bool,
310 diff_base_byte_range: Range<usize>,
311 display_row_range: Range<DisplayRow>,
312 multi_buffer_range: Range<Anchor>,
313 status: DiffHunkStatus,
314 word_diffs: Vec<Range<MultiBufferOffset>>,
315 },
316}
317
318pub enum HideMouseCursorOrigin {
319 TypingAction,
320 MovementAction,
321}
322
323pub fn init(cx: &mut App) {
324 cx.set_global(GlobalBlameRenderer(Arc::new(())));
325
326 workspace::register_project_item::<Editor>(cx);
327 workspace::FollowableViewRegistry::register::<Editor>(cx);
328 workspace::register_serializable_item::<Editor>(cx);
329
330 cx.observe_new(
331 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
332 workspace.register_action(Editor::new_file);
333 workspace.register_action(Editor::new_file_split);
334 workspace.register_action(Editor::new_file_vertical);
335 workspace.register_action(Editor::new_file_horizontal);
336 workspace.register_action(Editor::cancel_language_server_work);
337 workspace.register_action(Editor::toggle_focus);
338 },
339 )
340 .detach();
341
342 cx.on_action(move |_: &workspace::NewFile, cx| {
343 let app_state = workspace::AppState::global(cx);
344 if let Some(app_state) = app_state.upgrade() {
345 workspace::open_new(
346 Default::default(),
347 app_state,
348 cx,
349 |workspace, window, cx| {
350 Editor::new_file(workspace, &Default::default(), window, cx)
351 },
352 )
353 .detach();
354 }
355 })
356 .on_action(move |_: &workspace::NewWindow, cx| {
357 let app_state = workspace::AppState::global(cx);
358 if let Some(app_state) = app_state.upgrade() {
359 workspace::open_new(
360 Default::default(),
361 app_state,
362 cx,
363 |workspace, window, cx| {
364 cx.activate(true);
365 Editor::new_file(workspace, &Default::default(), window, cx)
366 },
367 )
368 .detach();
369 }
370 });
371}
372
373pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
374 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
375}
376
377pub trait DiagnosticRenderer {
378 fn render_group(
379 &self,
380 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
381 buffer_id: BufferId,
382 snapshot: EditorSnapshot,
383 editor: WeakEntity<Editor>,
384 language_registry: Option<Arc<LanguageRegistry>>,
385 cx: &mut App,
386 ) -> Vec<BlockProperties<Anchor>>;
387
388 fn render_hover(
389 &self,
390 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
391 range: Range<Point>,
392 buffer_id: BufferId,
393 language_registry: Option<Arc<LanguageRegistry>>,
394 cx: &mut App,
395 ) -> Option<Entity<markdown::Markdown>>;
396
397 fn open_link(
398 &self,
399 editor: &mut Editor,
400 link: SharedString,
401 window: &mut Window,
402 cx: &mut Context<Editor>,
403 );
404}
405
406pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
407
408impl GlobalDiagnosticRenderer {
409 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
410 cx.try_global::<Self>().map(|g| g.0.clone())
411 }
412}
413
414impl gpui::Global for GlobalDiagnosticRenderer {}
415pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
416 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
417}
418
419pub struct SearchWithinRange;
420
421trait InvalidationRegion {
422 fn ranges(&self) -> &[Range<Anchor>];
423}
424
425#[derive(Clone, Debug, PartialEq)]
426pub enum SelectPhase {
427 Begin {
428 position: DisplayPoint,
429 add: bool,
430 click_count: usize,
431 },
432 BeginColumnar {
433 position: DisplayPoint,
434 reset: bool,
435 mode: ColumnarMode,
436 goal_column: u32,
437 },
438 Extend {
439 position: DisplayPoint,
440 click_count: usize,
441 },
442 Update {
443 position: DisplayPoint,
444 goal_column: u32,
445 scroll_delta: gpui::Point<f32>,
446 },
447 End,
448}
449
450#[derive(Clone, Debug, PartialEq)]
451pub enum ColumnarMode {
452 FromMouse,
453 FromSelection,
454}
455
456#[derive(Clone, Debug)]
457pub enum SelectMode {
458 Character,
459 Word(Range<Anchor>),
460 Line(Range<Anchor>),
461 All,
462}
463
464#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
465pub enum SizingBehavior {
466 /// The editor will layout itself using `size_full` and will include the vertical
467 /// scroll margin as requested by user settings.
468 #[default]
469 Default,
470 /// The editor will layout itself using `size_full`, but will not have any
471 /// vertical overscroll.
472 ExcludeOverscrollMargin,
473 /// The editor will request a vertical size according to its content and will be
474 /// layouted without a vertical scroll margin.
475 SizeByContent,
476}
477
478#[derive(Clone, PartialEq, Eq, Debug)]
479pub enum EditorMode {
480 SingleLine,
481 AutoHeight {
482 min_lines: usize,
483 max_lines: Option<usize>,
484 },
485 Full {
486 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
487 scale_ui_elements_with_buffer_font_size: bool,
488 /// When set to `true`, the editor will render a background for the active line.
489 show_active_line_background: bool,
490 /// Determines the sizing behavior for this editor
491 sizing_behavior: SizingBehavior,
492 },
493 Minimap {
494 parent: WeakEntity<Editor>,
495 },
496}
497
498impl EditorMode {
499 pub fn full() -> Self {
500 Self::Full {
501 scale_ui_elements_with_buffer_font_size: true,
502 show_active_line_background: true,
503 sizing_behavior: SizingBehavior::Default,
504 }
505 }
506
507 #[inline]
508 pub fn is_full(&self) -> bool {
509 matches!(self, Self::Full { .. })
510 }
511
512 #[inline]
513 pub fn is_single_line(&self) -> bool {
514 matches!(self, Self::SingleLine { .. })
515 }
516
517 #[inline]
518 fn is_minimap(&self) -> bool {
519 matches!(self, Self::Minimap { .. })
520 }
521}
522
523#[derive(Copy, Clone, Debug)]
524pub enum SoftWrap {
525 /// Prefer not to wrap at all.
526 ///
527 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
528 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
529 GitDiff,
530 /// Prefer a single line generally, unless an overly long line is encountered.
531 None,
532 /// Soft wrap lines that exceed the editor width.
533 EditorWidth,
534 /// Soft wrap lines at the preferred line length.
535 Column(u32),
536 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
537 Bounded(u32),
538}
539
540#[derive(Clone)]
541pub struct EditorStyle {
542 pub background: Hsla,
543 pub border: Hsla,
544 pub local_player: PlayerColor,
545 pub text: TextStyle,
546 pub scrollbar_width: Pixels,
547 pub syntax: Arc<SyntaxTheme>,
548 pub status: StatusColors,
549 pub inlay_hints_style: HighlightStyle,
550 pub edit_prediction_styles: EditPredictionStyles,
551 pub unnecessary_code_fade: f32,
552 pub show_underlines: bool,
553}
554
555impl Default for EditorStyle {
556 fn default() -> Self {
557 Self {
558 background: Hsla::default(),
559 border: Hsla::default(),
560 local_player: PlayerColor::default(),
561 text: TextStyle::default(),
562 scrollbar_width: Pixels::default(),
563 syntax: Default::default(),
564 // HACK: Status colors don't have a real default.
565 // We should look into removing the status colors from the editor
566 // style and retrieve them directly from the theme.
567 status: StatusColors::dark(),
568 inlay_hints_style: HighlightStyle::default(),
569 edit_prediction_styles: EditPredictionStyles {
570 insertion: HighlightStyle::default(),
571 whitespace: HighlightStyle::default(),
572 },
573 unnecessary_code_fade: Default::default(),
574 show_underlines: true,
575 }
576 }
577}
578
579pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
580 let show_background = language_settings::language_settings(None, None, cx)
581 .inlay_hints
582 .show_background;
583
584 let mut style = cx.theme().syntax().get("hint");
585
586 if style.color.is_none() {
587 style.color = Some(cx.theme().status().hint);
588 }
589
590 if !show_background {
591 style.background_color = None;
592 return style;
593 }
594
595 if style.background_color.is_none() {
596 style.background_color = Some(cx.theme().status().hint_background);
597 }
598
599 style
600}
601
602pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
603 EditPredictionStyles {
604 insertion: HighlightStyle {
605 color: Some(cx.theme().status().predictive),
606 ..HighlightStyle::default()
607 },
608 whitespace: HighlightStyle {
609 background_color: Some(cx.theme().status().created_background),
610 ..HighlightStyle::default()
611 },
612 }
613}
614
615type CompletionId = usize;
616
617pub(crate) enum EditDisplayMode {
618 TabAccept,
619 DiffPopover,
620 Inline,
621}
622
623enum EditPrediction {
624 Edit {
625 edits: Vec<(Range<Anchor>, Arc<str>)>,
626 edit_preview: Option<EditPreview>,
627 display_mode: EditDisplayMode,
628 snapshot: BufferSnapshot,
629 },
630 /// Move to a specific location in the active editor
631 MoveWithin {
632 target: Anchor,
633 snapshot: BufferSnapshot,
634 },
635 /// Move to a specific location in a different editor (not the active one)
636 MoveOutside {
637 target: language::Anchor,
638 snapshot: BufferSnapshot,
639 },
640}
641
642struct EditPredictionState {
643 inlay_ids: Vec<InlayId>,
644 completion: EditPrediction,
645 completion_id: Option<SharedString>,
646 invalidation_range: Option<Range<Anchor>>,
647}
648
649enum EditPredictionSettings {
650 Disabled,
651 Enabled {
652 show_in_menu: bool,
653 preview_requires_modifier: bool,
654 },
655}
656
657enum EditPredictionHighlight {}
658
659#[derive(Debug, Clone)]
660struct InlineDiagnostic {
661 message: SharedString,
662 group_id: usize,
663 is_primary: bool,
664 start: Point,
665 severity: lsp::DiagnosticSeverity,
666}
667
668pub enum MenuEditPredictionsPolicy {
669 Never,
670 ByProvider,
671}
672
673pub enum EditPredictionPreview {
674 /// Modifier is not pressed
675 Inactive { released_too_fast: bool },
676 /// Modifier pressed
677 Active {
678 since: Instant,
679 previous_scroll_position: Option<ScrollAnchor>,
680 },
681}
682
683impl EditPredictionPreview {
684 pub fn released_too_fast(&self) -> bool {
685 match self {
686 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
687 EditPredictionPreview::Active { .. } => false,
688 }
689 }
690
691 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
692 if let EditPredictionPreview::Active {
693 previous_scroll_position,
694 ..
695 } = self
696 {
697 *previous_scroll_position = scroll_position;
698 }
699 }
700}
701
702pub struct ContextMenuOptions {
703 pub min_entries_visible: usize,
704 pub max_entries_visible: usize,
705 pub placement: Option<ContextMenuPlacement>,
706}
707
708#[derive(Debug, Clone, PartialEq, Eq)]
709pub enum ContextMenuPlacement {
710 Above,
711 Below,
712}
713
714#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
715struct EditorActionId(usize);
716
717impl EditorActionId {
718 pub fn post_inc(&mut self) -> Self {
719 let answer = self.0;
720
721 *self = Self(answer + 1);
722
723 Self(answer)
724 }
725}
726
727// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
728// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
729
730type BackgroundHighlight = (
731 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
732 Arc<[Range<Anchor>]>,
733);
734type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
735
736#[derive(Default)]
737struct ScrollbarMarkerState {
738 scrollbar_size: Size<Pixels>,
739 dirty: bool,
740 markers: Arc<[PaintQuad]>,
741 pending_refresh: Option<Task<Result<()>>>,
742}
743
744impl ScrollbarMarkerState {
745 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
746 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
747 }
748}
749
750#[derive(Clone, Copy, PartialEq, Eq)]
751pub enum MinimapVisibility {
752 Disabled,
753 Enabled {
754 /// The configuration currently present in the users settings.
755 setting_configuration: bool,
756 /// Whether to override the currently set visibility from the users setting.
757 toggle_override: bool,
758 },
759}
760
761impl MinimapVisibility {
762 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
763 if mode.is_full() {
764 Self::Enabled {
765 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
766 toggle_override: false,
767 }
768 } else {
769 Self::Disabled
770 }
771 }
772
773 fn hidden(&self) -> Self {
774 match *self {
775 Self::Enabled {
776 setting_configuration,
777 ..
778 } => Self::Enabled {
779 setting_configuration,
780 toggle_override: setting_configuration,
781 },
782 Self::Disabled => Self::Disabled,
783 }
784 }
785
786 fn disabled(&self) -> bool {
787 matches!(*self, Self::Disabled)
788 }
789
790 fn settings_visibility(&self) -> bool {
791 match *self {
792 Self::Enabled {
793 setting_configuration,
794 ..
795 } => setting_configuration,
796 _ => false,
797 }
798 }
799
800 fn visible(&self) -> bool {
801 match *self {
802 Self::Enabled {
803 setting_configuration,
804 toggle_override,
805 } => setting_configuration ^ toggle_override,
806 _ => false,
807 }
808 }
809
810 fn toggle_visibility(&self) -> Self {
811 match *self {
812 Self::Enabled {
813 toggle_override,
814 setting_configuration,
815 } => Self::Enabled {
816 setting_configuration,
817 toggle_override: !toggle_override,
818 },
819 Self::Disabled => Self::Disabled,
820 }
821 }
822}
823
824#[derive(Debug, Clone, Copy, PartialEq, Eq)]
825pub enum BufferSerialization {
826 All,
827 NonDirtyBuffers,
828}
829
830impl BufferSerialization {
831 fn new(restore_unsaved_buffers: bool) -> Self {
832 if restore_unsaved_buffers {
833 Self::All
834 } else {
835 Self::NonDirtyBuffers
836 }
837 }
838}
839
840#[derive(Clone, Debug)]
841struct RunnableTasks {
842 templates: Vec<(TaskSourceKind, TaskTemplate)>,
843 offset: multi_buffer::Anchor,
844 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
845 column: u32,
846 // Values of all named captures, including those starting with '_'
847 extra_variables: HashMap<String, String>,
848 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
849 context_range: Range<BufferOffset>,
850}
851
852impl RunnableTasks {
853 fn resolve<'a>(
854 &'a self,
855 cx: &'a task::TaskContext,
856 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
857 self.templates.iter().filter_map(|(kind, template)| {
858 template
859 .resolve_task(&kind.to_id_base(), cx)
860 .map(|task| (kind.clone(), task))
861 })
862 }
863}
864
865#[derive(Clone)]
866pub struct ResolvedTasks {
867 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
868 position: Anchor,
869}
870
871/// Addons allow storing per-editor state in other crates (e.g. Vim)
872pub trait Addon: 'static {
873 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
874
875 fn render_buffer_header_controls(
876 &self,
877 _: &ExcerptInfo,
878 _: &Window,
879 _: &App,
880 ) -> Option<AnyElement> {
881 None
882 }
883
884 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
885 None
886 }
887
888 fn to_any(&self) -> &dyn std::any::Any;
889
890 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
891 None
892 }
893}
894
895struct ChangeLocation {
896 current: Option<Vec<Anchor>>,
897 original: Vec<Anchor>,
898}
899impl ChangeLocation {
900 fn locations(&self) -> &[Anchor] {
901 self.current.as_ref().unwrap_or(&self.original)
902 }
903}
904
905/// A set of caret positions, registered when the editor was edited.
906pub struct ChangeList {
907 changes: Vec<ChangeLocation>,
908 /// Currently "selected" change.
909 position: Option<usize>,
910}
911
912impl ChangeList {
913 pub fn new() -> Self {
914 Self {
915 changes: Vec::new(),
916 position: None,
917 }
918 }
919
920 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
921 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
922 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
923 if self.changes.is_empty() {
924 return None;
925 }
926
927 let prev = self.position.unwrap_or(self.changes.len());
928 let next = if direction == Direction::Prev {
929 prev.saturating_sub(count)
930 } else {
931 (prev + count).min(self.changes.len() - 1)
932 };
933 self.position = Some(next);
934 self.changes.get(next).map(|change| change.locations())
935 }
936
937 /// Adds a new change to the list, resetting the change list position.
938 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
939 self.position.take();
940 if let Some(last) = self.changes.last_mut()
941 && group
942 {
943 last.current = Some(new_positions)
944 } else {
945 self.changes.push(ChangeLocation {
946 original: new_positions,
947 current: None,
948 });
949 }
950 }
951
952 pub fn last(&self) -> Option<&[Anchor]> {
953 self.changes.last().map(|change| change.locations())
954 }
955
956 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
957 self.changes.last().map(|change| change.original.as_slice())
958 }
959
960 pub fn invert_last_group(&mut self) {
961 if let Some(last) = self.changes.last_mut()
962 && let Some(current) = last.current.as_mut()
963 {
964 mem::swap(&mut last.original, current);
965 }
966 }
967}
968
969#[derive(Clone)]
970struct InlineBlamePopoverState {
971 scroll_handle: ScrollHandle,
972 commit_message: Option<ParsedCommitMessage>,
973 markdown: Entity<Markdown>,
974}
975
976struct InlineBlamePopover {
977 position: gpui::Point<Pixels>,
978 hide_task: Option<Task<()>>,
979 popover_bounds: Option<Bounds<Pixels>>,
980 popover_state: InlineBlamePopoverState,
981 keyboard_grace: bool,
982}
983
984enum SelectionDragState {
985 /// State when no drag related activity is detected.
986 None,
987 /// State when the mouse is down on a selection that is about to be dragged.
988 ReadyToDrag {
989 selection: Selection<Anchor>,
990 click_position: gpui::Point<Pixels>,
991 mouse_down_time: Instant,
992 },
993 /// State when the mouse is dragging the selection in the editor.
994 Dragging {
995 selection: Selection<Anchor>,
996 drop_cursor: Selection<Anchor>,
997 hide_drop_cursor: bool,
998 },
999}
1000
1001enum ColumnarSelectionState {
1002 FromMouse {
1003 selection_tail: Anchor,
1004 display_point: Option<DisplayPoint>,
1005 },
1006 FromSelection {
1007 selection_tail: Anchor,
1008 },
1009}
1010
1011/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1012/// a breakpoint on them.
1013#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1014struct PhantomBreakpointIndicator {
1015 display_row: DisplayRow,
1016 /// There's a small debounce between hovering over the line and showing the indicator.
1017 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1018 is_active: bool,
1019 collides_with_existing_breakpoint: bool,
1020}
1021
1022/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1023///
1024/// See the [module level documentation](self) for more information.
1025pub struct Editor {
1026 focus_handle: FocusHandle,
1027 last_focused_descendant: Option<WeakFocusHandle>,
1028 /// The text buffer being edited
1029 buffer: Entity<MultiBuffer>,
1030 /// Map of how text in the buffer should be displayed.
1031 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1032 pub display_map: Entity<DisplayMap>,
1033 placeholder_display_map: Option<Entity<DisplayMap>>,
1034 pub selections: SelectionsCollection,
1035 pub scroll_manager: ScrollManager,
1036 /// When inline assist editors are linked, they all render cursors because
1037 /// typing enters text into each of them, even the ones that aren't focused.
1038 pub(crate) show_cursor_when_unfocused: bool,
1039 columnar_selection_state: Option<ColumnarSelectionState>,
1040 add_selections_state: Option<AddSelectionsState>,
1041 select_next_state: Option<SelectNextState>,
1042 select_prev_state: Option<SelectNextState>,
1043 selection_history: SelectionHistory,
1044 defer_selection_effects: bool,
1045 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1046 autoclose_regions: Vec<AutocloseRegion>,
1047 snippet_stack: InvalidationStack<SnippetState>,
1048 select_syntax_node_history: SelectSyntaxNodeHistory,
1049 ime_transaction: Option<TransactionId>,
1050 pub diagnostics_max_severity: DiagnosticSeverity,
1051 active_diagnostics: ActiveDiagnostic,
1052 show_inline_diagnostics: bool,
1053 inline_diagnostics_update: Task<()>,
1054 inline_diagnostics_enabled: bool,
1055 diagnostics_enabled: bool,
1056 word_completions_enabled: bool,
1057 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1058 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1059 hard_wrap: Option<usize>,
1060 project: Option<Entity<Project>>,
1061 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1062 completion_provider: Option<Rc<dyn CompletionProvider>>,
1063 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1064 blink_manager: Entity<BlinkManager>,
1065 show_cursor_names: bool,
1066 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1067 pub show_local_selections: bool,
1068 mode: EditorMode,
1069 show_breadcrumbs: bool,
1070 show_gutter: bool,
1071 show_scrollbars: ScrollbarAxes,
1072 minimap_visibility: MinimapVisibility,
1073 offset_content: bool,
1074 disable_expand_excerpt_buttons: bool,
1075 delegate_expand_excerpts: bool,
1076 show_line_numbers: Option<bool>,
1077 use_relative_line_numbers: Option<bool>,
1078 show_git_diff_gutter: Option<bool>,
1079 show_code_actions: Option<bool>,
1080 show_runnables: Option<bool>,
1081 show_breakpoints: Option<bool>,
1082 show_wrap_guides: Option<bool>,
1083 show_indent_guides: Option<bool>,
1084 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1085 highlight_order: usize,
1086 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1087 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1088 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1089 scrollbar_marker_state: ScrollbarMarkerState,
1090 active_indent_guides_state: ActiveIndentGuidesState,
1091 nav_history: Option<ItemNavHistory>,
1092 context_menu: RefCell<Option<CodeContextMenu>>,
1093 context_menu_options: Option<ContextMenuOptions>,
1094 mouse_context_menu: Option<MouseContextMenu>,
1095 completion_tasks: Vec<(CompletionId, Task<()>)>,
1096 inline_blame_popover: Option<InlineBlamePopover>,
1097 inline_blame_popover_show_task: Option<Task<()>>,
1098 signature_help_state: SignatureHelpState,
1099 auto_signature_help: Option<bool>,
1100 find_all_references_task_sources: Vec<Anchor>,
1101 next_completion_id: CompletionId,
1102 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1103 code_actions_task: Option<Task<Result<()>>>,
1104 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1105 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1106 document_highlights_task: Option<Task<()>>,
1107 linked_editing_range_task: Option<Task<Option<()>>>,
1108 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1109 pending_rename: Option<RenameState>,
1110 searchable: bool,
1111 cursor_shape: CursorShape,
1112 /// Whether the cursor is offset one character to the left when something is
1113 /// selected (needed for vim visual mode)
1114 cursor_offset_on_selection: bool,
1115 current_line_highlight: Option<CurrentLineHighlight>,
1116 pub collapse_matches: bool,
1117 autoindent_mode: Option<AutoindentMode>,
1118 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1119 input_enabled: bool,
1120 use_modal_editing: bool,
1121 read_only: bool,
1122 leader_id: Option<CollaboratorId>,
1123 remote_id: Option<ViewId>,
1124 pub hover_state: HoverState,
1125 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1126 prev_pressure_stage: Option<PressureStage>,
1127 gutter_hovered: bool,
1128 hovered_link_state: Option<HoveredLinkState>,
1129 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1130 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1131 active_edit_prediction: Option<EditPredictionState>,
1132 /// Used to prevent flickering as the user types while the menu is open
1133 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1134 edit_prediction_settings: EditPredictionSettings,
1135 edit_predictions_hidden_for_vim_mode: bool,
1136 show_edit_predictions_override: Option<bool>,
1137 show_completions_on_input_override: Option<bool>,
1138 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1139 edit_prediction_preview: EditPredictionPreview,
1140 edit_prediction_indent_conflict: bool,
1141 edit_prediction_requires_modifier_in_indent_conflict: bool,
1142 next_inlay_id: usize,
1143 next_color_inlay_id: usize,
1144 _subscriptions: Vec<Subscription>,
1145 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1146 gutter_dimensions: GutterDimensions,
1147 style: Option<EditorStyle>,
1148 text_style_refinement: Option<TextStyleRefinement>,
1149 next_editor_action_id: EditorActionId,
1150 editor_actions: Rc<
1151 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1152 >,
1153 use_autoclose: bool,
1154 use_auto_surround: bool,
1155 auto_replace_emoji_shortcode: bool,
1156 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1157 show_git_blame_gutter: bool,
1158 show_git_blame_inline: bool,
1159 show_git_blame_inline_delay_task: Option<Task<()>>,
1160 git_blame_inline_enabled: bool,
1161 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1162 buffer_serialization: Option<BufferSerialization>,
1163 show_selection_menu: Option<bool>,
1164 blame: Option<Entity<GitBlame>>,
1165 blame_subscription: Option<Subscription>,
1166 custom_context_menu: Option<
1167 Box<
1168 dyn 'static
1169 + Fn(
1170 &mut Self,
1171 DisplayPoint,
1172 &mut Window,
1173 &mut Context<Self>,
1174 ) -> Option<Entity<ui::ContextMenu>>,
1175 >,
1176 >,
1177 last_bounds: Option<Bounds<Pixels>>,
1178 last_position_map: Option<Rc<PositionMap>>,
1179 expect_bounds_change: Option<Bounds<Pixels>>,
1180 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1181 tasks_update_task: Option<Task<()>>,
1182 breakpoint_store: Option<Entity<BreakpointStore>>,
1183 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1184 hovered_diff_hunk_row: Option<DisplayRow>,
1185 pull_diagnostics_task: Task<()>,
1186 pull_diagnostics_background_task: Task<()>,
1187 in_project_search: bool,
1188 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1189 breadcrumb_header: Option<String>,
1190 focused_block: Option<FocusedBlock>,
1191 next_scroll_position: NextScrollCursorCenterTopBottom,
1192 addons: HashMap<TypeId, Box<dyn Addon>>,
1193 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1194 load_diff_task: Option<Shared<Task<()>>>,
1195 /// Whether we are temporarily displaying a diff other than git's
1196 temporary_diff_override: bool,
1197 selection_mark_mode: bool,
1198 toggle_fold_multiple_buffers: Task<()>,
1199 _scroll_cursor_center_top_bottom_task: Task<()>,
1200 serialize_selections: Task<()>,
1201 serialize_folds: Task<()>,
1202 mouse_cursor_hidden: bool,
1203 minimap: Option<Entity<Self>>,
1204 hide_mouse_mode: HideMouseMode,
1205 pub change_list: ChangeList,
1206 inline_value_cache: InlineValueCache,
1207 number_deleted_lines: bool,
1208
1209 selection_drag_state: SelectionDragState,
1210 colors: Option<LspColorData>,
1211 post_scroll_update: Task<()>,
1212 refresh_colors_task: Task<()>,
1213 inlay_hints: Option<LspInlayHintData>,
1214 folding_newlines: Task<()>,
1215 select_next_is_case_sensitive: Option<bool>,
1216 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1217 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1218 accent_data: Option<AccentData>,
1219 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1220}
1221
1222#[derive(Debug, PartialEq)]
1223struct AccentData {
1224 colors: AccentColors,
1225 overrides: Vec<SharedString>,
1226}
1227
1228fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1229 if debounce_ms > 0 {
1230 Some(Duration::from_millis(debounce_ms))
1231 } else {
1232 None
1233 }
1234}
1235
1236#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1237enum NextScrollCursorCenterTopBottom {
1238 #[default]
1239 Center,
1240 Top,
1241 Bottom,
1242}
1243
1244impl NextScrollCursorCenterTopBottom {
1245 fn next(&self) -> Self {
1246 match self {
1247 Self::Center => Self::Top,
1248 Self::Top => Self::Bottom,
1249 Self::Bottom => Self::Center,
1250 }
1251 }
1252}
1253
1254#[derive(Clone)]
1255pub struct EditorSnapshot {
1256 pub mode: EditorMode,
1257 show_gutter: bool,
1258 offset_content: bool,
1259 show_line_numbers: Option<bool>,
1260 number_deleted_lines: bool,
1261 show_git_diff_gutter: Option<bool>,
1262 show_code_actions: Option<bool>,
1263 show_runnables: Option<bool>,
1264 show_breakpoints: Option<bool>,
1265 git_blame_gutter_max_author_length: Option<usize>,
1266 pub display_snapshot: DisplaySnapshot,
1267 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1268 is_focused: bool,
1269 scroll_anchor: ScrollAnchor,
1270 ongoing_scroll: OngoingScroll,
1271 current_line_highlight: CurrentLineHighlight,
1272 gutter_hovered: bool,
1273}
1274
1275#[derive(Default, Debug, Clone, Copy)]
1276pub struct GutterDimensions {
1277 pub left_padding: Pixels,
1278 pub right_padding: Pixels,
1279 pub width: Pixels,
1280 pub margin: Pixels,
1281 pub git_blame_entries_width: Option<Pixels>,
1282}
1283
1284impl GutterDimensions {
1285 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1286 Self {
1287 margin: Self::default_gutter_margin(font_id, font_size, cx),
1288 ..Default::default()
1289 }
1290 }
1291
1292 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1293 -cx.text_system().descent(font_id, font_size)
1294 }
1295 /// The full width of the space taken up by the gutter.
1296 pub fn full_width(&self) -> Pixels {
1297 self.margin + self.width
1298 }
1299
1300 /// The width of the space reserved for the fold indicators,
1301 /// use alongside 'justify_end' and `gutter_width` to
1302 /// right align content with the line numbers
1303 pub fn fold_area_width(&self) -> Pixels {
1304 self.margin + self.right_padding
1305 }
1306}
1307
1308struct CharacterDimensions {
1309 em_width: Pixels,
1310 em_advance: Pixels,
1311 line_height: Pixels,
1312}
1313
1314#[derive(Debug)]
1315pub struct RemoteSelection {
1316 pub replica_id: ReplicaId,
1317 pub selection: Selection<Anchor>,
1318 pub cursor_shape: CursorShape,
1319 pub collaborator_id: CollaboratorId,
1320 pub line_mode: bool,
1321 pub user_name: Option<SharedString>,
1322 pub color: PlayerColor,
1323}
1324
1325#[derive(Clone, Debug)]
1326struct SelectionHistoryEntry {
1327 selections: Arc<[Selection<Anchor>]>,
1328 select_next_state: Option<SelectNextState>,
1329 select_prev_state: Option<SelectNextState>,
1330 add_selections_state: Option<AddSelectionsState>,
1331}
1332
1333#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1334enum SelectionHistoryMode {
1335 #[default]
1336 Normal,
1337 Undoing,
1338 Redoing,
1339 Skipping,
1340}
1341
1342#[derive(Clone, PartialEq, Eq, Hash)]
1343struct HoveredCursor {
1344 replica_id: ReplicaId,
1345 selection_id: usize,
1346}
1347
1348#[derive(Debug)]
1349/// SelectionEffects controls the side-effects of updating the selection.
1350///
1351/// The default behaviour does "what you mostly want":
1352/// - it pushes to the nav history if the cursor moved by >10 lines
1353/// - it re-triggers completion requests
1354/// - it scrolls to fit
1355///
1356/// You might want to modify these behaviours. For example when doing a "jump"
1357/// like go to definition, we always want to add to nav history; but when scrolling
1358/// in vim mode we never do.
1359///
1360/// Similarly, you might want to disable scrolling if you don't want the viewport to
1361/// move.
1362#[derive(Clone)]
1363pub struct SelectionEffects {
1364 nav_history: Option<bool>,
1365 completions: bool,
1366 scroll: Option<Autoscroll>,
1367}
1368
1369impl Default for SelectionEffects {
1370 fn default() -> Self {
1371 Self {
1372 nav_history: None,
1373 completions: true,
1374 scroll: Some(Autoscroll::fit()),
1375 }
1376 }
1377}
1378impl SelectionEffects {
1379 pub fn scroll(scroll: Autoscroll) -> Self {
1380 Self {
1381 scroll: Some(scroll),
1382 ..Default::default()
1383 }
1384 }
1385
1386 pub fn no_scroll() -> Self {
1387 Self {
1388 scroll: None,
1389 ..Default::default()
1390 }
1391 }
1392
1393 pub fn completions(self, completions: bool) -> Self {
1394 Self {
1395 completions,
1396 ..self
1397 }
1398 }
1399
1400 pub fn nav_history(self, nav_history: bool) -> Self {
1401 Self {
1402 nav_history: Some(nav_history),
1403 ..self
1404 }
1405 }
1406}
1407
1408struct DeferredSelectionEffectsState {
1409 changed: bool,
1410 effects: SelectionEffects,
1411 old_cursor_position: Anchor,
1412 history_entry: SelectionHistoryEntry,
1413}
1414
1415#[derive(Default)]
1416struct SelectionHistory {
1417 #[allow(clippy::type_complexity)]
1418 selections_by_transaction:
1419 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1420 mode: SelectionHistoryMode,
1421 undo_stack: VecDeque<SelectionHistoryEntry>,
1422 redo_stack: VecDeque<SelectionHistoryEntry>,
1423}
1424
1425impl SelectionHistory {
1426 #[track_caller]
1427 fn insert_transaction(
1428 &mut self,
1429 transaction_id: TransactionId,
1430 selections: Arc<[Selection<Anchor>]>,
1431 ) {
1432 if selections.is_empty() {
1433 log::error!(
1434 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1435 std::panic::Location::caller()
1436 );
1437 return;
1438 }
1439 self.selections_by_transaction
1440 .insert(transaction_id, (selections, None));
1441 }
1442
1443 #[allow(clippy::type_complexity)]
1444 fn transaction(
1445 &self,
1446 transaction_id: TransactionId,
1447 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1448 self.selections_by_transaction.get(&transaction_id)
1449 }
1450
1451 #[allow(clippy::type_complexity)]
1452 fn transaction_mut(
1453 &mut self,
1454 transaction_id: TransactionId,
1455 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1456 self.selections_by_transaction.get_mut(&transaction_id)
1457 }
1458
1459 fn push(&mut self, entry: SelectionHistoryEntry) {
1460 if !entry.selections.is_empty() {
1461 match self.mode {
1462 SelectionHistoryMode::Normal => {
1463 self.push_undo(entry);
1464 self.redo_stack.clear();
1465 }
1466 SelectionHistoryMode::Undoing => self.push_redo(entry),
1467 SelectionHistoryMode::Redoing => self.push_undo(entry),
1468 SelectionHistoryMode::Skipping => {}
1469 }
1470 }
1471 }
1472
1473 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1474 if self
1475 .undo_stack
1476 .back()
1477 .is_none_or(|e| e.selections != entry.selections)
1478 {
1479 self.undo_stack.push_back(entry);
1480 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1481 self.undo_stack.pop_front();
1482 }
1483 }
1484 }
1485
1486 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1487 if self
1488 .redo_stack
1489 .back()
1490 .is_none_or(|e| e.selections != entry.selections)
1491 {
1492 self.redo_stack.push_back(entry);
1493 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1494 self.redo_stack.pop_front();
1495 }
1496 }
1497 }
1498}
1499
1500#[derive(Clone, Copy)]
1501pub struct RowHighlightOptions {
1502 pub autoscroll: bool,
1503 pub include_gutter: bool,
1504}
1505
1506impl Default for RowHighlightOptions {
1507 fn default() -> Self {
1508 Self {
1509 autoscroll: Default::default(),
1510 include_gutter: true,
1511 }
1512 }
1513}
1514
1515struct RowHighlight {
1516 index: usize,
1517 range: Range<Anchor>,
1518 color: Hsla,
1519 options: RowHighlightOptions,
1520 type_id: TypeId,
1521}
1522
1523#[derive(Clone, Debug)]
1524struct AddSelectionsState {
1525 groups: Vec<AddSelectionsGroup>,
1526}
1527
1528#[derive(Clone, Debug)]
1529struct AddSelectionsGroup {
1530 above: bool,
1531 stack: Vec<usize>,
1532}
1533
1534#[derive(Clone)]
1535struct SelectNextState {
1536 query: AhoCorasick,
1537 wordwise: bool,
1538 done: bool,
1539}
1540
1541impl std::fmt::Debug for SelectNextState {
1542 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1543 f.debug_struct(std::any::type_name::<Self>())
1544 .field("wordwise", &self.wordwise)
1545 .field("done", &self.done)
1546 .finish()
1547 }
1548}
1549
1550#[derive(Debug)]
1551struct AutocloseRegion {
1552 selection_id: usize,
1553 range: Range<Anchor>,
1554 pair: BracketPair,
1555}
1556
1557#[derive(Debug)]
1558struct SnippetState {
1559 ranges: Vec<Vec<Range<Anchor>>>,
1560 active_index: usize,
1561 choices: Vec<Option<Vec<String>>>,
1562}
1563
1564#[doc(hidden)]
1565pub struct RenameState {
1566 pub range: Range<Anchor>,
1567 pub old_name: Arc<str>,
1568 pub editor: Entity<Editor>,
1569 block_id: CustomBlockId,
1570}
1571
1572struct InvalidationStack<T>(Vec<T>);
1573
1574struct RegisteredEditPredictionDelegate {
1575 provider: Arc<dyn EditPredictionDelegateHandle>,
1576 _subscription: Subscription,
1577}
1578
1579#[derive(Debug, PartialEq, Eq)]
1580pub struct ActiveDiagnosticGroup {
1581 pub active_range: Range<Anchor>,
1582 pub active_message: String,
1583 pub group_id: usize,
1584 pub blocks: HashSet<CustomBlockId>,
1585}
1586
1587#[derive(Debug, PartialEq, Eq)]
1588
1589pub(crate) enum ActiveDiagnostic {
1590 None,
1591 All,
1592 Group(ActiveDiagnosticGroup),
1593}
1594
1595#[derive(Serialize, Deserialize, Clone, Debug)]
1596pub struct ClipboardSelection {
1597 /// The number of bytes in this selection.
1598 pub len: usize,
1599 /// Whether this was a full-line selection.
1600 pub is_entire_line: bool,
1601 /// The indentation of the first line when this content was originally copied.
1602 pub first_line_indent: u32,
1603 #[serde(default)]
1604 pub file_path: Option<PathBuf>,
1605 #[serde(default)]
1606 pub line_range: Option<RangeInclusive<u32>>,
1607}
1608
1609impl ClipboardSelection {
1610 pub fn for_buffer(
1611 len: usize,
1612 is_entire_line: bool,
1613 range: Range<Point>,
1614 buffer: &MultiBufferSnapshot,
1615 project: Option<&Entity<Project>>,
1616 cx: &App,
1617 ) -> Self {
1618 let first_line_indent = buffer
1619 .indent_size_for_line(MultiBufferRow(range.start.row))
1620 .len;
1621
1622 let file_path = util::maybe!({
1623 let project = project?.read(cx);
1624 let file = buffer.file_at(range.start)?;
1625 let project_path = ProjectPath {
1626 worktree_id: file.worktree_id(cx),
1627 path: file.path().clone(),
1628 };
1629 project.absolute_path(&project_path, cx)
1630 });
1631
1632 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1633
1634 Self {
1635 len,
1636 is_entire_line,
1637 first_line_indent,
1638 file_path,
1639 line_range,
1640 }
1641 }
1642}
1643
1644// selections, scroll behavior, was newest selection reversed
1645type SelectSyntaxNodeHistoryState = (
1646 Box<[Selection<MultiBufferOffset>]>,
1647 SelectSyntaxNodeScrollBehavior,
1648 bool,
1649);
1650
1651#[derive(Default)]
1652struct SelectSyntaxNodeHistory {
1653 stack: Vec<SelectSyntaxNodeHistoryState>,
1654 // disable temporarily to allow changing selections without losing the stack
1655 pub disable_clearing: bool,
1656}
1657
1658impl SelectSyntaxNodeHistory {
1659 pub fn try_clear(&mut self) {
1660 if !self.disable_clearing {
1661 self.stack.clear();
1662 }
1663 }
1664
1665 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1666 self.stack.push(selection);
1667 }
1668
1669 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1670 self.stack.pop()
1671 }
1672}
1673
1674enum SelectSyntaxNodeScrollBehavior {
1675 CursorTop,
1676 FitSelection,
1677 CursorBottom,
1678}
1679
1680#[derive(Debug)]
1681pub(crate) struct NavigationData {
1682 cursor_anchor: Anchor,
1683 cursor_position: Point,
1684 scroll_anchor: ScrollAnchor,
1685 scroll_top_row: u32,
1686}
1687
1688#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1689pub enum GotoDefinitionKind {
1690 Symbol,
1691 Declaration,
1692 Type,
1693 Implementation,
1694}
1695
1696pub enum FormatTarget {
1697 Buffers(HashSet<Entity<Buffer>>),
1698 Ranges(Vec<Range<MultiBufferPoint>>),
1699}
1700
1701pub(crate) struct FocusedBlock {
1702 id: BlockId,
1703 focus_handle: WeakFocusHandle,
1704}
1705
1706#[derive(Clone, Debug)]
1707enum JumpData {
1708 MultiBufferRow {
1709 row: MultiBufferRow,
1710 line_offset_from_top: u32,
1711 },
1712 MultiBufferPoint {
1713 excerpt_id: ExcerptId,
1714 position: Point,
1715 anchor: text::Anchor,
1716 line_offset_from_top: u32,
1717 },
1718}
1719
1720pub enum MultibufferSelectionMode {
1721 First,
1722 All,
1723}
1724
1725#[derive(Clone, Copy, Debug, Default)]
1726pub struct RewrapOptions {
1727 pub override_language_settings: bool,
1728 pub preserve_existing_whitespace: bool,
1729}
1730
1731impl Editor {
1732 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1733 let buffer = cx.new(|cx| Buffer::local("", cx));
1734 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1735 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1736 }
1737
1738 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1739 let buffer = cx.new(|cx| Buffer::local("", cx));
1740 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1741 Self::new(EditorMode::full(), buffer, None, window, cx)
1742 }
1743
1744 pub fn auto_height(
1745 min_lines: usize,
1746 max_lines: usize,
1747 window: &mut Window,
1748 cx: &mut Context<Self>,
1749 ) -> Self {
1750 let buffer = cx.new(|cx| Buffer::local("", cx));
1751 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1752 Self::new(
1753 EditorMode::AutoHeight {
1754 min_lines,
1755 max_lines: Some(max_lines),
1756 },
1757 buffer,
1758 None,
1759 window,
1760 cx,
1761 )
1762 }
1763
1764 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1765 /// The editor grows as tall as needed to fit its content.
1766 pub fn auto_height_unbounded(
1767 min_lines: usize,
1768 window: &mut Window,
1769 cx: &mut Context<Self>,
1770 ) -> Self {
1771 let buffer = cx.new(|cx| Buffer::local("", cx));
1772 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1773 Self::new(
1774 EditorMode::AutoHeight {
1775 min_lines,
1776 max_lines: None,
1777 },
1778 buffer,
1779 None,
1780 window,
1781 cx,
1782 )
1783 }
1784
1785 pub fn for_buffer(
1786 buffer: Entity<Buffer>,
1787 project: Option<Entity<Project>>,
1788 window: &mut Window,
1789 cx: &mut Context<Self>,
1790 ) -> Self {
1791 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1792 Self::new(EditorMode::full(), buffer, project, window, cx)
1793 }
1794
1795 pub fn for_multibuffer(
1796 buffer: Entity<MultiBuffer>,
1797 project: Option<Entity<Project>>,
1798 window: &mut Window,
1799 cx: &mut Context<Self>,
1800 ) -> Self {
1801 Self::new(EditorMode::full(), buffer, project, window, cx)
1802 }
1803
1804 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1805 let mut clone = Self::new(
1806 self.mode.clone(),
1807 self.buffer.clone(),
1808 self.project.clone(),
1809 window,
1810 cx,
1811 );
1812 self.display_map.update(cx, |display_map, cx| {
1813 let snapshot = display_map.snapshot(cx);
1814 clone.display_map.update(cx, |display_map, cx| {
1815 display_map.set_state(&snapshot, cx);
1816 });
1817 });
1818 clone.folds_did_change(cx);
1819 clone.selections.clone_state(&self.selections);
1820 clone.scroll_manager.clone_state(&self.scroll_manager);
1821 clone.searchable = self.searchable;
1822 clone.read_only = self.read_only;
1823 clone
1824 }
1825
1826 pub fn new(
1827 mode: EditorMode,
1828 buffer: Entity<MultiBuffer>,
1829 project: Option<Entity<Project>>,
1830 window: &mut Window,
1831 cx: &mut Context<Self>,
1832 ) -> Self {
1833 Editor::new_internal(mode, buffer, project, None, window, cx)
1834 }
1835
1836 pub fn sticky_headers(
1837 &self,
1838 style: &EditorStyle,
1839 cx: &App,
1840 ) -> Option<Vec<OutlineItem<Anchor>>> {
1841 let multi_buffer = self.buffer().read(cx);
1842 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1843 let multi_buffer_visible_start = self
1844 .scroll_manager
1845 .anchor()
1846 .anchor
1847 .to_point(&multi_buffer_snapshot);
1848 let max_row = multi_buffer_snapshot.max_point().row;
1849
1850 let start_row = (multi_buffer_visible_start.row).min(max_row);
1851 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1852
1853 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1854 let outline_items = buffer
1855 .outline_items_containing(
1856 Point::new(start_row, 0)..Point::new(end_row, 0),
1857 true,
1858 Some(style.syntax.as_ref()),
1859 )
1860 .into_iter()
1861 .map(|outline_item| OutlineItem {
1862 depth: outline_item.depth,
1863 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1864 source_range_for_text: Anchor::range_in_buffer(
1865 *excerpt_id,
1866 outline_item.source_range_for_text,
1867 ),
1868 text: outline_item.text,
1869 highlight_ranges: outline_item.highlight_ranges,
1870 name_ranges: outline_item.name_ranges,
1871 body_range: outline_item
1872 .body_range
1873 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1874 annotation_range: outline_item
1875 .annotation_range
1876 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1877 });
1878 return Some(outline_items.collect());
1879 }
1880
1881 None
1882 }
1883
1884 fn new_internal(
1885 mode: EditorMode,
1886 multi_buffer: Entity<MultiBuffer>,
1887 project: Option<Entity<Project>>,
1888 display_map: Option<Entity<DisplayMap>>,
1889 window: &mut Window,
1890 cx: &mut Context<Self>,
1891 ) -> Self {
1892 debug_assert!(
1893 display_map.is_none() || mode.is_minimap(),
1894 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1895 );
1896
1897 let full_mode = mode.is_full();
1898 let is_minimap = mode.is_minimap();
1899 let diagnostics_max_severity = if full_mode {
1900 EditorSettings::get_global(cx)
1901 .diagnostics_max_severity
1902 .unwrap_or(DiagnosticSeverity::Hint)
1903 } else {
1904 DiagnosticSeverity::Off
1905 };
1906 let style = window.text_style();
1907 let font_size = style.font_size.to_pixels(window.rem_size());
1908 let editor = cx.entity().downgrade();
1909 let fold_placeholder = FoldPlaceholder {
1910 constrain_width: false,
1911 render: Arc::new(move |fold_id, fold_range, cx| {
1912 let editor = editor.clone();
1913 div()
1914 .id(fold_id)
1915 .bg(cx.theme().colors().ghost_element_background)
1916 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1917 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1918 .rounded_xs()
1919 .size_full()
1920 .cursor_pointer()
1921 .child("⋯")
1922 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1923 .on_click(move |_, _window, cx| {
1924 editor
1925 .update(cx, |editor, cx| {
1926 editor.unfold_ranges(
1927 &[fold_range.start..fold_range.end],
1928 true,
1929 false,
1930 cx,
1931 );
1932 cx.stop_propagation();
1933 })
1934 .ok();
1935 })
1936 .into_any()
1937 }),
1938 merge_adjacent: true,
1939 ..FoldPlaceholder::default()
1940 };
1941 let display_map = display_map.unwrap_or_else(|| {
1942 cx.new(|cx| {
1943 DisplayMap::new(
1944 multi_buffer.clone(),
1945 style.font(),
1946 font_size,
1947 None,
1948 FILE_HEADER_HEIGHT,
1949 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1950 fold_placeholder,
1951 diagnostics_max_severity,
1952 cx,
1953 )
1954 })
1955 });
1956
1957 let selections = SelectionsCollection::new();
1958
1959 let blink_manager = cx.new(|cx| {
1960 let mut blink_manager = BlinkManager::new(
1961 CURSOR_BLINK_INTERVAL,
1962 |cx| EditorSettings::get_global(cx).cursor_blink,
1963 cx,
1964 );
1965 if is_minimap {
1966 blink_manager.disable(cx);
1967 }
1968 blink_manager
1969 });
1970
1971 let soft_wrap_mode_override =
1972 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1973
1974 let mut project_subscriptions = Vec::new();
1975 if full_mode && let Some(project) = project.as_ref() {
1976 project_subscriptions.push(cx.subscribe_in(
1977 project,
1978 window,
1979 |editor, _, event, window, cx| match event {
1980 project::Event::RefreshCodeLens => {
1981 // we always query lens with actions, without storing them, always refreshing them
1982 }
1983 project::Event::RefreshInlayHints {
1984 server_id,
1985 request_id,
1986 } => {
1987 editor.refresh_inlay_hints(
1988 InlayHintRefreshReason::RefreshRequested {
1989 server_id: *server_id,
1990 request_id: *request_id,
1991 },
1992 cx,
1993 );
1994 }
1995 project::Event::LanguageServerRemoved(..) => {
1996 if editor.tasks_update_task.is_none() {
1997 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1998 }
1999 editor.registered_buffers.clear();
2000 editor.register_visible_buffers(cx);
2001 }
2002 project::Event::LanguageServerAdded(..) => {
2003 if editor.tasks_update_task.is_none() {
2004 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2005 }
2006 }
2007 project::Event::SnippetEdit(id, snippet_edits) => {
2008 // todo(lw): Non singletons
2009 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2010 let snapshot = buffer.read(cx).snapshot();
2011 let focus_handle = editor.focus_handle(cx);
2012 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2013 for (range, snippet) in snippet_edits {
2014 let buffer_range =
2015 language::range_from_lsp(*range).to_offset(&snapshot);
2016 editor
2017 .insert_snippet(
2018 &[MultiBufferOffset(buffer_range.start)
2019 ..MultiBufferOffset(buffer_range.end)],
2020 snippet.clone(),
2021 window,
2022 cx,
2023 )
2024 .ok();
2025 }
2026 }
2027 }
2028 }
2029 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2030 let buffer_id = *buffer_id;
2031 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2032 editor.register_buffer(buffer_id, cx);
2033 editor.update_lsp_data(Some(buffer_id), window, cx);
2034 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2035 refresh_linked_ranges(editor, window, cx);
2036 editor.refresh_code_actions(window, cx);
2037 editor.refresh_document_highlights(cx);
2038 }
2039 }
2040
2041 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2042 let Some(workspace) = editor.workspace() else {
2043 return;
2044 };
2045 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2046 else {
2047 return;
2048 };
2049
2050 if active_editor.entity_id() == cx.entity_id() {
2051 let entity_id = cx.entity_id();
2052 workspace.update(cx, |this, cx| {
2053 this.panes_mut()
2054 .iter_mut()
2055 .filter(|pane| pane.entity_id() != entity_id)
2056 .for_each(|p| {
2057 p.update(cx, |pane, _| {
2058 pane.nav_history_mut().rename_item(
2059 entity_id,
2060 project_path.clone(),
2061 abs_path.clone().into(),
2062 );
2063 })
2064 });
2065 });
2066
2067 Self::open_transaction_for_hidden_buffers(
2068 workspace,
2069 transaction.clone(),
2070 "Rename".to_string(),
2071 window,
2072 cx,
2073 );
2074 }
2075 }
2076
2077 project::Event::WorkspaceEditApplied(transaction) => {
2078 let Some(workspace) = editor.workspace() else {
2079 return;
2080 };
2081 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2082 else {
2083 return;
2084 };
2085
2086 if active_editor.entity_id() == cx.entity_id() {
2087 Self::open_transaction_for_hidden_buffers(
2088 workspace,
2089 transaction.clone(),
2090 "LSP Edit".to_string(),
2091 window,
2092 cx,
2093 );
2094 }
2095 }
2096
2097 _ => {}
2098 },
2099 ));
2100 if let Some(task_inventory) = project
2101 .read(cx)
2102 .task_store()
2103 .read(cx)
2104 .task_inventory()
2105 .cloned()
2106 {
2107 project_subscriptions.push(cx.observe_in(
2108 &task_inventory,
2109 window,
2110 |editor, _, window, cx| {
2111 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2112 },
2113 ));
2114 };
2115
2116 project_subscriptions.push(cx.subscribe_in(
2117 &project.read(cx).breakpoint_store(),
2118 window,
2119 |editor, _, event, window, cx| match event {
2120 BreakpointStoreEvent::ClearDebugLines => {
2121 editor.clear_row_highlights::<ActiveDebugLine>();
2122 editor.refresh_inline_values(cx);
2123 }
2124 BreakpointStoreEvent::SetDebugLine => {
2125 if editor.go_to_active_debug_line(window, cx) {
2126 cx.stop_propagation();
2127 }
2128
2129 editor.refresh_inline_values(cx);
2130 }
2131 _ => {}
2132 },
2133 ));
2134 let git_store = project.read(cx).git_store().clone();
2135 let project = project.clone();
2136 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2137 if let GitStoreEvent::RepositoryAdded = event {
2138 this.load_diff_task = Some(
2139 update_uncommitted_diff_for_buffer(
2140 cx.entity(),
2141 &project,
2142 this.buffer.read(cx).all_buffers(),
2143 this.buffer.clone(),
2144 cx,
2145 )
2146 .shared(),
2147 );
2148 }
2149 }));
2150 }
2151
2152 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2153
2154 let inlay_hint_settings =
2155 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2156 let focus_handle = cx.focus_handle();
2157 if !is_minimap {
2158 cx.on_focus(&focus_handle, window, Self::handle_focus)
2159 .detach();
2160 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2161 .detach();
2162 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2163 .detach();
2164 cx.on_blur(&focus_handle, window, Self::handle_blur)
2165 .detach();
2166 cx.observe_pending_input(window, Self::observe_pending_input)
2167 .detach();
2168 }
2169
2170 let show_indent_guides =
2171 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2172 Some(false)
2173 } else {
2174 None
2175 };
2176
2177 let breakpoint_store = match (&mode, project.as_ref()) {
2178 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2179 _ => None,
2180 };
2181
2182 let mut code_action_providers = Vec::new();
2183 let mut load_uncommitted_diff = None;
2184 if let Some(project) = project.clone() {
2185 load_uncommitted_diff = Some(
2186 update_uncommitted_diff_for_buffer(
2187 cx.entity(),
2188 &project,
2189 multi_buffer.read(cx).all_buffers(),
2190 multi_buffer.clone(),
2191 cx,
2192 )
2193 .shared(),
2194 );
2195 code_action_providers.push(Rc::new(project) as Rc<_>);
2196 }
2197
2198 let mut editor = Self {
2199 focus_handle,
2200 show_cursor_when_unfocused: false,
2201 last_focused_descendant: None,
2202 buffer: multi_buffer.clone(),
2203 display_map: display_map.clone(),
2204 placeholder_display_map: None,
2205 selections,
2206 scroll_manager: ScrollManager::new(cx),
2207 columnar_selection_state: None,
2208 add_selections_state: None,
2209 select_next_state: None,
2210 select_prev_state: None,
2211 selection_history: SelectionHistory::default(),
2212 defer_selection_effects: false,
2213 deferred_selection_effects_state: None,
2214 autoclose_regions: Vec::new(),
2215 snippet_stack: InvalidationStack::default(),
2216 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2217 ime_transaction: None,
2218 active_diagnostics: ActiveDiagnostic::None,
2219 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2220 inline_diagnostics_update: Task::ready(()),
2221 inline_diagnostics: Vec::new(),
2222 soft_wrap_mode_override,
2223 diagnostics_max_severity,
2224 hard_wrap: None,
2225 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2226 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2227 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2228 project,
2229 blink_manager: blink_manager.clone(),
2230 show_local_selections: true,
2231 show_scrollbars: ScrollbarAxes {
2232 horizontal: full_mode,
2233 vertical: full_mode,
2234 },
2235 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2236 offset_content: !matches!(mode, EditorMode::SingleLine),
2237 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2238 show_gutter: full_mode,
2239 show_line_numbers: (!full_mode).then_some(false),
2240 use_relative_line_numbers: None,
2241 disable_expand_excerpt_buttons: !full_mode,
2242 delegate_expand_excerpts: false,
2243 show_git_diff_gutter: None,
2244 show_code_actions: None,
2245 show_runnables: None,
2246 show_breakpoints: None,
2247 show_wrap_guides: None,
2248 show_indent_guides,
2249 buffers_with_disabled_indent_guides: HashSet::default(),
2250 highlight_order: 0,
2251 highlighted_rows: HashMap::default(),
2252 background_highlights: HashMap::default(),
2253 gutter_highlights: HashMap::default(),
2254 scrollbar_marker_state: ScrollbarMarkerState::default(),
2255 active_indent_guides_state: ActiveIndentGuidesState::default(),
2256 nav_history: None,
2257 context_menu: RefCell::new(None),
2258 context_menu_options: None,
2259 mouse_context_menu: None,
2260 completion_tasks: Vec::new(),
2261 inline_blame_popover: None,
2262 inline_blame_popover_show_task: None,
2263 signature_help_state: SignatureHelpState::default(),
2264 auto_signature_help: None,
2265 find_all_references_task_sources: Vec::new(),
2266 next_completion_id: 0,
2267 next_inlay_id: 0,
2268 code_action_providers,
2269 available_code_actions: None,
2270 code_actions_task: None,
2271 quick_selection_highlight_task: None,
2272 debounced_selection_highlight_task: None,
2273 document_highlights_task: None,
2274 linked_editing_range_task: None,
2275 pending_rename: None,
2276 searchable: !is_minimap,
2277 cursor_shape: EditorSettings::get_global(cx)
2278 .cursor_shape
2279 .unwrap_or_default(),
2280 cursor_offset_on_selection: false,
2281 current_line_highlight: None,
2282 autoindent_mode: Some(AutoindentMode::EachLine),
2283 collapse_matches: false,
2284 workspace: None,
2285 input_enabled: !is_minimap,
2286 use_modal_editing: full_mode,
2287 read_only: is_minimap,
2288 use_autoclose: true,
2289 use_auto_surround: true,
2290 auto_replace_emoji_shortcode: false,
2291 jsx_tag_auto_close_enabled_in_any_buffer: false,
2292 leader_id: None,
2293 remote_id: None,
2294 hover_state: HoverState::default(),
2295 pending_mouse_down: None,
2296 prev_pressure_stage: None,
2297 hovered_link_state: None,
2298 edit_prediction_provider: None,
2299 active_edit_prediction: None,
2300 stale_edit_prediction_in_menu: None,
2301 edit_prediction_preview: EditPredictionPreview::Inactive {
2302 released_too_fast: false,
2303 },
2304 inline_diagnostics_enabled: full_mode,
2305 diagnostics_enabled: full_mode,
2306 word_completions_enabled: full_mode,
2307 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2308 gutter_hovered: false,
2309 pixel_position_of_newest_cursor: None,
2310 last_bounds: None,
2311 last_position_map: None,
2312 expect_bounds_change: None,
2313 gutter_dimensions: GutterDimensions::default(),
2314 style: None,
2315 show_cursor_names: false,
2316 hovered_cursors: HashMap::default(),
2317 next_editor_action_id: EditorActionId::default(),
2318 editor_actions: Rc::default(),
2319 edit_predictions_hidden_for_vim_mode: false,
2320 show_edit_predictions_override: None,
2321 show_completions_on_input_override: None,
2322 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2323 edit_prediction_settings: EditPredictionSettings::Disabled,
2324 edit_prediction_indent_conflict: false,
2325 edit_prediction_requires_modifier_in_indent_conflict: true,
2326 custom_context_menu: None,
2327 show_git_blame_gutter: false,
2328 show_git_blame_inline: false,
2329 show_selection_menu: None,
2330 show_git_blame_inline_delay_task: None,
2331 git_blame_inline_enabled: full_mode
2332 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2333 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2334 buffer_serialization: is_minimap.not().then(|| {
2335 BufferSerialization::new(
2336 ProjectSettings::get_global(cx)
2337 .session
2338 .restore_unsaved_buffers,
2339 )
2340 }),
2341 blame: None,
2342 blame_subscription: None,
2343 tasks: BTreeMap::default(),
2344
2345 breakpoint_store,
2346 gutter_breakpoint_indicator: (None, None),
2347 hovered_diff_hunk_row: None,
2348 _subscriptions: (!is_minimap)
2349 .then(|| {
2350 vec![
2351 cx.observe(&multi_buffer, Self::on_buffer_changed),
2352 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2353 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2354 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2355 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2356 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2357 cx.observe_window_activation(window, |editor, window, cx| {
2358 let active = window.is_window_active();
2359 editor.blink_manager.update(cx, |blink_manager, cx| {
2360 if active {
2361 blink_manager.enable(cx);
2362 } else {
2363 blink_manager.disable(cx);
2364 }
2365 });
2366 if active {
2367 editor.show_mouse_cursor(cx);
2368 }
2369 }),
2370 ]
2371 })
2372 .unwrap_or_default(),
2373 tasks_update_task: None,
2374 pull_diagnostics_task: Task::ready(()),
2375 pull_diagnostics_background_task: Task::ready(()),
2376 colors: None,
2377 refresh_colors_task: Task::ready(()),
2378 inlay_hints: None,
2379 next_color_inlay_id: 0,
2380 post_scroll_update: Task::ready(()),
2381 linked_edit_ranges: Default::default(),
2382 in_project_search: false,
2383 previous_search_ranges: None,
2384 breadcrumb_header: None,
2385 focused_block: None,
2386 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2387 addons: HashMap::default(),
2388 registered_buffers: HashMap::default(),
2389 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2390 selection_mark_mode: false,
2391 toggle_fold_multiple_buffers: Task::ready(()),
2392 serialize_selections: Task::ready(()),
2393 serialize_folds: Task::ready(()),
2394 text_style_refinement: None,
2395 load_diff_task: load_uncommitted_diff,
2396 temporary_diff_override: false,
2397 mouse_cursor_hidden: false,
2398 minimap: None,
2399 hide_mouse_mode: EditorSettings::get_global(cx)
2400 .hide_mouse
2401 .unwrap_or_default(),
2402 change_list: ChangeList::new(),
2403 mode,
2404 selection_drag_state: SelectionDragState::None,
2405 folding_newlines: Task::ready(()),
2406 lookup_key: None,
2407 select_next_is_case_sensitive: None,
2408 applicable_language_settings: HashMap::default(),
2409 accent_data: None,
2410 fetched_tree_sitter_chunks: HashMap::default(),
2411 number_deleted_lines: false,
2412 };
2413
2414 if is_minimap {
2415 return editor;
2416 }
2417
2418 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2419 editor.accent_data = editor.fetch_accent_data(cx);
2420
2421 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2422 editor
2423 ._subscriptions
2424 .push(cx.observe(breakpoints, |_, _, cx| {
2425 cx.notify();
2426 }));
2427 }
2428 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2429 editor._subscriptions.extend(project_subscriptions);
2430
2431 editor._subscriptions.push(cx.subscribe_in(
2432 &cx.entity(),
2433 window,
2434 |editor, _, e: &EditorEvent, window, cx| match e {
2435 EditorEvent::ScrollPositionChanged { local, .. } => {
2436 if *local {
2437 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2438 editor.inline_blame_popover.take();
2439 let new_anchor = editor.scroll_manager.anchor();
2440 let snapshot = editor.snapshot(window, cx);
2441 editor.update_restoration_data(cx, move |data| {
2442 data.scroll_position = (
2443 new_anchor.top_row(snapshot.buffer_snapshot()),
2444 new_anchor.offset,
2445 );
2446 });
2447
2448 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2449 cx.background_executor()
2450 .timer(Duration::from_millis(50))
2451 .await;
2452 editor
2453 .update_in(cx, |editor, window, cx| {
2454 editor.register_visible_buffers(cx);
2455 editor.refresh_colors_for_visible_range(None, window, cx);
2456 editor.refresh_inlay_hints(
2457 InlayHintRefreshReason::NewLinesShown,
2458 cx,
2459 );
2460 editor.colorize_brackets(false, cx);
2461 })
2462 .ok();
2463 });
2464 }
2465 }
2466 EditorEvent::Edited { .. } => {
2467 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2468 .map(|vim_mode| vim_mode.0)
2469 .unwrap_or(false);
2470 if !vim_mode {
2471 let display_map = editor.display_snapshot(cx);
2472 let selections = editor.selections.all_adjusted_display(&display_map);
2473 let pop_state = editor
2474 .change_list
2475 .last()
2476 .map(|previous| {
2477 previous.len() == selections.len()
2478 && previous.iter().enumerate().all(|(ix, p)| {
2479 p.to_display_point(&display_map).row()
2480 == selections[ix].head().row()
2481 })
2482 })
2483 .unwrap_or(false);
2484 let new_positions = selections
2485 .into_iter()
2486 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2487 .collect();
2488 editor
2489 .change_list
2490 .push_to_change_list(pop_state, new_positions);
2491 }
2492 }
2493 _ => (),
2494 },
2495 ));
2496
2497 if let Some(dap_store) = editor
2498 .project
2499 .as_ref()
2500 .map(|project| project.read(cx).dap_store())
2501 {
2502 let weak_editor = cx.weak_entity();
2503
2504 editor
2505 ._subscriptions
2506 .push(
2507 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2508 let session_entity = cx.entity();
2509 weak_editor
2510 .update(cx, |editor, cx| {
2511 editor._subscriptions.push(
2512 cx.subscribe(&session_entity, Self::on_debug_session_event),
2513 );
2514 })
2515 .ok();
2516 }),
2517 );
2518
2519 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2520 editor
2521 ._subscriptions
2522 .push(cx.subscribe(&session, Self::on_debug_session_event));
2523 }
2524 }
2525
2526 // skip adding the initial selection to selection history
2527 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2528 editor.end_selection(window, cx);
2529 editor.selection_history.mode = SelectionHistoryMode::Normal;
2530
2531 editor.scroll_manager.show_scrollbars(window, cx);
2532 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2533
2534 if full_mode {
2535 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2536 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2537
2538 if editor.git_blame_inline_enabled {
2539 editor.start_git_blame_inline(false, window, cx);
2540 }
2541
2542 editor.go_to_active_debug_line(window, cx);
2543
2544 editor.minimap =
2545 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2546 editor.colors = Some(LspColorData::new(cx));
2547 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2548
2549 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2550 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2551 }
2552 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2553 }
2554
2555 editor
2556 }
2557
2558 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2559 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2560 }
2561
2562 pub fn deploy_mouse_context_menu(
2563 &mut self,
2564 position: gpui::Point<Pixels>,
2565 context_menu: Entity<ContextMenu>,
2566 window: &mut Window,
2567 cx: &mut Context<Self>,
2568 ) {
2569 self.mouse_context_menu = Some(MouseContextMenu::new(
2570 self,
2571 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2572 context_menu,
2573 window,
2574 cx,
2575 ));
2576 }
2577
2578 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2579 self.mouse_context_menu
2580 .as_ref()
2581 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2582 }
2583
2584 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2585 if self
2586 .selections
2587 .pending_anchor()
2588 .is_some_and(|pending_selection| {
2589 let snapshot = self.buffer().read(cx).snapshot(cx);
2590 pending_selection.range().includes(range, &snapshot)
2591 })
2592 {
2593 return true;
2594 }
2595
2596 self.selections
2597 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2598 .into_iter()
2599 .any(|selection| {
2600 // This is needed to cover a corner case, if we just check for an existing
2601 // selection in the fold range, having a cursor at the start of the fold
2602 // marks it as selected. Non-empty selections don't cause this.
2603 let length = selection.end - selection.start;
2604 length > 0
2605 })
2606 }
2607
2608 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2609 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2610 }
2611
2612 fn key_context_internal(
2613 &self,
2614 has_active_edit_prediction: bool,
2615 window: &mut Window,
2616 cx: &mut App,
2617 ) -> KeyContext {
2618 let mut key_context = KeyContext::new_with_defaults();
2619 key_context.add("Editor");
2620 let mode = match self.mode {
2621 EditorMode::SingleLine => "single_line",
2622 EditorMode::AutoHeight { .. } => "auto_height",
2623 EditorMode::Minimap { .. } => "minimap",
2624 EditorMode::Full { .. } => "full",
2625 };
2626
2627 if EditorSettings::jupyter_enabled(cx) {
2628 key_context.add("jupyter");
2629 }
2630
2631 key_context.set("mode", mode);
2632 if self.pending_rename.is_some() {
2633 key_context.add("renaming");
2634 }
2635
2636 if let Some(snippet_stack) = self.snippet_stack.last() {
2637 key_context.add("in_snippet");
2638
2639 if snippet_stack.active_index > 0 {
2640 key_context.add("has_previous_tabstop");
2641 }
2642
2643 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2644 key_context.add("has_next_tabstop");
2645 }
2646 }
2647
2648 match self.context_menu.borrow().as_ref() {
2649 Some(CodeContextMenu::Completions(menu)) => {
2650 if menu.visible() {
2651 key_context.add("menu");
2652 key_context.add("showing_completions");
2653 }
2654 }
2655 Some(CodeContextMenu::CodeActions(menu)) => {
2656 if menu.visible() {
2657 key_context.add("menu");
2658 key_context.add("showing_code_actions")
2659 }
2660 }
2661 None => {}
2662 }
2663
2664 if self.signature_help_state.has_multiple_signatures() {
2665 key_context.add("showing_signature_help");
2666 }
2667
2668 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2669 if !self.focus_handle(cx).contains_focused(window, cx)
2670 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2671 {
2672 for addon in self.addons.values() {
2673 addon.extend_key_context(&mut key_context, cx)
2674 }
2675 }
2676
2677 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2678 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2679 Some(
2680 file.full_path(cx)
2681 .extension()?
2682 .to_string_lossy()
2683 .to_lowercase(),
2684 )
2685 }) {
2686 key_context.set("extension", extension);
2687 }
2688 } else {
2689 key_context.add("multibuffer");
2690 }
2691
2692 if has_active_edit_prediction {
2693 if self.edit_prediction_in_conflict() {
2694 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2695 } else {
2696 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2697 key_context.add("copilot_suggestion");
2698 }
2699 }
2700
2701 if self.selection_mark_mode {
2702 key_context.add("selection_mode");
2703 }
2704
2705 let disjoint = self.selections.disjoint_anchors();
2706 let snapshot = self.snapshot(window, cx);
2707 let snapshot = snapshot.buffer_snapshot();
2708 if self.mode == EditorMode::SingleLine
2709 && let [selection] = disjoint
2710 && selection.start == selection.end
2711 && selection.end.to_offset(snapshot) == snapshot.len()
2712 {
2713 key_context.add("end_of_input");
2714 }
2715
2716 if self.has_any_expanded_diff_hunks(cx) {
2717 key_context.add("diffs_expanded");
2718 }
2719
2720 key_context
2721 }
2722
2723 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2724 self.last_bounds.as_ref()
2725 }
2726
2727 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2728 if self.mouse_cursor_hidden {
2729 self.mouse_cursor_hidden = false;
2730 cx.notify();
2731 }
2732 }
2733
2734 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2735 let hide_mouse_cursor = match origin {
2736 HideMouseCursorOrigin::TypingAction => {
2737 matches!(
2738 self.hide_mouse_mode,
2739 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2740 )
2741 }
2742 HideMouseCursorOrigin::MovementAction => {
2743 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2744 }
2745 };
2746 if self.mouse_cursor_hidden != hide_mouse_cursor {
2747 self.mouse_cursor_hidden = hide_mouse_cursor;
2748 cx.notify();
2749 }
2750 }
2751
2752 pub fn edit_prediction_in_conflict(&self) -> bool {
2753 if !self.show_edit_predictions_in_menu() {
2754 return false;
2755 }
2756
2757 let showing_completions = self
2758 .context_menu
2759 .borrow()
2760 .as_ref()
2761 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2762
2763 showing_completions
2764 || self.edit_prediction_requires_modifier()
2765 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2766 // bindings to insert tab characters.
2767 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2768 }
2769
2770 pub fn accept_edit_prediction_keybind(
2771 &self,
2772 granularity: EditPredictionGranularity,
2773 window: &mut Window,
2774 cx: &mut App,
2775 ) -> AcceptEditPredictionBinding {
2776 let key_context = self.key_context_internal(true, window, cx);
2777 let in_conflict = self.edit_prediction_in_conflict();
2778
2779 let bindings =
2780 match granularity {
2781 EditPredictionGranularity::Word => window
2782 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2783 EditPredictionGranularity::Line => window
2784 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2785 EditPredictionGranularity::Full => {
2786 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2787 }
2788 };
2789
2790 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2791 !in_conflict
2792 || binding
2793 .keystrokes()
2794 .first()
2795 .is_some_and(|keystroke| keystroke.modifiers().modified())
2796 }))
2797 }
2798
2799 pub fn new_file(
2800 workspace: &mut Workspace,
2801 _: &workspace::NewFile,
2802 window: &mut Window,
2803 cx: &mut Context<Workspace>,
2804 ) {
2805 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2806 "Failed to create buffer",
2807 window,
2808 cx,
2809 |e, _, _| match e.error_code() {
2810 ErrorCode::RemoteUpgradeRequired => Some(format!(
2811 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2812 e.error_tag("required").unwrap_or("the latest version")
2813 )),
2814 _ => None,
2815 },
2816 );
2817 }
2818
2819 pub fn new_in_workspace(
2820 workspace: &mut Workspace,
2821 window: &mut Window,
2822 cx: &mut Context<Workspace>,
2823 ) -> Task<Result<Entity<Editor>>> {
2824 let project = workspace.project().clone();
2825 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2826
2827 cx.spawn_in(window, async move |workspace, cx| {
2828 let buffer = create.await?;
2829 workspace.update_in(cx, |workspace, window, cx| {
2830 let editor =
2831 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2832 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2833 editor
2834 })
2835 })
2836 }
2837
2838 fn new_file_vertical(
2839 workspace: &mut Workspace,
2840 _: &workspace::NewFileSplitVertical,
2841 window: &mut Window,
2842 cx: &mut Context<Workspace>,
2843 ) {
2844 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2845 }
2846
2847 fn new_file_horizontal(
2848 workspace: &mut Workspace,
2849 _: &workspace::NewFileSplitHorizontal,
2850 window: &mut Window,
2851 cx: &mut Context<Workspace>,
2852 ) {
2853 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2854 }
2855
2856 fn new_file_split(
2857 workspace: &mut Workspace,
2858 action: &workspace::NewFileSplit,
2859 window: &mut Window,
2860 cx: &mut Context<Workspace>,
2861 ) {
2862 Self::new_file_in_direction(workspace, action.0, window, cx)
2863 }
2864
2865 fn new_file_in_direction(
2866 workspace: &mut Workspace,
2867 direction: SplitDirection,
2868 window: &mut Window,
2869 cx: &mut Context<Workspace>,
2870 ) {
2871 let project = workspace.project().clone();
2872 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2873
2874 cx.spawn_in(window, async move |workspace, cx| {
2875 let buffer = create.await?;
2876 workspace.update_in(cx, move |workspace, window, cx| {
2877 workspace.split_item(
2878 direction,
2879 Box::new(
2880 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2881 ),
2882 window,
2883 cx,
2884 )
2885 })?;
2886 anyhow::Ok(())
2887 })
2888 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2889 match e.error_code() {
2890 ErrorCode::RemoteUpgradeRequired => Some(format!(
2891 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2892 e.error_tag("required").unwrap_or("the latest version")
2893 )),
2894 _ => None,
2895 }
2896 });
2897 }
2898
2899 pub fn leader_id(&self) -> Option<CollaboratorId> {
2900 self.leader_id
2901 }
2902
2903 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2904 &self.buffer
2905 }
2906
2907 pub fn project(&self) -> Option<&Entity<Project>> {
2908 self.project.as_ref()
2909 }
2910
2911 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2912 self.workspace.as_ref()?.0.upgrade()
2913 }
2914
2915 /// Returns the workspace serialization ID if this editor should be serialized.
2916 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2917 self.workspace
2918 .as_ref()
2919 .filter(|_| self.should_serialize_buffer())
2920 .and_then(|workspace| workspace.1)
2921 }
2922
2923 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2924 self.buffer().read(cx).title(cx)
2925 }
2926
2927 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2928 let git_blame_gutter_max_author_length = self
2929 .render_git_blame_gutter(cx)
2930 .then(|| {
2931 if let Some(blame) = self.blame.as_ref() {
2932 let max_author_length =
2933 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2934 Some(max_author_length)
2935 } else {
2936 None
2937 }
2938 })
2939 .flatten();
2940
2941 EditorSnapshot {
2942 mode: self.mode.clone(),
2943 show_gutter: self.show_gutter,
2944 offset_content: self.offset_content,
2945 show_line_numbers: self.show_line_numbers,
2946 number_deleted_lines: self.number_deleted_lines,
2947 show_git_diff_gutter: self.show_git_diff_gutter,
2948 show_code_actions: self.show_code_actions,
2949 show_runnables: self.show_runnables,
2950 show_breakpoints: self.show_breakpoints,
2951 git_blame_gutter_max_author_length,
2952 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2953 placeholder_display_snapshot: self
2954 .placeholder_display_map
2955 .as_ref()
2956 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2957 scroll_anchor: self.scroll_manager.anchor(),
2958 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2959 is_focused: self.focus_handle.is_focused(window),
2960 current_line_highlight: self
2961 .current_line_highlight
2962 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2963 gutter_hovered: self.gutter_hovered,
2964 }
2965 }
2966
2967 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2968 self.buffer.read(cx).language_at(point, cx)
2969 }
2970
2971 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2972 self.buffer.read(cx).read(cx).file_at(point).cloned()
2973 }
2974
2975 pub fn active_excerpt(
2976 &self,
2977 cx: &App,
2978 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2979 self.buffer
2980 .read(cx)
2981 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2982 }
2983
2984 pub fn mode(&self) -> &EditorMode {
2985 &self.mode
2986 }
2987
2988 pub fn set_mode(&mut self, mode: EditorMode) {
2989 self.mode = mode;
2990 }
2991
2992 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2993 self.collaboration_hub.as_deref()
2994 }
2995
2996 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2997 self.collaboration_hub = Some(hub);
2998 }
2999
3000 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3001 self.in_project_search = in_project_search;
3002 }
3003
3004 pub fn set_custom_context_menu(
3005 &mut self,
3006 f: impl 'static
3007 + Fn(
3008 &mut Self,
3009 DisplayPoint,
3010 &mut Window,
3011 &mut Context<Self>,
3012 ) -> Option<Entity<ui::ContextMenu>>,
3013 ) {
3014 self.custom_context_menu = Some(Box::new(f))
3015 }
3016
3017 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3018 self.completion_provider = provider;
3019 }
3020
3021 #[cfg(any(test, feature = "test-support"))]
3022 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3023 self.completion_provider.clone()
3024 }
3025
3026 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3027 self.semantics_provider.clone()
3028 }
3029
3030 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3031 self.semantics_provider = provider;
3032 }
3033
3034 pub fn set_edit_prediction_provider<T>(
3035 &mut self,
3036 provider: Option<Entity<T>>,
3037 window: &mut Window,
3038 cx: &mut Context<Self>,
3039 ) where
3040 T: EditPredictionDelegate,
3041 {
3042 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3043 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3044 if this.focus_handle.is_focused(window) {
3045 this.update_visible_edit_prediction(window, cx);
3046 }
3047 }),
3048 provider: Arc::new(provider),
3049 });
3050 self.update_edit_prediction_settings(cx);
3051 self.refresh_edit_prediction(false, false, window, cx);
3052 }
3053
3054 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3055 self.placeholder_display_map
3056 .as_ref()
3057 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3058 }
3059
3060 pub fn set_placeholder_text(
3061 &mut self,
3062 placeholder_text: &str,
3063 window: &mut Window,
3064 cx: &mut Context<Self>,
3065 ) {
3066 let multibuffer = cx
3067 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3068
3069 let style = window.text_style();
3070
3071 self.placeholder_display_map = Some(cx.new(|cx| {
3072 DisplayMap::new(
3073 multibuffer,
3074 style.font(),
3075 style.font_size.to_pixels(window.rem_size()),
3076 None,
3077 FILE_HEADER_HEIGHT,
3078 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3079 Default::default(),
3080 DiagnosticSeverity::Off,
3081 cx,
3082 )
3083 }));
3084 cx.notify();
3085 }
3086
3087 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3088 self.cursor_shape = cursor_shape;
3089
3090 // Disrupt blink for immediate user feedback that the cursor shape has changed
3091 self.blink_manager.update(cx, BlinkManager::show_cursor);
3092
3093 cx.notify();
3094 }
3095
3096 pub fn cursor_shape(&self) -> CursorShape {
3097 self.cursor_shape
3098 }
3099
3100 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3101 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3102 }
3103
3104 pub fn set_current_line_highlight(
3105 &mut self,
3106 current_line_highlight: Option<CurrentLineHighlight>,
3107 ) {
3108 self.current_line_highlight = current_line_highlight;
3109 }
3110
3111 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3112 self.collapse_matches = collapse_matches;
3113 }
3114
3115 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3116 if self.collapse_matches {
3117 return range.start..range.start;
3118 }
3119 range.clone()
3120 }
3121
3122 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3123 self.display_map.read(cx).clip_at_line_ends
3124 }
3125
3126 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3127 if self.display_map.read(cx).clip_at_line_ends != clip {
3128 self.display_map
3129 .update(cx, |map, _| map.clip_at_line_ends = clip);
3130 }
3131 }
3132
3133 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3134 self.input_enabled = input_enabled;
3135 }
3136
3137 pub fn set_edit_predictions_hidden_for_vim_mode(
3138 &mut self,
3139 hidden: bool,
3140 window: &mut Window,
3141 cx: &mut Context<Self>,
3142 ) {
3143 if hidden != self.edit_predictions_hidden_for_vim_mode {
3144 self.edit_predictions_hidden_for_vim_mode = hidden;
3145 if hidden {
3146 self.update_visible_edit_prediction(window, cx);
3147 } else {
3148 self.refresh_edit_prediction(true, false, window, cx);
3149 }
3150 }
3151 }
3152
3153 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3154 self.menu_edit_predictions_policy = value;
3155 }
3156
3157 pub fn set_autoindent(&mut self, autoindent: bool) {
3158 if autoindent {
3159 self.autoindent_mode = Some(AutoindentMode::EachLine);
3160 } else {
3161 self.autoindent_mode = None;
3162 }
3163 }
3164
3165 pub fn capability(&self, cx: &App) -> Capability {
3166 if self.read_only {
3167 Capability::ReadOnly
3168 } else {
3169 self.buffer.read(cx).capability()
3170 }
3171 }
3172
3173 pub fn read_only(&self, cx: &App) -> bool {
3174 self.read_only || self.buffer.read(cx).read_only()
3175 }
3176
3177 pub fn set_read_only(&mut self, read_only: bool) {
3178 self.read_only = read_only;
3179 }
3180
3181 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3182 self.use_autoclose = autoclose;
3183 }
3184
3185 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3186 self.use_auto_surround = auto_surround;
3187 }
3188
3189 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3190 self.auto_replace_emoji_shortcode = auto_replace;
3191 }
3192
3193 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3194 self.buffer_serialization = should_serialize.then(|| {
3195 BufferSerialization::new(
3196 ProjectSettings::get_global(cx)
3197 .session
3198 .restore_unsaved_buffers,
3199 )
3200 })
3201 }
3202
3203 fn should_serialize_buffer(&self) -> bool {
3204 self.buffer_serialization.is_some()
3205 }
3206
3207 pub fn toggle_edit_predictions(
3208 &mut self,
3209 _: &ToggleEditPrediction,
3210 window: &mut Window,
3211 cx: &mut Context<Self>,
3212 ) {
3213 if self.show_edit_predictions_override.is_some() {
3214 self.set_show_edit_predictions(None, window, cx);
3215 } else {
3216 let show_edit_predictions = !self.edit_predictions_enabled();
3217 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3218 }
3219 }
3220
3221 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3222 self.show_completions_on_input_override = show_completions_on_input;
3223 }
3224
3225 pub fn set_show_edit_predictions(
3226 &mut self,
3227 show_edit_predictions: Option<bool>,
3228 window: &mut Window,
3229 cx: &mut Context<Self>,
3230 ) {
3231 self.show_edit_predictions_override = show_edit_predictions;
3232 self.update_edit_prediction_settings(cx);
3233
3234 if let Some(false) = show_edit_predictions {
3235 self.discard_edit_prediction(false, cx);
3236 } else {
3237 self.refresh_edit_prediction(false, true, window, cx);
3238 }
3239 }
3240
3241 fn edit_predictions_disabled_in_scope(
3242 &self,
3243 buffer: &Entity<Buffer>,
3244 buffer_position: language::Anchor,
3245 cx: &App,
3246 ) -> bool {
3247 let snapshot = buffer.read(cx).snapshot();
3248 let settings = snapshot.settings_at(buffer_position, cx);
3249
3250 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3251 return false;
3252 };
3253
3254 scope.override_name().is_some_and(|scope_name| {
3255 settings
3256 .edit_predictions_disabled_in
3257 .iter()
3258 .any(|s| s == scope_name)
3259 })
3260 }
3261
3262 pub fn set_use_modal_editing(&mut self, to: bool) {
3263 self.use_modal_editing = to;
3264 }
3265
3266 pub fn use_modal_editing(&self) -> bool {
3267 self.use_modal_editing
3268 }
3269
3270 fn selections_did_change(
3271 &mut self,
3272 local: bool,
3273 old_cursor_position: &Anchor,
3274 effects: SelectionEffects,
3275 window: &mut Window,
3276 cx: &mut Context<Self>,
3277 ) {
3278 window.invalidate_character_coordinates();
3279
3280 // Copy selections to primary selection buffer
3281 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3282 if local {
3283 let selections = self
3284 .selections
3285 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3286 let buffer_handle = self.buffer.read(cx).read(cx);
3287
3288 let mut text = String::new();
3289 for (index, selection) in selections.iter().enumerate() {
3290 let text_for_selection = buffer_handle
3291 .text_for_range(selection.start..selection.end)
3292 .collect::<String>();
3293
3294 text.push_str(&text_for_selection);
3295 if index != selections.len() - 1 {
3296 text.push('\n');
3297 }
3298 }
3299
3300 if !text.is_empty() {
3301 cx.write_to_primary(ClipboardItem::new_string(text));
3302 }
3303 }
3304
3305 let selection_anchors = self.selections.disjoint_anchors_arc();
3306
3307 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3308 self.buffer.update(cx, |buffer, cx| {
3309 buffer.set_active_selections(
3310 &selection_anchors,
3311 self.selections.line_mode(),
3312 self.cursor_shape,
3313 cx,
3314 )
3315 });
3316 }
3317 let display_map = self
3318 .display_map
3319 .update(cx, |display_map, cx| display_map.snapshot(cx));
3320 let buffer = display_map.buffer_snapshot();
3321 if self.selections.count() == 1 {
3322 self.add_selections_state = None;
3323 }
3324 self.select_next_state = None;
3325 self.select_prev_state = None;
3326 self.select_syntax_node_history.try_clear();
3327 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3328 self.snippet_stack.invalidate(&selection_anchors, buffer);
3329 self.take_rename(false, window, cx);
3330
3331 let newest_selection = self.selections.newest_anchor();
3332 let new_cursor_position = newest_selection.head();
3333 let selection_start = newest_selection.start;
3334
3335 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3336 self.push_to_nav_history(
3337 *old_cursor_position,
3338 Some(new_cursor_position.to_point(buffer)),
3339 false,
3340 effects.nav_history == Some(true),
3341 cx,
3342 );
3343 }
3344
3345 if local {
3346 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3347 self.register_buffer(buffer_id, cx);
3348 }
3349
3350 let mut context_menu = self.context_menu.borrow_mut();
3351 let completion_menu = match context_menu.as_ref() {
3352 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3353 Some(CodeContextMenu::CodeActions(_)) => {
3354 *context_menu = None;
3355 None
3356 }
3357 None => None,
3358 };
3359 let completion_position = completion_menu.map(|menu| menu.initial_position);
3360 drop(context_menu);
3361
3362 if effects.completions
3363 && let Some(completion_position) = completion_position
3364 {
3365 let start_offset = selection_start.to_offset(buffer);
3366 let position_matches = start_offset == completion_position.to_offset(buffer);
3367 let continue_showing = if let Some((snap, ..)) =
3368 buffer.point_to_buffer_offset(completion_position)
3369 && !snap.capability.editable()
3370 {
3371 false
3372 } else if position_matches {
3373 if self.snippet_stack.is_empty() {
3374 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3375 == Some(CharKind::Word)
3376 } else {
3377 // Snippet choices can be shown even when the cursor is in whitespace.
3378 // Dismissing the menu with actions like backspace is handled by
3379 // invalidation regions.
3380 true
3381 }
3382 } else {
3383 false
3384 };
3385
3386 if continue_showing {
3387 self.open_or_update_completions_menu(None, None, false, window, cx);
3388 } else {
3389 self.hide_context_menu(window, cx);
3390 }
3391 }
3392
3393 hide_hover(self, cx);
3394
3395 if old_cursor_position.to_display_point(&display_map).row()
3396 != new_cursor_position.to_display_point(&display_map).row()
3397 {
3398 self.available_code_actions.take();
3399 }
3400 self.refresh_code_actions(window, cx);
3401 self.refresh_document_highlights(cx);
3402 refresh_linked_ranges(self, window, cx);
3403
3404 self.refresh_selected_text_highlights(false, window, cx);
3405 self.refresh_matching_bracket_highlights(window, cx);
3406 self.update_visible_edit_prediction(window, cx);
3407 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3408 self.inline_blame_popover.take();
3409 if self.git_blame_inline_enabled {
3410 self.start_inline_blame_timer(window, cx);
3411 }
3412 }
3413
3414 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3415 cx.emit(EditorEvent::SelectionsChanged { local });
3416
3417 let selections = &self.selections.disjoint_anchors_arc();
3418 if selections.len() == 1 {
3419 cx.emit(SearchEvent::ActiveMatchChanged)
3420 }
3421 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3422 let inmemory_selections = selections
3423 .iter()
3424 .map(|s| {
3425 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3426 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3427 })
3428 .collect();
3429 self.update_restoration_data(cx, |data| {
3430 data.selections = inmemory_selections;
3431 });
3432
3433 if WorkspaceSettings::get(None, cx).restore_on_startup
3434 != RestoreOnStartupBehavior::EmptyTab
3435 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3436 {
3437 let snapshot = self.buffer().read(cx).snapshot(cx);
3438 let selections = selections.clone();
3439 let background_executor = cx.background_executor().clone();
3440 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3441 self.serialize_selections = cx.background_spawn(async move {
3442 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3443 let db_selections = selections
3444 .iter()
3445 .map(|selection| {
3446 (
3447 selection.start.to_offset(&snapshot).0,
3448 selection.end.to_offset(&snapshot).0,
3449 )
3450 })
3451 .collect();
3452
3453 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3454 .await
3455 .with_context(|| {
3456 format!(
3457 "persisting editor selections for editor {editor_id}, \
3458 workspace {workspace_id:?}"
3459 )
3460 })
3461 .log_err();
3462 });
3463 }
3464 }
3465
3466 cx.notify();
3467 }
3468
3469 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3470 use text::ToOffset as _;
3471 use text::ToPoint as _;
3472
3473 if self.mode.is_minimap()
3474 || WorkspaceSettings::get(None, cx).restore_on_startup
3475 == RestoreOnStartupBehavior::EmptyTab
3476 {
3477 return;
3478 }
3479
3480 if !self.buffer().read(cx).is_singleton() {
3481 return;
3482 }
3483
3484 let display_snapshot = self
3485 .display_map
3486 .update(cx, |display_map, cx| display_map.snapshot(cx));
3487 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3488 return;
3489 };
3490 let inmemory_folds = display_snapshot
3491 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3492 .map(|fold| {
3493 fold.range.start.text_anchor.to_point(&snapshot)
3494 ..fold.range.end.text_anchor.to_point(&snapshot)
3495 })
3496 .collect();
3497 self.update_restoration_data(cx, |data| {
3498 data.folds = inmemory_folds;
3499 });
3500
3501 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3502 return;
3503 };
3504 let background_executor = cx.background_executor().clone();
3505 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3506 let db_folds = display_snapshot
3507 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3508 .map(|fold| {
3509 (
3510 fold.range.start.text_anchor.to_offset(&snapshot),
3511 fold.range.end.text_anchor.to_offset(&snapshot),
3512 )
3513 })
3514 .collect();
3515 self.serialize_folds = cx.background_spawn(async move {
3516 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3517 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3518 .await
3519 .with_context(|| {
3520 format!(
3521 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3522 )
3523 })
3524 .log_err();
3525 });
3526 }
3527
3528 pub fn sync_selections(
3529 &mut self,
3530 other: Entity<Editor>,
3531 cx: &mut Context<Self>,
3532 ) -> gpui::Subscription {
3533 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3534 if !other_selections.is_empty() {
3535 self.selections
3536 .change_with(&self.display_snapshot(cx), |selections| {
3537 selections.select_anchors(other_selections);
3538 });
3539 }
3540
3541 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3542 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3543 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3544 if other_selections.is_empty() {
3545 return;
3546 }
3547 let snapshot = this.display_snapshot(cx);
3548 this.selections.change_with(&snapshot, |selections| {
3549 selections.select_anchors(other_selections);
3550 });
3551 }
3552 });
3553
3554 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3555 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3556 let these_selections = this.selections.disjoint_anchors().to_vec();
3557 if these_selections.is_empty() {
3558 return;
3559 }
3560 other.update(cx, |other_editor, cx| {
3561 let snapshot = other_editor.display_snapshot(cx);
3562 other_editor
3563 .selections
3564 .change_with(&snapshot, |selections| {
3565 selections.select_anchors(these_selections);
3566 })
3567 });
3568 }
3569 });
3570
3571 Subscription::join(other_subscription, this_subscription)
3572 }
3573
3574 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3575 if self.buffer().read(cx).is_singleton() {
3576 return;
3577 }
3578 let snapshot = self.buffer.read(cx).snapshot(cx);
3579 let buffer_ids: HashSet<BufferId> = self
3580 .selections
3581 .disjoint_anchor_ranges()
3582 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3583 .collect();
3584 for buffer_id in buffer_ids {
3585 self.unfold_buffer(buffer_id, cx);
3586 }
3587 }
3588
3589 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3590 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3591 /// effects of selection change occur at the end of the transaction.
3592 pub fn change_selections<R>(
3593 &mut self,
3594 effects: SelectionEffects,
3595 window: &mut Window,
3596 cx: &mut Context<Self>,
3597 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3598 ) -> R {
3599 let snapshot = self.display_snapshot(cx);
3600 if let Some(state) = &mut self.deferred_selection_effects_state {
3601 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3602 state.effects.completions = effects.completions;
3603 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3604 let (changed, result) = self.selections.change_with(&snapshot, change);
3605 state.changed |= changed;
3606 return result;
3607 }
3608 let mut state = DeferredSelectionEffectsState {
3609 changed: false,
3610 effects,
3611 old_cursor_position: self.selections.newest_anchor().head(),
3612 history_entry: SelectionHistoryEntry {
3613 selections: self.selections.disjoint_anchors_arc(),
3614 select_next_state: self.select_next_state.clone(),
3615 select_prev_state: self.select_prev_state.clone(),
3616 add_selections_state: self.add_selections_state.clone(),
3617 },
3618 };
3619 let (changed, result) = self.selections.change_with(&snapshot, change);
3620 state.changed = state.changed || changed;
3621 if self.defer_selection_effects {
3622 self.deferred_selection_effects_state = Some(state);
3623 } else {
3624 self.apply_selection_effects(state, window, cx);
3625 }
3626 result
3627 }
3628
3629 /// Defers the effects of selection change, so that the effects of multiple calls to
3630 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3631 /// to selection history and the state of popovers based on selection position aren't
3632 /// erroneously updated.
3633 pub fn with_selection_effects_deferred<R>(
3634 &mut self,
3635 window: &mut Window,
3636 cx: &mut Context<Self>,
3637 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3638 ) -> R {
3639 let already_deferred = self.defer_selection_effects;
3640 self.defer_selection_effects = true;
3641 let result = update(self, window, cx);
3642 if !already_deferred {
3643 self.defer_selection_effects = false;
3644 if let Some(state) = self.deferred_selection_effects_state.take() {
3645 self.apply_selection_effects(state, window, cx);
3646 }
3647 }
3648 result
3649 }
3650
3651 fn apply_selection_effects(
3652 &mut self,
3653 state: DeferredSelectionEffectsState,
3654 window: &mut Window,
3655 cx: &mut Context<Self>,
3656 ) {
3657 if state.changed {
3658 self.selection_history.push(state.history_entry);
3659
3660 if let Some(autoscroll) = state.effects.scroll {
3661 self.request_autoscroll(autoscroll, cx);
3662 }
3663
3664 let old_cursor_position = &state.old_cursor_position;
3665
3666 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3667
3668 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3669 self.show_signature_help(&ShowSignatureHelp, window, cx);
3670 }
3671 }
3672 }
3673
3674 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3675 where
3676 I: IntoIterator<Item = (Range<S>, T)>,
3677 S: ToOffset,
3678 T: Into<Arc<str>>,
3679 {
3680 if self.read_only(cx) {
3681 return;
3682 }
3683
3684 self.buffer
3685 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3686 }
3687
3688 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3689 where
3690 I: IntoIterator<Item = (Range<S>, T)>,
3691 S: ToOffset,
3692 T: Into<Arc<str>>,
3693 {
3694 if self.read_only(cx) {
3695 return;
3696 }
3697
3698 self.buffer.update(cx, |buffer, cx| {
3699 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3700 });
3701 }
3702
3703 pub fn edit_with_block_indent<I, S, T>(
3704 &mut self,
3705 edits: I,
3706 original_indent_columns: Vec<Option<u32>>,
3707 cx: &mut Context<Self>,
3708 ) where
3709 I: IntoIterator<Item = (Range<S>, T)>,
3710 S: ToOffset,
3711 T: Into<Arc<str>>,
3712 {
3713 if self.read_only(cx) {
3714 return;
3715 }
3716
3717 self.buffer.update(cx, |buffer, cx| {
3718 buffer.edit(
3719 edits,
3720 Some(AutoindentMode::Block {
3721 original_indent_columns,
3722 }),
3723 cx,
3724 )
3725 });
3726 }
3727
3728 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3729 self.hide_context_menu(window, cx);
3730
3731 match phase {
3732 SelectPhase::Begin {
3733 position,
3734 add,
3735 click_count,
3736 } => self.begin_selection(position, add, click_count, window, cx),
3737 SelectPhase::BeginColumnar {
3738 position,
3739 goal_column,
3740 reset,
3741 mode,
3742 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3743 SelectPhase::Extend {
3744 position,
3745 click_count,
3746 } => self.extend_selection(position, click_count, window, cx),
3747 SelectPhase::Update {
3748 position,
3749 goal_column,
3750 scroll_delta,
3751 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3752 SelectPhase::End => self.end_selection(window, cx),
3753 }
3754 }
3755
3756 fn extend_selection(
3757 &mut self,
3758 position: DisplayPoint,
3759 click_count: usize,
3760 window: &mut Window,
3761 cx: &mut Context<Self>,
3762 ) {
3763 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3764 let tail = self
3765 .selections
3766 .newest::<MultiBufferOffset>(&display_map)
3767 .tail();
3768 let click_count = click_count.max(match self.selections.select_mode() {
3769 SelectMode::Character => 1,
3770 SelectMode::Word(_) => 2,
3771 SelectMode::Line(_) => 3,
3772 SelectMode::All => 4,
3773 });
3774 self.begin_selection(position, false, click_count, window, cx);
3775
3776 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3777
3778 let current_selection = match self.selections.select_mode() {
3779 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3780 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3781 };
3782
3783 let mut pending_selection = self
3784 .selections
3785 .pending_anchor()
3786 .cloned()
3787 .expect("extend_selection not called with pending selection");
3788
3789 if pending_selection
3790 .start
3791 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3792 == Ordering::Greater
3793 {
3794 pending_selection.start = current_selection.start;
3795 }
3796 if pending_selection
3797 .end
3798 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3799 == Ordering::Less
3800 {
3801 pending_selection.end = current_selection.end;
3802 pending_selection.reversed = true;
3803 }
3804
3805 let mut pending_mode = self.selections.pending_mode().unwrap();
3806 match &mut pending_mode {
3807 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3808 _ => {}
3809 }
3810
3811 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3812 SelectionEffects::scroll(Autoscroll::fit())
3813 } else {
3814 SelectionEffects::no_scroll()
3815 };
3816
3817 self.change_selections(effects, window, cx, |s| {
3818 s.set_pending(pending_selection.clone(), pending_mode);
3819 s.set_is_extending(true);
3820 });
3821 }
3822
3823 fn begin_selection(
3824 &mut self,
3825 position: DisplayPoint,
3826 add: bool,
3827 click_count: usize,
3828 window: &mut Window,
3829 cx: &mut Context<Self>,
3830 ) {
3831 if !self.focus_handle.is_focused(window) {
3832 self.last_focused_descendant = None;
3833 window.focus(&self.focus_handle, cx);
3834 }
3835
3836 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3837 let buffer = display_map.buffer_snapshot();
3838 let position = display_map.clip_point(position, Bias::Left);
3839
3840 let start;
3841 let end;
3842 let mode;
3843 let mut auto_scroll;
3844 match click_count {
3845 1 => {
3846 start = buffer.anchor_before(position.to_point(&display_map));
3847 end = start;
3848 mode = SelectMode::Character;
3849 auto_scroll = true;
3850 }
3851 2 => {
3852 let position = display_map
3853 .clip_point(position, Bias::Left)
3854 .to_offset(&display_map, Bias::Left);
3855 let (range, _) = buffer.surrounding_word(position, None);
3856 start = buffer.anchor_before(range.start);
3857 end = buffer.anchor_before(range.end);
3858 mode = SelectMode::Word(start..end);
3859 auto_scroll = true;
3860 }
3861 3 => {
3862 let position = display_map
3863 .clip_point(position, Bias::Left)
3864 .to_point(&display_map);
3865 let line_start = display_map.prev_line_boundary(position).0;
3866 let next_line_start = buffer.clip_point(
3867 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3868 Bias::Left,
3869 );
3870 start = buffer.anchor_before(line_start);
3871 end = buffer.anchor_before(next_line_start);
3872 mode = SelectMode::Line(start..end);
3873 auto_scroll = true;
3874 }
3875 _ => {
3876 start = buffer.anchor_before(MultiBufferOffset(0));
3877 end = buffer.anchor_before(buffer.len());
3878 mode = SelectMode::All;
3879 auto_scroll = false;
3880 }
3881 }
3882 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3883
3884 let point_to_delete: Option<usize> = {
3885 let selected_points: Vec<Selection<Point>> =
3886 self.selections.disjoint_in_range(start..end, &display_map);
3887
3888 if !add || click_count > 1 {
3889 None
3890 } else if !selected_points.is_empty() {
3891 Some(selected_points[0].id)
3892 } else {
3893 let clicked_point_already_selected =
3894 self.selections.disjoint_anchors().iter().find(|selection| {
3895 selection.start.to_point(buffer) == start.to_point(buffer)
3896 || selection.end.to_point(buffer) == end.to_point(buffer)
3897 });
3898
3899 clicked_point_already_selected.map(|selection| selection.id)
3900 }
3901 };
3902
3903 let selections_count = self.selections.count();
3904 let effects = if auto_scroll {
3905 SelectionEffects::default()
3906 } else {
3907 SelectionEffects::no_scroll()
3908 };
3909
3910 self.change_selections(effects, window, cx, |s| {
3911 if let Some(point_to_delete) = point_to_delete {
3912 s.delete(point_to_delete);
3913
3914 if selections_count == 1 {
3915 s.set_pending_anchor_range(start..end, mode);
3916 }
3917 } else {
3918 if !add {
3919 s.clear_disjoint();
3920 }
3921
3922 s.set_pending_anchor_range(start..end, mode);
3923 }
3924 });
3925 }
3926
3927 fn begin_columnar_selection(
3928 &mut self,
3929 position: DisplayPoint,
3930 goal_column: u32,
3931 reset: bool,
3932 mode: ColumnarMode,
3933 window: &mut Window,
3934 cx: &mut Context<Self>,
3935 ) {
3936 if !self.focus_handle.is_focused(window) {
3937 self.last_focused_descendant = None;
3938 window.focus(&self.focus_handle, cx);
3939 }
3940
3941 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3942
3943 if reset {
3944 let pointer_position = display_map
3945 .buffer_snapshot()
3946 .anchor_before(position.to_point(&display_map));
3947
3948 self.change_selections(
3949 SelectionEffects::scroll(Autoscroll::newest()),
3950 window,
3951 cx,
3952 |s| {
3953 s.clear_disjoint();
3954 s.set_pending_anchor_range(
3955 pointer_position..pointer_position,
3956 SelectMode::Character,
3957 );
3958 },
3959 );
3960 };
3961
3962 let tail = self.selections.newest::<Point>(&display_map).tail();
3963 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3964 self.columnar_selection_state = match mode {
3965 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3966 selection_tail: selection_anchor,
3967 display_point: if reset {
3968 if position.column() != goal_column {
3969 Some(DisplayPoint::new(position.row(), goal_column))
3970 } else {
3971 None
3972 }
3973 } else {
3974 None
3975 },
3976 }),
3977 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3978 selection_tail: selection_anchor,
3979 }),
3980 };
3981
3982 if !reset {
3983 self.select_columns(position, goal_column, &display_map, window, cx);
3984 }
3985 }
3986
3987 fn update_selection(
3988 &mut self,
3989 position: DisplayPoint,
3990 goal_column: u32,
3991 scroll_delta: gpui::Point<f32>,
3992 window: &mut Window,
3993 cx: &mut Context<Self>,
3994 ) {
3995 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3996
3997 if self.columnar_selection_state.is_some() {
3998 self.select_columns(position, goal_column, &display_map, window, cx);
3999 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
4000 let buffer = display_map.buffer_snapshot();
4001 let head;
4002 let tail;
4003 let mode = self.selections.pending_mode().unwrap();
4004 match &mode {
4005 SelectMode::Character => {
4006 head = position.to_point(&display_map);
4007 tail = pending.tail().to_point(buffer);
4008 }
4009 SelectMode::Word(original_range) => {
4010 let offset = display_map
4011 .clip_point(position, Bias::Left)
4012 .to_offset(&display_map, Bias::Left);
4013 let original_range = original_range.to_offset(buffer);
4014
4015 let head_offset = if buffer.is_inside_word(offset, None)
4016 || original_range.contains(&offset)
4017 {
4018 let (word_range, _) = buffer.surrounding_word(offset, None);
4019 if word_range.start < original_range.start {
4020 word_range.start
4021 } else {
4022 word_range.end
4023 }
4024 } else {
4025 offset
4026 };
4027
4028 head = head_offset.to_point(buffer);
4029 if head_offset <= original_range.start {
4030 tail = original_range.end.to_point(buffer);
4031 } else {
4032 tail = original_range.start.to_point(buffer);
4033 }
4034 }
4035 SelectMode::Line(original_range) => {
4036 let original_range = original_range.to_point(display_map.buffer_snapshot());
4037
4038 let position = display_map
4039 .clip_point(position, Bias::Left)
4040 .to_point(&display_map);
4041 let line_start = display_map.prev_line_boundary(position).0;
4042 let next_line_start = buffer.clip_point(
4043 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4044 Bias::Left,
4045 );
4046
4047 if line_start < original_range.start {
4048 head = line_start
4049 } else {
4050 head = next_line_start
4051 }
4052
4053 if head <= original_range.start {
4054 tail = original_range.end;
4055 } else {
4056 tail = original_range.start;
4057 }
4058 }
4059 SelectMode::All => {
4060 return;
4061 }
4062 };
4063
4064 if head < tail {
4065 pending.start = buffer.anchor_before(head);
4066 pending.end = buffer.anchor_before(tail);
4067 pending.reversed = true;
4068 } else {
4069 pending.start = buffer.anchor_before(tail);
4070 pending.end = buffer.anchor_before(head);
4071 pending.reversed = false;
4072 }
4073
4074 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4075 s.set_pending(pending.clone(), mode);
4076 });
4077 } else {
4078 log::error!("update_selection dispatched with no pending selection");
4079 return;
4080 }
4081
4082 self.apply_scroll_delta(scroll_delta, window, cx);
4083 cx.notify();
4084 }
4085
4086 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4087 self.columnar_selection_state.take();
4088 if let Some(pending_mode) = self.selections.pending_mode() {
4089 let selections = self
4090 .selections
4091 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4092 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4093 s.select(selections);
4094 s.clear_pending();
4095 if s.is_extending() {
4096 s.set_is_extending(false);
4097 } else {
4098 s.set_select_mode(pending_mode);
4099 }
4100 });
4101 }
4102 }
4103
4104 fn select_columns(
4105 &mut self,
4106 head: DisplayPoint,
4107 goal_column: u32,
4108 display_map: &DisplaySnapshot,
4109 window: &mut Window,
4110 cx: &mut Context<Self>,
4111 ) {
4112 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4113 return;
4114 };
4115
4116 let tail = match columnar_state {
4117 ColumnarSelectionState::FromMouse {
4118 selection_tail,
4119 display_point,
4120 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4121 ColumnarSelectionState::FromSelection { selection_tail } => {
4122 selection_tail.to_display_point(display_map)
4123 }
4124 };
4125
4126 let start_row = cmp::min(tail.row(), head.row());
4127 let end_row = cmp::max(tail.row(), head.row());
4128 let start_column = cmp::min(tail.column(), goal_column);
4129 let end_column = cmp::max(tail.column(), goal_column);
4130 let reversed = start_column < tail.column();
4131
4132 let selection_ranges = (start_row.0..=end_row.0)
4133 .map(DisplayRow)
4134 .filter_map(|row| {
4135 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4136 || start_column <= display_map.line_len(row))
4137 && !display_map.is_block_line(row)
4138 {
4139 let start = display_map
4140 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4141 .to_point(display_map);
4142 let end = display_map
4143 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4144 .to_point(display_map);
4145 if reversed {
4146 Some(end..start)
4147 } else {
4148 Some(start..end)
4149 }
4150 } else {
4151 None
4152 }
4153 })
4154 .collect::<Vec<_>>();
4155 if selection_ranges.is_empty() {
4156 return;
4157 }
4158
4159 let ranges = match columnar_state {
4160 ColumnarSelectionState::FromMouse { .. } => {
4161 let mut non_empty_ranges = selection_ranges
4162 .iter()
4163 .filter(|selection_range| selection_range.start != selection_range.end)
4164 .peekable();
4165 if non_empty_ranges.peek().is_some() {
4166 non_empty_ranges.cloned().collect()
4167 } else {
4168 selection_ranges
4169 }
4170 }
4171 _ => selection_ranges,
4172 };
4173
4174 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4175 s.select_ranges(ranges);
4176 });
4177 cx.notify();
4178 }
4179
4180 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4181 self.selections
4182 .all_adjusted(snapshot)
4183 .iter()
4184 .any(|selection| !selection.is_empty())
4185 }
4186
4187 pub fn has_pending_nonempty_selection(&self) -> bool {
4188 let pending_nonempty_selection = match self.selections.pending_anchor() {
4189 Some(Selection { start, end, .. }) => start != end,
4190 None => false,
4191 };
4192
4193 pending_nonempty_selection
4194 || (self.columnar_selection_state.is_some()
4195 && self.selections.disjoint_anchors().len() > 1)
4196 }
4197
4198 pub fn has_pending_selection(&self) -> bool {
4199 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4200 }
4201
4202 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4203 self.selection_mark_mode = false;
4204 self.selection_drag_state = SelectionDragState::None;
4205
4206 if self.dismiss_menus_and_popups(true, window, cx) {
4207 cx.notify();
4208 return;
4209 }
4210 if self.clear_expanded_diff_hunks(cx) {
4211 cx.notify();
4212 return;
4213 }
4214 if self.show_git_blame_gutter {
4215 self.show_git_blame_gutter = false;
4216 cx.notify();
4217 return;
4218 }
4219
4220 if self.mode.is_full()
4221 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4222 {
4223 cx.notify();
4224 return;
4225 }
4226
4227 cx.propagate();
4228 }
4229
4230 pub fn dismiss_menus_and_popups(
4231 &mut self,
4232 is_user_requested: bool,
4233 window: &mut Window,
4234 cx: &mut Context<Self>,
4235 ) -> bool {
4236 let mut dismissed = false;
4237
4238 dismissed |= self.take_rename(false, window, cx).is_some();
4239 dismissed |= self.hide_blame_popover(true, cx);
4240 dismissed |= hide_hover(self, cx);
4241 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4242 dismissed |= self.hide_context_menu(window, cx).is_some();
4243 dismissed |= self.mouse_context_menu.take().is_some();
4244 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4245 dismissed |= self.snippet_stack.pop().is_some();
4246
4247 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4248 self.dismiss_diagnostics(cx);
4249 dismissed = true;
4250 }
4251
4252 dismissed
4253 }
4254
4255 fn linked_editing_ranges_for(
4256 &self,
4257 selection: Range<text::Anchor>,
4258 cx: &App,
4259 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4260 if self.linked_edit_ranges.is_empty() {
4261 return None;
4262 }
4263 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4264 selection.end.buffer_id.and_then(|end_buffer_id| {
4265 if selection.start.buffer_id != Some(end_buffer_id) {
4266 return None;
4267 }
4268 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4269 let snapshot = buffer.read(cx).snapshot();
4270 self.linked_edit_ranges
4271 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4272 .map(|ranges| (ranges, snapshot, buffer))
4273 })?;
4274 use text::ToOffset as TO;
4275 // find offset from the start of current range to current cursor position
4276 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4277
4278 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4279 let start_difference = start_offset - start_byte_offset;
4280 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4281 let end_difference = end_offset - start_byte_offset;
4282 // Current range has associated linked ranges.
4283 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4284 for range in linked_ranges.iter() {
4285 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4286 let end_offset = start_offset + end_difference;
4287 let start_offset = start_offset + start_difference;
4288 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4289 continue;
4290 }
4291 if self.selections.disjoint_anchor_ranges().any(|s| {
4292 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4293 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4294 {
4295 return false;
4296 }
4297 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4298 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4299 }) {
4300 continue;
4301 }
4302 let start = buffer_snapshot.anchor_after(start_offset);
4303 let end = buffer_snapshot.anchor_after(end_offset);
4304 linked_edits
4305 .entry(buffer.clone())
4306 .or_default()
4307 .push(start..end);
4308 }
4309 Some(linked_edits)
4310 }
4311
4312 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4313 let text: Arc<str> = text.into();
4314
4315 if self.read_only(cx) {
4316 return;
4317 }
4318
4319 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4320
4321 self.unfold_buffers_with_selections(cx);
4322
4323 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4324 let mut bracket_inserted = false;
4325 let mut edits = Vec::new();
4326 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4327 let mut new_selections = Vec::with_capacity(selections.len());
4328 let mut new_autoclose_regions = Vec::new();
4329 let snapshot = self.buffer.read(cx).read(cx);
4330 let mut clear_linked_edit_ranges = false;
4331 let mut all_selections_read_only = true;
4332 let mut has_adjacent_edits = false;
4333 let mut in_adjacent_group = false;
4334
4335 let mut regions = self
4336 .selections_with_autoclose_regions(selections, &snapshot)
4337 .peekable();
4338
4339 while let Some((selection, autoclose_region)) = regions.next() {
4340 if snapshot
4341 .point_to_buffer_point(selection.head())
4342 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4343 {
4344 continue;
4345 }
4346 if snapshot
4347 .point_to_buffer_point(selection.tail())
4348 .is_none_or(|(snapshot, ..)| !snapshot.capability.editable())
4349 {
4350 // note, ideally we'd clip the tail to the closest writeable region towards the head
4351 continue;
4352 }
4353 all_selections_read_only = false;
4354
4355 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4356 // Determine if the inserted text matches the opening or closing
4357 // bracket of any of this language's bracket pairs.
4358 let mut bracket_pair = None;
4359 let mut is_bracket_pair_start = false;
4360 let mut is_bracket_pair_end = false;
4361 if !text.is_empty() {
4362 let mut bracket_pair_matching_end = None;
4363 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4364 // and they are removing the character that triggered IME popup.
4365 for (pair, enabled) in scope.brackets() {
4366 if !pair.close && !pair.surround {
4367 continue;
4368 }
4369
4370 if enabled && pair.start.ends_with(text.as_ref()) {
4371 let prefix_len = pair.start.len() - text.len();
4372 let preceding_text_matches_prefix = prefix_len == 0
4373 || (selection.start.column >= (prefix_len as u32)
4374 && snapshot.contains_str_at(
4375 Point::new(
4376 selection.start.row,
4377 selection.start.column - (prefix_len as u32),
4378 ),
4379 &pair.start[..prefix_len],
4380 ));
4381 if preceding_text_matches_prefix {
4382 bracket_pair = Some(pair.clone());
4383 is_bracket_pair_start = true;
4384 break;
4385 }
4386 }
4387 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4388 {
4389 // take first bracket pair matching end, but don't break in case a later bracket
4390 // pair matches start
4391 bracket_pair_matching_end = Some(pair.clone());
4392 }
4393 }
4394 if let Some(end) = bracket_pair_matching_end
4395 && bracket_pair.is_none()
4396 {
4397 bracket_pair = Some(end);
4398 is_bracket_pair_end = true;
4399 }
4400 }
4401
4402 if let Some(bracket_pair) = bracket_pair {
4403 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4404 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4405 let auto_surround =
4406 self.use_auto_surround && snapshot_settings.use_auto_surround;
4407 if selection.is_empty() {
4408 if is_bracket_pair_start {
4409 // If the inserted text is a suffix of an opening bracket and the
4410 // selection is preceded by the rest of the opening bracket, then
4411 // insert the closing bracket.
4412 let following_text_allows_autoclose = snapshot
4413 .chars_at(selection.start)
4414 .next()
4415 .is_none_or(|c| scope.should_autoclose_before(c));
4416
4417 let preceding_text_allows_autoclose = selection.start.column == 0
4418 || snapshot
4419 .reversed_chars_at(selection.start)
4420 .next()
4421 .is_none_or(|c| {
4422 bracket_pair.start != bracket_pair.end
4423 || !snapshot
4424 .char_classifier_at(selection.start)
4425 .is_word(c)
4426 });
4427
4428 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4429 && bracket_pair.start.len() == 1
4430 {
4431 let target = bracket_pair.start.chars().next().unwrap();
4432 let mut byte_offset = 0u32;
4433 let current_line_count = snapshot
4434 .reversed_chars_at(selection.start)
4435 .take_while(|&c| c != '\n')
4436 .filter(|c| {
4437 byte_offset += c.len_utf8() as u32;
4438 if *c != target {
4439 return false;
4440 }
4441
4442 let point = Point::new(
4443 selection.start.row,
4444 selection.start.column.saturating_sub(byte_offset),
4445 );
4446
4447 let is_enabled = snapshot
4448 .language_scope_at(point)
4449 .and_then(|scope| {
4450 scope
4451 .brackets()
4452 .find(|(pair, _)| {
4453 pair.start == bracket_pair.start
4454 })
4455 .map(|(_, enabled)| enabled)
4456 })
4457 .unwrap_or(true);
4458
4459 let is_delimiter = snapshot
4460 .language_scope_at(Point::new(
4461 point.row,
4462 point.column + 1,
4463 ))
4464 .and_then(|scope| {
4465 scope
4466 .brackets()
4467 .find(|(pair, _)| {
4468 pair.start == bracket_pair.start
4469 })
4470 .map(|(_, enabled)| !enabled)
4471 })
4472 .unwrap_or(false);
4473
4474 is_enabled && !is_delimiter
4475 })
4476 .count();
4477 current_line_count % 2 == 1
4478 } else {
4479 false
4480 };
4481
4482 if autoclose
4483 && bracket_pair.close
4484 && following_text_allows_autoclose
4485 && preceding_text_allows_autoclose
4486 && !is_closing_quote
4487 {
4488 let anchor = snapshot.anchor_before(selection.end);
4489 new_selections.push((selection.map(|_| anchor), text.len()));
4490 new_autoclose_regions.push((
4491 anchor,
4492 text.len(),
4493 selection.id,
4494 bracket_pair.clone(),
4495 ));
4496 edits.push((
4497 selection.range(),
4498 format!("{}{}", text, bracket_pair.end).into(),
4499 ));
4500 bracket_inserted = true;
4501 continue;
4502 }
4503 }
4504
4505 if let Some(region) = autoclose_region {
4506 // If the selection is followed by an auto-inserted closing bracket,
4507 // then don't insert that closing bracket again; just move the selection
4508 // past the closing bracket.
4509 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4510 && text.as_ref() == region.pair.end.as_str()
4511 && snapshot.contains_str_at(region.range.end, text.as_ref());
4512 if should_skip {
4513 let anchor = snapshot.anchor_after(selection.end);
4514 new_selections
4515 .push((selection.map(|_| anchor), region.pair.end.len()));
4516 continue;
4517 }
4518 }
4519
4520 let always_treat_brackets_as_autoclosed = snapshot
4521 .language_settings_at(selection.start, cx)
4522 .always_treat_brackets_as_autoclosed;
4523 if always_treat_brackets_as_autoclosed
4524 && is_bracket_pair_end
4525 && snapshot.contains_str_at(selection.end, text.as_ref())
4526 {
4527 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4528 // and the inserted text is a closing bracket and the selection is followed
4529 // by the closing bracket then move the selection past the closing bracket.
4530 let anchor = snapshot.anchor_after(selection.end);
4531 new_selections.push((selection.map(|_| anchor), text.len()));
4532 continue;
4533 }
4534 }
4535 // If an opening bracket is 1 character long and is typed while
4536 // text is selected, then surround that text with the bracket pair.
4537 else if auto_surround
4538 && bracket_pair.surround
4539 && is_bracket_pair_start
4540 && bracket_pair.start.chars().count() == 1
4541 {
4542 edits.push((selection.start..selection.start, text.clone()));
4543 edits.push((
4544 selection.end..selection.end,
4545 bracket_pair.end.as_str().into(),
4546 ));
4547 bracket_inserted = true;
4548 new_selections.push((
4549 Selection {
4550 id: selection.id,
4551 start: snapshot.anchor_after(selection.start),
4552 end: snapshot.anchor_before(selection.end),
4553 reversed: selection.reversed,
4554 goal: selection.goal,
4555 },
4556 0,
4557 ));
4558 continue;
4559 }
4560 }
4561 }
4562
4563 if self.auto_replace_emoji_shortcode
4564 && selection.is_empty()
4565 && text.as_ref().ends_with(':')
4566 && let Some(possible_emoji_short_code) =
4567 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4568 && !possible_emoji_short_code.is_empty()
4569 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4570 {
4571 let emoji_shortcode_start = Point::new(
4572 selection.start.row,
4573 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4574 );
4575
4576 // Remove shortcode from buffer
4577 edits.push((
4578 emoji_shortcode_start..selection.start,
4579 "".to_string().into(),
4580 ));
4581 new_selections.push((
4582 Selection {
4583 id: selection.id,
4584 start: snapshot.anchor_after(emoji_shortcode_start),
4585 end: snapshot.anchor_before(selection.start),
4586 reversed: selection.reversed,
4587 goal: selection.goal,
4588 },
4589 0,
4590 ));
4591
4592 // Insert emoji
4593 let selection_start_anchor = snapshot.anchor_after(selection.start);
4594 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4595 edits.push((selection.start..selection.end, emoji.to_string().into()));
4596
4597 continue;
4598 }
4599
4600 let next_is_adjacent = regions
4601 .peek()
4602 .is_some_and(|(next, _)| selection.end == next.start);
4603
4604 // If not handling any auto-close operation, then just replace the selected
4605 // text with the given input and move the selection to the end of the
4606 // newly inserted text.
4607 let anchor = if in_adjacent_group || next_is_adjacent {
4608 // After edits the right bias would shift those anchor to the next visible fragment
4609 // but we want to resolve to the previous one
4610 snapshot.anchor_before(selection.end)
4611 } else {
4612 snapshot.anchor_after(selection.end)
4613 };
4614
4615 if !self.linked_edit_ranges.is_empty() {
4616 let start_anchor = snapshot.anchor_before(selection.start);
4617
4618 let is_word_char = text.chars().next().is_none_or(|char| {
4619 let classifier = snapshot
4620 .char_classifier_at(start_anchor.to_offset(&snapshot))
4621 .scope_context(Some(CharScopeContext::LinkedEdit));
4622 classifier.is_word(char)
4623 });
4624
4625 if is_word_char {
4626 if let Some(ranges) = self
4627 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4628 {
4629 for (buffer, edits) in ranges {
4630 linked_edits
4631 .entry(buffer.clone())
4632 .or_default()
4633 .extend(edits.into_iter().map(|range| (range, text.clone())));
4634 }
4635 }
4636 } else {
4637 clear_linked_edit_ranges = true;
4638 }
4639 }
4640
4641 new_selections.push((selection.map(|_| anchor), 0));
4642 edits.push((selection.start..selection.end, text.clone()));
4643
4644 has_adjacent_edits |= next_is_adjacent;
4645 in_adjacent_group = next_is_adjacent;
4646 }
4647
4648 if all_selections_read_only {
4649 return;
4650 }
4651
4652 drop(regions);
4653 drop(snapshot);
4654
4655 self.transact(window, cx, |this, window, cx| {
4656 if clear_linked_edit_ranges {
4657 this.linked_edit_ranges.clear();
4658 }
4659 let initial_buffer_versions =
4660 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4661
4662 this.buffer.update(cx, |buffer, cx| {
4663 if has_adjacent_edits {
4664 buffer.edit_non_coalesce(edits, this.autoindent_mode.clone(), cx);
4665 } else {
4666 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4667 }
4668 });
4669 for (buffer, edits) in linked_edits {
4670 buffer.update(cx, |buffer, cx| {
4671 let snapshot = buffer.snapshot();
4672 let edits = edits
4673 .into_iter()
4674 .map(|(range, text)| {
4675 use text::ToPoint as TP;
4676 let end_point = TP::to_point(&range.end, &snapshot);
4677 let start_point = TP::to_point(&range.start, &snapshot);
4678 (start_point..end_point, text)
4679 })
4680 .sorted_by_key(|(range, _)| range.start);
4681 buffer.edit(edits, None, cx);
4682 })
4683 }
4684 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4685 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4686 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4687 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4688 new_anchor_selections,
4689 &map,
4690 )
4691 .zip(new_selection_deltas)
4692 .map(|(selection, delta)| Selection {
4693 id: selection.id,
4694 start: selection.start + delta,
4695 end: selection.end + delta,
4696 reversed: selection.reversed,
4697 goal: SelectionGoal::None,
4698 })
4699 .collect::<Vec<_>>();
4700
4701 let mut i = 0;
4702 for (position, delta, selection_id, pair) in new_autoclose_regions {
4703 let position = position.to_offset(map.buffer_snapshot()) + delta;
4704 let start = map.buffer_snapshot().anchor_before(position);
4705 let end = map.buffer_snapshot().anchor_after(position);
4706 while let Some(existing_state) = this.autoclose_regions.get(i) {
4707 match existing_state
4708 .range
4709 .start
4710 .cmp(&start, map.buffer_snapshot())
4711 {
4712 Ordering::Less => i += 1,
4713 Ordering::Greater => break,
4714 Ordering::Equal => {
4715 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4716 Ordering::Less => i += 1,
4717 Ordering::Equal => break,
4718 Ordering::Greater => break,
4719 }
4720 }
4721 }
4722 }
4723 this.autoclose_regions.insert(
4724 i,
4725 AutocloseRegion {
4726 selection_id,
4727 range: start..end,
4728 pair,
4729 },
4730 );
4731 }
4732
4733 let had_active_edit_prediction = this.has_active_edit_prediction();
4734 this.change_selections(
4735 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4736 window,
4737 cx,
4738 |s| s.select(new_selections),
4739 );
4740
4741 if !bracket_inserted
4742 && let Some(on_type_format_task) =
4743 this.trigger_on_type_formatting(text.to_string(), window, cx)
4744 {
4745 on_type_format_task.detach_and_log_err(cx);
4746 }
4747
4748 let editor_settings = EditorSettings::get_global(cx);
4749 if bracket_inserted
4750 && (editor_settings.auto_signature_help
4751 || editor_settings.show_signature_help_after_edits)
4752 {
4753 this.show_signature_help(&ShowSignatureHelp, window, cx);
4754 }
4755
4756 let trigger_in_words =
4757 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4758 if this.hard_wrap.is_some() {
4759 let latest: Range<Point> = this.selections.newest(&map).range();
4760 if latest.is_empty()
4761 && this
4762 .buffer()
4763 .read(cx)
4764 .snapshot(cx)
4765 .line_len(MultiBufferRow(latest.start.row))
4766 == latest.start.column
4767 {
4768 this.rewrap_impl(
4769 RewrapOptions {
4770 override_language_settings: true,
4771 preserve_existing_whitespace: true,
4772 },
4773 cx,
4774 )
4775 }
4776 }
4777 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4778 refresh_linked_ranges(this, window, cx);
4779 this.refresh_edit_prediction(true, false, window, cx);
4780 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4781 });
4782 }
4783
4784 fn find_possible_emoji_shortcode_at_position(
4785 snapshot: &MultiBufferSnapshot,
4786 position: Point,
4787 ) -> Option<String> {
4788 let mut chars = Vec::new();
4789 let mut found_colon = false;
4790 for char in snapshot.reversed_chars_at(position).take(100) {
4791 // Found a possible emoji shortcode in the middle of the buffer
4792 if found_colon {
4793 if char.is_whitespace() {
4794 chars.reverse();
4795 return Some(chars.iter().collect());
4796 }
4797 // If the previous character is not a whitespace, we are in the middle of a word
4798 // and we only want to complete the shortcode if the word is made up of other emojis
4799 let mut containing_word = String::new();
4800 for ch in snapshot
4801 .reversed_chars_at(position)
4802 .skip(chars.len() + 1)
4803 .take(100)
4804 {
4805 if ch.is_whitespace() {
4806 break;
4807 }
4808 containing_word.push(ch);
4809 }
4810 let containing_word = containing_word.chars().rev().collect::<String>();
4811 if util::word_consists_of_emojis(containing_word.as_str()) {
4812 chars.reverse();
4813 return Some(chars.iter().collect());
4814 }
4815 }
4816
4817 if char.is_whitespace() || !char.is_ascii() {
4818 return None;
4819 }
4820 if char == ':' {
4821 found_colon = true;
4822 } else {
4823 chars.push(char);
4824 }
4825 }
4826 // Found a possible emoji shortcode at the beginning of the buffer
4827 chars.reverse();
4828 Some(chars.iter().collect())
4829 }
4830
4831 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4832 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4833 self.transact(window, cx, |this, window, cx| {
4834 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4835 let selections = this
4836 .selections
4837 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4838 let multi_buffer = this.buffer.read(cx);
4839 let buffer = multi_buffer.snapshot(cx);
4840 selections
4841 .iter()
4842 .map(|selection| {
4843 let start_point = selection.start.to_point(&buffer);
4844 let mut existing_indent =
4845 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4846 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4847 let start = selection.start;
4848 let end = selection.end;
4849 let selection_is_empty = start == end;
4850 let language_scope = buffer.language_scope_at(start);
4851 let (delimiter, newline_config) = if let Some(language) = &language_scope {
4852 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
4853 &buffer,
4854 start..end,
4855 language,
4856 )
4857 || NewlineConfig::insert_extra_newline_tree_sitter(
4858 &buffer,
4859 start..end,
4860 );
4861
4862 let mut newline_config = NewlineConfig::Newline {
4863 additional_indent: IndentSize::spaces(0),
4864 extra_line_additional_indent: if needs_extra_newline {
4865 Some(IndentSize::spaces(0))
4866 } else {
4867 None
4868 },
4869 prevent_auto_indent: false,
4870 };
4871
4872 let comment_delimiter = maybe!({
4873 if !selection_is_empty {
4874 return None;
4875 }
4876
4877 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4878 return None;
4879 }
4880
4881 return comment_delimiter_for_newline(
4882 &start_point,
4883 &buffer,
4884 language,
4885 );
4886 });
4887
4888 let doc_delimiter = maybe!({
4889 if !selection_is_empty {
4890 return None;
4891 }
4892
4893 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4894 return None;
4895 }
4896
4897 return documentation_delimiter_for_newline(
4898 &start_point,
4899 &buffer,
4900 language,
4901 &mut newline_config,
4902 );
4903 });
4904
4905 let list_delimiter = maybe!({
4906 if !selection_is_empty {
4907 return None;
4908 }
4909
4910 if !multi_buffer.language_settings(cx).extend_list_on_newline {
4911 return None;
4912 }
4913
4914 return list_delimiter_for_newline(
4915 &start_point,
4916 &buffer,
4917 language,
4918 &mut newline_config,
4919 );
4920 });
4921
4922 (
4923 comment_delimiter.or(doc_delimiter).or(list_delimiter),
4924 newline_config,
4925 )
4926 } else {
4927 (
4928 None,
4929 NewlineConfig::Newline {
4930 additional_indent: IndentSize::spaces(0),
4931 extra_line_additional_indent: None,
4932 prevent_auto_indent: false,
4933 },
4934 )
4935 };
4936
4937 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
4938 NewlineConfig::ClearCurrentLine => {
4939 let row_start =
4940 buffer.point_to_offset(Point::new(start_point.row, 0));
4941 (row_start, String::new(), false)
4942 }
4943 NewlineConfig::UnindentCurrentLine { continuation } => {
4944 let row_start =
4945 buffer.point_to_offset(Point::new(start_point.row, 0));
4946 let tab_size = buffer.language_settings_at(start, cx).tab_size;
4947 let tab_size_indent = IndentSize::spaces(tab_size.get());
4948 let reduced_indent =
4949 existing_indent.with_delta(Ordering::Less, tab_size_indent);
4950 let mut new_text = String::new();
4951 new_text.extend(reduced_indent.chars());
4952 new_text.push_str(continuation);
4953 (row_start, new_text, true)
4954 }
4955 NewlineConfig::Newline {
4956 additional_indent,
4957 extra_line_additional_indent,
4958 prevent_auto_indent,
4959 } => {
4960 let capacity_for_delimiter =
4961 delimiter.as_deref().map(str::len).unwrap_or_default();
4962 let extra_line_len = extra_line_additional_indent
4963 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
4964 .unwrap_or(0);
4965 let mut new_text = String::with_capacity(
4966 1 + capacity_for_delimiter
4967 + existing_indent.len as usize
4968 + additional_indent.len as usize
4969 + extra_line_len,
4970 );
4971 new_text.push('\n');
4972 new_text.extend(existing_indent.chars());
4973 new_text.extend(additional_indent.chars());
4974 if let Some(delimiter) = &delimiter {
4975 new_text.push_str(delimiter);
4976 }
4977 if let Some(extra_indent) = extra_line_additional_indent {
4978 new_text.push('\n');
4979 new_text.extend(existing_indent.chars());
4980 new_text.extend(extra_indent.chars());
4981 }
4982 (start, new_text, *prevent_auto_indent)
4983 }
4984 };
4985
4986 let anchor = buffer.anchor_after(end);
4987 let new_selection = selection.map(|_| anchor);
4988 (
4989 ((edit_start..end, new_text), prevent_auto_indent),
4990 (newline_config.has_extra_line(), new_selection),
4991 )
4992 })
4993 .unzip()
4994 };
4995
4996 let mut auto_indent_edits = Vec::new();
4997 let mut edits = Vec::new();
4998 for (edit, prevent_auto_indent) in edits_with_flags {
4999 if prevent_auto_indent {
5000 edits.push(edit);
5001 } else {
5002 auto_indent_edits.push(edit);
5003 }
5004 }
5005 if !edits.is_empty() {
5006 this.edit(edits, cx);
5007 }
5008 if !auto_indent_edits.is_empty() {
5009 this.edit_with_autoindent(auto_indent_edits, cx);
5010 }
5011
5012 let buffer = this.buffer.read(cx).snapshot(cx);
5013 let new_selections = selection_info
5014 .into_iter()
5015 .map(|(extra_newline_inserted, new_selection)| {
5016 let mut cursor = new_selection.end.to_point(&buffer);
5017 if extra_newline_inserted {
5018 cursor.row -= 1;
5019 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5020 }
5021 new_selection.map(|_| cursor)
5022 })
5023 .collect();
5024
5025 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5026 this.refresh_edit_prediction(true, false, window, cx);
5027 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5028 task.detach_and_log_err(cx);
5029 }
5030 });
5031 }
5032
5033 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5034 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5035
5036 let buffer = self.buffer.read(cx);
5037 let snapshot = buffer.snapshot(cx);
5038
5039 let mut edits = Vec::new();
5040 let mut rows = Vec::new();
5041
5042 for (rows_inserted, selection) in self
5043 .selections
5044 .all_adjusted(&self.display_snapshot(cx))
5045 .into_iter()
5046 .enumerate()
5047 {
5048 let cursor = selection.head();
5049 let row = cursor.row;
5050
5051 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5052
5053 let newline = "\n".to_string();
5054 edits.push((start_of_line..start_of_line, newline));
5055
5056 rows.push(row + rows_inserted as u32);
5057 }
5058
5059 self.transact(window, cx, |editor, window, cx| {
5060 editor.edit(edits, cx);
5061
5062 editor.change_selections(Default::default(), window, cx, |s| {
5063 let mut index = 0;
5064 s.move_cursors_with(|map, _, _| {
5065 let row = rows[index];
5066 index += 1;
5067
5068 let point = Point::new(row, 0);
5069 let boundary = map.next_line_boundary(point).1;
5070 let clipped = map.clip_point(boundary, Bias::Left);
5071
5072 (clipped, SelectionGoal::None)
5073 });
5074 });
5075
5076 let mut indent_edits = Vec::new();
5077 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5078 for row in rows {
5079 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5080 for (row, indent) in indents {
5081 if indent.len == 0 {
5082 continue;
5083 }
5084
5085 let text = match indent.kind {
5086 IndentKind::Space => " ".repeat(indent.len as usize),
5087 IndentKind::Tab => "\t".repeat(indent.len as usize),
5088 };
5089 let point = Point::new(row.0, 0);
5090 indent_edits.push((point..point, text));
5091 }
5092 }
5093 editor.edit(indent_edits, cx);
5094 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5095 format.detach_and_log_err(cx);
5096 }
5097 });
5098 }
5099
5100 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5101 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5102
5103 let buffer = self.buffer.read(cx);
5104 let snapshot = buffer.snapshot(cx);
5105
5106 let mut edits = Vec::new();
5107 let mut rows = Vec::new();
5108 let mut rows_inserted = 0;
5109
5110 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5111 let cursor = selection.head();
5112 let row = cursor.row;
5113
5114 let point = Point::new(row + 1, 0);
5115 let start_of_line = snapshot.clip_point(point, Bias::Left);
5116
5117 let newline = "\n".to_string();
5118 edits.push((start_of_line..start_of_line, newline));
5119
5120 rows_inserted += 1;
5121 rows.push(row + rows_inserted);
5122 }
5123
5124 self.transact(window, cx, |editor, window, cx| {
5125 editor.edit(edits, cx);
5126
5127 editor.change_selections(Default::default(), window, cx, |s| {
5128 let mut index = 0;
5129 s.move_cursors_with(|map, _, _| {
5130 let row = rows[index];
5131 index += 1;
5132
5133 let point = Point::new(row, 0);
5134 let boundary = map.next_line_boundary(point).1;
5135 let clipped = map.clip_point(boundary, Bias::Left);
5136
5137 (clipped, SelectionGoal::None)
5138 });
5139 });
5140
5141 let mut indent_edits = Vec::new();
5142 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5143 for row in rows {
5144 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5145 for (row, indent) in indents {
5146 if indent.len == 0 {
5147 continue;
5148 }
5149
5150 let text = match indent.kind {
5151 IndentKind::Space => " ".repeat(indent.len as usize),
5152 IndentKind::Tab => "\t".repeat(indent.len as usize),
5153 };
5154 let point = Point::new(row.0, 0);
5155 indent_edits.push((point..point, text));
5156 }
5157 }
5158 editor.edit(indent_edits, cx);
5159 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5160 format.detach_and_log_err(cx);
5161 }
5162 });
5163 }
5164
5165 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5166 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5167 original_indent_columns: Vec::new(),
5168 });
5169 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5170 }
5171
5172 fn insert_with_autoindent_mode(
5173 &mut self,
5174 text: &str,
5175 autoindent_mode: Option<AutoindentMode>,
5176 window: &mut Window,
5177 cx: &mut Context<Self>,
5178 ) {
5179 if self.read_only(cx) {
5180 return;
5181 }
5182
5183 let text: Arc<str> = text.into();
5184 self.transact(window, cx, |this, window, cx| {
5185 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5186 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5187 let anchors = {
5188 let snapshot = buffer.read(cx);
5189 old_selections
5190 .iter()
5191 .map(|s| {
5192 let anchor = snapshot.anchor_after(s.head());
5193 s.map(|_| anchor)
5194 })
5195 .collect::<Vec<_>>()
5196 };
5197 buffer.edit(
5198 old_selections
5199 .iter()
5200 .map(|s| (s.start..s.end, text.clone())),
5201 autoindent_mode,
5202 cx,
5203 );
5204 anchors
5205 });
5206
5207 this.change_selections(Default::default(), window, cx, |s| {
5208 s.select_anchors(selection_anchors);
5209 });
5210
5211 cx.notify();
5212 });
5213 }
5214
5215 fn trigger_completion_on_input(
5216 &mut self,
5217 text: &str,
5218 trigger_in_words: bool,
5219 window: &mut Window,
5220 cx: &mut Context<Self>,
5221 ) {
5222 let completions_source = self
5223 .context_menu
5224 .borrow()
5225 .as_ref()
5226 .and_then(|menu| match menu {
5227 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5228 CodeContextMenu::CodeActions(_) => None,
5229 });
5230
5231 match completions_source {
5232 Some(CompletionsMenuSource::Words { .. }) => {
5233 self.open_or_update_completions_menu(
5234 Some(CompletionsMenuSource::Words {
5235 ignore_threshold: false,
5236 }),
5237 None,
5238 trigger_in_words,
5239 window,
5240 cx,
5241 );
5242 }
5243 _ => self.open_or_update_completions_menu(
5244 None,
5245 Some(text.to_owned()).filter(|x| !x.is_empty()),
5246 true,
5247 window,
5248 cx,
5249 ),
5250 }
5251 }
5252
5253 /// If any empty selections is touching the start of its innermost containing autoclose
5254 /// region, expand it to select the brackets.
5255 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5256 let selections = self
5257 .selections
5258 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5259 let buffer = self.buffer.read(cx).read(cx);
5260 let new_selections = self
5261 .selections_with_autoclose_regions(selections, &buffer)
5262 .map(|(mut selection, region)| {
5263 if !selection.is_empty() {
5264 return selection;
5265 }
5266
5267 if let Some(region) = region {
5268 let mut range = region.range.to_offset(&buffer);
5269 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5270 range.start -= region.pair.start.len();
5271 if buffer.contains_str_at(range.start, ®ion.pair.start)
5272 && buffer.contains_str_at(range.end, ®ion.pair.end)
5273 {
5274 range.end += region.pair.end.len();
5275 selection.start = range.start;
5276 selection.end = range.end;
5277
5278 return selection;
5279 }
5280 }
5281 }
5282
5283 let always_treat_brackets_as_autoclosed = buffer
5284 .language_settings_at(selection.start, cx)
5285 .always_treat_brackets_as_autoclosed;
5286
5287 if !always_treat_brackets_as_autoclosed {
5288 return selection;
5289 }
5290
5291 if let Some(scope) = buffer.language_scope_at(selection.start) {
5292 for (pair, enabled) in scope.brackets() {
5293 if !enabled || !pair.close {
5294 continue;
5295 }
5296
5297 if buffer.contains_str_at(selection.start, &pair.end) {
5298 let pair_start_len = pair.start.len();
5299 if buffer.contains_str_at(
5300 selection.start.saturating_sub_usize(pair_start_len),
5301 &pair.start,
5302 ) {
5303 selection.start -= pair_start_len;
5304 selection.end += pair.end.len();
5305
5306 return selection;
5307 }
5308 }
5309 }
5310 }
5311
5312 selection
5313 })
5314 .collect();
5315
5316 drop(buffer);
5317 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5318 selections.select(new_selections)
5319 });
5320 }
5321
5322 /// Iterate the given selections, and for each one, find the smallest surrounding
5323 /// autoclose region. This uses the ordering of the selections and the autoclose
5324 /// regions to avoid repeated comparisons.
5325 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5326 &'a self,
5327 selections: impl IntoIterator<Item = Selection<D>>,
5328 buffer: &'a MultiBufferSnapshot,
5329 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5330 let mut i = 0;
5331 let mut regions = self.autoclose_regions.as_slice();
5332 selections.into_iter().map(move |selection| {
5333 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5334
5335 let mut enclosing = None;
5336 while let Some(pair_state) = regions.get(i) {
5337 if pair_state.range.end.to_offset(buffer) < range.start {
5338 regions = ®ions[i + 1..];
5339 i = 0;
5340 } else if pair_state.range.start.to_offset(buffer) > range.end {
5341 break;
5342 } else {
5343 if pair_state.selection_id == selection.id {
5344 enclosing = Some(pair_state);
5345 }
5346 i += 1;
5347 }
5348 }
5349
5350 (selection, enclosing)
5351 })
5352 }
5353
5354 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5355 fn invalidate_autoclose_regions(
5356 &mut self,
5357 mut selections: &[Selection<Anchor>],
5358 buffer: &MultiBufferSnapshot,
5359 ) {
5360 self.autoclose_regions.retain(|state| {
5361 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5362 return false;
5363 }
5364
5365 let mut i = 0;
5366 while let Some(selection) = selections.get(i) {
5367 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5368 selections = &selections[1..];
5369 continue;
5370 }
5371 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5372 break;
5373 }
5374 if selection.id == state.selection_id {
5375 return true;
5376 } else {
5377 i += 1;
5378 }
5379 }
5380 false
5381 });
5382 }
5383
5384 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5385 let offset = position.to_offset(buffer);
5386 let (word_range, kind) =
5387 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5388 if offset > word_range.start && kind == Some(CharKind::Word) {
5389 Some(
5390 buffer
5391 .text_for_range(word_range.start..offset)
5392 .collect::<String>(),
5393 )
5394 } else {
5395 None
5396 }
5397 }
5398
5399 pub fn visible_excerpts(
5400 &self,
5401 lsp_related_only: bool,
5402 cx: &mut Context<Editor>,
5403 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5404 let project = self.project().cloned();
5405 let multi_buffer = self.buffer().read(cx);
5406 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5407 let multi_buffer_visible_start = self
5408 .scroll_manager
5409 .anchor()
5410 .anchor
5411 .to_point(&multi_buffer_snapshot);
5412 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5413 multi_buffer_visible_start
5414 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5415 Bias::Left,
5416 );
5417 multi_buffer_snapshot
5418 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5419 .into_iter()
5420 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5421 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5422 if !lsp_related_only {
5423 return Some((
5424 excerpt_id,
5425 (
5426 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5427 buffer.version().clone(),
5428 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5429 ),
5430 ));
5431 }
5432
5433 let project = project.as_ref()?.read(cx);
5434 let buffer_file = project::File::from_dyn(buffer.file())?;
5435 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5436 let worktree_entry = buffer_worktree
5437 .read(cx)
5438 .entry_for_id(buffer_file.project_entry_id()?)?;
5439 if worktree_entry.is_ignored {
5440 None
5441 } else {
5442 Some((
5443 excerpt_id,
5444 (
5445 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5446 buffer.version().clone(),
5447 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5448 ),
5449 ))
5450 }
5451 })
5452 .collect()
5453 }
5454
5455 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5456 TextLayoutDetails {
5457 text_system: window.text_system().clone(),
5458 editor_style: self.style.clone().unwrap(),
5459 rem_size: window.rem_size(),
5460 scroll_anchor: self.scroll_manager.anchor(),
5461 visible_rows: self.visible_line_count(),
5462 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5463 }
5464 }
5465
5466 fn trigger_on_type_formatting(
5467 &self,
5468 input: String,
5469 window: &mut Window,
5470 cx: &mut Context<Self>,
5471 ) -> Option<Task<Result<()>>> {
5472 if input.chars().count() != 1 {
5473 return None;
5474 }
5475
5476 let project = self.project()?;
5477 let position = self.selections.newest_anchor().head();
5478 let (buffer, buffer_position) = self
5479 .buffer
5480 .read(cx)
5481 .text_anchor_for_position(position, cx)?;
5482
5483 let settings = language_settings::language_settings(
5484 buffer
5485 .read(cx)
5486 .language_at(buffer_position)
5487 .map(|l| l.name()),
5488 buffer.read(cx).file(),
5489 cx,
5490 );
5491 if !settings.use_on_type_format {
5492 return None;
5493 }
5494
5495 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5496 // hence we do LSP request & edit on host side only — add formats to host's history.
5497 let push_to_lsp_host_history = true;
5498 // If this is not the host, append its history with new edits.
5499 let push_to_client_history = project.read(cx).is_via_collab();
5500
5501 let on_type_formatting = project.update(cx, |project, cx| {
5502 project.on_type_format(
5503 buffer.clone(),
5504 buffer_position,
5505 input,
5506 push_to_lsp_host_history,
5507 cx,
5508 )
5509 });
5510 Some(cx.spawn_in(window, async move |editor, cx| {
5511 if let Some(transaction) = on_type_formatting.await? {
5512 if push_to_client_history {
5513 buffer
5514 .update(cx, |buffer, _| {
5515 buffer.push_transaction(transaction, Instant::now());
5516 buffer.finalize_last_transaction();
5517 })
5518 .ok();
5519 }
5520 editor.update(cx, |editor, cx| {
5521 editor.refresh_document_highlights(cx);
5522 })?;
5523 }
5524 Ok(())
5525 }))
5526 }
5527
5528 pub fn show_word_completions(
5529 &mut self,
5530 _: &ShowWordCompletions,
5531 window: &mut Window,
5532 cx: &mut Context<Self>,
5533 ) {
5534 self.open_or_update_completions_menu(
5535 Some(CompletionsMenuSource::Words {
5536 ignore_threshold: true,
5537 }),
5538 None,
5539 false,
5540 window,
5541 cx,
5542 );
5543 }
5544
5545 pub fn show_completions(
5546 &mut self,
5547 _: &ShowCompletions,
5548 window: &mut Window,
5549 cx: &mut Context<Self>,
5550 ) {
5551 self.open_or_update_completions_menu(None, None, false, window, cx);
5552 }
5553
5554 fn open_or_update_completions_menu(
5555 &mut self,
5556 requested_source: Option<CompletionsMenuSource>,
5557 trigger: Option<String>,
5558 trigger_in_words: bool,
5559 window: &mut Window,
5560 cx: &mut Context<Self>,
5561 ) {
5562 if self.pending_rename.is_some() {
5563 return;
5564 }
5565
5566 let completions_source = self
5567 .context_menu
5568 .borrow()
5569 .as_ref()
5570 .and_then(|menu| match menu {
5571 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5572 CodeContextMenu::CodeActions(_) => None,
5573 });
5574
5575 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5576
5577 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5578 // inserted and selected. To handle that case, the start of the selection is used so that
5579 // the menu starts with all choices.
5580 let position = self
5581 .selections
5582 .newest_anchor()
5583 .start
5584 .bias_right(&multibuffer_snapshot);
5585 if position.diff_base_anchor.is_some() {
5586 return;
5587 }
5588 let buffer_position = multibuffer_snapshot.anchor_before(position);
5589 let Some(buffer) = buffer_position
5590 .text_anchor
5591 .buffer_id
5592 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5593 else {
5594 return;
5595 };
5596 let buffer_snapshot = buffer.read(cx).snapshot();
5597
5598 let menu_is_open = matches!(
5599 self.context_menu.borrow().as_ref(),
5600 Some(CodeContextMenu::Completions(_))
5601 );
5602
5603 let language = buffer_snapshot
5604 .language_at(buffer_position.text_anchor)
5605 .map(|language| language.name());
5606
5607 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5608 let completion_settings = language_settings.completions.clone();
5609
5610 let show_completions_on_input = self
5611 .show_completions_on_input_override
5612 .unwrap_or(language_settings.show_completions_on_input);
5613 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5614 return;
5615 }
5616
5617 let query: Option<Arc<String>> =
5618 Self::completion_query(&multibuffer_snapshot, buffer_position)
5619 .map(|query| query.into());
5620
5621 drop(multibuffer_snapshot);
5622
5623 // Hide the current completions menu when query is empty. Without this, cached
5624 // completions from before the trigger char may be reused (#32774).
5625 if query.is_none() && menu_is_open {
5626 self.hide_context_menu(window, cx);
5627 }
5628
5629 let mut ignore_word_threshold = false;
5630 let provider = match requested_source {
5631 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5632 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5633 ignore_word_threshold = ignore_threshold;
5634 None
5635 }
5636 Some(CompletionsMenuSource::SnippetChoices)
5637 | Some(CompletionsMenuSource::SnippetsOnly) => {
5638 log::error!("bug: SnippetChoices requested_source is not handled");
5639 None
5640 }
5641 };
5642
5643 let sort_completions = provider
5644 .as_ref()
5645 .is_some_and(|provider| provider.sort_completions());
5646
5647 let filter_completions = provider
5648 .as_ref()
5649 .is_none_or(|provider| provider.filter_completions());
5650
5651 let was_snippets_only = matches!(
5652 completions_source,
5653 Some(CompletionsMenuSource::SnippetsOnly)
5654 );
5655
5656 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5657 if filter_completions {
5658 menu.filter(
5659 query.clone().unwrap_or_default(),
5660 buffer_position.text_anchor,
5661 &buffer,
5662 provider.clone(),
5663 window,
5664 cx,
5665 );
5666 }
5667 // When `is_incomplete` is false, no need to re-query completions when the current query
5668 // is a suffix of the initial query.
5669 let was_complete = !menu.is_incomplete;
5670 if was_complete && !was_snippets_only {
5671 // If the new query is a suffix of the old query (typing more characters) and
5672 // the previous result was complete, the existing completions can be filtered.
5673 //
5674 // Note that snippet completions are always complete.
5675 let query_matches = match (&menu.initial_query, &query) {
5676 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5677 (None, _) => true,
5678 _ => false,
5679 };
5680 if query_matches {
5681 let position_matches = if menu.initial_position == position {
5682 true
5683 } else {
5684 let snapshot = self.buffer.read(cx).read(cx);
5685 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5686 };
5687 if position_matches {
5688 return;
5689 }
5690 }
5691 }
5692 };
5693
5694 let Anchor {
5695 excerpt_id: buffer_excerpt_id,
5696 text_anchor: buffer_position,
5697 ..
5698 } = buffer_position;
5699
5700 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5701 buffer_snapshot.surrounding_word(buffer_position, None)
5702 {
5703 let word_to_exclude = buffer_snapshot
5704 .text_for_range(word_range.clone())
5705 .collect::<String>();
5706 (
5707 buffer_snapshot.anchor_before(word_range.start)
5708 ..buffer_snapshot.anchor_after(buffer_position),
5709 Some(word_to_exclude),
5710 )
5711 } else {
5712 (buffer_position..buffer_position, None)
5713 };
5714
5715 let show_completion_documentation = buffer_snapshot
5716 .settings_at(buffer_position, cx)
5717 .show_completion_documentation;
5718
5719 // The document can be large, so stay in reasonable bounds when searching for words,
5720 // otherwise completion pop-up might be slow to appear.
5721 const WORD_LOOKUP_ROWS: u32 = 5_000;
5722 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5723 let min_word_search = buffer_snapshot.clip_point(
5724 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5725 Bias::Left,
5726 );
5727 let max_word_search = buffer_snapshot.clip_point(
5728 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5729 Bias::Right,
5730 );
5731 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5732 ..buffer_snapshot.point_to_offset(max_word_search);
5733
5734 let skip_digits = query
5735 .as_ref()
5736 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5737
5738 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5739 trigger.as_ref().is_none_or(|trigger| {
5740 provider.is_completion_trigger(
5741 &buffer,
5742 position.text_anchor,
5743 trigger,
5744 trigger_in_words,
5745 cx,
5746 )
5747 })
5748 });
5749
5750 let provider_responses = if let Some(provider) = &provider
5751 && load_provider_completions
5752 {
5753 let trigger_character =
5754 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5755 let completion_context = CompletionContext {
5756 trigger_kind: match &trigger_character {
5757 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5758 None => CompletionTriggerKind::INVOKED,
5759 },
5760 trigger_character,
5761 };
5762
5763 provider.completions(
5764 buffer_excerpt_id,
5765 &buffer,
5766 buffer_position,
5767 completion_context,
5768 window,
5769 cx,
5770 )
5771 } else {
5772 Task::ready(Ok(Vec::new()))
5773 };
5774
5775 let load_word_completions = if !self.word_completions_enabled {
5776 false
5777 } else if requested_source
5778 == Some(CompletionsMenuSource::Words {
5779 ignore_threshold: true,
5780 })
5781 {
5782 true
5783 } else {
5784 load_provider_completions
5785 && completion_settings.words != WordsCompletionMode::Disabled
5786 && (ignore_word_threshold || {
5787 let words_min_length = completion_settings.words_min_length;
5788 // check whether word has at least `words_min_length` characters
5789 let query_chars = query.iter().flat_map(|q| q.chars());
5790 query_chars.take(words_min_length).count() == words_min_length
5791 })
5792 };
5793
5794 let mut words = if load_word_completions {
5795 cx.background_spawn({
5796 let buffer_snapshot = buffer_snapshot.clone();
5797 async move {
5798 buffer_snapshot.words_in_range(WordsQuery {
5799 fuzzy_contents: None,
5800 range: word_search_range,
5801 skip_digits,
5802 })
5803 }
5804 })
5805 } else {
5806 Task::ready(BTreeMap::default())
5807 };
5808
5809 let snippets = if let Some(provider) = &provider
5810 && provider.show_snippets()
5811 && let Some(project) = self.project()
5812 {
5813 let char_classifier = buffer_snapshot
5814 .char_classifier_at(buffer_position)
5815 .scope_context(Some(CharScopeContext::Completion));
5816 project.update(cx, |project, cx| {
5817 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5818 })
5819 } else {
5820 Task::ready(Ok(CompletionResponse {
5821 completions: Vec::new(),
5822 display_options: Default::default(),
5823 is_incomplete: false,
5824 }))
5825 };
5826
5827 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5828
5829 let id = post_inc(&mut self.next_completion_id);
5830 let task = cx.spawn_in(window, async move |editor, cx| {
5831 let Ok(()) = editor.update(cx, |this, _| {
5832 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5833 }) else {
5834 return;
5835 };
5836
5837 // TODO: Ideally completions from different sources would be selectively re-queried, so
5838 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5839 let mut completions = Vec::new();
5840 let mut is_incomplete = false;
5841 let mut display_options: Option<CompletionDisplayOptions> = None;
5842 if let Some(provider_responses) = provider_responses.await.log_err()
5843 && !provider_responses.is_empty()
5844 {
5845 for response in provider_responses {
5846 completions.extend(response.completions);
5847 is_incomplete = is_incomplete || response.is_incomplete;
5848 match display_options.as_mut() {
5849 None => {
5850 display_options = Some(response.display_options);
5851 }
5852 Some(options) => options.merge(&response.display_options),
5853 }
5854 }
5855 if completion_settings.words == WordsCompletionMode::Fallback {
5856 words = Task::ready(BTreeMap::default());
5857 }
5858 }
5859 let display_options = display_options.unwrap_or_default();
5860
5861 let mut words = words.await;
5862 if let Some(word_to_exclude) = &word_to_exclude {
5863 words.remove(word_to_exclude);
5864 }
5865 for lsp_completion in &completions {
5866 words.remove(&lsp_completion.new_text);
5867 }
5868 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5869 replace_range: word_replace_range.clone(),
5870 new_text: word.clone(),
5871 label: CodeLabel::plain(word, None),
5872 match_start: None,
5873 snippet_deduplication_key: None,
5874 icon_path: None,
5875 documentation: None,
5876 source: CompletionSource::BufferWord {
5877 word_range,
5878 resolved: false,
5879 },
5880 insert_text_mode: Some(InsertTextMode::AS_IS),
5881 confirm: None,
5882 }));
5883
5884 completions.extend(
5885 snippets
5886 .await
5887 .into_iter()
5888 .flat_map(|response| response.completions),
5889 );
5890
5891 let menu = if completions.is_empty() {
5892 None
5893 } else {
5894 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5895 let languages = editor
5896 .workspace
5897 .as_ref()
5898 .and_then(|(workspace, _)| workspace.upgrade())
5899 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5900 let menu = CompletionsMenu::new(
5901 id,
5902 requested_source.unwrap_or(if load_provider_completions {
5903 CompletionsMenuSource::Normal
5904 } else {
5905 CompletionsMenuSource::SnippetsOnly
5906 }),
5907 sort_completions,
5908 show_completion_documentation,
5909 position,
5910 query.clone(),
5911 is_incomplete,
5912 buffer.clone(),
5913 completions.into(),
5914 editor
5915 .context_menu()
5916 .borrow_mut()
5917 .as_ref()
5918 .map(|menu| menu.primary_scroll_handle()),
5919 display_options,
5920 snippet_sort_order,
5921 languages,
5922 language,
5923 cx,
5924 );
5925
5926 let query = if filter_completions { query } else { None };
5927 let matches_task = menu.do_async_filtering(
5928 query.unwrap_or_default(),
5929 buffer_position,
5930 &buffer,
5931 cx,
5932 );
5933 (menu, matches_task)
5934 }) else {
5935 return;
5936 };
5937
5938 let matches = matches_task.await;
5939
5940 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5941 // Newer menu already set, so exit.
5942 if let Some(CodeContextMenu::Completions(prev_menu)) =
5943 editor.context_menu.borrow().as_ref()
5944 && prev_menu.id > id
5945 {
5946 return;
5947 };
5948
5949 // Only valid to take prev_menu because either the new menu is immediately set
5950 // below, or the menu is hidden.
5951 if let Some(CodeContextMenu::Completions(prev_menu)) =
5952 editor.context_menu.borrow_mut().take()
5953 {
5954 let position_matches =
5955 if prev_menu.initial_position == menu.initial_position {
5956 true
5957 } else {
5958 let snapshot = editor.buffer.read(cx).read(cx);
5959 prev_menu.initial_position.to_offset(&snapshot)
5960 == menu.initial_position.to_offset(&snapshot)
5961 };
5962 if position_matches {
5963 // Preserve markdown cache before `set_filter_results` because it will
5964 // try to populate the documentation cache.
5965 menu.preserve_markdown_cache(prev_menu);
5966 }
5967 };
5968
5969 menu.set_filter_results(matches, provider, window, cx);
5970 }) else {
5971 return;
5972 };
5973
5974 menu.visible().then_some(menu)
5975 };
5976
5977 editor
5978 .update_in(cx, |editor, window, cx| {
5979 if editor.focus_handle.is_focused(window)
5980 && let Some(menu) = menu
5981 {
5982 *editor.context_menu.borrow_mut() =
5983 Some(CodeContextMenu::Completions(menu));
5984
5985 crate::hover_popover::hide_hover(editor, cx);
5986 if editor.show_edit_predictions_in_menu() {
5987 editor.update_visible_edit_prediction(window, cx);
5988 } else {
5989 editor.discard_edit_prediction(false, cx);
5990 }
5991
5992 cx.notify();
5993 return;
5994 }
5995
5996 if editor.completion_tasks.len() <= 1 {
5997 // If there are no more completion tasks and the last menu was empty, we should hide it.
5998 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5999 // If it was already hidden and we don't show edit predictions in the menu,
6000 // we should also show the edit prediction when available.
6001 if was_hidden && editor.show_edit_predictions_in_menu() {
6002 editor.update_visible_edit_prediction(window, cx);
6003 }
6004 }
6005 })
6006 .ok();
6007 });
6008
6009 self.completion_tasks.push((id, task));
6010 }
6011
6012 #[cfg(feature = "test-support")]
6013 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6014 let menu = self.context_menu.borrow();
6015 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6016 let completions = menu.completions.borrow();
6017 Some(completions.to_vec())
6018 } else {
6019 None
6020 }
6021 }
6022
6023 pub fn with_completions_menu_matching_id<R>(
6024 &self,
6025 id: CompletionId,
6026 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6027 ) -> R {
6028 let mut context_menu = self.context_menu.borrow_mut();
6029 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6030 return f(None);
6031 };
6032 if completions_menu.id != id {
6033 return f(None);
6034 }
6035 f(Some(completions_menu))
6036 }
6037
6038 pub fn confirm_completion(
6039 &mut self,
6040 action: &ConfirmCompletion,
6041 window: &mut Window,
6042 cx: &mut Context<Self>,
6043 ) -> Option<Task<Result<()>>> {
6044 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6045 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6046 }
6047
6048 pub fn confirm_completion_insert(
6049 &mut self,
6050 _: &ConfirmCompletionInsert,
6051 window: &mut Window,
6052 cx: &mut Context<Self>,
6053 ) -> Option<Task<Result<()>>> {
6054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6055 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6056 }
6057
6058 pub fn confirm_completion_replace(
6059 &mut self,
6060 _: &ConfirmCompletionReplace,
6061 window: &mut Window,
6062 cx: &mut Context<Self>,
6063 ) -> Option<Task<Result<()>>> {
6064 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6065 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6066 }
6067
6068 pub fn compose_completion(
6069 &mut self,
6070 action: &ComposeCompletion,
6071 window: &mut Window,
6072 cx: &mut Context<Self>,
6073 ) -> Option<Task<Result<()>>> {
6074 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6075 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6076 }
6077
6078 fn do_completion(
6079 &mut self,
6080 item_ix: Option<usize>,
6081 intent: CompletionIntent,
6082 window: &mut Window,
6083 cx: &mut Context<Editor>,
6084 ) -> Option<Task<Result<()>>> {
6085 use language::ToOffset as _;
6086
6087 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6088 else {
6089 return None;
6090 };
6091
6092 let candidate_id = {
6093 let entries = completions_menu.entries.borrow();
6094 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6095 if self.show_edit_predictions_in_menu() {
6096 self.discard_edit_prediction(true, cx);
6097 }
6098 mat.candidate_id
6099 };
6100
6101 let completion = completions_menu
6102 .completions
6103 .borrow()
6104 .get(candidate_id)?
6105 .clone();
6106 cx.stop_propagation();
6107
6108 let buffer_handle = completions_menu.buffer.clone();
6109
6110 let CompletionEdit {
6111 new_text,
6112 snippet,
6113 replace_range,
6114 } = process_completion_for_edit(
6115 &completion,
6116 intent,
6117 &buffer_handle,
6118 &completions_menu.initial_position.text_anchor,
6119 cx,
6120 );
6121
6122 let buffer = buffer_handle.read(cx);
6123 let snapshot = self.buffer.read(cx).snapshot(cx);
6124 let newest_anchor = self.selections.newest_anchor();
6125 let replace_range_multibuffer = {
6126 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6127 excerpt.map_range_from_buffer(replace_range.clone())
6128 };
6129 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6130 return None;
6131 }
6132
6133 let old_text = buffer
6134 .text_for_range(replace_range.clone())
6135 .collect::<String>();
6136 let lookbehind = newest_anchor
6137 .start
6138 .text_anchor
6139 .to_offset(buffer)
6140 .saturating_sub(replace_range.start.0);
6141 let lookahead = replace_range
6142 .end
6143 .0
6144 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6145 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6146 let suffix = &old_text[lookbehind.min(old_text.len())..];
6147
6148 let selections = self
6149 .selections
6150 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6151 let mut ranges = Vec::new();
6152 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6153
6154 for selection in &selections {
6155 let range = if selection.id == newest_anchor.id {
6156 replace_range_multibuffer.clone()
6157 } else {
6158 let mut range = selection.range();
6159
6160 // if prefix is present, don't duplicate it
6161 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6162 range.start = range.start.saturating_sub_usize(lookbehind);
6163
6164 // if suffix is also present, mimic the newest cursor and replace it
6165 if selection.id != newest_anchor.id
6166 && snapshot.contains_str_at(range.end, suffix)
6167 {
6168 range.end += lookahead;
6169 }
6170 }
6171 range
6172 };
6173
6174 ranges.push(range.clone());
6175
6176 if !self.linked_edit_ranges.is_empty() {
6177 let start_anchor = snapshot.anchor_before(range.start);
6178 let end_anchor = snapshot.anchor_after(range.end);
6179 if let Some(ranges) = self
6180 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6181 {
6182 for (buffer, edits) in ranges {
6183 linked_edits
6184 .entry(buffer.clone())
6185 .or_default()
6186 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6187 }
6188 }
6189 }
6190 }
6191
6192 let common_prefix_len = old_text
6193 .chars()
6194 .zip(new_text.chars())
6195 .take_while(|(a, b)| a == b)
6196 .map(|(a, _)| a.len_utf8())
6197 .sum::<usize>();
6198
6199 cx.emit(EditorEvent::InputHandled {
6200 utf16_range_to_replace: None,
6201 text: new_text[common_prefix_len..].into(),
6202 });
6203
6204 self.transact(window, cx, |editor, window, cx| {
6205 if let Some(mut snippet) = snippet {
6206 snippet.text = new_text.to_string();
6207 editor
6208 .insert_snippet(&ranges, snippet, window, cx)
6209 .log_err();
6210 } else {
6211 editor.buffer.update(cx, |multi_buffer, cx| {
6212 let auto_indent = match completion.insert_text_mode {
6213 Some(InsertTextMode::AS_IS) => None,
6214 _ => editor.autoindent_mode.clone(),
6215 };
6216 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6217 multi_buffer.edit(edits, auto_indent, cx);
6218 });
6219 }
6220 for (buffer, edits) in linked_edits {
6221 buffer.update(cx, |buffer, cx| {
6222 let snapshot = buffer.snapshot();
6223 let edits = edits
6224 .into_iter()
6225 .map(|(range, text)| {
6226 use text::ToPoint as TP;
6227 let end_point = TP::to_point(&range.end, &snapshot);
6228 let start_point = TP::to_point(&range.start, &snapshot);
6229 (start_point..end_point, text)
6230 })
6231 .sorted_by_key(|(range, _)| range.start);
6232 buffer.edit(edits, None, cx);
6233 })
6234 }
6235
6236 editor.refresh_edit_prediction(true, false, window, cx);
6237 });
6238 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6239
6240 let show_new_completions_on_confirm = completion
6241 .confirm
6242 .as_ref()
6243 .is_some_and(|confirm| confirm(intent, window, cx));
6244 if show_new_completions_on_confirm {
6245 self.open_or_update_completions_menu(None, None, false, window, cx);
6246 }
6247
6248 let provider = self.completion_provider.as_ref()?;
6249
6250 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6251 let command = lsp_store.as_ref().and_then(|lsp_store| {
6252 let CompletionSource::Lsp {
6253 lsp_completion,
6254 server_id,
6255 ..
6256 } = &completion.source
6257 else {
6258 return None;
6259 };
6260 let lsp_command = lsp_completion.command.as_ref()?;
6261 let available_commands = lsp_store
6262 .read(cx)
6263 .lsp_server_capabilities
6264 .get(server_id)
6265 .and_then(|server_capabilities| {
6266 server_capabilities
6267 .execute_command_provider
6268 .as_ref()
6269 .map(|options| options.commands.as_slice())
6270 })?;
6271 if available_commands.contains(&lsp_command.command) {
6272 Some(CodeAction {
6273 server_id: *server_id,
6274 range: language::Anchor::MIN..language::Anchor::MIN,
6275 lsp_action: LspAction::Command(lsp_command.clone()),
6276 resolved: false,
6277 })
6278 } else {
6279 None
6280 }
6281 });
6282
6283 drop(completion);
6284 let apply_edits = provider.apply_additional_edits_for_completion(
6285 buffer_handle.clone(),
6286 completions_menu.completions.clone(),
6287 candidate_id,
6288 true,
6289 cx,
6290 );
6291
6292 let editor_settings = EditorSettings::get_global(cx);
6293 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6294 // After the code completion is finished, users often want to know what signatures are needed.
6295 // so we should automatically call signature_help
6296 self.show_signature_help(&ShowSignatureHelp, window, cx);
6297 }
6298
6299 Some(cx.spawn_in(window, async move |editor, cx| {
6300 apply_edits.await?;
6301
6302 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6303 let title = command.lsp_action.title().to_owned();
6304 let project_transaction = lsp_store
6305 .update(cx, |lsp_store, cx| {
6306 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6307 })?
6308 .await
6309 .context("applying post-completion command")?;
6310 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6311 Self::open_project_transaction(
6312 &editor,
6313 workspace.downgrade(),
6314 project_transaction,
6315 title,
6316 cx,
6317 )
6318 .await?;
6319 }
6320 }
6321
6322 Ok(())
6323 }))
6324 }
6325
6326 pub fn toggle_code_actions(
6327 &mut self,
6328 action: &ToggleCodeActions,
6329 window: &mut Window,
6330 cx: &mut Context<Self>,
6331 ) {
6332 let quick_launch = action.quick_launch;
6333 let mut context_menu = self.context_menu.borrow_mut();
6334 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6335 if code_actions.deployed_from == action.deployed_from {
6336 // Toggle if we're selecting the same one
6337 *context_menu = None;
6338 cx.notify();
6339 return;
6340 } else {
6341 // Otherwise, clear it and start a new one
6342 *context_menu = None;
6343 cx.notify();
6344 }
6345 }
6346 drop(context_menu);
6347 let snapshot = self.snapshot(window, cx);
6348 let deployed_from = action.deployed_from.clone();
6349 let action = action.clone();
6350 self.completion_tasks.clear();
6351 self.discard_edit_prediction(false, cx);
6352
6353 let multibuffer_point = match &action.deployed_from {
6354 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6355 DisplayPoint::new(*row, 0).to_point(&snapshot)
6356 }
6357 _ => self
6358 .selections
6359 .newest::<Point>(&snapshot.display_snapshot)
6360 .head(),
6361 };
6362 let Some((buffer, buffer_row)) = snapshot
6363 .buffer_snapshot()
6364 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6365 .and_then(|(buffer_snapshot, range)| {
6366 self.buffer()
6367 .read(cx)
6368 .buffer(buffer_snapshot.remote_id())
6369 .map(|buffer| (buffer, range.start.row))
6370 })
6371 else {
6372 return;
6373 };
6374 let buffer_id = buffer.read(cx).remote_id();
6375 let tasks = self
6376 .tasks
6377 .get(&(buffer_id, buffer_row))
6378 .map(|t| Arc::new(t.to_owned()));
6379
6380 if !self.focus_handle.is_focused(window) {
6381 return;
6382 }
6383 let project = self.project.clone();
6384
6385 let code_actions_task = match deployed_from {
6386 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6387 _ => self.code_actions(buffer_row, window, cx),
6388 };
6389
6390 let runnable_task = match deployed_from {
6391 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6392 _ => {
6393 let mut task_context_task = Task::ready(None);
6394 if let Some(tasks) = &tasks
6395 && let Some(project) = project
6396 {
6397 task_context_task =
6398 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6399 }
6400
6401 cx.spawn_in(window, {
6402 let buffer = buffer.clone();
6403 async move |editor, cx| {
6404 let task_context = task_context_task.await;
6405
6406 let resolved_tasks =
6407 tasks
6408 .zip(task_context.clone())
6409 .map(|(tasks, task_context)| ResolvedTasks {
6410 templates: tasks.resolve(&task_context).collect(),
6411 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6412 multibuffer_point.row,
6413 tasks.column,
6414 )),
6415 });
6416 let debug_scenarios = editor
6417 .update(cx, |editor, cx| {
6418 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6419 })?
6420 .await;
6421 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6422 }
6423 })
6424 }
6425 };
6426
6427 cx.spawn_in(window, async move |editor, cx| {
6428 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6429 let code_actions = code_actions_task.await;
6430 let spawn_straight_away = quick_launch
6431 && resolved_tasks
6432 .as_ref()
6433 .is_some_and(|tasks| tasks.templates.len() == 1)
6434 && code_actions
6435 .as_ref()
6436 .is_none_or(|actions| actions.is_empty())
6437 && debug_scenarios.is_empty();
6438
6439 editor.update_in(cx, |editor, window, cx| {
6440 crate::hover_popover::hide_hover(editor, cx);
6441 let actions = CodeActionContents::new(
6442 resolved_tasks,
6443 code_actions,
6444 debug_scenarios,
6445 task_context.unwrap_or_default(),
6446 );
6447
6448 // Don't show the menu if there are no actions available
6449 if actions.is_empty() {
6450 cx.notify();
6451 return Task::ready(Ok(()));
6452 }
6453
6454 *editor.context_menu.borrow_mut() =
6455 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6456 buffer,
6457 actions,
6458 selected_item: Default::default(),
6459 scroll_handle: UniformListScrollHandle::default(),
6460 deployed_from,
6461 }));
6462 cx.notify();
6463 if spawn_straight_away
6464 && let Some(task) = editor.confirm_code_action(
6465 &ConfirmCodeAction { item_ix: Some(0) },
6466 window,
6467 cx,
6468 )
6469 {
6470 return task;
6471 }
6472
6473 Task::ready(Ok(()))
6474 })
6475 })
6476 .detach_and_log_err(cx);
6477 }
6478
6479 fn debug_scenarios(
6480 &mut self,
6481 resolved_tasks: &Option<ResolvedTasks>,
6482 buffer: &Entity<Buffer>,
6483 cx: &mut App,
6484 ) -> Task<Vec<task::DebugScenario>> {
6485 maybe!({
6486 let project = self.project()?;
6487 let dap_store = project.read(cx).dap_store();
6488 let mut scenarios = vec![];
6489 let resolved_tasks = resolved_tasks.as_ref()?;
6490 let buffer = buffer.read(cx);
6491 let language = buffer.language()?;
6492 let file = buffer.file();
6493 let debug_adapter = language_settings(language.name().into(), file, cx)
6494 .debuggers
6495 .first()
6496 .map(SharedString::from)
6497 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6498
6499 dap_store.update(cx, |dap_store, cx| {
6500 for (_, task) in &resolved_tasks.templates {
6501 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6502 task.original_task().clone(),
6503 debug_adapter.clone().into(),
6504 task.display_label().to_owned().into(),
6505 cx,
6506 );
6507 scenarios.push(maybe_scenario);
6508 }
6509 });
6510 Some(cx.background_spawn(async move {
6511 futures::future::join_all(scenarios)
6512 .await
6513 .into_iter()
6514 .flatten()
6515 .collect::<Vec<_>>()
6516 }))
6517 })
6518 .unwrap_or_else(|| Task::ready(vec![]))
6519 }
6520
6521 fn code_actions(
6522 &mut self,
6523 buffer_row: u32,
6524 window: &mut Window,
6525 cx: &mut Context<Self>,
6526 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6527 let mut task = self.code_actions_task.take();
6528 cx.spawn_in(window, async move |editor, cx| {
6529 while let Some(prev_task) = task {
6530 prev_task.await.log_err();
6531 task = editor
6532 .update(cx, |this, _| this.code_actions_task.take())
6533 .ok()?;
6534 }
6535
6536 editor
6537 .update(cx, |editor, cx| {
6538 editor
6539 .available_code_actions
6540 .clone()
6541 .and_then(|(location, code_actions)| {
6542 let snapshot = location.buffer.read(cx).snapshot();
6543 let point_range = location.range.to_point(&snapshot);
6544 let point_range = point_range.start.row..=point_range.end.row;
6545 if point_range.contains(&buffer_row) {
6546 Some(code_actions)
6547 } else {
6548 None
6549 }
6550 })
6551 })
6552 .ok()
6553 .flatten()
6554 })
6555 }
6556
6557 pub fn confirm_code_action(
6558 &mut self,
6559 action: &ConfirmCodeAction,
6560 window: &mut Window,
6561 cx: &mut Context<Self>,
6562 ) -> Option<Task<Result<()>>> {
6563 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6564
6565 let actions_menu =
6566 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6567 menu
6568 } else {
6569 return None;
6570 };
6571
6572 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6573 let action = actions_menu.actions.get(action_ix)?;
6574 let title = action.label();
6575 let buffer = actions_menu.buffer;
6576 let workspace = self.workspace()?;
6577
6578 match action {
6579 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6580 workspace.update(cx, |workspace, cx| {
6581 workspace.schedule_resolved_task(
6582 task_source_kind,
6583 resolved_task,
6584 false,
6585 window,
6586 cx,
6587 );
6588
6589 Some(Task::ready(Ok(())))
6590 })
6591 }
6592 CodeActionsItem::CodeAction {
6593 excerpt_id,
6594 action,
6595 provider,
6596 } => {
6597 let apply_code_action =
6598 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6599 let workspace = workspace.downgrade();
6600 Some(cx.spawn_in(window, async move |editor, cx| {
6601 let project_transaction = apply_code_action.await?;
6602 Self::open_project_transaction(
6603 &editor,
6604 workspace,
6605 project_transaction,
6606 title,
6607 cx,
6608 )
6609 .await
6610 }))
6611 }
6612 CodeActionsItem::DebugScenario(scenario) => {
6613 let context = actions_menu.actions.context;
6614
6615 workspace.update(cx, |workspace, cx| {
6616 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6617 workspace.start_debug_session(
6618 scenario,
6619 context,
6620 Some(buffer),
6621 None,
6622 window,
6623 cx,
6624 );
6625 });
6626 Some(Task::ready(Ok(())))
6627 }
6628 }
6629 }
6630
6631 fn open_transaction_for_hidden_buffers(
6632 workspace: Entity<Workspace>,
6633 transaction: ProjectTransaction,
6634 title: String,
6635 window: &mut Window,
6636 cx: &mut Context<Self>,
6637 ) {
6638 if transaction.0.is_empty() {
6639 return;
6640 }
6641
6642 let edited_buffers_already_open = {
6643 let other_editors: Vec<Entity<Editor>> = workspace
6644 .read(cx)
6645 .panes()
6646 .iter()
6647 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6648 .filter(|editor| editor.entity_id() != cx.entity_id())
6649 .collect();
6650
6651 transaction.0.keys().all(|buffer| {
6652 other_editors.iter().any(|editor| {
6653 let multi_buffer = editor.read(cx).buffer();
6654 multi_buffer.read(cx).is_singleton()
6655 && multi_buffer
6656 .read(cx)
6657 .as_singleton()
6658 .map_or(false, |singleton| {
6659 singleton.entity_id() == buffer.entity_id()
6660 })
6661 })
6662 })
6663 };
6664 if !edited_buffers_already_open {
6665 let workspace = workspace.downgrade();
6666 cx.defer_in(window, move |_, window, cx| {
6667 cx.spawn_in(window, async move |editor, cx| {
6668 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6669 .await
6670 .ok()
6671 })
6672 .detach();
6673 });
6674 }
6675 }
6676
6677 pub async fn open_project_transaction(
6678 editor: &WeakEntity<Editor>,
6679 workspace: WeakEntity<Workspace>,
6680 transaction: ProjectTransaction,
6681 title: String,
6682 cx: &mut AsyncWindowContext,
6683 ) -> Result<()> {
6684 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6685 cx.update(|_, cx| {
6686 entries.sort_unstable_by_key(|(buffer, _)| {
6687 buffer.read(cx).file().map(|f| f.path().clone())
6688 });
6689 })?;
6690 if entries.is_empty() {
6691 return Ok(());
6692 }
6693
6694 // If the project transaction's edits are all contained within this editor, then
6695 // avoid opening a new editor to display them.
6696
6697 if let [(buffer, transaction)] = &*entries {
6698 let excerpt = editor.update(cx, |editor, cx| {
6699 editor
6700 .buffer()
6701 .read(cx)
6702 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6703 })?;
6704 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6705 && excerpted_buffer == *buffer
6706 {
6707 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6708 let excerpt_range = excerpt_range.to_offset(buffer);
6709 buffer
6710 .edited_ranges_for_transaction::<usize>(transaction)
6711 .all(|range| {
6712 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6713 })
6714 })?;
6715
6716 if all_edits_within_excerpt {
6717 return Ok(());
6718 }
6719 }
6720 }
6721
6722 let mut ranges_to_highlight = Vec::new();
6723 let excerpt_buffer = cx.new(|cx| {
6724 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6725 for (buffer_handle, transaction) in &entries {
6726 let edited_ranges = buffer_handle
6727 .read(cx)
6728 .edited_ranges_for_transaction::<Point>(transaction)
6729 .collect::<Vec<_>>();
6730 let (ranges, _) = multibuffer.set_excerpts_for_path(
6731 PathKey::for_buffer(buffer_handle, cx),
6732 buffer_handle.clone(),
6733 edited_ranges,
6734 multibuffer_context_lines(cx),
6735 cx,
6736 );
6737
6738 ranges_to_highlight.extend(ranges);
6739 }
6740 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6741 multibuffer
6742 })?;
6743
6744 workspace.update_in(cx, |workspace, window, cx| {
6745 let project = workspace.project().clone();
6746 let editor =
6747 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6748 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6749 editor.update(cx, |editor, cx| {
6750 editor.highlight_background::<Self>(
6751 &ranges_to_highlight,
6752 |_, theme| theme.colors().editor_highlighted_line_background,
6753 cx,
6754 );
6755 });
6756 })?;
6757
6758 Ok(())
6759 }
6760
6761 pub fn clear_code_action_providers(&mut self) {
6762 self.code_action_providers.clear();
6763 self.available_code_actions.take();
6764 }
6765
6766 pub fn add_code_action_provider(
6767 &mut self,
6768 provider: Rc<dyn CodeActionProvider>,
6769 window: &mut Window,
6770 cx: &mut Context<Self>,
6771 ) {
6772 if self
6773 .code_action_providers
6774 .iter()
6775 .any(|existing_provider| existing_provider.id() == provider.id())
6776 {
6777 return;
6778 }
6779
6780 self.code_action_providers.push(provider);
6781 self.refresh_code_actions(window, cx);
6782 }
6783
6784 pub fn remove_code_action_provider(
6785 &mut self,
6786 id: Arc<str>,
6787 window: &mut Window,
6788 cx: &mut Context<Self>,
6789 ) {
6790 self.code_action_providers
6791 .retain(|provider| provider.id() != id);
6792 self.refresh_code_actions(window, cx);
6793 }
6794
6795 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6796 !self.code_action_providers.is_empty()
6797 && EditorSettings::get_global(cx).toolbar.code_actions
6798 }
6799
6800 pub fn has_available_code_actions(&self) -> bool {
6801 self.available_code_actions
6802 .as_ref()
6803 .is_some_and(|(_, actions)| !actions.is_empty())
6804 }
6805
6806 fn render_inline_code_actions(
6807 &self,
6808 icon_size: ui::IconSize,
6809 display_row: DisplayRow,
6810 is_active: bool,
6811 cx: &mut Context<Self>,
6812 ) -> AnyElement {
6813 let show_tooltip = !self.context_menu_visible();
6814 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6815 .icon_size(icon_size)
6816 .shape(ui::IconButtonShape::Square)
6817 .icon_color(ui::Color::Hidden)
6818 .toggle_state(is_active)
6819 .when(show_tooltip, |this| {
6820 this.tooltip({
6821 let focus_handle = self.focus_handle.clone();
6822 move |_window, cx| {
6823 Tooltip::for_action_in(
6824 "Toggle Code Actions",
6825 &ToggleCodeActions {
6826 deployed_from: None,
6827 quick_launch: false,
6828 },
6829 &focus_handle,
6830 cx,
6831 )
6832 }
6833 })
6834 })
6835 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6836 window.focus(&editor.focus_handle(cx), cx);
6837 editor.toggle_code_actions(
6838 &crate::actions::ToggleCodeActions {
6839 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6840 display_row,
6841 )),
6842 quick_launch: false,
6843 },
6844 window,
6845 cx,
6846 );
6847 }))
6848 .into_any_element()
6849 }
6850
6851 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6852 &self.context_menu
6853 }
6854
6855 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6856 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6857 cx.background_executor()
6858 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6859 .await;
6860
6861 let (start_buffer, start, _, end, newest_selection) = this
6862 .update(cx, |this, cx| {
6863 let newest_selection = this.selections.newest_anchor().clone();
6864 if newest_selection.head().diff_base_anchor.is_some() {
6865 return None;
6866 }
6867 let display_snapshot = this.display_snapshot(cx);
6868 let newest_selection_adjusted =
6869 this.selections.newest_adjusted(&display_snapshot);
6870 let buffer = this.buffer.read(cx);
6871
6872 let (start_buffer, start) =
6873 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6874 let (end_buffer, end) =
6875 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6876
6877 Some((start_buffer, start, end_buffer, end, newest_selection))
6878 })?
6879 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6880 .context(
6881 "Expected selection to lie in a single buffer when refreshing code actions",
6882 )?;
6883 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6884 let providers = this.code_action_providers.clone();
6885 let tasks = this
6886 .code_action_providers
6887 .iter()
6888 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6889 .collect::<Vec<_>>();
6890 (providers, tasks)
6891 })?;
6892
6893 let mut actions = Vec::new();
6894 for (provider, provider_actions) in
6895 providers.into_iter().zip(future::join_all(tasks).await)
6896 {
6897 if let Some(provider_actions) = provider_actions.log_err() {
6898 actions.extend(provider_actions.into_iter().map(|action| {
6899 AvailableCodeAction {
6900 excerpt_id: newest_selection.start.excerpt_id,
6901 action,
6902 provider: provider.clone(),
6903 }
6904 }));
6905 }
6906 }
6907
6908 this.update(cx, |this, cx| {
6909 this.available_code_actions = if actions.is_empty() {
6910 None
6911 } else {
6912 Some((
6913 Location {
6914 buffer: start_buffer,
6915 range: start..end,
6916 },
6917 actions.into(),
6918 ))
6919 };
6920 cx.notify();
6921 })
6922 }));
6923 }
6924
6925 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6926 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6927 self.show_git_blame_inline = false;
6928
6929 self.show_git_blame_inline_delay_task =
6930 Some(cx.spawn_in(window, async move |this, cx| {
6931 cx.background_executor().timer(delay).await;
6932
6933 this.update(cx, |this, cx| {
6934 this.show_git_blame_inline = true;
6935 cx.notify();
6936 })
6937 .log_err();
6938 }));
6939 }
6940 }
6941
6942 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6943 let snapshot = self.snapshot(window, cx);
6944 let cursor = self
6945 .selections
6946 .newest::<Point>(&snapshot.display_snapshot)
6947 .head();
6948 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6949 else {
6950 return;
6951 };
6952
6953 if self.blame.is_none() {
6954 self.start_git_blame(true, window, cx);
6955 }
6956 let Some(blame) = self.blame.as_ref() else {
6957 return;
6958 };
6959
6960 let row_info = RowInfo {
6961 buffer_id: Some(buffer.remote_id()),
6962 buffer_row: Some(point.row),
6963 ..Default::default()
6964 };
6965 let Some((buffer, blame_entry)) = blame
6966 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6967 .flatten()
6968 else {
6969 return;
6970 };
6971
6972 let anchor = self.selections.newest_anchor().head();
6973 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6974 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6975 self.show_blame_popover(
6976 buffer,
6977 &blame_entry,
6978 position + last_bounds.origin,
6979 true,
6980 cx,
6981 );
6982 };
6983 }
6984
6985 fn show_blame_popover(
6986 &mut self,
6987 buffer: BufferId,
6988 blame_entry: &BlameEntry,
6989 position: gpui::Point<Pixels>,
6990 ignore_timeout: bool,
6991 cx: &mut Context<Self>,
6992 ) {
6993 if let Some(state) = &mut self.inline_blame_popover {
6994 state.hide_task.take();
6995 } else {
6996 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6997 let blame_entry = blame_entry.clone();
6998 let show_task = cx.spawn(async move |editor, cx| {
6999 if !ignore_timeout {
7000 cx.background_executor()
7001 .timer(std::time::Duration::from_millis(blame_popover_delay))
7002 .await;
7003 }
7004 editor
7005 .update(cx, |editor, cx| {
7006 editor.inline_blame_popover_show_task.take();
7007 let Some(blame) = editor.blame.as_ref() else {
7008 return;
7009 };
7010 let blame = blame.read(cx);
7011 let details = blame.details_for_entry(buffer, &blame_entry);
7012 let markdown = cx.new(|cx| {
7013 Markdown::new(
7014 details
7015 .as_ref()
7016 .map(|message| message.message.clone())
7017 .unwrap_or_default(),
7018 None,
7019 None,
7020 cx,
7021 )
7022 });
7023 editor.inline_blame_popover = Some(InlineBlamePopover {
7024 position,
7025 hide_task: None,
7026 popover_bounds: None,
7027 popover_state: InlineBlamePopoverState {
7028 scroll_handle: ScrollHandle::new(),
7029 commit_message: details,
7030 markdown,
7031 },
7032 keyboard_grace: ignore_timeout,
7033 });
7034 cx.notify();
7035 })
7036 .ok();
7037 });
7038 self.inline_blame_popover_show_task = Some(show_task);
7039 }
7040 }
7041
7042 pub fn has_mouse_context_menu(&self) -> bool {
7043 self.mouse_context_menu.is_some()
7044 }
7045
7046 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7047 self.inline_blame_popover_show_task.take();
7048 if let Some(state) = &mut self.inline_blame_popover {
7049 let hide_task = cx.spawn(async move |editor, cx| {
7050 if !ignore_timeout {
7051 cx.background_executor()
7052 .timer(std::time::Duration::from_millis(100))
7053 .await;
7054 }
7055 editor
7056 .update(cx, |editor, cx| {
7057 editor.inline_blame_popover.take();
7058 cx.notify();
7059 })
7060 .ok();
7061 });
7062 state.hide_task = Some(hide_task);
7063 true
7064 } else {
7065 false
7066 }
7067 }
7068
7069 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7070 if self.pending_rename.is_some() {
7071 return None;
7072 }
7073
7074 let provider = self.semantics_provider.clone()?;
7075 let buffer = self.buffer.read(cx);
7076 let newest_selection = self.selections.newest_anchor().clone();
7077 let cursor_position = newest_selection.head();
7078 let (cursor_buffer, cursor_buffer_position) =
7079 buffer.text_anchor_for_position(cursor_position, cx)?;
7080 let (tail_buffer, tail_buffer_position) =
7081 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7082 if cursor_buffer != tail_buffer {
7083 return None;
7084 }
7085
7086 let snapshot = cursor_buffer.read(cx).snapshot();
7087 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7088 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7089 if start_word_range != end_word_range {
7090 self.document_highlights_task.take();
7091 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7092 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7093 return None;
7094 }
7095
7096 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7097 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7098 cx.background_executor()
7099 .timer(Duration::from_millis(debounce))
7100 .await;
7101
7102 let highlights = if let Some(highlights) = cx
7103 .update(|cx| {
7104 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7105 })
7106 .ok()
7107 .flatten()
7108 {
7109 highlights.await.log_err()
7110 } else {
7111 None
7112 };
7113
7114 if let Some(highlights) = highlights {
7115 this.update(cx, |this, cx| {
7116 if this.pending_rename.is_some() {
7117 return;
7118 }
7119
7120 let buffer = this.buffer.read(cx);
7121 if buffer
7122 .text_anchor_for_position(cursor_position, cx)
7123 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7124 {
7125 return;
7126 }
7127
7128 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7129 let mut write_ranges = Vec::new();
7130 let mut read_ranges = Vec::new();
7131 for highlight in highlights {
7132 let buffer_id = cursor_buffer.read(cx).remote_id();
7133 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7134 {
7135 let start = highlight
7136 .range
7137 .start
7138 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7139 let end = highlight
7140 .range
7141 .end
7142 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7143 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7144 continue;
7145 }
7146
7147 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7148 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7149 write_ranges.push(range);
7150 } else {
7151 read_ranges.push(range);
7152 }
7153 }
7154 }
7155
7156 this.highlight_background::<DocumentHighlightRead>(
7157 &read_ranges,
7158 |_, theme| theme.colors().editor_document_highlight_read_background,
7159 cx,
7160 );
7161 this.highlight_background::<DocumentHighlightWrite>(
7162 &write_ranges,
7163 |_, theme| theme.colors().editor_document_highlight_write_background,
7164 cx,
7165 );
7166 cx.notify();
7167 })
7168 .log_err();
7169 }
7170 }));
7171 None
7172 }
7173
7174 fn prepare_highlight_query_from_selection(
7175 &mut self,
7176 window: &Window,
7177 cx: &mut Context<Editor>,
7178 ) -> Option<(String, Range<Anchor>)> {
7179 if matches!(self.mode, EditorMode::SingleLine) {
7180 return None;
7181 }
7182 if !EditorSettings::get_global(cx).selection_highlight {
7183 return None;
7184 }
7185 if self.selections.count() != 1 || self.selections.line_mode() {
7186 return None;
7187 }
7188 let snapshot = self.snapshot(window, cx);
7189 let selection = self.selections.newest::<Point>(&snapshot);
7190 // If the selection spans multiple rows OR it is empty
7191 if selection.start.row != selection.end.row
7192 || selection.start.column == selection.end.column
7193 {
7194 return None;
7195 }
7196 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7197 let query = snapshot
7198 .buffer_snapshot()
7199 .text_for_range(selection_anchor_range.clone())
7200 .collect::<String>();
7201 if query.trim().is_empty() {
7202 return None;
7203 }
7204 Some((query, selection_anchor_range))
7205 }
7206
7207 #[ztracing::instrument(skip_all)]
7208 fn update_selection_occurrence_highlights(
7209 &mut self,
7210 query_text: String,
7211 query_range: Range<Anchor>,
7212 multi_buffer_range_to_query: Range<Point>,
7213 use_debounce: bool,
7214 window: &mut Window,
7215 cx: &mut Context<Editor>,
7216 ) -> Task<()> {
7217 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7218 cx.spawn_in(window, async move |editor, cx| {
7219 if use_debounce {
7220 cx.background_executor()
7221 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7222 .await;
7223 }
7224 let match_task = cx.background_spawn(async move {
7225 let buffer_ranges = multi_buffer_snapshot
7226 .range_to_buffer_ranges(multi_buffer_range_to_query)
7227 .into_iter()
7228 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7229 let mut match_ranges = Vec::new();
7230 let Ok(regex) = project::search::SearchQuery::text(
7231 query_text.clone(),
7232 false,
7233 false,
7234 false,
7235 Default::default(),
7236 Default::default(),
7237 false,
7238 None,
7239 ) else {
7240 return Vec::default();
7241 };
7242 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7243 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7244 match_ranges.extend(
7245 regex
7246 .search(
7247 buffer_snapshot,
7248 Some(search_range.start.0..search_range.end.0),
7249 )
7250 .await
7251 .into_iter()
7252 .filter_map(|match_range| {
7253 let match_start = buffer_snapshot
7254 .anchor_after(search_range.start + match_range.start);
7255 let match_end = buffer_snapshot
7256 .anchor_before(search_range.start + match_range.end);
7257 let match_anchor_range =
7258 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7259 (match_anchor_range != query_range).then_some(match_anchor_range)
7260 }),
7261 );
7262 }
7263 match_ranges
7264 });
7265 let match_ranges = match_task.await;
7266 editor
7267 .update_in(cx, |editor, _, cx| {
7268 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7269 if !match_ranges.is_empty() {
7270 editor.highlight_background::<SelectedTextHighlight>(
7271 &match_ranges,
7272 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7273 cx,
7274 )
7275 }
7276 })
7277 .log_err();
7278 })
7279 }
7280
7281 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7282 struct NewlineFold;
7283 let type_id = std::any::TypeId::of::<NewlineFold>();
7284 if !self.mode.is_single_line() {
7285 return;
7286 }
7287 let snapshot = self.snapshot(window, cx);
7288 if snapshot.buffer_snapshot().max_point().row == 0 {
7289 return;
7290 }
7291 let task = cx.background_spawn(async move {
7292 let new_newlines = snapshot
7293 .buffer_chars_at(MultiBufferOffset(0))
7294 .filter_map(|(c, i)| {
7295 if c == '\n' {
7296 Some(
7297 snapshot.buffer_snapshot().anchor_after(i)
7298 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7299 )
7300 } else {
7301 None
7302 }
7303 })
7304 .collect::<Vec<_>>();
7305 let existing_newlines = snapshot
7306 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7307 .filter_map(|fold| {
7308 if fold.placeholder.type_tag == Some(type_id) {
7309 Some(fold.range.start..fold.range.end)
7310 } else {
7311 None
7312 }
7313 })
7314 .collect::<Vec<_>>();
7315
7316 (new_newlines, existing_newlines)
7317 });
7318 self.folding_newlines = cx.spawn(async move |this, cx| {
7319 let (new_newlines, existing_newlines) = task.await;
7320 if new_newlines == existing_newlines {
7321 return;
7322 }
7323 let placeholder = FoldPlaceholder {
7324 render: Arc::new(move |_, _, cx| {
7325 div()
7326 .bg(cx.theme().status().hint_background)
7327 .border_b_1()
7328 .size_full()
7329 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7330 .border_color(cx.theme().status().hint)
7331 .child("\\n")
7332 .into_any()
7333 }),
7334 constrain_width: false,
7335 merge_adjacent: false,
7336 type_tag: Some(type_id),
7337 };
7338 let creases = new_newlines
7339 .into_iter()
7340 .map(|range| Crease::simple(range, placeholder.clone()))
7341 .collect();
7342 this.update(cx, |this, cx| {
7343 this.display_map.update(cx, |display_map, cx| {
7344 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7345 display_map.fold(creases, cx);
7346 });
7347 })
7348 .ok();
7349 });
7350 }
7351
7352 #[ztracing::instrument(skip_all)]
7353 fn refresh_selected_text_highlights(
7354 &mut self,
7355 on_buffer_edit: bool,
7356 window: &mut Window,
7357 cx: &mut Context<Editor>,
7358 ) {
7359 let Some((query_text, query_range)) =
7360 self.prepare_highlight_query_from_selection(window, cx)
7361 else {
7362 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7363 self.quick_selection_highlight_task.take();
7364 self.debounced_selection_highlight_task.take();
7365 return;
7366 };
7367 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7368 if on_buffer_edit
7369 || self
7370 .quick_selection_highlight_task
7371 .as_ref()
7372 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7373 {
7374 let multi_buffer_visible_start = self
7375 .scroll_manager
7376 .anchor()
7377 .anchor
7378 .to_point(&multi_buffer_snapshot);
7379 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7380 multi_buffer_visible_start
7381 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7382 Bias::Left,
7383 );
7384 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7385 self.quick_selection_highlight_task = Some((
7386 query_range.clone(),
7387 self.update_selection_occurrence_highlights(
7388 query_text.clone(),
7389 query_range.clone(),
7390 multi_buffer_visible_range,
7391 false,
7392 window,
7393 cx,
7394 ),
7395 ));
7396 }
7397 if on_buffer_edit
7398 || self
7399 .debounced_selection_highlight_task
7400 .as_ref()
7401 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7402 {
7403 let multi_buffer_start = multi_buffer_snapshot
7404 .anchor_before(MultiBufferOffset(0))
7405 .to_point(&multi_buffer_snapshot);
7406 let multi_buffer_end = multi_buffer_snapshot
7407 .anchor_after(multi_buffer_snapshot.len())
7408 .to_point(&multi_buffer_snapshot);
7409 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7410 self.debounced_selection_highlight_task = Some((
7411 query_range.clone(),
7412 self.update_selection_occurrence_highlights(
7413 query_text,
7414 query_range,
7415 multi_buffer_full_range,
7416 true,
7417 window,
7418 cx,
7419 ),
7420 ));
7421 }
7422 }
7423
7424 pub fn refresh_edit_prediction(
7425 &mut self,
7426 debounce: bool,
7427 user_requested: bool,
7428 window: &mut Window,
7429 cx: &mut Context<Self>,
7430 ) -> Option<()> {
7431 if DisableAiSettings::get_global(cx).disable_ai {
7432 return None;
7433 }
7434
7435 let provider = self.edit_prediction_provider()?;
7436 let cursor = self.selections.newest_anchor().head();
7437 let (buffer, cursor_buffer_position) =
7438 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7439
7440 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7441 self.discard_edit_prediction(false, cx);
7442 return None;
7443 }
7444
7445 self.update_visible_edit_prediction(window, cx);
7446
7447 if !user_requested
7448 && (!self.should_show_edit_predictions()
7449 || !self.is_focused(window)
7450 || buffer.read(cx).is_empty())
7451 {
7452 self.discard_edit_prediction(false, cx);
7453 return None;
7454 }
7455
7456 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7457 Some(())
7458 }
7459
7460 fn show_edit_predictions_in_menu(&self) -> bool {
7461 match self.edit_prediction_settings {
7462 EditPredictionSettings::Disabled => false,
7463 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7464 }
7465 }
7466
7467 pub fn edit_predictions_enabled(&self) -> bool {
7468 match self.edit_prediction_settings {
7469 EditPredictionSettings::Disabled => false,
7470 EditPredictionSettings::Enabled { .. } => true,
7471 }
7472 }
7473
7474 fn edit_prediction_requires_modifier(&self) -> bool {
7475 match self.edit_prediction_settings {
7476 EditPredictionSettings::Disabled => false,
7477 EditPredictionSettings::Enabled {
7478 preview_requires_modifier,
7479 ..
7480 } => preview_requires_modifier,
7481 }
7482 }
7483
7484 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7485 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7486 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7487 self.discard_edit_prediction(false, cx);
7488 } else {
7489 let selection = self.selections.newest_anchor();
7490 let cursor = selection.head();
7491
7492 if let Some((buffer, cursor_buffer_position)) =
7493 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7494 {
7495 self.edit_prediction_settings =
7496 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7497 }
7498 }
7499 }
7500
7501 fn edit_prediction_settings_at_position(
7502 &self,
7503 buffer: &Entity<Buffer>,
7504 buffer_position: language::Anchor,
7505 cx: &App,
7506 ) -> EditPredictionSettings {
7507 if !self.mode.is_full()
7508 || !self.show_edit_predictions_override.unwrap_or(true)
7509 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7510 {
7511 return EditPredictionSettings::Disabled;
7512 }
7513
7514 let buffer = buffer.read(cx);
7515
7516 let file = buffer.file();
7517
7518 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7519 return EditPredictionSettings::Disabled;
7520 };
7521
7522 let by_provider = matches!(
7523 self.menu_edit_predictions_policy,
7524 MenuEditPredictionsPolicy::ByProvider
7525 );
7526
7527 let show_in_menu = by_provider
7528 && self
7529 .edit_prediction_provider
7530 .as_ref()
7531 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7532
7533 let preview_requires_modifier =
7534 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7535
7536 EditPredictionSettings::Enabled {
7537 show_in_menu,
7538 preview_requires_modifier,
7539 }
7540 }
7541
7542 fn should_show_edit_predictions(&self) -> bool {
7543 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7544 }
7545
7546 pub fn edit_prediction_preview_is_active(&self) -> bool {
7547 matches!(
7548 self.edit_prediction_preview,
7549 EditPredictionPreview::Active { .. }
7550 )
7551 }
7552
7553 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7554 let cursor = self.selections.newest_anchor().head();
7555 if let Some((buffer, cursor_position)) =
7556 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7557 {
7558 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7559 } else {
7560 false
7561 }
7562 }
7563
7564 pub fn supports_minimap(&self, cx: &App) -> bool {
7565 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7566 }
7567
7568 fn edit_predictions_enabled_in_buffer(
7569 &self,
7570 buffer: &Entity<Buffer>,
7571 buffer_position: language::Anchor,
7572 cx: &App,
7573 ) -> bool {
7574 maybe!({
7575 if self.read_only(cx) {
7576 return Some(false);
7577 }
7578 let provider = self.edit_prediction_provider()?;
7579 if !provider.is_enabled(buffer, buffer_position, cx) {
7580 return Some(false);
7581 }
7582 let buffer = buffer.read(cx);
7583 let Some(file) = buffer.file() else {
7584 return Some(true);
7585 };
7586 let settings = all_language_settings(Some(file), cx);
7587 Some(settings.edit_predictions_enabled_for_file(file, cx))
7588 })
7589 .unwrap_or(false)
7590 }
7591
7592 pub fn show_edit_prediction(
7593 &mut self,
7594 _: &ShowEditPrediction,
7595 window: &mut Window,
7596 cx: &mut Context<Self>,
7597 ) {
7598 if !self.has_active_edit_prediction() {
7599 self.refresh_edit_prediction(false, true, window, cx);
7600 return;
7601 }
7602
7603 self.update_visible_edit_prediction(window, cx);
7604 }
7605
7606 pub fn display_cursor_names(
7607 &mut self,
7608 _: &DisplayCursorNames,
7609 window: &mut Window,
7610 cx: &mut Context<Self>,
7611 ) {
7612 self.show_cursor_names(window, cx);
7613 }
7614
7615 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7616 self.show_cursor_names = true;
7617 cx.notify();
7618 cx.spawn_in(window, async move |this, cx| {
7619 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7620 this.update(cx, |this, cx| {
7621 this.show_cursor_names = false;
7622 cx.notify()
7623 })
7624 .ok()
7625 })
7626 .detach();
7627 }
7628
7629 pub fn accept_partial_edit_prediction(
7630 &mut self,
7631 granularity: EditPredictionGranularity,
7632 window: &mut Window,
7633 cx: &mut Context<Self>,
7634 ) {
7635 if self.show_edit_predictions_in_menu() {
7636 self.hide_context_menu(window, cx);
7637 }
7638
7639 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7640 return;
7641 };
7642
7643 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7644 return;
7645 }
7646
7647 match &active_edit_prediction.completion {
7648 EditPrediction::MoveWithin { target, .. } => {
7649 let target = *target;
7650
7651 if matches!(granularity, EditPredictionGranularity::Full) {
7652 if let Some(position_map) = &self.last_position_map {
7653 let target_row = target.to_display_point(&position_map.snapshot).row();
7654 let is_visible = position_map.visible_row_range.contains(&target_row);
7655
7656 if is_visible || !self.edit_prediction_requires_modifier() {
7657 self.unfold_ranges(&[target..target], true, false, cx);
7658 self.change_selections(
7659 SelectionEffects::scroll(Autoscroll::newest()),
7660 window,
7661 cx,
7662 |selections| {
7663 selections.select_anchor_ranges([target..target]);
7664 },
7665 );
7666 self.clear_row_highlights::<EditPredictionPreview>();
7667 self.edit_prediction_preview
7668 .set_previous_scroll_position(None);
7669 } else {
7670 // Highlight and request scroll
7671 self.edit_prediction_preview
7672 .set_previous_scroll_position(Some(
7673 position_map.snapshot.scroll_anchor,
7674 ));
7675 self.highlight_rows::<EditPredictionPreview>(
7676 target..target,
7677 cx.theme().colors().editor_highlighted_line_background,
7678 RowHighlightOptions {
7679 autoscroll: true,
7680 ..Default::default()
7681 },
7682 cx,
7683 );
7684 self.request_autoscroll(Autoscroll::fit(), cx);
7685 }
7686 }
7687 } else {
7688 self.change_selections(
7689 SelectionEffects::scroll(Autoscroll::newest()),
7690 window,
7691 cx,
7692 |selections| {
7693 selections.select_anchor_ranges([target..target]);
7694 },
7695 );
7696 }
7697 }
7698 EditPrediction::MoveOutside { snapshot, target } => {
7699 if let Some(workspace) = self.workspace() {
7700 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7701 .detach_and_log_err(cx);
7702 }
7703 }
7704 EditPrediction::Edit { edits, .. } => {
7705 self.report_edit_prediction_event(
7706 active_edit_prediction.completion_id.clone(),
7707 true,
7708 cx,
7709 );
7710
7711 match granularity {
7712 EditPredictionGranularity::Full => {
7713 if let Some(provider) = self.edit_prediction_provider() {
7714 provider.accept(cx);
7715 }
7716
7717 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7718 let snapshot = self.buffer.read(cx).snapshot(cx);
7719 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7720
7721 self.buffer.update(cx, |buffer, cx| {
7722 buffer.edit(edits.iter().cloned(), None, cx)
7723 });
7724
7725 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7726 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7727 });
7728
7729 let selections = self.selections.disjoint_anchors_arc();
7730 if let Some(transaction_id_now) =
7731 self.buffer.read(cx).last_transaction_id(cx)
7732 {
7733 if transaction_id_prev != Some(transaction_id_now) {
7734 self.selection_history
7735 .insert_transaction(transaction_id_now, selections);
7736 }
7737 }
7738
7739 self.update_visible_edit_prediction(window, cx);
7740 if self.active_edit_prediction.is_none() {
7741 self.refresh_edit_prediction(true, true, window, cx);
7742 }
7743 cx.notify();
7744 }
7745 _ => {
7746 let snapshot = self.buffer.read(cx).snapshot(cx);
7747 let cursor_offset = self
7748 .selections
7749 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7750 .head();
7751
7752 let insertion = edits.iter().find_map(|(range, text)| {
7753 let range = range.to_offset(&snapshot);
7754 if range.is_empty() && range.start == cursor_offset {
7755 Some(text)
7756 } else {
7757 None
7758 }
7759 });
7760
7761 if let Some(text) = insertion {
7762 let text_to_insert = match granularity {
7763 EditPredictionGranularity::Word => {
7764 let mut partial = text
7765 .chars()
7766 .by_ref()
7767 .take_while(|c| c.is_alphabetic())
7768 .collect::<String>();
7769 if partial.is_empty() {
7770 partial = text
7771 .chars()
7772 .by_ref()
7773 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7774 .collect::<String>();
7775 }
7776 partial
7777 }
7778 EditPredictionGranularity::Line => {
7779 if let Some(line) = text.split_inclusive('\n').next() {
7780 line.to_string()
7781 } else {
7782 text.to_string()
7783 }
7784 }
7785 EditPredictionGranularity::Full => unreachable!(),
7786 };
7787
7788 cx.emit(EditorEvent::InputHandled {
7789 utf16_range_to_replace: None,
7790 text: text_to_insert.clone().into(),
7791 });
7792
7793 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7794 self.refresh_edit_prediction(true, true, window, cx);
7795 cx.notify();
7796 } else {
7797 self.accept_partial_edit_prediction(
7798 EditPredictionGranularity::Full,
7799 window,
7800 cx,
7801 );
7802 }
7803 }
7804 }
7805 }
7806 }
7807
7808 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7809 }
7810
7811 pub fn accept_next_word_edit_prediction(
7812 &mut self,
7813 _: &AcceptNextWordEditPrediction,
7814 window: &mut Window,
7815 cx: &mut Context<Self>,
7816 ) {
7817 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7818 }
7819
7820 pub fn accept_next_line_edit_prediction(
7821 &mut self,
7822 _: &AcceptNextLineEditPrediction,
7823 window: &mut Window,
7824 cx: &mut Context<Self>,
7825 ) {
7826 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7827 }
7828
7829 pub fn accept_edit_prediction(
7830 &mut self,
7831 _: &AcceptEditPrediction,
7832 window: &mut Window,
7833 cx: &mut Context<Self>,
7834 ) {
7835 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7836 }
7837
7838 fn discard_edit_prediction(
7839 &mut self,
7840 should_report_edit_prediction_event: bool,
7841 cx: &mut Context<Self>,
7842 ) -> bool {
7843 if should_report_edit_prediction_event {
7844 let completion_id = self
7845 .active_edit_prediction
7846 .as_ref()
7847 .and_then(|active_completion| active_completion.completion_id.clone());
7848
7849 self.report_edit_prediction_event(completion_id, false, cx);
7850 }
7851
7852 if let Some(provider) = self.edit_prediction_provider() {
7853 provider.discard(cx);
7854 }
7855
7856 self.take_active_edit_prediction(cx)
7857 }
7858
7859 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7860 let Some(provider) = self.edit_prediction_provider() else {
7861 return;
7862 };
7863
7864 let Some((_, buffer, _)) = self
7865 .buffer
7866 .read(cx)
7867 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7868 else {
7869 return;
7870 };
7871
7872 let extension = buffer
7873 .read(cx)
7874 .file()
7875 .and_then(|file| Some(file.path().extension()?.to_string()));
7876
7877 let event_type = match accepted {
7878 true => "Edit Prediction Accepted",
7879 false => "Edit Prediction Discarded",
7880 };
7881 telemetry::event!(
7882 event_type,
7883 provider = provider.name(),
7884 prediction_id = id,
7885 suggestion_accepted = accepted,
7886 file_extension = extension,
7887 );
7888 }
7889
7890 fn open_editor_at_anchor(
7891 snapshot: &language::BufferSnapshot,
7892 target: language::Anchor,
7893 workspace: &Entity<Workspace>,
7894 window: &mut Window,
7895 cx: &mut App,
7896 ) -> Task<Result<()>> {
7897 workspace.update(cx, |workspace, cx| {
7898 let path = snapshot.file().map(|file| file.full_path(cx));
7899 let Some(path) =
7900 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7901 else {
7902 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7903 };
7904 let target = text::ToPoint::to_point(&target, snapshot);
7905 let item = workspace.open_path(path, None, true, window, cx);
7906 window.spawn(cx, async move |cx| {
7907 let Some(editor) = item.await?.downcast::<Editor>() else {
7908 return Ok(());
7909 };
7910 editor
7911 .update_in(cx, |editor, window, cx| {
7912 editor.go_to_singleton_buffer_point(target, window, cx);
7913 })
7914 .ok();
7915 anyhow::Ok(())
7916 })
7917 })
7918 }
7919
7920 pub fn has_active_edit_prediction(&self) -> bool {
7921 self.active_edit_prediction.is_some()
7922 }
7923
7924 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7925 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7926 return false;
7927 };
7928
7929 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7930 self.clear_highlights::<EditPredictionHighlight>(cx);
7931 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7932 true
7933 }
7934
7935 /// Returns true when we're displaying the edit prediction popover below the cursor
7936 /// like we are not previewing and the LSP autocomplete menu is visible
7937 /// or we are in `when_holding_modifier` mode.
7938 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7939 if self.edit_prediction_preview_is_active()
7940 || !self.show_edit_predictions_in_menu()
7941 || !self.edit_predictions_enabled()
7942 {
7943 return false;
7944 }
7945
7946 if self.has_visible_completions_menu() {
7947 return true;
7948 }
7949
7950 has_completion && self.edit_prediction_requires_modifier()
7951 }
7952
7953 fn handle_modifiers_changed(
7954 &mut self,
7955 modifiers: Modifiers,
7956 position_map: &PositionMap,
7957 window: &mut Window,
7958 cx: &mut Context<Self>,
7959 ) {
7960 // Ensure that the edit prediction preview is updated, even when not
7961 // enabled, if there's an active edit prediction preview.
7962 if self.show_edit_predictions_in_menu()
7963 || matches!(
7964 self.edit_prediction_preview,
7965 EditPredictionPreview::Active { .. }
7966 )
7967 {
7968 self.update_edit_prediction_preview(&modifiers, window, cx);
7969 }
7970
7971 self.update_selection_mode(&modifiers, position_map, window, cx);
7972
7973 let mouse_position = window.mouse_position();
7974 if !position_map.text_hitbox.is_hovered(window) {
7975 return;
7976 }
7977
7978 self.update_hovered_link(
7979 position_map.point_for_position(mouse_position),
7980 &position_map.snapshot,
7981 modifiers,
7982 window,
7983 cx,
7984 )
7985 }
7986
7987 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7988 match EditorSettings::get_global(cx).multi_cursor_modifier {
7989 MultiCursorModifier::Alt => modifiers.secondary(),
7990 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7991 }
7992 }
7993
7994 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7995 match EditorSettings::get_global(cx).multi_cursor_modifier {
7996 MultiCursorModifier::Alt => modifiers.alt,
7997 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7998 }
7999 }
8000
8001 fn columnar_selection_mode(
8002 modifiers: &Modifiers,
8003 cx: &mut Context<Self>,
8004 ) -> Option<ColumnarMode> {
8005 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8006 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8007 Some(ColumnarMode::FromMouse)
8008 } else if Self::is_alt_pressed(modifiers, cx) {
8009 Some(ColumnarMode::FromSelection)
8010 } else {
8011 None
8012 }
8013 } else {
8014 None
8015 }
8016 }
8017
8018 fn update_selection_mode(
8019 &mut self,
8020 modifiers: &Modifiers,
8021 position_map: &PositionMap,
8022 window: &mut Window,
8023 cx: &mut Context<Self>,
8024 ) {
8025 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8026 return;
8027 };
8028 if self.selections.pending_anchor().is_none() {
8029 return;
8030 }
8031
8032 let mouse_position = window.mouse_position();
8033 let point_for_position = position_map.point_for_position(mouse_position);
8034 let position = point_for_position.previous_valid;
8035
8036 self.select(
8037 SelectPhase::BeginColumnar {
8038 position,
8039 reset: false,
8040 mode,
8041 goal_column: point_for_position.exact_unclipped.column(),
8042 },
8043 window,
8044 cx,
8045 );
8046 }
8047
8048 fn update_edit_prediction_preview(
8049 &mut self,
8050 modifiers: &Modifiers,
8051 window: &mut Window,
8052 cx: &mut Context<Self>,
8053 ) {
8054 let mut modifiers_held = false;
8055
8056 // Check bindings for all granularities.
8057 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8058 let granularities = [
8059 EditPredictionGranularity::Full,
8060 EditPredictionGranularity::Line,
8061 EditPredictionGranularity::Word,
8062 ];
8063
8064 for granularity in granularities {
8065 if let Some(keystroke) = self
8066 .accept_edit_prediction_keybind(granularity, window, cx)
8067 .keystroke()
8068 {
8069 modifiers_held = modifiers_held
8070 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8071 }
8072 }
8073
8074 if modifiers_held {
8075 if matches!(
8076 self.edit_prediction_preview,
8077 EditPredictionPreview::Inactive { .. }
8078 ) {
8079 if let Some(provider) = self.edit_prediction_provider.as_ref() {
8080 provider.provider.did_show(cx)
8081 }
8082
8083 self.edit_prediction_preview = EditPredictionPreview::Active {
8084 previous_scroll_position: None,
8085 since: Instant::now(),
8086 };
8087
8088 self.update_visible_edit_prediction(window, cx);
8089 cx.notify();
8090 }
8091 } else if let EditPredictionPreview::Active {
8092 previous_scroll_position,
8093 since,
8094 } = self.edit_prediction_preview
8095 {
8096 if let (Some(previous_scroll_position), Some(position_map)) =
8097 (previous_scroll_position, self.last_position_map.as_ref())
8098 {
8099 self.set_scroll_position(
8100 previous_scroll_position
8101 .scroll_position(&position_map.snapshot.display_snapshot),
8102 window,
8103 cx,
8104 );
8105 }
8106
8107 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8108 released_too_fast: since.elapsed() < Duration::from_millis(200),
8109 };
8110 self.clear_row_highlights::<EditPredictionPreview>();
8111 self.update_visible_edit_prediction(window, cx);
8112 cx.notify();
8113 }
8114 }
8115
8116 fn update_visible_edit_prediction(
8117 &mut self,
8118 _window: &mut Window,
8119 cx: &mut Context<Self>,
8120 ) -> Option<()> {
8121 if DisableAiSettings::get_global(cx).disable_ai {
8122 return None;
8123 }
8124
8125 if self.ime_transaction.is_some() {
8126 self.discard_edit_prediction(false, cx);
8127 return None;
8128 }
8129
8130 let selection = self.selections.newest_anchor();
8131 let cursor = selection.head();
8132 let multibuffer = self.buffer.read(cx).snapshot(cx);
8133 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8134 let excerpt_id = cursor.excerpt_id;
8135
8136 let show_in_menu = self.show_edit_predictions_in_menu();
8137 let completions_menu_has_precedence = !show_in_menu
8138 && (self.context_menu.borrow().is_some()
8139 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8140
8141 if completions_menu_has_precedence
8142 || !offset_selection.is_empty()
8143 || self
8144 .active_edit_prediction
8145 .as_ref()
8146 .is_some_and(|completion| {
8147 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8148 return false;
8149 };
8150 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8151 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8152 !invalidation_range.contains(&offset_selection.head())
8153 })
8154 {
8155 self.discard_edit_prediction(false, cx);
8156 return None;
8157 }
8158
8159 self.take_active_edit_prediction(cx);
8160 let Some(provider) = self.edit_prediction_provider() else {
8161 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8162 return None;
8163 };
8164
8165 let (buffer, cursor_buffer_position) =
8166 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8167
8168 self.edit_prediction_settings =
8169 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8170
8171 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8172
8173 if self.edit_prediction_indent_conflict {
8174 let cursor_point = cursor.to_point(&multibuffer);
8175 let mut suggested_indent = None;
8176 multibuffer.suggested_indents_callback(
8177 cursor_point.row..cursor_point.row + 1,
8178 |_, indent| {
8179 suggested_indent = Some(indent);
8180 ControlFlow::Break(())
8181 },
8182 cx,
8183 );
8184
8185 if let Some(indent) = suggested_indent
8186 && indent.len == cursor_point.column
8187 {
8188 self.edit_prediction_indent_conflict = false;
8189 }
8190 }
8191
8192 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8193
8194 let (completion_id, edits, edit_preview) = match edit_prediction {
8195 edit_prediction_types::EditPrediction::Local {
8196 id,
8197 edits,
8198 edit_preview,
8199 } => (id, edits, edit_preview),
8200 edit_prediction_types::EditPrediction::Jump {
8201 id,
8202 snapshot,
8203 target,
8204 } => {
8205 self.stale_edit_prediction_in_menu = None;
8206 self.active_edit_prediction = Some(EditPredictionState {
8207 inlay_ids: vec![],
8208 completion: EditPrediction::MoveOutside { snapshot, target },
8209 completion_id: id,
8210 invalidation_range: None,
8211 });
8212 cx.notify();
8213 return Some(());
8214 }
8215 };
8216
8217 let edits = edits
8218 .into_iter()
8219 .flat_map(|(range, new_text)| {
8220 Some((
8221 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8222 new_text,
8223 ))
8224 })
8225 .collect::<Vec<_>>();
8226 if edits.is_empty() {
8227 return None;
8228 }
8229
8230 let first_edit_start = edits.first().unwrap().0.start;
8231 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8232 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8233
8234 let last_edit_end = edits.last().unwrap().0.end;
8235 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8236 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8237
8238 let cursor_row = cursor.to_point(&multibuffer).row;
8239
8240 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8241
8242 let mut inlay_ids = Vec::new();
8243 let invalidation_row_range;
8244 let move_invalidation_row_range = if cursor_row < edit_start_row {
8245 Some(cursor_row..edit_end_row)
8246 } else if cursor_row > edit_end_row {
8247 Some(edit_start_row..cursor_row)
8248 } else {
8249 None
8250 };
8251 let supports_jump = self
8252 .edit_prediction_provider
8253 .as_ref()
8254 .map(|provider| provider.provider.supports_jump_to_edit())
8255 .unwrap_or(true);
8256
8257 let is_move = supports_jump
8258 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8259 let completion = if is_move {
8260 invalidation_row_range =
8261 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8262 let target = first_edit_start;
8263 EditPrediction::MoveWithin { target, snapshot }
8264 } else {
8265 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8266 && !self.edit_predictions_hidden_for_vim_mode;
8267
8268 if show_completions_in_buffer {
8269 if let Some(provider) = &self.edit_prediction_provider {
8270 provider.provider.did_show(cx);
8271 }
8272 if edits
8273 .iter()
8274 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8275 {
8276 let mut inlays = Vec::new();
8277 for (range, new_text) in &edits {
8278 let inlay = Inlay::edit_prediction(
8279 post_inc(&mut self.next_inlay_id),
8280 range.start,
8281 new_text.as_ref(),
8282 );
8283 inlay_ids.push(inlay.id);
8284 inlays.push(inlay);
8285 }
8286
8287 self.splice_inlays(&[], inlays, cx);
8288 } else {
8289 let background_color = cx.theme().status().deleted_background;
8290 self.highlight_text::<EditPredictionHighlight>(
8291 edits.iter().map(|(range, _)| range.clone()).collect(),
8292 HighlightStyle {
8293 background_color: Some(background_color),
8294 ..Default::default()
8295 },
8296 cx,
8297 );
8298 }
8299 }
8300
8301 invalidation_row_range = edit_start_row..edit_end_row;
8302
8303 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8304 if provider.show_tab_accept_marker() {
8305 EditDisplayMode::TabAccept
8306 } else {
8307 EditDisplayMode::Inline
8308 }
8309 } else {
8310 EditDisplayMode::DiffPopover
8311 };
8312
8313 EditPrediction::Edit {
8314 edits,
8315 edit_preview,
8316 display_mode,
8317 snapshot,
8318 }
8319 };
8320
8321 let invalidation_range = multibuffer
8322 .anchor_before(Point::new(invalidation_row_range.start, 0))
8323 ..multibuffer.anchor_after(Point::new(
8324 invalidation_row_range.end,
8325 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8326 ));
8327
8328 self.stale_edit_prediction_in_menu = None;
8329 self.active_edit_prediction = Some(EditPredictionState {
8330 inlay_ids,
8331 completion,
8332 completion_id,
8333 invalidation_range: Some(invalidation_range),
8334 });
8335
8336 cx.notify();
8337
8338 Some(())
8339 }
8340
8341 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8342 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8343 }
8344
8345 fn clear_tasks(&mut self) {
8346 self.tasks.clear()
8347 }
8348
8349 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8350 if self.tasks.insert(key, value).is_some() {
8351 // This case should hopefully be rare, but just in case...
8352 log::error!(
8353 "multiple different run targets found on a single line, only the last target will be rendered"
8354 )
8355 }
8356 }
8357
8358 /// Get all display points of breakpoints that will be rendered within editor
8359 ///
8360 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8361 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8362 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8363 fn active_breakpoints(
8364 &self,
8365 range: Range<DisplayRow>,
8366 window: &mut Window,
8367 cx: &mut Context<Self>,
8368 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8369 let mut breakpoint_display_points = HashMap::default();
8370
8371 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8372 return breakpoint_display_points;
8373 };
8374
8375 let snapshot = self.snapshot(window, cx);
8376
8377 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8378 let Some(project) = self.project() else {
8379 return breakpoint_display_points;
8380 };
8381
8382 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8383 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8384
8385 for (buffer_snapshot, range, excerpt_id) in
8386 multi_buffer_snapshot.range_to_buffer_ranges(range)
8387 {
8388 let Some(buffer) = project
8389 .read(cx)
8390 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8391 else {
8392 continue;
8393 };
8394 let breakpoints = breakpoint_store.read(cx).breakpoints(
8395 &buffer,
8396 Some(
8397 buffer_snapshot.anchor_before(range.start)
8398 ..buffer_snapshot.anchor_after(range.end),
8399 ),
8400 buffer_snapshot,
8401 cx,
8402 );
8403 for (breakpoint, state) in breakpoints {
8404 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8405 let position = multi_buffer_anchor
8406 .to_point(&multi_buffer_snapshot)
8407 .to_display_point(&snapshot);
8408
8409 breakpoint_display_points.insert(
8410 position.row(),
8411 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8412 );
8413 }
8414 }
8415
8416 breakpoint_display_points
8417 }
8418
8419 fn breakpoint_context_menu(
8420 &self,
8421 anchor: Anchor,
8422 window: &mut Window,
8423 cx: &mut Context<Self>,
8424 ) -> Entity<ui::ContextMenu> {
8425 let weak_editor = cx.weak_entity();
8426 let focus_handle = self.focus_handle(cx);
8427
8428 let row = self
8429 .buffer
8430 .read(cx)
8431 .snapshot(cx)
8432 .summary_for_anchor::<Point>(&anchor)
8433 .row;
8434
8435 let breakpoint = self
8436 .breakpoint_at_row(row, window, cx)
8437 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8438
8439 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8440 "Edit Log Breakpoint"
8441 } else {
8442 "Set Log Breakpoint"
8443 };
8444
8445 let condition_breakpoint_msg = if breakpoint
8446 .as_ref()
8447 .is_some_and(|bp| bp.1.condition.is_some())
8448 {
8449 "Edit Condition Breakpoint"
8450 } else {
8451 "Set Condition Breakpoint"
8452 };
8453
8454 let hit_condition_breakpoint_msg = if breakpoint
8455 .as_ref()
8456 .is_some_and(|bp| bp.1.hit_condition.is_some())
8457 {
8458 "Edit Hit Condition Breakpoint"
8459 } else {
8460 "Set Hit Condition Breakpoint"
8461 };
8462
8463 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8464 "Unset Breakpoint"
8465 } else {
8466 "Set Breakpoint"
8467 };
8468
8469 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8470
8471 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8472 BreakpointState::Enabled => Some("Disable"),
8473 BreakpointState::Disabled => Some("Enable"),
8474 });
8475
8476 let (anchor, breakpoint) =
8477 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8478
8479 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8480 menu.on_blur_subscription(Subscription::new(|| {}))
8481 .context(focus_handle)
8482 .when(run_to_cursor, |this| {
8483 let weak_editor = weak_editor.clone();
8484 this.entry("Run to cursor", None, move |window, cx| {
8485 weak_editor
8486 .update(cx, |editor, cx| {
8487 editor.change_selections(
8488 SelectionEffects::no_scroll(),
8489 window,
8490 cx,
8491 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8492 );
8493 })
8494 .ok();
8495
8496 window.dispatch_action(Box::new(RunToCursor), cx);
8497 })
8498 .separator()
8499 })
8500 .when_some(toggle_state_msg, |this, msg| {
8501 this.entry(msg, None, {
8502 let weak_editor = weak_editor.clone();
8503 let breakpoint = breakpoint.clone();
8504 move |_window, cx| {
8505 weak_editor
8506 .update(cx, |this, cx| {
8507 this.edit_breakpoint_at_anchor(
8508 anchor,
8509 breakpoint.as_ref().clone(),
8510 BreakpointEditAction::InvertState,
8511 cx,
8512 );
8513 })
8514 .log_err();
8515 }
8516 })
8517 })
8518 .entry(set_breakpoint_msg, None, {
8519 let weak_editor = weak_editor.clone();
8520 let breakpoint = breakpoint.clone();
8521 move |_window, cx| {
8522 weak_editor
8523 .update(cx, |this, cx| {
8524 this.edit_breakpoint_at_anchor(
8525 anchor,
8526 breakpoint.as_ref().clone(),
8527 BreakpointEditAction::Toggle,
8528 cx,
8529 );
8530 })
8531 .log_err();
8532 }
8533 })
8534 .entry(log_breakpoint_msg, None, {
8535 let breakpoint = breakpoint.clone();
8536 let weak_editor = weak_editor.clone();
8537 move |window, cx| {
8538 weak_editor
8539 .update(cx, |this, cx| {
8540 this.add_edit_breakpoint_block(
8541 anchor,
8542 breakpoint.as_ref(),
8543 BreakpointPromptEditAction::Log,
8544 window,
8545 cx,
8546 );
8547 })
8548 .log_err();
8549 }
8550 })
8551 .entry(condition_breakpoint_msg, None, {
8552 let breakpoint = breakpoint.clone();
8553 let weak_editor = weak_editor.clone();
8554 move |window, cx| {
8555 weak_editor
8556 .update(cx, |this, cx| {
8557 this.add_edit_breakpoint_block(
8558 anchor,
8559 breakpoint.as_ref(),
8560 BreakpointPromptEditAction::Condition,
8561 window,
8562 cx,
8563 );
8564 })
8565 .log_err();
8566 }
8567 })
8568 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8569 weak_editor
8570 .update(cx, |this, cx| {
8571 this.add_edit_breakpoint_block(
8572 anchor,
8573 breakpoint.as_ref(),
8574 BreakpointPromptEditAction::HitCondition,
8575 window,
8576 cx,
8577 );
8578 })
8579 .log_err();
8580 })
8581 })
8582 }
8583
8584 fn render_breakpoint(
8585 &self,
8586 position: Anchor,
8587 row: DisplayRow,
8588 breakpoint: &Breakpoint,
8589 state: Option<BreakpointSessionState>,
8590 cx: &mut Context<Self>,
8591 ) -> IconButton {
8592 let is_rejected = state.is_some_and(|s| !s.verified);
8593 // Is it a breakpoint that shows up when hovering over gutter?
8594 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8595 (false, false),
8596 |PhantomBreakpointIndicator {
8597 is_active,
8598 display_row,
8599 collides_with_existing_breakpoint,
8600 }| {
8601 (
8602 is_active && display_row == row,
8603 collides_with_existing_breakpoint,
8604 )
8605 },
8606 );
8607
8608 let (color, icon) = {
8609 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8610 (false, false) => ui::IconName::DebugBreakpoint,
8611 (true, false) => ui::IconName::DebugLogBreakpoint,
8612 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8613 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8614 };
8615
8616 let color = cx.theme().colors();
8617
8618 let color = if is_phantom {
8619 if collides_with_existing {
8620 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8621 } else {
8622 Color::Hint
8623 }
8624 } else if is_rejected {
8625 Color::Disabled
8626 } else {
8627 Color::Debugger
8628 };
8629
8630 (color, icon)
8631 };
8632
8633 let breakpoint = Arc::from(breakpoint.clone());
8634
8635 let alt_as_text = gpui::Keystroke {
8636 modifiers: Modifiers::secondary_key(),
8637 ..Default::default()
8638 };
8639 let primary_action_text = if breakpoint.is_disabled() {
8640 "Enable breakpoint"
8641 } else if is_phantom && !collides_with_existing {
8642 "Set breakpoint"
8643 } else {
8644 "Unset breakpoint"
8645 };
8646 let focus_handle = self.focus_handle.clone();
8647
8648 let meta = if is_rejected {
8649 SharedString::from("No executable code is associated with this line.")
8650 } else if collides_with_existing && !breakpoint.is_disabled() {
8651 SharedString::from(format!(
8652 "{alt_as_text}-click to disable,\nright-click for more options."
8653 ))
8654 } else {
8655 SharedString::from("Right-click for more options.")
8656 };
8657 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8658 .icon_size(IconSize::XSmall)
8659 .size(ui::ButtonSize::None)
8660 .when(is_rejected, |this| {
8661 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8662 })
8663 .icon_color(color)
8664 .style(ButtonStyle::Transparent)
8665 .on_click(cx.listener({
8666 move |editor, event: &ClickEvent, window, cx| {
8667 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8668 BreakpointEditAction::InvertState
8669 } else {
8670 BreakpointEditAction::Toggle
8671 };
8672
8673 window.focus(&editor.focus_handle(cx), cx);
8674 editor.edit_breakpoint_at_anchor(
8675 position,
8676 breakpoint.as_ref().clone(),
8677 edit_action,
8678 cx,
8679 );
8680 }
8681 }))
8682 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8683 editor.set_breakpoint_context_menu(
8684 row,
8685 Some(position),
8686 event.position(),
8687 window,
8688 cx,
8689 );
8690 }))
8691 .tooltip(move |_window, cx| {
8692 Tooltip::with_meta_in(
8693 primary_action_text,
8694 Some(&ToggleBreakpoint),
8695 meta.clone(),
8696 &focus_handle,
8697 cx,
8698 )
8699 })
8700 }
8701
8702 fn build_tasks_context(
8703 project: &Entity<Project>,
8704 buffer: &Entity<Buffer>,
8705 buffer_row: u32,
8706 tasks: &Arc<RunnableTasks>,
8707 cx: &mut Context<Self>,
8708 ) -> Task<Option<task::TaskContext>> {
8709 let position = Point::new(buffer_row, tasks.column);
8710 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8711 let location = Location {
8712 buffer: buffer.clone(),
8713 range: range_start..range_start,
8714 };
8715 // Fill in the environmental variables from the tree-sitter captures
8716 let mut captured_task_variables = TaskVariables::default();
8717 for (capture_name, value) in tasks.extra_variables.clone() {
8718 captured_task_variables.insert(
8719 task::VariableName::Custom(capture_name.into()),
8720 value.clone(),
8721 );
8722 }
8723 project.update(cx, |project, cx| {
8724 project.task_store().update(cx, |task_store, cx| {
8725 task_store.task_context_for_location(captured_task_variables, location, cx)
8726 })
8727 })
8728 }
8729
8730 pub fn spawn_nearest_task(
8731 &mut self,
8732 action: &SpawnNearestTask,
8733 window: &mut Window,
8734 cx: &mut Context<Self>,
8735 ) {
8736 let Some((workspace, _)) = self.workspace.clone() else {
8737 return;
8738 };
8739 let Some(project) = self.project.clone() else {
8740 return;
8741 };
8742
8743 // Try to find a closest, enclosing node using tree-sitter that has a task
8744 let Some((buffer, buffer_row, tasks)) = self
8745 .find_enclosing_node_task(cx)
8746 // Or find the task that's closest in row-distance.
8747 .or_else(|| self.find_closest_task(cx))
8748 else {
8749 return;
8750 };
8751
8752 let reveal_strategy = action.reveal;
8753 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8754 cx.spawn_in(window, async move |_, cx| {
8755 let context = task_context.await?;
8756 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8757
8758 let resolved = &mut resolved_task.resolved;
8759 resolved.reveal = reveal_strategy;
8760
8761 workspace
8762 .update_in(cx, |workspace, window, cx| {
8763 workspace.schedule_resolved_task(
8764 task_source_kind,
8765 resolved_task,
8766 false,
8767 window,
8768 cx,
8769 );
8770 })
8771 .ok()
8772 })
8773 .detach();
8774 }
8775
8776 fn find_closest_task(
8777 &mut self,
8778 cx: &mut Context<Self>,
8779 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8780 let cursor_row = self
8781 .selections
8782 .newest_adjusted(&self.display_snapshot(cx))
8783 .head()
8784 .row;
8785
8786 let ((buffer_id, row), tasks) = self
8787 .tasks
8788 .iter()
8789 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8790
8791 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8792 let tasks = Arc::new(tasks.to_owned());
8793 Some((buffer, *row, tasks))
8794 }
8795
8796 fn find_enclosing_node_task(
8797 &mut self,
8798 cx: &mut Context<Self>,
8799 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8800 let snapshot = self.buffer.read(cx).snapshot(cx);
8801 let offset = self
8802 .selections
8803 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8804 .head();
8805 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8806 let offset = excerpt.map_offset_to_buffer(offset);
8807 let buffer_id = excerpt.buffer().remote_id();
8808
8809 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8810 let mut cursor = layer.node().walk();
8811
8812 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8813 if cursor.node().end_byte() == offset.0 {
8814 cursor.goto_next_sibling();
8815 }
8816 }
8817
8818 // Ascend to the smallest ancestor that contains the range and has a task.
8819 loop {
8820 let node = cursor.node();
8821 let node_range = node.byte_range();
8822 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8823
8824 // Check if this node contains our offset
8825 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8826 // If it contains offset, check for task
8827 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8828 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8829 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8830 }
8831 }
8832
8833 if !cursor.goto_parent() {
8834 break;
8835 }
8836 }
8837 None
8838 }
8839
8840 fn render_run_indicator(
8841 &self,
8842 _style: &EditorStyle,
8843 is_active: bool,
8844 row: DisplayRow,
8845 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8846 cx: &mut Context<Self>,
8847 ) -> IconButton {
8848 let color = Color::Muted;
8849 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8850
8851 IconButton::new(
8852 ("run_indicator", row.0 as usize),
8853 ui::IconName::PlayOutlined,
8854 )
8855 .shape(ui::IconButtonShape::Square)
8856 .icon_size(IconSize::XSmall)
8857 .icon_color(color)
8858 .toggle_state(is_active)
8859 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8860 let quick_launch = match e {
8861 ClickEvent::Keyboard(_) => true,
8862 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8863 };
8864
8865 window.focus(&editor.focus_handle(cx), cx);
8866 editor.toggle_code_actions(
8867 &ToggleCodeActions {
8868 deployed_from: Some(CodeActionSource::RunMenu(row)),
8869 quick_launch,
8870 },
8871 window,
8872 cx,
8873 );
8874 }))
8875 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8876 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8877 }))
8878 }
8879
8880 pub fn context_menu_visible(&self) -> bool {
8881 !self.edit_prediction_preview_is_active()
8882 && self
8883 .context_menu
8884 .borrow()
8885 .as_ref()
8886 .is_some_and(|menu| menu.visible())
8887 }
8888
8889 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8890 self.context_menu
8891 .borrow()
8892 .as_ref()
8893 .map(|menu| menu.origin())
8894 }
8895
8896 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8897 self.context_menu_options = Some(options);
8898 }
8899
8900 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8901 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8902
8903 fn render_edit_prediction_popover(
8904 &mut self,
8905 text_bounds: &Bounds<Pixels>,
8906 content_origin: gpui::Point<Pixels>,
8907 right_margin: Pixels,
8908 editor_snapshot: &EditorSnapshot,
8909 visible_row_range: Range<DisplayRow>,
8910 scroll_top: ScrollOffset,
8911 scroll_bottom: ScrollOffset,
8912 line_layouts: &[LineWithInvisibles],
8913 line_height: Pixels,
8914 scroll_position: gpui::Point<ScrollOffset>,
8915 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8916 newest_selection_head: Option<DisplayPoint>,
8917 editor_width: Pixels,
8918 style: &EditorStyle,
8919 window: &mut Window,
8920 cx: &mut App,
8921 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8922 if self.mode().is_minimap() {
8923 return None;
8924 }
8925 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8926
8927 if self.edit_prediction_visible_in_cursor_popover(true) {
8928 return None;
8929 }
8930
8931 match &active_edit_prediction.completion {
8932 EditPrediction::MoveWithin { target, .. } => {
8933 let target_display_point = target.to_display_point(editor_snapshot);
8934
8935 if self.edit_prediction_requires_modifier() {
8936 if !self.edit_prediction_preview_is_active() {
8937 return None;
8938 }
8939
8940 self.render_edit_prediction_modifier_jump_popover(
8941 text_bounds,
8942 content_origin,
8943 visible_row_range,
8944 line_layouts,
8945 line_height,
8946 scroll_pixel_position,
8947 newest_selection_head,
8948 target_display_point,
8949 window,
8950 cx,
8951 )
8952 } else {
8953 self.render_edit_prediction_eager_jump_popover(
8954 text_bounds,
8955 content_origin,
8956 editor_snapshot,
8957 visible_row_range,
8958 scroll_top,
8959 scroll_bottom,
8960 line_height,
8961 scroll_pixel_position,
8962 target_display_point,
8963 editor_width,
8964 window,
8965 cx,
8966 )
8967 }
8968 }
8969 EditPrediction::Edit {
8970 display_mode: EditDisplayMode::Inline,
8971 ..
8972 } => None,
8973 EditPrediction::Edit {
8974 display_mode: EditDisplayMode::TabAccept,
8975 edits,
8976 ..
8977 } => {
8978 let range = &edits.first()?.0;
8979 let target_display_point = range.end.to_display_point(editor_snapshot);
8980
8981 self.render_edit_prediction_end_of_line_popover(
8982 "Accept",
8983 editor_snapshot,
8984 visible_row_range,
8985 target_display_point,
8986 line_height,
8987 scroll_pixel_position,
8988 content_origin,
8989 editor_width,
8990 window,
8991 cx,
8992 )
8993 }
8994 EditPrediction::Edit {
8995 edits,
8996 edit_preview,
8997 display_mode: EditDisplayMode::DiffPopover,
8998 snapshot,
8999 } => self.render_edit_prediction_diff_popover(
9000 text_bounds,
9001 content_origin,
9002 right_margin,
9003 editor_snapshot,
9004 visible_row_range,
9005 line_layouts,
9006 line_height,
9007 scroll_position,
9008 scroll_pixel_position,
9009 newest_selection_head,
9010 editor_width,
9011 style,
9012 edits,
9013 edit_preview,
9014 snapshot,
9015 window,
9016 cx,
9017 ),
9018 EditPrediction::MoveOutside { snapshot, .. } => {
9019 let mut element = self
9020 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9021 .into_any();
9022
9023 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9024 let origin_x = text_bounds.size.width - size.width - px(30.);
9025 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9026 element.prepaint_at(origin, window, cx);
9027
9028 Some((element, origin))
9029 }
9030 }
9031 }
9032
9033 fn render_edit_prediction_modifier_jump_popover(
9034 &mut self,
9035 text_bounds: &Bounds<Pixels>,
9036 content_origin: gpui::Point<Pixels>,
9037 visible_row_range: Range<DisplayRow>,
9038 line_layouts: &[LineWithInvisibles],
9039 line_height: Pixels,
9040 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9041 newest_selection_head: Option<DisplayPoint>,
9042 target_display_point: DisplayPoint,
9043 window: &mut Window,
9044 cx: &mut App,
9045 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9046 let scrolled_content_origin =
9047 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9048
9049 const SCROLL_PADDING_Y: Pixels = px(12.);
9050
9051 if target_display_point.row() < visible_row_range.start {
9052 return self.render_edit_prediction_scroll_popover(
9053 |_| SCROLL_PADDING_Y,
9054 IconName::ArrowUp,
9055 visible_row_range,
9056 line_layouts,
9057 newest_selection_head,
9058 scrolled_content_origin,
9059 window,
9060 cx,
9061 );
9062 } else if target_display_point.row() >= visible_row_range.end {
9063 return self.render_edit_prediction_scroll_popover(
9064 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9065 IconName::ArrowDown,
9066 visible_row_range,
9067 line_layouts,
9068 newest_selection_head,
9069 scrolled_content_origin,
9070 window,
9071 cx,
9072 );
9073 }
9074
9075 const POLE_WIDTH: Pixels = px(2.);
9076
9077 let line_layout =
9078 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9079 let target_column = target_display_point.column() as usize;
9080
9081 let target_x = line_layout.x_for_index(target_column);
9082 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9083 - scroll_pixel_position.y;
9084
9085 let flag_on_right = target_x < text_bounds.size.width / 2.;
9086
9087 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9088 border_color.l += 0.001;
9089
9090 let mut element = v_flex()
9091 .items_end()
9092 .when(flag_on_right, |el| el.items_start())
9093 .child(if flag_on_right {
9094 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9095 .rounded_bl(px(0.))
9096 .rounded_tl(px(0.))
9097 .border_l_2()
9098 .border_color(border_color)
9099 } else {
9100 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9101 .rounded_br(px(0.))
9102 .rounded_tr(px(0.))
9103 .border_r_2()
9104 .border_color(border_color)
9105 })
9106 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9107 .into_any();
9108
9109 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9110
9111 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9112 - point(
9113 if flag_on_right {
9114 POLE_WIDTH
9115 } else {
9116 size.width - POLE_WIDTH
9117 },
9118 size.height - line_height,
9119 );
9120
9121 origin.x = origin.x.max(content_origin.x);
9122
9123 element.prepaint_at(origin, window, cx);
9124
9125 Some((element, origin))
9126 }
9127
9128 fn render_edit_prediction_scroll_popover(
9129 &mut self,
9130 to_y: impl Fn(Size<Pixels>) -> Pixels,
9131 scroll_icon: IconName,
9132 visible_row_range: Range<DisplayRow>,
9133 line_layouts: &[LineWithInvisibles],
9134 newest_selection_head: Option<DisplayPoint>,
9135 scrolled_content_origin: gpui::Point<Pixels>,
9136 window: &mut Window,
9137 cx: &mut App,
9138 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9139 let mut element = self
9140 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9141 .into_any();
9142
9143 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9144
9145 let cursor = newest_selection_head?;
9146 let cursor_row_layout =
9147 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9148 let cursor_column = cursor.column() as usize;
9149
9150 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9151
9152 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9153
9154 element.prepaint_at(origin, window, cx);
9155 Some((element, origin))
9156 }
9157
9158 fn render_edit_prediction_eager_jump_popover(
9159 &mut self,
9160 text_bounds: &Bounds<Pixels>,
9161 content_origin: gpui::Point<Pixels>,
9162 editor_snapshot: &EditorSnapshot,
9163 visible_row_range: Range<DisplayRow>,
9164 scroll_top: ScrollOffset,
9165 scroll_bottom: ScrollOffset,
9166 line_height: Pixels,
9167 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9168 target_display_point: DisplayPoint,
9169 editor_width: Pixels,
9170 window: &mut Window,
9171 cx: &mut App,
9172 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9173 if target_display_point.row().as_f64() < scroll_top {
9174 let mut element = self
9175 .render_edit_prediction_line_popover(
9176 "Jump to Edit",
9177 Some(IconName::ArrowUp),
9178 window,
9179 cx,
9180 )
9181 .into_any();
9182
9183 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9184 let offset = point(
9185 (text_bounds.size.width - size.width) / 2.,
9186 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9187 );
9188
9189 let origin = text_bounds.origin + offset;
9190 element.prepaint_at(origin, window, cx);
9191 Some((element, origin))
9192 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9193 let mut element = self
9194 .render_edit_prediction_line_popover(
9195 "Jump to Edit",
9196 Some(IconName::ArrowDown),
9197 window,
9198 cx,
9199 )
9200 .into_any();
9201
9202 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9203 let offset = point(
9204 (text_bounds.size.width - size.width) / 2.,
9205 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9206 );
9207
9208 let origin = text_bounds.origin + offset;
9209 element.prepaint_at(origin, window, cx);
9210 Some((element, origin))
9211 } else {
9212 self.render_edit_prediction_end_of_line_popover(
9213 "Jump to Edit",
9214 editor_snapshot,
9215 visible_row_range,
9216 target_display_point,
9217 line_height,
9218 scroll_pixel_position,
9219 content_origin,
9220 editor_width,
9221 window,
9222 cx,
9223 )
9224 }
9225 }
9226
9227 fn render_edit_prediction_end_of_line_popover(
9228 self: &mut Editor,
9229 label: &'static str,
9230 editor_snapshot: &EditorSnapshot,
9231 visible_row_range: Range<DisplayRow>,
9232 target_display_point: DisplayPoint,
9233 line_height: Pixels,
9234 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9235 content_origin: gpui::Point<Pixels>,
9236 editor_width: Pixels,
9237 window: &mut Window,
9238 cx: &mut App,
9239 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9240 let target_line_end = DisplayPoint::new(
9241 target_display_point.row(),
9242 editor_snapshot.line_len(target_display_point.row()),
9243 );
9244
9245 let mut element = self
9246 .render_edit_prediction_line_popover(label, None, window, cx)
9247 .into_any();
9248
9249 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9250
9251 let line_origin =
9252 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9253
9254 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9255 let mut origin = start_point
9256 + line_origin
9257 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9258 origin.x = origin.x.max(content_origin.x);
9259
9260 let max_x = content_origin.x + editor_width - size.width;
9261
9262 if origin.x > max_x {
9263 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9264
9265 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9266 origin.y += offset;
9267 IconName::ArrowUp
9268 } else {
9269 origin.y -= offset;
9270 IconName::ArrowDown
9271 };
9272
9273 element = self
9274 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9275 .into_any();
9276
9277 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9278
9279 origin.x = content_origin.x + editor_width - size.width - px(2.);
9280 }
9281
9282 element.prepaint_at(origin, window, cx);
9283 Some((element, origin))
9284 }
9285
9286 fn render_edit_prediction_diff_popover(
9287 self: &Editor,
9288 text_bounds: &Bounds<Pixels>,
9289 content_origin: gpui::Point<Pixels>,
9290 right_margin: Pixels,
9291 editor_snapshot: &EditorSnapshot,
9292 visible_row_range: Range<DisplayRow>,
9293 line_layouts: &[LineWithInvisibles],
9294 line_height: Pixels,
9295 scroll_position: gpui::Point<ScrollOffset>,
9296 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9297 newest_selection_head: Option<DisplayPoint>,
9298 editor_width: Pixels,
9299 style: &EditorStyle,
9300 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9301 edit_preview: &Option<language::EditPreview>,
9302 snapshot: &language::BufferSnapshot,
9303 window: &mut Window,
9304 cx: &mut App,
9305 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9306 let edit_start = edits
9307 .first()
9308 .unwrap()
9309 .0
9310 .start
9311 .to_display_point(editor_snapshot);
9312 let edit_end = edits
9313 .last()
9314 .unwrap()
9315 .0
9316 .end
9317 .to_display_point(editor_snapshot);
9318
9319 let is_visible = visible_row_range.contains(&edit_start.row())
9320 || visible_row_range.contains(&edit_end.row());
9321 if !is_visible {
9322 return None;
9323 }
9324
9325 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9326 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9327 } else {
9328 // Fallback for providers without edit_preview
9329 crate::edit_prediction_fallback_text(edits, cx)
9330 };
9331
9332 let styled_text = highlighted_edits.to_styled_text(&style.text);
9333 let line_count = highlighted_edits.text.lines().count();
9334
9335 const BORDER_WIDTH: Pixels = px(1.);
9336
9337 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9338 let has_keybind = keybind.is_some();
9339
9340 let mut element = h_flex()
9341 .items_start()
9342 .child(
9343 h_flex()
9344 .bg(cx.theme().colors().editor_background)
9345 .border(BORDER_WIDTH)
9346 .shadow_xs()
9347 .border_color(cx.theme().colors().border)
9348 .rounded_l_lg()
9349 .when(line_count > 1, |el| el.rounded_br_lg())
9350 .pr_1()
9351 .child(styled_text),
9352 )
9353 .child(
9354 h_flex()
9355 .h(line_height + BORDER_WIDTH * 2.)
9356 .px_1p5()
9357 .gap_1()
9358 // Workaround: For some reason, there's a gap if we don't do this
9359 .ml(-BORDER_WIDTH)
9360 .shadow(vec![gpui::BoxShadow {
9361 color: gpui::black().opacity(0.05),
9362 offset: point(px(1.), px(1.)),
9363 blur_radius: px(2.),
9364 spread_radius: px(0.),
9365 }])
9366 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9367 .border(BORDER_WIDTH)
9368 .border_color(cx.theme().colors().border)
9369 .rounded_r_lg()
9370 .id("edit_prediction_diff_popover_keybind")
9371 .when(!has_keybind, |el| {
9372 let status_colors = cx.theme().status();
9373
9374 el.bg(status_colors.error_background)
9375 .border_color(status_colors.error.opacity(0.6))
9376 .child(Icon::new(IconName::Info).color(Color::Error))
9377 .cursor_default()
9378 .hoverable_tooltip(move |_window, cx| {
9379 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9380 })
9381 })
9382 .children(keybind),
9383 )
9384 .into_any();
9385
9386 let longest_row =
9387 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9388 let longest_line_width = if visible_row_range.contains(&longest_row) {
9389 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9390 } else {
9391 layout_line(
9392 longest_row,
9393 editor_snapshot,
9394 style,
9395 editor_width,
9396 |_| false,
9397 window,
9398 cx,
9399 )
9400 .width
9401 };
9402
9403 let viewport_bounds =
9404 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9405 right: -right_margin,
9406 ..Default::default()
9407 });
9408
9409 let x_after_longest = Pixels::from(
9410 ScrollPixelOffset::from(
9411 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9412 ) - scroll_pixel_position.x,
9413 );
9414
9415 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9416
9417 // Fully visible if it can be displayed within the window (allow overlapping other
9418 // panes). However, this is only allowed if the popover starts within text_bounds.
9419 let can_position_to_the_right = x_after_longest < text_bounds.right()
9420 && x_after_longest + element_bounds.width < viewport_bounds.right();
9421
9422 let mut origin = if can_position_to_the_right {
9423 point(
9424 x_after_longest,
9425 text_bounds.origin.y
9426 + Pixels::from(
9427 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9428 - scroll_pixel_position.y,
9429 ),
9430 )
9431 } else {
9432 let cursor_row = newest_selection_head.map(|head| head.row());
9433 let above_edit = edit_start
9434 .row()
9435 .0
9436 .checked_sub(line_count as u32)
9437 .map(DisplayRow);
9438 let below_edit = Some(edit_end.row() + 1);
9439 let above_cursor =
9440 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9441 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9442
9443 // Place the edit popover adjacent to the edit if there is a location
9444 // available that is onscreen and does not obscure the cursor. Otherwise,
9445 // place it adjacent to the cursor.
9446 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9447 .into_iter()
9448 .flatten()
9449 .find(|&start_row| {
9450 let end_row = start_row + line_count as u32;
9451 visible_row_range.contains(&start_row)
9452 && visible_row_range.contains(&end_row)
9453 && cursor_row
9454 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9455 })?;
9456
9457 content_origin
9458 + point(
9459 Pixels::from(-scroll_pixel_position.x),
9460 Pixels::from(
9461 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9462 ),
9463 )
9464 };
9465
9466 origin.x -= BORDER_WIDTH;
9467
9468 window.defer_draw(element, origin, 1);
9469
9470 // Do not return an element, since it will already be drawn due to defer_draw.
9471 None
9472 }
9473
9474 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9475 px(30.)
9476 }
9477
9478 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9479 if self.read_only(cx) {
9480 cx.theme().players().read_only()
9481 } else {
9482 self.style.as_ref().unwrap().local_player
9483 }
9484 }
9485
9486 fn render_edit_prediction_accept_keybind(
9487 &self,
9488 window: &mut Window,
9489 cx: &mut App,
9490 ) -> Option<AnyElement> {
9491 let accept_binding =
9492 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9493 let accept_keystroke = accept_binding.keystroke()?;
9494
9495 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9496
9497 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9498 Color::Accent
9499 } else {
9500 Color::Muted
9501 };
9502
9503 h_flex()
9504 .px_0p5()
9505 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9506 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9507 .text_size(TextSize::XSmall.rems(cx))
9508 .child(h_flex().children(ui::render_modifiers(
9509 accept_keystroke.modifiers(),
9510 PlatformStyle::platform(),
9511 Some(modifiers_color),
9512 Some(IconSize::XSmall.rems().into()),
9513 true,
9514 )))
9515 .when(is_platform_style_mac, |parent| {
9516 parent.child(accept_keystroke.key().to_string())
9517 })
9518 .when(!is_platform_style_mac, |parent| {
9519 parent.child(
9520 Key::new(
9521 util::capitalize(accept_keystroke.key()),
9522 Some(Color::Default),
9523 )
9524 .size(Some(IconSize::XSmall.rems().into())),
9525 )
9526 })
9527 .into_any()
9528 .into()
9529 }
9530
9531 fn render_edit_prediction_line_popover(
9532 &self,
9533 label: impl Into<SharedString>,
9534 icon: Option<IconName>,
9535 window: &mut Window,
9536 cx: &mut App,
9537 ) -> Stateful<Div> {
9538 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9539
9540 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9541 let has_keybind = keybind.is_some();
9542
9543 h_flex()
9544 .id("ep-line-popover")
9545 .py_0p5()
9546 .pl_1()
9547 .pr(padding_right)
9548 .gap_1()
9549 .rounded_md()
9550 .border_1()
9551 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9552 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9553 .shadow_xs()
9554 .when(!has_keybind, |el| {
9555 let status_colors = cx.theme().status();
9556
9557 el.bg(status_colors.error_background)
9558 .border_color(status_colors.error.opacity(0.6))
9559 .pl_2()
9560 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9561 .cursor_default()
9562 .hoverable_tooltip(move |_window, cx| {
9563 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9564 })
9565 })
9566 .children(keybind)
9567 .child(
9568 Label::new(label)
9569 .size(LabelSize::Small)
9570 .when(!has_keybind, |el| {
9571 el.color(cx.theme().status().error.into()).strikethrough()
9572 }),
9573 )
9574 .when(!has_keybind, |el| {
9575 el.child(
9576 h_flex().ml_1().child(
9577 Icon::new(IconName::Info)
9578 .size(IconSize::Small)
9579 .color(cx.theme().status().error.into()),
9580 ),
9581 )
9582 })
9583 .when_some(icon, |element, icon| {
9584 element.child(
9585 div()
9586 .mt(px(1.5))
9587 .child(Icon::new(icon).size(IconSize::Small)),
9588 )
9589 })
9590 }
9591
9592 fn render_edit_prediction_jump_outside_popover(
9593 &self,
9594 snapshot: &BufferSnapshot,
9595 window: &mut Window,
9596 cx: &mut App,
9597 ) -> Stateful<Div> {
9598 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9599 let has_keybind = keybind.is_some();
9600
9601 let file_name = snapshot
9602 .file()
9603 .map(|file| SharedString::new(file.file_name(cx)))
9604 .unwrap_or(SharedString::new_static("untitled"));
9605
9606 h_flex()
9607 .id("ep-jump-outside-popover")
9608 .py_1()
9609 .px_2()
9610 .gap_1()
9611 .rounded_md()
9612 .border_1()
9613 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9614 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9615 .shadow_xs()
9616 .when(!has_keybind, |el| {
9617 let status_colors = cx.theme().status();
9618
9619 el.bg(status_colors.error_background)
9620 .border_color(status_colors.error.opacity(0.6))
9621 .pl_2()
9622 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9623 .cursor_default()
9624 .hoverable_tooltip(move |_window, cx| {
9625 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9626 })
9627 })
9628 .children(keybind)
9629 .child(
9630 Label::new(file_name)
9631 .size(LabelSize::Small)
9632 .buffer_font(cx)
9633 .when(!has_keybind, |el| {
9634 el.color(cx.theme().status().error.into()).strikethrough()
9635 }),
9636 )
9637 .when(!has_keybind, |el| {
9638 el.child(
9639 h_flex().ml_1().child(
9640 Icon::new(IconName::Info)
9641 .size(IconSize::Small)
9642 .color(cx.theme().status().error.into()),
9643 ),
9644 )
9645 })
9646 .child(
9647 div()
9648 .mt(px(1.5))
9649 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9650 )
9651 }
9652
9653 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9654 let accent_color = cx.theme().colors().text_accent;
9655 let editor_bg_color = cx.theme().colors().editor_background;
9656 editor_bg_color.blend(accent_color.opacity(0.1))
9657 }
9658
9659 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9660 let accent_color = cx.theme().colors().text_accent;
9661 let editor_bg_color = cx.theme().colors().editor_background;
9662 editor_bg_color.blend(accent_color.opacity(0.6))
9663 }
9664 fn get_prediction_provider_icon_name(
9665 provider: &Option<RegisteredEditPredictionDelegate>,
9666 ) -> IconName {
9667 match provider {
9668 Some(provider) => match provider.provider.name() {
9669 "copilot" => IconName::Copilot,
9670 "supermaven" => IconName::Supermaven,
9671 _ => IconName::ZedPredict,
9672 },
9673 None => IconName::ZedPredict,
9674 }
9675 }
9676
9677 fn render_edit_prediction_cursor_popover(
9678 &self,
9679 min_width: Pixels,
9680 max_width: Pixels,
9681 cursor_point: Point,
9682 style: &EditorStyle,
9683 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9684 _window: &Window,
9685 cx: &mut Context<Editor>,
9686 ) -> Option<AnyElement> {
9687 let provider = self.edit_prediction_provider.as_ref()?;
9688 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9689
9690 let is_refreshing = provider.provider.is_refreshing(cx);
9691
9692 fn pending_completion_container(icon: IconName) -> Div {
9693 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9694 }
9695
9696 let completion = match &self.active_edit_prediction {
9697 Some(prediction) => {
9698 if !self.has_visible_completions_menu() {
9699 const RADIUS: Pixels = px(6.);
9700 const BORDER_WIDTH: Pixels = px(1.);
9701
9702 return Some(
9703 h_flex()
9704 .elevation_2(cx)
9705 .border(BORDER_WIDTH)
9706 .border_color(cx.theme().colors().border)
9707 .when(accept_keystroke.is_none(), |el| {
9708 el.border_color(cx.theme().status().error)
9709 })
9710 .rounded(RADIUS)
9711 .rounded_tl(px(0.))
9712 .overflow_hidden()
9713 .child(div().px_1p5().child(match &prediction.completion {
9714 EditPrediction::MoveWithin { target, snapshot } => {
9715 use text::ToPoint as _;
9716 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9717 {
9718 Icon::new(IconName::ZedPredictDown)
9719 } else {
9720 Icon::new(IconName::ZedPredictUp)
9721 }
9722 }
9723 EditPrediction::MoveOutside { .. } => {
9724 // TODO [zeta2] custom icon for external jump?
9725 Icon::new(provider_icon)
9726 }
9727 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9728 }))
9729 .child(
9730 h_flex()
9731 .gap_1()
9732 .py_1()
9733 .px_2()
9734 .rounded_r(RADIUS - BORDER_WIDTH)
9735 .border_l_1()
9736 .border_color(cx.theme().colors().border)
9737 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9738 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9739 el.child(
9740 Label::new("Hold")
9741 .size(LabelSize::Small)
9742 .when(accept_keystroke.is_none(), |el| {
9743 el.strikethrough()
9744 })
9745 .line_height_style(LineHeightStyle::UiLabel),
9746 )
9747 })
9748 .id("edit_prediction_cursor_popover_keybind")
9749 .when(accept_keystroke.is_none(), |el| {
9750 let status_colors = cx.theme().status();
9751
9752 el.bg(status_colors.error_background)
9753 .border_color(status_colors.error.opacity(0.6))
9754 .child(Icon::new(IconName::Info).color(Color::Error))
9755 .cursor_default()
9756 .hoverable_tooltip(move |_window, cx| {
9757 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9758 .into()
9759 })
9760 })
9761 .when_some(
9762 accept_keystroke.as_ref(),
9763 |el, accept_keystroke| {
9764 el.child(h_flex().children(ui::render_modifiers(
9765 accept_keystroke.modifiers(),
9766 PlatformStyle::platform(),
9767 Some(Color::Default),
9768 Some(IconSize::XSmall.rems().into()),
9769 false,
9770 )))
9771 },
9772 ),
9773 )
9774 .into_any(),
9775 );
9776 }
9777
9778 self.render_edit_prediction_cursor_popover_preview(
9779 prediction,
9780 cursor_point,
9781 style,
9782 cx,
9783 )?
9784 }
9785
9786 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9787 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9788 stale_completion,
9789 cursor_point,
9790 style,
9791 cx,
9792 )?,
9793
9794 None => pending_completion_container(provider_icon)
9795 .child(Label::new("...").size(LabelSize::Small)),
9796 },
9797
9798 None => pending_completion_container(provider_icon)
9799 .child(Label::new("...").size(LabelSize::Small)),
9800 };
9801
9802 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9803 completion
9804 .with_animation(
9805 "loading-completion",
9806 Animation::new(Duration::from_secs(2))
9807 .repeat()
9808 .with_easing(pulsating_between(0.4, 0.8)),
9809 |label, delta| label.opacity(delta),
9810 )
9811 .into_any_element()
9812 } else {
9813 completion.into_any_element()
9814 };
9815
9816 let has_completion = self.active_edit_prediction.is_some();
9817
9818 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9819 Some(
9820 h_flex()
9821 .min_w(min_width)
9822 .max_w(max_width)
9823 .flex_1()
9824 .elevation_2(cx)
9825 .border_color(cx.theme().colors().border)
9826 .child(
9827 div()
9828 .flex_1()
9829 .py_1()
9830 .px_2()
9831 .overflow_hidden()
9832 .child(completion),
9833 )
9834 .when_some(accept_keystroke, |el, accept_keystroke| {
9835 if !accept_keystroke.modifiers().modified() {
9836 return el;
9837 }
9838
9839 el.child(
9840 h_flex()
9841 .h_full()
9842 .border_l_1()
9843 .rounded_r_lg()
9844 .border_color(cx.theme().colors().border)
9845 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9846 .gap_1()
9847 .py_1()
9848 .px_2()
9849 .child(
9850 h_flex()
9851 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9852 .when(is_platform_style_mac, |parent| parent.gap_1())
9853 .child(h_flex().children(ui::render_modifiers(
9854 accept_keystroke.modifiers(),
9855 PlatformStyle::platform(),
9856 Some(if !has_completion {
9857 Color::Muted
9858 } else {
9859 Color::Default
9860 }),
9861 None,
9862 false,
9863 ))),
9864 )
9865 .child(Label::new("Preview").into_any_element())
9866 .opacity(if has_completion { 1.0 } else { 0.4 }),
9867 )
9868 })
9869 .into_any(),
9870 )
9871 }
9872
9873 fn render_edit_prediction_cursor_popover_preview(
9874 &self,
9875 completion: &EditPredictionState,
9876 cursor_point: Point,
9877 style: &EditorStyle,
9878 cx: &mut Context<Editor>,
9879 ) -> Option<Div> {
9880 use text::ToPoint as _;
9881
9882 fn render_relative_row_jump(
9883 prefix: impl Into<String>,
9884 current_row: u32,
9885 target_row: u32,
9886 ) -> Div {
9887 let (row_diff, arrow) = if target_row < current_row {
9888 (current_row - target_row, IconName::ArrowUp)
9889 } else {
9890 (target_row - current_row, IconName::ArrowDown)
9891 };
9892
9893 h_flex()
9894 .child(
9895 Label::new(format!("{}{}", prefix.into(), row_diff))
9896 .color(Color::Muted)
9897 .size(LabelSize::Small),
9898 )
9899 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9900 }
9901
9902 let supports_jump = self
9903 .edit_prediction_provider
9904 .as_ref()
9905 .map(|provider| provider.provider.supports_jump_to_edit())
9906 .unwrap_or(true);
9907
9908 match &completion.completion {
9909 EditPrediction::MoveWithin {
9910 target, snapshot, ..
9911 } => {
9912 if !supports_jump {
9913 return None;
9914 }
9915
9916 Some(
9917 h_flex()
9918 .px_2()
9919 .gap_2()
9920 .flex_1()
9921 .child(
9922 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9923 Icon::new(IconName::ZedPredictDown)
9924 } else {
9925 Icon::new(IconName::ZedPredictUp)
9926 },
9927 )
9928 .child(Label::new("Jump to Edit")),
9929 )
9930 }
9931 EditPrediction::MoveOutside { snapshot, .. } => {
9932 let file_name = snapshot
9933 .file()
9934 .map(|file| file.file_name(cx))
9935 .unwrap_or("untitled");
9936 Some(
9937 h_flex()
9938 .px_2()
9939 .gap_2()
9940 .flex_1()
9941 .child(Icon::new(IconName::ZedPredict))
9942 .child(Label::new(format!("Jump to {file_name}"))),
9943 )
9944 }
9945 EditPrediction::Edit {
9946 edits,
9947 edit_preview,
9948 snapshot,
9949 display_mode: _,
9950 } => {
9951 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9952
9953 let (highlighted_edits, has_more_lines) =
9954 if let Some(edit_preview) = edit_preview.as_ref() {
9955 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9956 .first_line_preview()
9957 } else {
9958 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9959 };
9960
9961 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9962 .with_default_highlights(&style.text, highlighted_edits.highlights);
9963
9964 let preview = h_flex()
9965 .gap_1()
9966 .min_w_16()
9967 .child(styled_text)
9968 .when(has_more_lines, |parent| parent.child("…"));
9969
9970 let left = if supports_jump && first_edit_row != cursor_point.row {
9971 render_relative_row_jump("", cursor_point.row, first_edit_row)
9972 .into_any_element()
9973 } else {
9974 let icon_name =
9975 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9976 Icon::new(icon_name).into_any_element()
9977 };
9978
9979 Some(
9980 h_flex()
9981 .h_full()
9982 .flex_1()
9983 .gap_2()
9984 .pr_1()
9985 .overflow_x_hidden()
9986 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9987 .child(left)
9988 .child(preview),
9989 )
9990 }
9991 }
9992 }
9993
9994 pub fn render_context_menu(
9995 &mut self,
9996 max_height_in_lines: u32,
9997 window: &mut Window,
9998 cx: &mut Context<Editor>,
9999 ) -> Option<AnyElement> {
10000 let menu = self.context_menu.borrow();
10001 let menu = menu.as_ref()?;
10002 if !menu.visible() {
10003 return None;
10004 };
10005 self.style
10006 .as_ref()
10007 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10008 }
10009
10010 fn render_context_menu_aside(
10011 &mut self,
10012 max_size: Size<Pixels>,
10013 window: &mut Window,
10014 cx: &mut Context<Editor>,
10015 ) -> Option<AnyElement> {
10016 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10017 if menu.visible() {
10018 menu.render_aside(max_size, window, cx)
10019 } else {
10020 None
10021 }
10022 })
10023 }
10024
10025 fn hide_context_menu(
10026 &mut self,
10027 window: &mut Window,
10028 cx: &mut Context<Self>,
10029 ) -> Option<CodeContextMenu> {
10030 cx.notify();
10031 self.completion_tasks.clear();
10032 let context_menu = self.context_menu.borrow_mut().take();
10033 self.stale_edit_prediction_in_menu.take();
10034 self.update_visible_edit_prediction(window, cx);
10035 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10036 && let Some(completion_provider) = &self.completion_provider
10037 {
10038 completion_provider.selection_changed(None, window, cx);
10039 }
10040 context_menu
10041 }
10042
10043 fn show_snippet_choices(
10044 &mut self,
10045 choices: &Vec<String>,
10046 selection: Range<Anchor>,
10047 cx: &mut Context<Self>,
10048 ) {
10049 let Some((_, buffer, _)) = self
10050 .buffer()
10051 .read(cx)
10052 .excerpt_containing(selection.start, cx)
10053 else {
10054 return;
10055 };
10056 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10057 else {
10058 return;
10059 };
10060 if buffer != end_buffer {
10061 log::error!("expected anchor range to have matching buffer IDs");
10062 return;
10063 }
10064
10065 let id = post_inc(&mut self.next_completion_id);
10066 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10067 let mut context_menu = self.context_menu.borrow_mut();
10068 let old_menu = context_menu.take();
10069 *context_menu = Some(CodeContextMenu::Completions(
10070 CompletionsMenu::new_snippet_choices(
10071 id,
10072 true,
10073 choices,
10074 selection,
10075 buffer,
10076 old_menu.map(|menu| menu.primary_scroll_handle()),
10077 snippet_sort_order,
10078 ),
10079 ));
10080 }
10081
10082 pub fn insert_snippet(
10083 &mut self,
10084 insertion_ranges: &[Range<MultiBufferOffset>],
10085 snippet: Snippet,
10086 window: &mut Window,
10087 cx: &mut Context<Self>,
10088 ) -> Result<()> {
10089 struct Tabstop<T> {
10090 is_end_tabstop: bool,
10091 ranges: Vec<Range<T>>,
10092 choices: Option<Vec<String>>,
10093 }
10094
10095 let tabstops = self.buffer.update(cx, |buffer, cx| {
10096 let snippet_text: Arc<str> = snippet.text.clone().into();
10097 let edits = insertion_ranges
10098 .iter()
10099 .cloned()
10100 .map(|range| (range, snippet_text.clone()));
10101 let autoindent_mode = AutoindentMode::Block {
10102 original_indent_columns: Vec::new(),
10103 };
10104 buffer.edit(edits, Some(autoindent_mode), cx);
10105
10106 let snapshot = &*buffer.read(cx);
10107 let snippet = &snippet;
10108 snippet
10109 .tabstops
10110 .iter()
10111 .map(|tabstop| {
10112 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10113 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10114 });
10115 let mut tabstop_ranges = tabstop
10116 .ranges
10117 .iter()
10118 .flat_map(|tabstop_range| {
10119 let mut delta = 0_isize;
10120 insertion_ranges.iter().map(move |insertion_range| {
10121 let insertion_start = insertion_range.start + delta;
10122 delta += snippet.text.len() as isize
10123 - (insertion_range.end - insertion_range.start) as isize;
10124
10125 let start =
10126 (insertion_start + tabstop_range.start).min(snapshot.len());
10127 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10128 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10129 })
10130 })
10131 .collect::<Vec<_>>();
10132 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10133
10134 Tabstop {
10135 is_end_tabstop,
10136 ranges: tabstop_ranges,
10137 choices: tabstop.choices.clone(),
10138 }
10139 })
10140 .collect::<Vec<_>>()
10141 });
10142 if let Some(tabstop) = tabstops.first() {
10143 self.change_selections(Default::default(), window, cx, |s| {
10144 // Reverse order so that the first range is the newest created selection.
10145 // Completions will use it and autoscroll will prioritize it.
10146 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10147 });
10148
10149 if let Some(choices) = &tabstop.choices
10150 && let Some(selection) = tabstop.ranges.first()
10151 {
10152 self.show_snippet_choices(choices, selection.clone(), cx)
10153 }
10154
10155 // If we're already at the last tabstop and it's at the end of the snippet,
10156 // we're done, we don't need to keep the state around.
10157 if !tabstop.is_end_tabstop {
10158 let choices = tabstops
10159 .iter()
10160 .map(|tabstop| tabstop.choices.clone())
10161 .collect();
10162
10163 let ranges = tabstops
10164 .into_iter()
10165 .map(|tabstop| tabstop.ranges)
10166 .collect::<Vec<_>>();
10167
10168 self.snippet_stack.push(SnippetState {
10169 active_index: 0,
10170 ranges,
10171 choices,
10172 });
10173 }
10174
10175 // Check whether the just-entered snippet ends with an auto-closable bracket.
10176 if self.autoclose_regions.is_empty() {
10177 let snapshot = self.buffer.read(cx).snapshot(cx);
10178 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10179 let selection_head = selection.head();
10180 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10181 continue;
10182 };
10183
10184 let mut bracket_pair = None;
10185 let max_lookup_length = scope
10186 .brackets()
10187 .map(|(pair, _)| {
10188 pair.start
10189 .as_str()
10190 .chars()
10191 .count()
10192 .max(pair.end.as_str().chars().count())
10193 })
10194 .max();
10195 if let Some(max_lookup_length) = max_lookup_length {
10196 let next_text = snapshot
10197 .chars_at(selection_head)
10198 .take(max_lookup_length)
10199 .collect::<String>();
10200 let prev_text = snapshot
10201 .reversed_chars_at(selection_head)
10202 .take(max_lookup_length)
10203 .collect::<String>();
10204
10205 for (pair, enabled) in scope.brackets() {
10206 if enabled
10207 && pair.close
10208 && prev_text.starts_with(pair.start.as_str())
10209 && next_text.starts_with(pair.end.as_str())
10210 {
10211 bracket_pair = Some(pair.clone());
10212 break;
10213 }
10214 }
10215 }
10216
10217 if let Some(pair) = bracket_pair {
10218 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10219 let autoclose_enabled =
10220 self.use_autoclose && snapshot_settings.use_autoclose;
10221 if autoclose_enabled {
10222 let start = snapshot.anchor_after(selection_head);
10223 let end = snapshot.anchor_after(selection_head);
10224 self.autoclose_regions.push(AutocloseRegion {
10225 selection_id: selection.id,
10226 range: start..end,
10227 pair,
10228 });
10229 }
10230 }
10231 }
10232 }
10233 }
10234 Ok(())
10235 }
10236
10237 pub fn move_to_next_snippet_tabstop(
10238 &mut self,
10239 window: &mut Window,
10240 cx: &mut Context<Self>,
10241 ) -> bool {
10242 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10243 }
10244
10245 pub fn move_to_prev_snippet_tabstop(
10246 &mut self,
10247 window: &mut Window,
10248 cx: &mut Context<Self>,
10249 ) -> bool {
10250 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10251 }
10252
10253 pub fn move_to_snippet_tabstop(
10254 &mut self,
10255 bias: Bias,
10256 window: &mut Window,
10257 cx: &mut Context<Self>,
10258 ) -> bool {
10259 if let Some(mut snippet) = self.snippet_stack.pop() {
10260 match bias {
10261 Bias::Left => {
10262 if snippet.active_index > 0 {
10263 snippet.active_index -= 1;
10264 } else {
10265 self.snippet_stack.push(snippet);
10266 return false;
10267 }
10268 }
10269 Bias::Right => {
10270 if snippet.active_index + 1 < snippet.ranges.len() {
10271 snippet.active_index += 1;
10272 } else {
10273 self.snippet_stack.push(snippet);
10274 return false;
10275 }
10276 }
10277 }
10278 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10279 self.change_selections(Default::default(), window, cx, |s| {
10280 // Reverse order so that the first range is the newest created selection.
10281 // Completions will use it and autoscroll will prioritize it.
10282 s.select_ranges(current_ranges.iter().rev().cloned())
10283 });
10284
10285 if let Some(choices) = &snippet.choices[snippet.active_index]
10286 && let Some(selection) = current_ranges.first()
10287 {
10288 self.show_snippet_choices(choices, selection.clone(), cx);
10289 }
10290
10291 // If snippet state is not at the last tabstop, push it back on the stack
10292 if snippet.active_index + 1 < snippet.ranges.len() {
10293 self.snippet_stack.push(snippet);
10294 }
10295 return true;
10296 }
10297 }
10298
10299 false
10300 }
10301
10302 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10303 self.transact(window, cx, |this, window, cx| {
10304 this.select_all(&SelectAll, window, cx);
10305 this.insert("", window, cx);
10306 });
10307 }
10308
10309 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10310 if self.read_only(cx) {
10311 return;
10312 }
10313 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10314 self.transact(window, cx, |this, window, cx| {
10315 this.select_autoclose_pair(window, cx);
10316
10317 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10318
10319 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10320 if !this.linked_edit_ranges.is_empty() {
10321 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10322 let snapshot = this.buffer.read(cx).snapshot(cx);
10323
10324 for selection in selections.iter() {
10325 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10326 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10327 if selection_start.buffer_id != selection_end.buffer_id {
10328 continue;
10329 }
10330 if let Some(ranges) =
10331 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10332 {
10333 for (buffer, entries) in ranges {
10334 linked_ranges.entry(buffer).or_default().extend(entries);
10335 }
10336 }
10337 }
10338 }
10339
10340 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10341 for selection in &mut selections {
10342 if selection.is_empty() {
10343 let old_head = selection.head();
10344 let mut new_head =
10345 movement::left(&display_map, old_head.to_display_point(&display_map))
10346 .to_point(&display_map);
10347 if let Some((buffer, line_buffer_range)) = display_map
10348 .buffer_snapshot()
10349 .buffer_line_for_row(MultiBufferRow(old_head.row))
10350 {
10351 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10352 let indent_len = match indent_size.kind {
10353 IndentKind::Space => {
10354 buffer.settings_at(line_buffer_range.start, cx).tab_size
10355 }
10356 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10357 };
10358 if old_head.column <= indent_size.len && old_head.column > 0 {
10359 let indent_len = indent_len.get();
10360 new_head = cmp::min(
10361 new_head,
10362 MultiBufferPoint::new(
10363 old_head.row,
10364 ((old_head.column - 1) / indent_len) * indent_len,
10365 ),
10366 );
10367 }
10368 }
10369
10370 selection.set_head(new_head, SelectionGoal::None);
10371 }
10372 }
10373
10374 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10375 this.insert("", window, cx);
10376 let empty_str: Arc<str> = Arc::from("");
10377 for (buffer, edits) in linked_ranges {
10378 let snapshot = buffer.read(cx).snapshot();
10379 use text::ToPoint as TP;
10380
10381 let edits = edits
10382 .into_iter()
10383 .map(|range| {
10384 let end_point = TP::to_point(&range.end, &snapshot);
10385 let mut start_point = TP::to_point(&range.start, &snapshot);
10386
10387 if end_point == start_point {
10388 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10389 .saturating_sub(1);
10390 start_point =
10391 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10392 };
10393
10394 (start_point..end_point, empty_str.clone())
10395 })
10396 .sorted_by_key(|(range, _)| range.start)
10397 .collect::<Vec<_>>();
10398 buffer.update(cx, |this, cx| {
10399 this.edit(edits, None, cx);
10400 })
10401 }
10402 this.refresh_edit_prediction(true, false, window, cx);
10403 refresh_linked_ranges(this, window, cx);
10404 });
10405 }
10406
10407 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10408 if self.read_only(cx) {
10409 return;
10410 }
10411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10412 self.transact(window, cx, |this, window, cx| {
10413 this.change_selections(Default::default(), window, cx, |s| {
10414 s.move_with(|map, selection| {
10415 if selection.is_empty() {
10416 let cursor = movement::right(map, selection.head());
10417 selection.end = cursor;
10418 selection.reversed = true;
10419 selection.goal = SelectionGoal::None;
10420 }
10421 })
10422 });
10423 this.insert("", window, cx);
10424 this.refresh_edit_prediction(true, false, window, cx);
10425 });
10426 }
10427
10428 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10429 if self.mode.is_single_line() {
10430 cx.propagate();
10431 return;
10432 }
10433
10434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10435 if self.move_to_prev_snippet_tabstop(window, cx) {
10436 return;
10437 }
10438 self.outdent(&Outdent, window, cx);
10439 }
10440
10441 pub fn next_snippet_tabstop(
10442 &mut self,
10443 _: &NextSnippetTabstop,
10444 window: &mut Window,
10445 cx: &mut Context<Self>,
10446 ) {
10447 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10448 cx.propagate();
10449 return;
10450 }
10451
10452 if self.move_to_next_snippet_tabstop(window, cx) {
10453 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10454 return;
10455 }
10456 cx.propagate();
10457 }
10458
10459 pub fn previous_snippet_tabstop(
10460 &mut self,
10461 _: &PreviousSnippetTabstop,
10462 window: &mut Window,
10463 cx: &mut Context<Self>,
10464 ) {
10465 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10466 cx.propagate();
10467 return;
10468 }
10469
10470 if self.move_to_prev_snippet_tabstop(window, cx) {
10471 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10472 return;
10473 }
10474 cx.propagate();
10475 }
10476
10477 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10478 if self.mode.is_single_line() {
10479 cx.propagate();
10480 return;
10481 }
10482
10483 if self.move_to_next_snippet_tabstop(window, cx) {
10484 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10485 return;
10486 }
10487 if self.read_only(cx) {
10488 return;
10489 }
10490 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10491 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10492 let buffer = self.buffer.read(cx);
10493 let snapshot = buffer.snapshot(cx);
10494 let rows_iter = selections.iter().map(|s| s.head().row);
10495 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10496
10497 let has_some_cursor_in_whitespace = selections
10498 .iter()
10499 .filter(|selection| selection.is_empty())
10500 .any(|selection| {
10501 let cursor = selection.head();
10502 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10503 cursor.column < current_indent.len
10504 });
10505
10506 let mut edits = Vec::new();
10507 let mut prev_edited_row = 0;
10508 let mut row_delta = 0;
10509 for selection in &mut selections {
10510 if selection.start.row != prev_edited_row {
10511 row_delta = 0;
10512 }
10513 prev_edited_row = selection.end.row;
10514
10515 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10516 if selection.is_empty() {
10517 let cursor = selection.head();
10518 let settings = buffer.language_settings_at(cursor, cx);
10519 if settings.indent_list_on_tab {
10520 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10521 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10522 row_delta = Self::indent_selection(
10523 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10524 );
10525 continue;
10526 }
10527 }
10528 }
10529 }
10530
10531 // If the selection is non-empty, then increase the indentation of the selected lines.
10532 if !selection.is_empty() {
10533 row_delta =
10534 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10535 continue;
10536 }
10537
10538 let cursor = selection.head();
10539 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10540 if let Some(suggested_indent) =
10541 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10542 {
10543 // Don't do anything if already at suggested indent
10544 // and there is any other cursor which is not
10545 if has_some_cursor_in_whitespace
10546 && cursor.column == current_indent.len
10547 && current_indent.len == suggested_indent.len
10548 {
10549 continue;
10550 }
10551
10552 // Adjust line and move cursor to suggested indent
10553 // if cursor is not at suggested indent
10554 if cursor.column < suggested_indent.len
10555 && cursor.column <= current_indent.len
10556 && current_indent.len <= suggested_indent.len
10557 {
10558 selection.start = Point::new(cursor.row, suggested_indent.len);
10559 selection.end = selection.start;
10560 if row_delta == 0 {
10561 edits.extend(Buffer::edit_for_indent_size_adjustment(
10562 cursor.row,
10563 current_indent,
10564 suggested_indent,
10565 ));
10566 row_delta = suggested_indent.len - current_indent.len;
10567 }
10568 continue;
10569 }
10570
10571 // If current indent is more than suggested indent
10572 // only move cursor to current indent and skip indent
10573 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10574 selection.start = Point::new(cursor.row, current_indent.len);
10575 selection.end = selection.start;
10576 continue;
10577 }
10578 }
10579
10580 // Otherwise, insert a hard or soft tab.
10581 let settings = buffer.language_settings_at(cursor, cx);
10582 let tab_size = if settings.hard_tabs {
10583 IndentSize::tab()
10584 } else {
10585 let tab_size = settings.tab_size.get();
10586 let indent_remainder = snapshot
10587 .text_for_range(Point::new(cursor.row, 0)..cursor)
10588 .flat_map(str::chars)
10589 .fold(row_delta % tab_size, |counter: u32, c| {
10590 if c == '\t' {
10591 0
10592 } else {
10593 (counter + 1) % tab_size
10594 }
10595 });
10596
10597 let chars_to_next_tab_stop = tab_size - indent_remainder;
10598 IndentSize::spaces(chars_to_next_tab_stop)
10599 };
10600 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10601 selection.end = selection.start;
10602 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10603 row_delta += tab_size.len;
10604 }
10605
10606 self.transact(window, cx, |this, window, cx| {
10607 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10608 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10609 this.refresh_edit_prediction(true, false, window, cx);
10610 });
10611 }
10612
10613 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10614 if self.read_only(cx) {
10615 return;
10616 }
10617 if self.mode.is_single_line() {
10618 cx.propagate();
10619 return;
10620 }
10621
10622 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10623 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10624 let mut prev_edited_row = 0;
10625 let mut row_delta = 0;
10626 let mut edits = Vec::new();
10627 let buffer = self.buffer.read(cx);
10628 let snapshot = buffer.snapshot(cx);
10629 for selection in &mut selections {
10630 if selection.start.row != prev_edited_row {
10631 row_delta = 0;
10632 }
10633 prev_edited_row = selection.end.row;
10634
10635 row_delta =
10636 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10637 }
10638
10639 self.transact(window, cx, |this, window, cx| {
10640 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10641 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10642 });
10643 }
10644
10645 fn indent_selection(
10646 buffer: &MultiBuffer,
10647 snapshot: &MultiBufferSnapshot,
10648 selection: &mut Selection<Point>,
10649 edits: &mut Vec<(Range<Point>, String)>,
10650 delta_for_start_row: u32,
10651 cx: &App,
10652 ) -> u32 {
10653 let settings = buffer.language_settings_at(selection.start, cx);
10654 let tab_size = settings.tab_size.get();
10655 let indent_kind = if settings.hard_tabs {
10656 IndentKind::Tab
10657 } else {
10658 IndentKind::Space
10659 };
10660 let mut start_row = selection.start.row;
10661 let mut end_row = selection.end.row + 1;
10662
10663 // If a selection ends at the beginning of a line, don't indent
10664 // that last line.
10665 if selection.end.column == 0 && selection.end.row > selection.start.row {
10666 end_row -= 1;
10667 }
10668
10669 // Avoid re-indenting a row that has already been indented by a
10670 // previous selection, but still update this selection's column
10671 // to reflect that indentation.
10672 if delta_for_start_row > 0 {
10673 start_row += 1;
10674 selection.start.column += delta_for_start_row;
10675 if selection.end.row == selection.start.row {
10676 selection.end.column += delta_for_start_row;
10677 }
10678 }
10679
10680 let mut delta_for_end_row = 0;
10681 let has_multiple_rows = start_row + 1 != end_row;
10682 for row in start_row..end_row {
10683 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10684 let indent_delta = match (current_indent.kind, indent_kind) {
10685 (IndentKind::Space, IndentKind::Space) => {
10686 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10687 IndentSize::spaces(columns_to_next_tab_stop)
10688 }
10689 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10690 (_, IndentKind::Tab) => IndentSize::tab(),
10691 };
10692
10693 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10694 0
10695 } else {
10696 selection.start.column
10697 };
10698 let row_start = Point::new(row, start);
10699 edits.push((
10700 row_start..row_start,
10701 indent_delta.chars().collect::<String>(),
10702 ));
10703
10704 // Update this selection's endpoints to reflect the indentation.
10705 if row == selection.start.row {
10706 selection.start.column += indent_delta.len;
10707 }
10708 if row == selection.end.row {
10709 selection.end.column += indent_delta.len;
10710 delta_for_end_row = indent_delta.len;
10711 }
10712 }
10713
10714 if selection.start.row == selection.end.row {
10715 delta_for_start_row + delta_for_end_row
10716 } else {
10717 delta_for_end_row
10718 }
10719 }
10720
10721 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10722 if self.read_only(cx) {
10723 return;
10724 }
10725 if self.mode.is_single_line() {
10726 cx.propagate();
10727 return;
10728 }
10729
10730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10731 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10732 let selections = self.selections.all::<Point>(&display_map);
10733 let mut deletion_ranges = Vec::new();
10734 let mut last_outdent = None;
10735 {
10736 let buffer = self.buffer.read(cx);
10737 let snapshot = buffer.snapshot(cx);
10738 for selection in &selections {
10739 let settings = buffer.language_settings_at(selection.start, cx);
10740 let tab_size = settings.tab_size.get();
10741 let mut rows = selection.spanned_rows(false, &display_map);
10742
10743 // Avoid re-outdenting a row that has already been outdented by a
10744 // previous selection.
10745 if let Some(last_row) = last_outdent
10746 && last_row == rows.start
10747 {
10748 rows.start = rows.start.next_row();
10749 }
10750 let has_multiple_rows = rows.len() > 1;
10751 for row in rows.iter_rows() {
10752 let indent_size = snapshot.indent_size_for_line(row);
10753 if indent_size.len > 0 {
10754 let deletion_len = match indent_size.kind {
10755 IndentKind::Space => {
10756 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10757 if columns_to_prev_tab_stop == 0 {
10758 tab_size
10759 } else {
10760 columns_to_prev_tab_stop
10761 }
10762 }
10763 IndentKind::Tab => 1,
10764 };
10765 let start = if has_multiple_rows
10766 || deletion_len > selection.start.column
10767 || indent_size.len < selection.start.column
10768 {
10769 0
10770 } else {
10771 selection.start.column - deletion_len
10772 };
10773 deletion_ranges.push(
10774 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10775 );
10776 last_outdent = Some(row);
10777 }
10778 }
10779 }
10780 }
10781
10782 self.transact(window, cx, |this, window, cx| {
10783 this.buffer.update(cx, |buffer, cx| {
10784 let empty_str: Arc<str> = Arc::default();
10785 buffer.edit(
10786 deletion_ranges
10787 .into_iter()
10788 .map(|range| (range, empty_str.clone())),
10789 None,
10790 cx,
10791 );
10792 });
10793 let selections = this
10794 .selections
10795 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10796 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10797 });
10798 }
10799
10800 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10801 if self.read_only(cx) {
10802 return;
10803 }
10804 if self.mode.is_single_line() {
10805 cx.propagate();
10806 return;
10807 }
10808
10809 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10810 let selections = self
10811 .selections
10812 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10813 .into_iter()
10814 .map(|s| s.range());
10815
10816 self.transact(window, cx, |this, window, cx| {
10817 this.buffer.update(cx, |buffer, cx| {
10818 buffer.autoindent_ranges(selections, cx);
10819 });
10820 let selections = this
10821 .selections
10822 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10823 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10824 });
10825 }
10826
10827 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10828 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10829 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10830 let selections = self.selections.all::<Point>(&display_map);
10831
10832 let mut new_cursors = Vec::new();
10833 let mut edit_ranges = Vec::new();
10834 let mut selections = selections.iter().peekable();
10835 while let Some(selection) = selections.next() {
10836 let mut rows = selection.spanned_rows(false, &display_map);
10837
10838 // Accumulate contiguous regions of rows that we want to delete.
10839 while let Some(next_selection) = selections.peek() {
10840 let next_rows = next_selection.spanned_rows(false, &display_map);
10841 if next_rows.start <= rows.end {
10842 rows.end = next_rows.end;
10843 selections.next().unwrap();
10844 } else {
10845 break;
10846 }
10847 }
10848
10849 let buffer = display_map.buffer_snapshot();
10850 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10851 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10852 // If there's a line after the range, delete the \n from the end of the row range
10853 (
10854 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10855 rows.end,
10856 )
10857 } else {
10858 // If there isn't a line after the range, delete the \n from the line before the
10859 // start of the row range
10860 edit_start = edit_start.saturating_sub_usize(1);
10861 (buffer.len(), rows.start.previous_row())
10862 };
10863
10864 let text_layout_details = self.text_layout_details(window);
10865 let x = display_map.x_for_display_point(
10866 selection.head().to_display_point(&display_map),
10867 &text_layout_details,
10868 );
10869 let row = Point::new(target_row.0, 0)
10870 .to_display_point(&display_map)
10871 .row();
10872 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10873
10874 new_cursors.push((
10875 selection.id,
10876 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10877 SelectionGoal::None,
10878 ));
10879 edit_ranges.push(edit_start..edit_end);
10880 }
10881
10882 self.transact(window, cx, |this, window, cx| {
10883 let buffer = this.buffer.update(cx, |buffer, cx| {
10884 let empty_str: Arc<str> = Arc::default();
10885 buffer.edit(
10886 edit_ranges
10887 .into_iter()
10888 .map(|range| (range, empty_str.clone())),
10889 None,
10890 cx,
10891 );
10892 buffer.snapshot(cx)
10893 });
10894 let new_selections = new_cursors
10895 .into_iter()
10896 .map(|(id, cursor, goal)| {
10897 let cursor = cursor.to_point(&buffer);
10898 Selection {
10899 id,
10900 start: cursor,
10901 end: cursor,
10902 reversed: false,
10903 goal,
10904 }
10905 })
10906 .collect();
10907
10908 this.change_selections(Default::default(), window, cx, |s| {
10909 s.select(new_selections);
10910 });
10911 });
10912 }
10913
10914 pub fn join_lines_impl(
10915 &mut self,
10916 insert_whitespace: bool,
10917 window: &mut Window,
10918 cx: &mut Context<Self>,
10919 ) {
10920 if self.read_only(cx) {
10921 return;
10922 }
10923 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10924 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10925 let start = MultiBufferRow(selection.start.row);
10926 // Treat single line selections as if they include the next line. Otherwise this action
10927 // would do nothing for single line selections individual cursors.
10928 let end = if selection.start.row == selection.end.row {
10929 MultiBufferRow(selection.start.row + 1)
10930 } else {
10931 MultiBufferRow(selection.end.row)
10932 };
10933
10934 if let Some(last_row_range) = row_ranges.last_mut()
10935 && start <= last_row_range.end
10936 {
10937 last_row_range.end = end;
10938 continue;
10939 }
10940 row_ranges.push(start..end);
10941 }
10942
10943 let snapshot = self.buffer.read(cx).snapshot(cx);
10944 let mut cursor_positions = Vec::new();
10945 for row_range in &row_ranges {
10946 let anchor = snapshot.anchor_before(Point::new(
10947 row_range.end.previous_row().0,
10948 snapshot.line_len(row_range.end.previous_row()),
10949 ));
10950 cursor_positions.push(anchor..anchor);
10951 }
10952
10953 self.transact(window, cx, |this, window, cx| {
10954 for row_range in row_ranges.into_iter().rev() {
10955 for row in row_range.iter_rows().rev() {
10956 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10957 let next_line_row = row.next_row();
10958 let indent = snapshot.indent_size_for_line(next_line_row);
10959 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10960
10961 let replace =
10962 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10963 " "
10964 } else {
10965 ""
10966 };
10967
10968 this.buffer.update(cx, |buffer, cx| {
10969 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10970 });
10971 }
10972 }
10973
10974 this.change_selections(Default::default(), window, cx, |s| {
10975 s.select_anchor_ranges(cursor_positions)
10976 });
10977 });
10978 }
10979
10980 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10981 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10982 self.join_lines_impl(true, window, cx);
10983 }
10984
10985 pub fn sort_lines_case_sensitive(
10986 &mut self,
10987 _: &SortLinesCaseSensitive,
10988 window: &mut Window,
10989 cx: &mut Context<Self>,
10990 ) {
10991 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10992 }
10993
10994 pub fn sort_lines_by_length(
10995 &mut self,
10996 _: &SortLinesByLength,
10997 window: &mut Window,
10998 cx: &mut Context<Self>,
10999 ) {
11000 self.manipulate_immutable_lines(window, cx, |lines| {
11001 lines.sort_by_key(|&line| line.chars().count())
11002 })
11003 }
11004
11005 pub fn sort_lines_case_insensitive(
11006 &mut self,
11007 _: &SortLinesCaseInsensitive,
11008 window: &mut Window,
11009 cx: &mut Context<Self>,
11010 ) {
11011 self.manipulate_immutable_lines(window, cx, |lines| {
11012 lines.sort_by_key(|line| line.to_lowercase())
11013 })
11014 }
11015
11016 pub fn unique_lines_case_insensitive(
11017 &mut self,
11018 _: &UniqueLinesCaseInsensitive,
11019 window: &mut Window,
11020 cx: &mut Context<Self>,
11021 ) {
11022 self.manipulate_immutable_lines(window, cx, |lines| {
11023 let mut seen = HashSet::default();
11024 lines.retain(|line| seen.insert(line.to_lowercase()));
11025 })
11026 }
11027
11028 pub fn unique_lines_case_sensitive(
11029 &mut self,
11030 _: &UniqueLinesCaseSensitive,
11031 window: &mut Window,
11032 cx: &mut Context<Self>,
11033 ) {
11034 self.manipulate_immutable_lines(window, cx, |lines| {
11035 let mut seen = HashSet::default();
11036 lines.retain(|line| seen.insert(*line));
11037 })
11038 }
11039
11040 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11041 let snapshot = self.buffer.read(cx).snapshot(cx);
11042 for selection in self.selections.disjoint_anchors_arc().iter() {
11043 if snapshot
11044 .language_at(selection.start)
11045 .and_then(|lang| lang.config().wrap_characters.as_ref())
11046 .is_some()
11047 {
11048 return true;
11049 }
11050 }
11051 false
11052 }
11053
11054 fn wrap_selections_in_tag(
11055 &mut self,
11056 _: &WrapSelectionsInTag,
11057 window: &mut Window,
11058 cx: &mut Context<Self>,
11059 ) {
11060 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11061
11062 let snapshot = self.buffer.read(cx).snapshot(cx);
11063
11064 let mut edits = Vec::new();
11065 let mut boundaries = Vec::new();
11066
11067 for selection in self
11068 .selections
11069 .all_adjusted(&self.display_snapshot(cx))
11070 .iter()
11071 {
11072 let Some(wrap_config) = snapshot
11073 .language_at(selection.start)
11074 .and_then(|lang| lang.config().wrap_characters.clone())
11075 else {
11076 continue;
11077 };
11078
11079 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11080 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11081
11082 let start_before = snapshot.anchor_before(selection.start);
11083 let end_after = snapshot.anchor_after(selection.end);
11084
11085 edits.push((start_before..start_before, open_tag));
11086 edits.push((end_after..end_after, close_tag));
11087
11088 boundaries.push((
11089 start_before,
11090 end_after,
11091 wrap_config.start_prefix.len(),
11092 wrap_config.end_suffix.len(),
11093 ));
11094 }
11095
11096 if edits.is_empty() {
11097 return;
11098 }
11099
11100 self.transact(window, cx, |this, window, cx| {
11101 let buffer = this.buffer.update(cx, |buffer, cx| {
11102 buffer.edit(edits, None, cx);
11103 buffer.snapshot(cx)
11104 });
11105
11106 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11107 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11108 boundaries.into_iter()
11109 {
11110 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11111 let close_offset = end_after
11112 .to_offset(&buffer)
11113 .saturating_sub_usize(end_suffix_len);
11114 new_selections.push(open_offset..open_offset);
11115 new_selections.push(close_offset..close_offset);
11116 }
11117
11118 this.change_selections(Default::default(), window, cx, |s| {
11119 s.select_ranges(new_selections);
11120 });
11121
11122 this.request_autoscroll(Autoscroll::fit(), cx);
11123 });
11124 }
11125
11126 pub fn toggle_read_only(
11127 &mut self,
11128 _: &workspace::ToggleReadOnlyFile,
11129 _: &mut Window,
11130 cx: &mut Context<Self>,
11131 ) {
11132 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
11133 buffer.update(cx, |buffer, cx| {
11134 buffer.set_capability(
11135 match buffer.capability() {
11136 Capability::ReadWrite => Capability::Read,
11137 Capability::Read => Capability::ReadWrite,
11138 Capability::ReadOnly => Capability::ReadOnly,
11139 },
11140 cx,
11141 );
11142 })
11143 }
11144 }
11145
11146 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11147 let Some(project) = self.project.clone() else {
11148 return;
11149 };
11150 self.reload(project, window, cx)
11151 .detach_and_notify_err(window, cx);
11152 }
11153
11154 pub fn restore_file(
11155 &mut self,
11156 _: &::git::RestoreFile,
11157 window: &mut Window,
11158 cx: &mut Context<Self>,
11159 ) {
11160 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11161 let mut buffer_ids = HashSet::default();
11162 let snapshot = self.buffer().read(cx).snapshot(cx);
11163 for selection in self
11164 .selections
11165 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11166 {
11167 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11168 }
11169
11170 let buffer = self.buffer().read(cx);
11171 let ranges = buffer_ids
11172 .into_iter()
11173 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11174 .collect::<Vec<_>>();
11175
11176 self.restore_hunks_in_ranges(ranges, window, cx);
11177 }
11178
11179 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11180 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11181 let selections = self
11182 .selections
11183 .all(&self.display_snapshot(cx))
11184 .into_iter()
11185 .map(|s| s.range())
11186 .collect();
11187 self.restore_hunks_in_ranges(selections, window, cx);
11188 }
11189
11190 pub fn restore_hunks_in_ranges(
11191 &mut self,
11192 ranges: Vec<Range<Point>>,
11193 window: &mut Window,
11194 cx: &mut Context<Editor>,
11195 ) {
11196 let mut revert_changes = HashMap::default();
11197 let chunk_by = self
11198 .snapshot(window, cx)
11199 .hunks_for_ranges(ranges)
11200 .into_iter()
11201 .chunk_by(|hunk| hunk.buffer_id);
11202 for (buffer_id, hunks) in &chunk_by {
11203 let hunks = hunks.collect::<Vec<_>>();
11204 for hunk in &hunks {
11205 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11206 }
11207 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11208 }
11209 drop(chunk_by);
11210 if !revert_changes.is_empty() {
11211 self.transact(window, cx, |editor, window, cx| {
11212 editor.restore(revert_changes, window, cx);
11213 });
11214 }
11215 }
11216
11217 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11218 if let Some(status) = self
11219 .addons
11220 .iter()
11221 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11222 {
11223 return Some(status);
11224 }
11225 self.project
11226 .as_ref()?
11227 .read(cx)
11228 .status_for_buffer_id(buffer_id, cx)
11229 }
11230
11231 pub fn open_active_item_in_terminal(
11232 &mut self,
11233 _: &OpenInTerminal,
11234 window: &mut Window,
11235 cx: &mut Context<Self>,
11236 ) {
11237 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11238 let project_path = buffer.read(cx).project_path(cx)?;
11239 let project = self.project()?.read(cx);
11240 let entry = project.entry_for_path(&project_path, cx)?;
11241 let parent = match &entry.canonical_path {
11242 Some(canonical_path) => canonical_path.to_path_buf(),
11243 None => project.absolute_path(&project_path, cx)?,
11244 }
11245 .parent()?
11246 .to_path_buf();
11247 Some(parent)
11248 }) {
11249 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11250 }
11251 }
11252
11253 fn set_breakpoint_context_menu(
11254 &mut self,
11255 display_row: DisplayRow,
11256 position: Option<Anchor>,
11257 clicked_point: gpui::Point<Pixels>,
11258 window: &mut Window,
11259 cx: &mut Context<Self>,
11260 ) {
11261 let source = self
11262 .buffer
11263 .read(cx)
11264 .snapshot(cx)
11265 .anchor_before(Point::new(display_row.0, 0u32));
11266
11267 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11268
11269 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11270 self,
11271 source,
11272 clicked_point,
11273 context_menu,
11274 window,
11275 cx,
11276 );
11277 }
11278
11279 fn add_edit_breakpoint_block(
11280 &mut self,
11281 anchor: Anchor,
11282 breakpoint: &Breakpoint,
11283 edit_action: BreakpointPromptEditAction,
11284 window: &mut Window,
11285 cx: &mut Context<Self>,
11286 ) {
11287 let weak_editor = cx.weak_entity();
11288 let bp_prompt = cx.new(|cx| {
11289 BreakpointPromptEditor::new(
11290 weak_editor,
11291 anchor,
11292 breakpoint.clone(),
11293 edit_action,
11294 window,
11295 cx,
11296 )
11297 });
11298
11299 let height = bp_prompt.update(cx, |this, cx| {
11300 this.prompt
11301 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11302 });
11303 let cloned_prompt = bp_prompt.clone();
11304 let blocks = vec![BlockProperties {
11305 style: BlockStyle::Sticky,
11306 placement: BlockPlacement::Above(anchor),
11307 height: Some(height),
11308 render: Arc::new(move |cx| {
11309 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11310 cloned_prompt.clone().into_any_element()
11311 }),
11312 priority: 0,
11313 }];
11314
11315 let focus_handle = bp_prompt.focus_handle(cx);
11316 window.focus(&focus_handle, cx);
11317
11318 let block_ids = self.insert_blocks(blocks, None, cx);
11319 bp_prompt.update(cx, |prompt, _| {
11320 prompt.add_block_ids(block_ids);
11321 });
11322 }
11323
11324 pub(crate) fn breakpoint_at_row(
11325 &self,
11326 row: u32,
11327 window: &mut Window,
11328 cx: &mut Context<Self>,
11329 ) -> Option<(Anchor, Breakpoint)> {
11330 let snapshot = self.snapshot(window, cx);
11331 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11332
11333 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11334 }
11335
11336 pub(crate) fn breakpoint_at_anchor(
11337 &self,
11338 breakpoint_position: Anchor,
11339 snapshot: &EditorSnapshot,
11340 cx: &mut Context<Self>,
11341 ) -> Option<(Anchor, Breakpoint)> {
11342 let buffer = self
11343 .buffer
11344 .read(cx)
11345 .buffer_for_anchor(breakpoint_position, cx)?;
11346
11347 let enclosing_excerpt = breakpoint_position.excerpt_id;
11348 let buffer_snapshot = buffer.read(cx).snapshot();
11349
11350 let row = buffer_snapshot
11351 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11352 .row;
11353
11354 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11355 let anchor_end = snapshot
11356 .buffer_snapshot()
11357 .anchor_after(Point::new(row, line_len));
11358
11359 self.breakpoint_store
11360 .as_ref()?
11361 .read_with(cx, |breakpoint_store, cx| {
11362 breakpoint_store
11363 .breakpoints(
11364 &buffer,
11365 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11366 &buffer_snapshot,
11367 cx,
11368 )
11369 .next()
11370 .and_then(|(bp, _)| {
11371 let breakpoint_row = buffer_snapshot
11372 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11373 .row;
11374
11375 if breakpoint_row == row {
11376 snapshot
11377 .buffer_snapshot()
11378 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11379 .map(|position| (position, bp.bp.clone()))
11380 } else {
11381 None
11382 }
11383 })
11384 })
11385 }
11386
11387 pub fn edit_log_breakpoint(
11388 &mut self,
11389 _: &EditLogBreakpoint,
11390 window: &mut Window,
11391 cx: &mut Context<Self>,
11392 ) {
11393 if self.breakpoint_store.is_none() {
11394 return;
11395 }
11396
11397 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11398 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11399 message: None,
11400 state: BreakpointState::Enabled,
11401 condition: None,
11402 hit_condition: None,
11403 });
11404
11405 self.add_edit_breakpoint_block(
11406 anchor,
11407 &breakpoint,
11408 BreakpointPromptEditAction::Log,
11409 window,
11410 cx,
11411 );
11412 }
11413 }
11414
11415 fn breakpoints_at_cursors(
11416 &self,
11417 window: &mut Window,
11418 cx: &mut Context<Self>,
11419 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11420 let snapshot = self.snapshot(window, cx);
11421 let cursors = self
11422 .selections
11423 .disjoint_anchors_arc()
11424 .iter()
11425 .map(|selection| {
11426 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11427
11428 let breakpoint_position = self
11429 .breakpoint_at_row(cursor_position.row, window, cx)
11430 .map(|bp| bp.0)
11431 .unwrap_or_else(|| {
11432 snapshot
11433 .display_snapshot
11434 .buffer_snapshot()
11435 .anchor_after(Point::new(cursor_position.row, 0))
11436 });
11437
11438 let breakpoint = self
11439 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11440 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11441
11442 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11443 })
11444 // 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.
11445 .collect::<HashMap<Anchor, _>>();
11446
11447 cursors.into_iter().collect()
11448 }
11449
11450 pub fn enable_breakpoint(
11451 &mut self,
11452 _: &crate::actions::EnableBreakpoint,
11453 window: &mut Window,
11454 cx: &mut Context<Self>,
11455 ) {
11456 if self.breakpoint_store.is_none() {
11457 return;
11458 }
11459
11460 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11461 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11462 continue;
11463 };
11464 self.edit_breakpoint_at_anchor(
11465 anchor,
11466 breakpoint,
11467 BreakpointEditAction::InvertState,
11468 cx,
11469 );
11470 }
11471 }
11472
11473 pub fn disable_breakpoint(
11474 &mut self,
11475 _: &crate::actions::DisableBreakpoint,
11476 window: &mut Window,
11477 cx: &mut Context<Self>,
11478 ) {
11479 if self.breakpoint_store.is_none() {
11480 return;
11481 }
11482
11483 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11484 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11485 continue;
11486 };
11487 self.edit_breakpoint_at_anchor(
11488 anchor,
11489 breakpoint,
11490 BreakpointEditAction::InvertState,
11491 cx,
11492 );
11493 }
11494 }
11495
11496 pub fn toggle_breakpoint(
11497 &mut self,
11498 _: &crate::actions::ToggleBreakpoint,
11499 window: &mut Window,
11500 cx: &mut Context<Self>,
11501 ) {
11502 if self.breakpoint_store.is_none() {
11503 return;
11504 }
11505
11506 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11507 if let Some(breakpoint) = breakpoint {
11508 self.edit_breakpoint_at_anchor(
11509 anchor,
11510 breakpoint,
11511 BreakpointEditAction::Toggle,
11512 cx,
11513 );
11514 } else {
11515 self.edit_breakpoint_at_anchor(
11516 anchor,
11517 Breakpoint::new_standard(),
11518 BreakpointEditAction::Toggle,
11519 cx,
11520 );
11521 }
11522 }
11523 }
11524
11525 pub fn edit_breakpoint_at_anchor(
11526 &mut self,
11527 breakpoint_position: Anchor,
11528 breakpoint: Breakpoint,
11529 edit_action: BreakpointEditAction,
11530 cx: &mut Context<Self>,
11531 ) {
11532 let Some(breakpoint_store) = &self.breakpoint_store else {
11533 return;
11534 };
11535
11536 let Some(buffer) = self
11537 .buffer
11538 .read(cx)
11539 .buffer_for_anchor(breakpoint_position, cx)
11540 else {
11541 return;
11542 };
11543
11544 breakpoint_store.update(cx, |breakpoint_store, cx| {
11545 breakpoint_store.toggle_breakpoint(
11546 buffer,
11547 BreakpointWithPosition {
11548 position: breakpoint_position.text_anchor,
11549 bp: breakpoint,
11550 },
11551 edit_action,
11552 cx,
11553 );
11554 });
11555
11556 cx.notify();
11557 }
11558
11559 #[cfg(any(test, feature = "test-support"))]
11560 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11561 self.breakpoint_store.clone()
11562 }
11563
11564 pub fn prepare_restore_change(
11565 &self,
11566 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11567 hunk: &MultiBufferDiffHunk,
11568 cx: &mut App,
11569 ) -> Option<()> {
11570 if hunk.is_created_file() {
11571 return None;
11572 }
11573 let buffer = self.buffer.read(cx);
11574 let diff = buffer.diff_for(hunk.buffer_id)?;
11575 let buffer = buffer.buffer(hunk.buffer_id)?;
11576 let buffer = buffer.read(cx);
11577 let original_text = diff
11578 .read(cx)
11579 .base_text(cx)
11580 .as_rope()
11581 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11582 let buffer_snapshot = buffer.snapshot();
11583 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11584 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11585 probe
11586 .0
11587 .start
11588 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11589 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11590 }) {
11591 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11592 Some(())
11593 } else {
11594 None
11595 }
11596 }
11597
11598 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11599 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11600 }
11601
11602 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11603 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11604 }
11605
11606 pub fn rotate_selections_forward(
11607 &mut self,
11608 _: &RotateSelectionsForward,
11609 window: &mut Window,
11610 cx: &mut Context<Self>,
11611 ) {
11612 self.rotate_selections(window, cx, false)
11613 }
11614
11615 pub fn rotate_selections_backward(
11616 &mut self,
11617 _: &RotateSelectionsBackward,
11618 window: &mut Window,
11619 cx: &mut Context<Self>,
11620 ) {
11621 self.rotate_selections(window, cx, true)
11622 }
11623
11624 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11625 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11626 let display_snapshot = self.display_snapshot(cx);
11627 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11628
11629 if selections.len() < 2 {
11630 return;
11631 }
11632
11633 let (edits, new_selections) = {
11634 let buffer = self.buffer.read(cx).read(cx);
11635 let has_selections = selections.iter().any(|s| !s.is_empty());
11636 if has_selections {
11637 let mut selected_texts: Vec<String> = selections
11638 .iter()
11639 .map(|selection| {
11640 buffer
11641 .text_for_range(selection.start..selection.end)
11642 .collect()
11643 })
11644 .collect();
11645
11646 if reverse {
11647 selected_texts.rotate_left(1);
11648 } else {
11649 selected_texts.rotate_right(1);
11650 }
11651
11652 let mut offset_delta: i64 = 0;
11653 let mut new_selections = Vec::new();
11654 let edits: Vec<_> = selections
11655 .iter()
11656 .zip(selected_texts.iter())
11657 .map(|(selection, new_text)| {
11658 let old_len = (selection.end.0 - selection.start.0) as i64;
11659 let new_len = new_text.len() as i64;
11660 let adjusted_start =
11661 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11662 let adjusted_end =
11663 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11664
11665 new_selections.push(Selection {
11666 id: selection.id,
11667 start: adjusted_start,
11668 end: adjusted_end,
11669 reversed: selection.reversed,
11670 goal: selection.goal,
11671 });
11672
11673 offset_delta += new_len - old_len;
11674 (selection.start..selection.end, new_text.clone())
11675 })
11676 .collect();
11677 (edits, new_selections)
11678 } else {
11679 let mut all_rows: Vec<u32> = selections
11680 .iter()
11681 .map(|selection| buffer.offset_to_point(selection.start).row)
11682 .collect();
11683 all_rows.sort_unstable();
11684 all_rows.dedup();
11685
11686 if all_rows.len() < 2 {
11687 return;
11688 }
11689
11690 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11691 .iter()
11692 .map(|&row| {
11693 let start = Point::new(row, 0);
11694 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11695 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11696 })
11697 .collect();
11698
11699 let mut line_texts: Vec<String> = line_ranges
11700 .iter()
11701 .map(|range| buffer.text_for_range(range.clone()).collect())
11702 .collect();
11703
11704 if reverse {
11705 line_texts.rotate_left(1);
11706 } else {
11707 line_texts.rotate_right(1);
11708 }
11709
11710 let edits = line_ranges
11711 .iter()
11712 .zip(line_texts.iter())
11713 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11714 .collect();
11715
11716 let num_rows = all_rows.len();
11717 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11718 .iter()
11719 .enumerate()
11720 .map(|(i, &row)| (row, i))
11721 .collect();
11722
11723 // Compute new line start offsets after rotation (handles CRLF)
11724 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11725 let first_line_start = line_ranges[0].start.0;
11726 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11727 for text in line_texts.iter().take(num_rows - 1) {
11728 let prev_start = *new_line_starts.last().unwrap();
11729 new_line_starts.push(prev_start + text.len() + newline_len);
11730 }
11731
11732 let new_selections = selections
11733 .iter()
11734 .map(|selection| {
11735 let point = buffer.offset_to_point(selection.start);
11736 let old_index = row_to_index[&point.row];
11737 let new_index = if reverse {
11738 (old_index + num_rows - 1) % num_rows
11739 } else {
11740 (old_index + 1) % num_rows
11741 };
11742 let new_offset =
11743 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11744 Selection {
11745 id: selection.id,
11746 start: new_offset,
11747 end: new_offset,
11748 reversed: selection.reversed,
11749 goal: selection.goal,
11750 }
11751 })
11752 .collect();
11753
11754 (edits, new_selections)
11755 }
11756 };
11757
11758 self.transact(window, cx, |this, window, cx| {
11759 this.buffer.update(cx, |buffer, cx| {
11760 buffer.edit(edits, None, cx);
11761 });
11762 this.change_selections(Default::default(), window, cx, |s| {
11763 s.select(new_selections);
11764 });
11765 });
11766 }
11767
11768 fn manipulate_lines<M>(
11769 &mut self,
11770 window: &mut Window,
11771 cx: &mut Context<Self>,
11772 mut manipulate: M,
11773 ) where
11774 M: FnMut(&str) -> LineManipulationResult,
11775 {
11776 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11777
11778 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11779 let buffer = self.buffer.read(cx).snapshot(cx);
11780
11781 let mut edits = Vec::new();
11782
11783 let selections = self.selections.all::<Point>(&display_map);
11784 let mut selections = selections.iter().peekable();
11785 let mut contiguous_row_selections = Vec::new();
11786 let mut new_selections = Vec::new();
11787 let mut added_lines = 0;
11788 let mut removed_lines = 0;
11789
11790 while let Some(selection) = selections.next() {
11791 let (start_row, end_row) = consume_contiguous_rows(
11792 &mut contiguous_row_selections,
11793 selection,
11794 &display_map,
11795 &mut selections,
11796 );
11797
11798 let start_point = Point::new(start_row.0, 0);
11799 let end_point = Point::new(
11800 end_row.previous_row().0,
11801 buffer.line_len(end_row.previous_row()),
11802 );
11803 let text = buffer
11804 .text_for_range(start_point..end_point)
11805 .collect::<String>();
11806
11807 let LineManipulationResult {
11808 new_text,
11809 line_count_before,
11810 line_count_after,
11811 } = manipulate(&text);
11812
11813 edits.push((start_point..end_point, new_text));
11814
11815 // Selections must change based on added and removed line count
11816 let start_row =
11817 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11818 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11819 new_selections.push(Selection {
11820 id: selection.id,
11821 start: start_row,
11822 end: end_row,
11823 goal: SelectionGoal::None,
11824 reversed: selection.reversed,
11825 });
11826
11827 if line_count_after > line_count_before {
11828 added_lines += line_count_after - line_count_before;
11829 } else if line_count_before > line_count_after {
11830 removed_lines += line_count_before - line_count_after;
11831 }
11832 }
11833
11834 self.transact(window, cx, |this, window, cx| {
11835 let buffer = this.buffer.update(cx, |buffer, cx| {
11836 buffer.edit(edits, None, cx);
11837 buffer.snapshot(cx)
11838 });
11839
11840 // Recalculate offsets on newly edited buffer
11841 let new_selections = new_selections
11842 .iter()
11843 .map(|s| {
11844 let start_point = Point::new(s.start.0, 0);
11845 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11846 Selection {
11847 id: s.id,
11848 start: buffer.point_to_offset(start_point),
11849 end: buffer.point_to_offset(end_point),
11850 goal: s.goal,
11851 reversed: s.reversed,
11852 }
11853 })
11854 .collect();
11855
11856 this.change_selections(Default::default(), window, cx, |s| {
11857 s.select(new_selections);
11858 });
11859
11860 this.request_autoscroll(Autoscroll::fit(), cx);
11861 });
11862 }
11863
11864 fn manipulate_immutable_lines<Fn>(
11865 &mut self,
11866 window: &mut Window,
11867 cx: &mut Context<Self>,
11868 mut callback: Fn,
11869 ) where
11870 Fn: FnMut(&mut Vec<&str>),
11871 {
11872 self.manipulate_lines(window, cx, |text| {
11873 let mut lines: Vec<&str> = text.split('\n').collect();
11874 let line_count_before = lines.len();
11875
11876 callback(&mut lines);
11877
11878 LineManipulationResult {
11879 new_text: lines.join("\n"),
11880 line_count_before,
11881 line_count_after: lines.len(),
11882 }
11883 });
11884 }
11885
11886 fn manipulate_mutable_lines<Fn>(
11887 &mut self,
11888 window: &mut Window,
11889 cx: &mut Context<Self>,
11890 mut callback: Fn,
11891 ) where
11892 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11893 {
11894 self.manipulate_lines(window, cx, |text| {
11895 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11896 let line_count_before = lines.len();
11897
11898 callback(&mut lines);
11899
11900 LineManipulationResult {
11901 new_text: lines.join("\n"),
11902 line_count_before,
11903 line_count_after: lines.len(),
11904 }
11905 });
11906 }
11907
11908 pub fn convert_indentation_to_spaces(
11909 &mut self,
11910 _: &ConvertIndentationToSpaces,
11911 window: &mut Window,
11912 cx: &mut Context<Self>,
11913 ) {
11914 let settings = self.buffer.read(cx).language_settings(cx);
11915 let tab_size = settings.tab_size.get() as usize;
11916
11917 self.manipulate_mutable_lines(window, cx, |lines| {
11918 // Allocates a reasonably sized scratch buffer once for the whole loop
11919 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11920 // Avoids recomputing spaces that could be inserted many times
11921 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11922 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11923 .collect();
11924
11925 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11926 let mut chars = line.as_ref().chars();
11927 let mut col = 0;
11928 let mut changed = false;
11929
11930 for ch in chars.by_ref() {
11931 match ch {
11932 ' ' => {
11933 reindented_line.push(' ');
11934 col += 1;
11935 }
11936 '\t' => {
11937 // \t are converted to spaces depending on the current column
11938 let spaces_len = tab_size - (col % tab_size);
11939 reindented_line.extend(&space_cache[spaces_len - 1]);
11940 col += spaces_len;
11941 changed = true;
11942 }
11943 _ => {
11944 // If we dont append before break, the character is consumed
11945 reindented_line.push(ch);
11946 break;
11947 }
11948 }
11949 }
11950
11951 if !changed {
11952 reindented_line.clear();
11953 continue;
11954 }
11955 // Append the rest of the line and replace old reference with new one
11956 reindented_line.extend(chars);
11957 *line = Cow::Owned(reindented_line.clone());
11958 reindented_line.clear();
11959 }
11960 });
11961 }
11962
11963 pub fn convert_indentation_to_tabs(
11964 &mut self,
11965 _: &ConvertIndentationToTabs,
11966 window: &mut Window,
11967 cx: &mut Context<Self>,
11968 ) {
11969 let settings = self.buffer.read(cx).language_settings(cx);
11970 let tab_size = settings.tab_size.get() as usize;
11971
11972 self.manipulate_mutable_lines(window, cx, |lines| {
11973 // Allocates a reasonably sized buffer once for the whole loop
11974 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11975 // Avoids recomputing spaces that could be inserted many times
11976 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11977 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11978 .collect();
11979
11980 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11981 let mut chars = line.chars();
11982 let mut spaces_count = 0;
11983 let mut first_non_indent_char = None;
11984 let mut changed = false;
11985
11986 for ch in chars.by_ref() {
11987 match ch {
11988 ' ' => {
11989 // Keep track of spaces. Append \t when we reach tab_size
11990 spaces_count += 1;
11991 changed = true;
11992 if spaces_count == tab_size {
11993 reindented_line.push('\t');
11994 spaces_count = 0;
11995 }
11996 }
11997 '\t' => {
11998 reindented_line.push('\t');
11999 spaces_count = 0;
12000 }
12001 _ => {
12002 // Dont append it yet, we might have remaining spaces
12003 first_non_indent_char = Some(ch);
12004 break;
12005 }
12006 }
12007 }
12008
12009 if !changed {
12010 reindented_line.clear();
12011 continue;
12012 }
12013 // Remaining spaces that didn't make a full tab stop
12014 if spaces_count > 0 {
12015 reindented_line.extend(&space_cache[spaces_count - 1]);
12016 }
12017 // If we consume an extra character that was not indentation, add it back
12018 if let Some(extra_char) = first_non_indent_char {
12019 reindented_line.push(extra_char);
12020 }
12021 // Append the rest of the line and replace old reference with new one
12022 reindented_line.extend(chars);
12023 *line = Cow::Owned(reindented_line.clone());
12024 reindented_line.clear();
12025 }
12026 });
12027 }
12028
12029 pub fn convert_to_upper_case(
12030 &mut self,
12031 _: &ConvertToUpperCase,
12032 window: &mut Window,
12033 cx: &mut Context<Self>,
12034 ) {
12035 self.manipulate_text(window, cx, |text| text.to_uppercase())
12036 }
12037
12038 pub fn convert_to_lower_case(
12039 &mut self,
12040 _: &ConvertToLowerCase,
12041 window: &mut Window,
12042 cx: &mut Context<Self>,
12043 ) {
12044 self.manipulate_text(window, cx, |text| text.to_lowercase())
12045 }
12046
12047 pub fn convert_to_title_case(
12048 &mut self,
12049 _: &ConvertToTitleCase,
12050 window: &mut Window,
12051 cx: &mut Context<Self>,
12052 ) {
12053 self.manipulate_text(window, cx, |text| {
12054 text.split('\n')
12055 .map(|line| line.to_case(Case::Title))
12056 .join("\n")
12057 })
12058 }
12059
12060 pub fn convert_to_snake_case(
12061 &mut self,
12062 _: &ConvertToSnakeCase,
12063 window: &mut Window,
12064 cx: &mut Context<Self>,
12065 ) {
12066 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12067 }
12068
12069 pub fn convert_to_kebab_case(
12070 &mut self,
12071 _: &ConvertToKebabCase,
12072 window: &mut Window,
12073 cx: &mut Context<Self>,
12074 ) {
12075 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12076 }
12077
12078 pub fn convert_to_upper_camel_case(
12079 &mut self,
12080 _: &ConvertToUpperCamelCase,
12081 window: &mut Window,
12082 cx: &mut Context<Self>,
12083 ) {
12084 self.manipulate_text(window, cx, |text| {
12085 text.split('\n')
12086 .map(|line| line.to_case(Case::UpperCamel))
12087 .join("\n")
12088 })
12089 }
12090
12091 pub fn convert_to_lower_camel_case(
12092 &mut self,
12093 _: &ConvertToLowerCamelCase,
12094 window: &mut Window,
12095 cx: &mut Context<Self>,
12096 ) {
12097 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12098 }
12099
12100 pub fn convert_to_opposite_case(
12101 &mut self,
12102 _: &ConvertToOppositeCase,
12103 window: &mut Window,
12104 cx: &mut Context<Self>,
12105 ) {
12106 self.manipulate_text(window, cx, |text| {
12107 text.chars()
12108 .fold(String::with_capacity(text.len()), |mut t, c| {
12109 if c.is_uppercase() {
12110 t.extend(c.to_lowercase());
12111 } else {
12112 t.extend(c.to_uppercase());
12113 }
12114 t
12115 })
12116 })
12117 }
12118
12119 pub fn convert_to_sentence_case(
12120 &mut self,
12121 _: &ConvertToSentenceCase,
12122 window: &mut Window,
12123 cx: &mut Context<Self>,
12124 ) {
12125 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12126 }
12127
12128 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12129 self.manipulate_text(window, cx, |text| {
12130 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12131 if has_upper_case_characters {
12132 text.to_lowercase()
12133 } else {
12134 text.to_uppercase()
12135 }
12136 })
12137 }
12138
12139 pub fn convert_to_rot13(
12140 &mut self,
12141 _: &ConvertToRot13,
12142 window: &mut Window,
12143 cx: &mut Context<Self>,
12144 ) {
12145 self.manipulate_text(window, cx, |text| {
12146 text.chars()
12147 .map(|c| match c {
12148 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12149 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12150 _ => c,
12151 })
12152 .collect()
12153 })
12154 }
12155
12156 pub fn convert_to_rot47(
12157 &mut self,
12158 _: &ConvertToRot47,
12159 window: &mut Window,
12160 cx: &mut Context<Self>,
12161 ) {
12162 self.manipulate_text(window, cx, |text| {
12163 text.chars()
12164 .map(|c| {
12165 let code_point = c as u32;
12166 if code_point >= 33 && code_point <= 126 {
12167 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12168 }
12169 c
12170 })
12171 .collect()
12172 })
12173 }
12174
12175 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12176 where
12177 Fn: FnMut(&str) -> String,
12178 {
12179 let buffer = self.buffer.read(cx).snapshot(cx);
12180
12181 let mut new_selections = Vec::new();
12182 let mut edits = Vec::new();
12183 let mut selection_adjustment = 0isize;
12184
12185 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12186 let selection_is_empty = selection.is_empty();
12187
12188 let (start, end) = if selection_is_empty {
12189 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12190 (word_range.start, word_range.end)
12191 } else {
12192 (
12193 buffer.point_to_offset(selection.start),
12194 buffer.point_to_offset(selection.end),
12195 )
12196 };
12197
12198 let text = buffer.text_for_range(start..end).collect::<String>();
12199 let old_length = text.len() as isize;
12200 let text = callback(&text);
12201
12202 new_selections.push(Selection {
12203 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12204 end: MultiBufferOffset(
12205 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12206 ),
12207 goal: SelectionGoal::None,
12208 id: selection.id,
12209 reversed: selection.reversed,
12210 });
12211
12212 selection_adjustment += old_length - text.len() as isize;
12213
12214 edits.push((start..end, text));
12215 }
12216
12217 self.transact(window, cx, |this, window, cx| {
12218 this.buffer.update(cx, |buffer, cx| {
12219 buffer.edit(edits, None, cx);
12220 });
12221
12222 this.change_selections(Default::default(), window, cx, |s| {
12223 s.select(new_selections);
12224 });
12225
12226 this.request_autoscroll(Autoscroll::fit(), cx);
12227 });
12228 }
12229
12230 pub fn move_selection_on_drop(
12231 &mut self,
12232 selection: &Selection<Anchor>,
12233 target: DisplayPoint,
12234 is_cut: bool,
12235 window: &mut Window,
12236 cx: &mut Context<Self>,
12237 ) {
12238 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12239 let buffer = display_map.buffer_snapshot();
12240 let mut edits = Vec::new();
12241 let insert_point = display_map
12242 .clip_point(target, Bias::Left)
12243 .to_point(&display_map);
12244 let text = buffer
12245 .text_for_range(selection.start..selection.end)
12246 .collect::<String>();
12247 if is_cut {
12248 edits.push(((selection.start..selection.end), String::new()));
12249 }
12250 let insert_anchor = buffer.anchor_before(insert_point);
12251 edits.push(((insert_anchor..insert_anchor), text));
12252 let last_edit_start = insert_anchor.bias_left(buffer);
12253 let last_edit_end = insert_anchor.bias_right(buffer);
12254 self.transact(window, cx, |this, window, cx| {
12255 this.buffer.update(cx, |buffer, cx| {
12256 buffer.edit(edits, None, cx);
12257 });
12258 this.change_selections(Default::default(), window, cx, |s| {
12259 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12260 });
12261 });
12262 }
12263
12264 pub fn clear_selection_drag_state(&mut self) {
12265 self.selection_drag_state = SelectionDragState::None;
12266 }
12267
12268 pub fn duplicate(
12269 &mut self,
12270 upwards: bool,
12271 whole_lines: bool,
12272 window: &mut Window,
12273 cx: &mut Context<Self>,
12274 ) {
12275 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12276
12277 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12278 let buffer = display_map.buffer_snapshot();
12279 let selections = self.selections.all::<Point>(&display_map);
12280
12281 let mut edits = Vec::new();
12282 let mut selections_iter = selections.iter().peekable();
12283 while let Some(selection) = selections_iter.next() {
12284 let mut rows = selection.spanned_rows(false, &display_map);
12285 // duplicate line-wise
12286 if whole_lines || selection.start == selection.end {
12287 // Avoid duplicating the same lines twice.
12288 while let Some(next_selection) = selections_iter.peek() {
12289 let next_rows = next_selection.spanned_rows(false, &display_map);
12290 if next_rows.start < rows.end {
12291 rows.end = next_rows.end;
12292 selections_iter.next().unwrap();
12293 } else {
12294 break;
12295 }
12296 }
12297
12298 // Copy the text from the selected row region and splice it either at the start
12299 // or end of the region.
12300 let start = Point::new(rows.start.0, 0);
12301 let end = Point::new(
12302 rows.end.previous_row().0,
12303 buffer.line_len(rows.end.previous_row()),
12304 );
12305
12306 let mut text = buffer.text_for_range(start..end).collect::<String>();
12307
12308 let insert_location = if upwards {
12309 // When duplicating upward, we need to insert before the current line.
12310 // If we're on the last line and it doesn't end with a newline,
12311 // we need to add a newline before the duplicated content.
12312 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12313 && buffer.max_point().column > 0
12314 && !text.ends_with('\n');
12315
12316 if needs_leading_newline {
12317 text.insert(0, '\n');
12318 end
12319 } else {
12320 text.push('\n');
12321 Point::new(rows.start.0, 0)
12322 }
12323 } else {
12324 text.push('\n');
12325 start
12326 };
12327 edits.push((insert_location..insert_location, text));
12328 } else {
12329 // duplicate character-wise
12330 let start = selection.start;
12331 let end = selection.end;
12332 let text = buffer.text_for_range(start..end).collect::<String>();
12333 edits.push((selection.end..selection.end, text));
12334 }
12335 }
12336
12337 self.transact(window, cx, |this, window, cx| {
12338 this.buffer.update(cx, |buffer, cx| {
12339 buffer.edit(edits, None, cx);
12340 });
12341
12342 // When duplicating upward with whole lines, move the cursor to the duplicated line
12343 if upwards && whole_lines {
12344 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12345
12346 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12347 let mut new_ranges = Vec::new();
12348 let selections = s.all::<Point>(&display_map);
12349 let mut selections_iter = selections.iter().peekable();
12350
12351 while let Some(first_selection) = selections_iter.next() {
12352 // Group contiguous selections together to find the total row span
12353 let mut group_selections = vec![first_selection];
12354 let mut rows = first_selection.spanned_rows(false, &display_map);
12355
12356 while let Some(next_selection) = selections_iter.peek() {
12357 let next_rows = next_selection.spanned_rows(false, &display_map);
12358 if next_rows.start < rows.end {
12359 rows.end = next_rows.end;
12360 group_selections.push(selections_iter.next().unwrap());
12361 } else {
12362 break;
12363 }
12364 }
12365
12366 let row_count = rows.end.0 - rows.start.0;
12367
12368 // Move all selections in this group up by the total number of duplicated rows
12369 for selection in group_selections {
12370 let new_start = Point::new(
12371 selection.start.row.saturating_sub(row_count),
12372 selection.start.column,
12373 );
12374
12375 let new_end = Point::new(
12376 selection.end.row.saturating_sub(row_count),
12377 selection.end.column,
12378 );
12379
12380 new_ranges.push(new_start..new_end);
12381 }
12382 }
12383
12384 s.select_ranges(new_ranges);
12385 });
12386 }
12387
12388 this.request_autoscroll(Autoscroll::fit(), cx);
12389 });
12390 }
12391
12392 pub fn duplicate_line_up(
12393 &mut self,
12394 _: &DuplicateLineUp,
12395 window: &mut Window,
12396 cx: &mut Context<Self>,
12397 ) {
12398 self.duplicate(true, true, window, cx);
12399 }
12400
12401 pub fn duplicate_line_down(
12402 &mut self,
12403 _: &DuplicateLineDown,
12404 window: &mut Window,
12405 cx: &mut Context<Self>,
12406 ) {
12407 self.duplicate(false, true, window, cx);
12408 }
12409
12410 pub fn duplicate_selection(
12411 &mut self,
12412 _: &DuplicateSelection,
12413 window: &mut Window,
12414 cx: &mut Context<Self>,
12415 ) {
12416 self.duplicate(false, false, window, cx);
12417 }
12418
12419 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12420 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12421 if self.mode.is_single_line() {
12422 cx.propagate();
12423 return;
12424 }
12425
12426 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12427 let buffer = self.buffer.read(cx).snapshot(cx);
12428
12429 let mut edits = Vec::new();
12430 let mut unfold_ranges = Vec::new();
12431 let mut refold_creases = Vec::new();
12432
12433 let selections = self.selections.all::<Point>(&display_map);
12434 let mut selections = selections.iter().peekable();
12435 let mut contiguous_row_selections = Vec::new();
12436 let mut new_selections = Vec::new();
12437
12438 while let Some(selection) = selections.next() {
12439 // Find all the selections that span a contiguous row range
12440 let (start_row, end_row) = consume_contiguous_rows(
12441 &mut contiguous_row_selections,
12442 selection,
12443 &display_map,
12444 &mut selections,
12445 );
12446
12447 // Move the text spanned by the row range to be before the line preceding the row range
12448 if start_row.0 > 0 {
12449 let range_to_move = Point::new(
12450 start_row.previous_row().0,
12451 buffer.line_len(start_row.previous_row()),
12452 )
12453 ..Point::new(
12454 end_row.previous_row().0,
12455 buffer.line_len(end_row.previous_row()),
12456 );
12457 let insertion_point = display_map
12458 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12459 .0;
12460
12461 // Don't move lines across excerpts
12462 if buffer
12463 .excerpt_containing(insertion_point..range_to_move.end)
12464 .is_some()
12465 {
12466 let text = buffer
12467 .text_for_range(range_to_move.clone())
12468 .flat_map(|s| s.chars())
12469 .skip(1)
12470 .chain(['\n'])
12471 .collect::<String>();
12472
12473 edits.push((
12474 buffer.anchor_after(range_to_move.start)
12475 ..buffer.anchor_before(range_to_move.end),
12476 String::new(),
12477 ));
12478 let insertion_anchor = buffer.anchor_after(insertion_point);
12479 edits.push((insertion_anchor..insertion_anchor, text));
12480
12481 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12482
12483 // Move selections up
12484 new_selections.extend(contiguous_row_selections.drain(..).map(
12485 |mut selection| {
12486 selection.start.row -= row_delta;
12487 selection.end.row -= row_delta;
12488 selection
12489 },
12490 ));
12491
12492 // Move folds up
12493 unfold_ranges.push(range_to_move.clone());
12494 for fold in display_map.folds_in_range(
12495 buffer.anchor_before(range_to_move.start)
12496 ..buffer.anchor_after(range_to_move.end),
12497 ) {
12498 let mut start = fold.range.start.to_point(&buffer);
12499 let mut end = fold.range.end.to_point(&buffer);
12500 start.row -= row_delta;
12501 end.row -= row_delta;
12502 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12503 }
12504 }
12505 }
12506
12507 // If we didn't move line(s), preserve the existing selections
12508 new_selections.append(&mut contiguous_row_selections);
12509 }
12510
12511 self.transact(window, cx, |this, window, cx| {
12512 this.unfold_ranges(&unfold_ranges, true, true, cx);
12513 this.buffer.update(cx, |buffer, cx| {
12514 for (range, text) in edits {
12515 buffer.edit([(range, text)], None, cx);
12516 }
12517 });
12518 this.fold_creases(refold_creases, true, window, cx);
12519 this.change_selections(Default::default(), window, cx, |s| {
12520 s.select(new_selections);
12521 })
12522 });
12523 }
12524
12525 pub fn move_line_down(
12526 &mut self,
12527 _: &MoveLineDown,
12528 window: &mut Window,
12529 cx: &mut Context<Self>,
12530 ) {
12531 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12532 if self.mode.is_single_line() {
12533 cx.propagate();
12534 return;
12535 }
12536
12537 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12538 let buffer = self.buffer.read(cx).snapshot(cx);
12539
12540 let mut edits = Vec::new();
12541 let mut unfold_ranges = Vec::new();
12542 let mut refold_creases = Vec::new();
12543
12544 let selections = self.selections.all::<Point>(&display_map);
12545 let mut selections = selections.iter().peekable();
12546 let mut contiguous_row_selections = Vec::new();
12547 let mut new_selections = Vec::new();
12548
12549 while let Some(selection) = selections.next() {
12550 // Find all the selections that span a contiguous row range
12551 let (start_row, end_row) = consume_contiguous_rows(
12552 &mut contiguous_row_selections,
12553 selection,
12554 &display_map,
12555 &mut selections,
12556 );
12557
12558 // Move the text spanned by the row range to be after the last line of the row range
12559 if end_row.0 <= buffer.max_point().row {
12560 let range_to_move =
12561 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12562 let insertion_point = display_map
12563 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12564 .0;
12565
12566 // Don't move lines across excerpt boundaries
12567 if buffer
12568 .excerpt_containing(range_to_move.start..insertion_point)
12569 .is_some()
12570 {
12571 let mut text = String::from("\n");
12572 text.extend(buffer.text_for_range(range_to_move.clone()));
12573 text.pop(); // Drop trailing newline
12574 edits.push((
12575 buffer.anchor_after(range_to_move.start)
12576 ..buffer.anchor_before(range_to_move.end),
12577 String::new(),
12578 ));
12579 let insertion_anchor = buffer.anchor_after(insertion_point);
12580 edits.push((insertion_anchor..insertion_anchor, text));
12581
12582 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12583
12584 // Move selections down
12585 new_selections.extend(contiguous_row_selections.drain(..).map(
12586 |mut selection| {
12587 selection.start.row += row_delta;
12588 selection.end.row += row_delta;
12589 selection
12590 },
12591 ));
12592
12593 // Move folds down
12594 unfold_ranges.push(range_to_move.clone());
12595 for fold in display_map.folds_in_range(
12596 buffer.anchor_before(range_to_move.start)
12597 ..buffer.anchor_after(range_to_move.end),
12598 ) {
12599 let mut start = fold.range.start.to_point(&buffer);
12600 let mut end = fold.range.end.to_point(&buffer);
12601 start.row += row_delta;
12602 end.row += row_delta;
12603 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12604 }
12605 }
12606 }
12607
12608 // If we didn't move line(s), preserve the existing selections
12609 new_selections.append(&mut contiguous_row_selections);
12610 }
12611
12612 self.transact(window, cx, |this, window, cx| {
12613 this.unfold_ranges(&unfold_ranges, true, true, cx);
12614 this.buffer.update(cx, |buffer, cx| {
12615 for (range, text) in edits {
12616 buffer.edit([(range, text)], None, cx);
12617 }
12618 });
12619 this.fold_creases(refold_creases, true, window, cx);
12620 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12621 });
12622 }
12623
12624 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12625 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12626 let text_layout_details = &self.text_layout_details(window);
12627 self.transact(window, cx, |this, window, cx| {
12628 let edits = this.change_selections(Default::default(), window, cx, |s| {
12629 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12630 s.move_with(|display_map, selection| {
12631 if !selection.is_empty() {
12632 return;
12633 }
12634
12635 let mut head = selection.head();
12636 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12637 if head.column() == display_map.line_len(head.row()) {
12638 transpose_offset = display_map
12639 .buffer_snapshot()
12640 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12641 }
12642
12643 if transpose_offset == MultiBufferOffset(0) {
12644 return;
12645 }
12646
12647 *head.column_mut() += 1;
12648 head = display_map.clip_point(head, Bias::Right);
12649 let goal = SelectionGoal::HorizontalPosition(
12650 display_map
12651 .x_for_display_point(head, text_layout_details)
12652 .into(),
12653 );
12654 selection.collapse_to(head, goal);
12655
12656 let transpose_start = display_map
12657 .buffer_snapshot()
12658 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12659 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12660 let transpose_end = display_map
12661 .buffer_snapshot()
12662 .clip_offset(transpose_offset + 1usize, Bias::Right);
12663 if let Some(ch) = display_map
12664 .buffer_snapshot()
12665 .chars_at(transpose_start)
12666 .next()
12667 {
12668 edits.push((transpose_start..transpose_offset, String::new()));
12669 edits.push((transpose_end..transpose_end, ch.to_string()));
12670 }
12671 }
12672 });
12673 edits
12674 });
12675 this.buffer
12676 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12677 let selections = this
12678 .selections
12679 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12680 this.change_selections(Default::default(), window, cx, |s| {
12681 s.select(selections);
12682 });
12683 });
12684 }
12685
12686 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12687 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12688 if self.mode.is_single_line() {
12689 cx.propagate();
12690 return;
12691 }
12692
12693 self.rewrap_impl(RewrapOptions::default(), cx)
12694 }
12695
12696 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12697 let buffer = self.buffer.read(cx).snapshot(cx);
12698 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12699
12700 #[derive(Clone, Debug, PartialEq)]
12701 enum CommentFormat {
12702 /// single line comment, with prefix for line
12703 Line(String),
12704 /// single line within a block comment, with prefix for line
12705 BlockLine(String),
12706 /// a single line of a block comment that includes the initial delimiter
12707 BlockCommentWithStart(BlockCommentConfig),
12708 /// a single line of a block comment that includes the ending delimiter
12709 BlockCommentWithEnd(BlockCommentConfig),
12710 }
12711
12712 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12713 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12714 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12715 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12716 .peekable();
12717
12718 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12719 row
12720 } else {
12721 return Vec::new();
12722 };
12723
12724 let language_settings = buffer.language_settings_at(selection.head(), cx);
12725 let language_scope = buffer.language_scope_at(selection.head());
12726
12727 let indent_and_prefix_for_row =
12728 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12729 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12730 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12731 &language_scope
12732 {
12733 let indent_end = Point::new(row, indent.len);
12734 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12735 let line_text_after_indent = buffer
12736 .text_for_range(indent_end..line_end)
12737 .collect::<String>();
12738
12739 let is_within_comment_override = buffer
12740 .language_scope_at(indent_end)
12741 .is_some_and(|scope| scope.override_name() == Some("comment"));
12742 let comment_delimiters = if is_within_comment_override {
12743 // we are within a comment syntax node, but we don't
12744 // yet know what kind of comment: block, doc or line
12745 match (
12746 language_scope.documentation_comment(),
12747 language_scope.block_comment(),
12748 ) {
12749 (Some(config), _) | (_, Some(config))
12750 if buffer.contains_str_at(indent_end, &config.start) =>
12751 {
12752 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12753 }
12754 (Some(config), _) | (_, Some(config))
12755 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12756 {
12757 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12758 }
12759 (Some(config), _) | (_, Some(config))
12760 if buffer.contains_str_at(indent_end, &config.prefix) =>
12761 {
12762 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12763 }
12764 (_, _) => language_scope
12765 .line_comment_prefixes()
12766 .iter()
12767 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12768 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12769 }
12770 } else {
12771 // we not in an overridden comment node, but we may
12772 // be within a non-overridden line comment node
12773 language_scope
12774 .line_comment_prefixes()
12775 .iter()
12776 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12777 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12778 };
12779
12780 let rewrap_prefix = language_scope
12781 .rewrap_prefixes()
12782 .iter()
12783 .find_map(|prefix_regex| {
12784 prefix_regex.find(&line_text_after_indent).map(|mat| {
12785 if mat.start() == 0 {
12786 Some(mat.as_str().to_string())
12787 } else {
12788 None
12789 }
12790 })
12791 })
12792 .flatten();
12793 (comment_delimiters, rewrap_prefix)
12794 } else {
12795 (None, None)
12796 };
12797 (indent, comment_prefix, rewrap_prefix)
12798 };
12799
12800 let mut ranges = Vec::new();
12801 let from_empty_selection = selection.is_empty();
12802
12803 let mut current_range_start = first_row;
12804 let mut prev_row = first_row;
12805 let (
12806 mut current_range_indent,
12807 mut current_range_comment_delimiters,
12808 mut current_range_rewrap_prefix,
12809 ) = indent_and_prefix_for_row(first_row);
12810
12811 for row in non_blank_rows_iter.skip(1) {
12812 let has_paragraph_break = row > prev_row + 1;
12813
12814 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12815 indent_and_prefix_for_row(row);
12816
12817 let has_indent_change = row_indent != current_range_indent;
12818 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12819
12820 let has_boundary_change = has_comment_change
12821 || row_rewrap_prefix.is_some()
12822 || (has_indent_change && current_range_comment_delimiters.is_some());
12823
12824 if has_paragraph_break || has_boundary_change {
12825 ranges.push((
12826 language_settings.clone(),
12827 Point::new(current_range_start, 0)
12828 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12829 current_range_indent,
12830 current_range_comment_delimiters.clone(),
12831 current_range_rewrap_prefix.clone(),
12832 from_empty_selection,
12833 ));
12834 current_range_start = row;
12835 current_range_indent = row_indent;
12836 current_range_comment_delimiters = row_comment_delimiters;
12837 current_range_rewrap_prefix = row_rewrap_prefix;
12838 }
12839 prev_row = row;
12840 }
12841
12842 ranges.push((
12843 language_settings.clone(),
12844 Point::new(current_range_start, 0)
12845 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12846 current_range_indent,
12847 current_range_comment_delimiters,
12848 current_range_rewrap_prefix,
12849 from_empty_selection,
12850 ));
12851
12852 ranges
12853 });
12854
12855 let mut edits = Vec::new();
12856 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12857
12858 for (
12859 language_settings,
12860 wrap_range,
12861 mut indent_size,
12862 comment_prefix,
12863 rewrap_prefix,
12864 from_empty_selection,
12865 ) in wrap_ranges
12866 {
12867 let mut start_row = wrap_range.start.row;
12868 let mut end_row = wrap_range.end.row;
12869
12870 // Skip selections that overlap with a range that has already been rewrapped.
12871 let selection_range = start_row..end_row;
12872 if rewrapped_row_ranges
12873 .iter()
12874 .any(|range| range.overlaps(&selection_range))
12875 {
12876 continue;
12877 }
12878
12879 let tab_size = language_settings.tab_size;
12880
12881 let (line_prefix, inside_comment) = match &comment_prefix {
12882 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12883 (Some(prefix.as_str()), true)
12884 }
12885 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12886 (Some(prefix.as_ref()), true)
12887 }
12888 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12889 start: _,
12890 end: _,
12891 prefix,
12892 tab_size,
12893 })) => {
12894 indent_size.len += tab_size;
12895 (Some(prefix.as_ref()), true)
12896 }
12897 None => (None, false),
12898 };
12899 let indent_prefix = indent_size.chars().collect::<String>();
12900 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12901
12902 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12903 RewrapBehavior::InComments => inside_comment,
12904 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12905 RewrapBehavior::Anywhere => true,
12906 };
12907
12908 let should_rewrap = options.override_language_settings
12909 || allow_rewrap_based_on_language
12910 || self.hard_wrap.is_some();
12911 if !should_rewrap {
12912 continue;
12913 }
12914
12915 if from_empty_selection {
12916 'expand_upwards: while start_row > 0 {
12917 let prev_row = start_row - 1;
12918 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12919 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12920 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12921 {
12922 start_row = prev_row;
12923 } else {
12924 break 'expand_upwards;
12925 }
12926 }
12927
12928 'expand_downwards: while end_row < buffer.max_point().row {
12929 let next_row = end_row + 1;
12930 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12931 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12932 && !buffer.is_line_blank(MultiBufferRow(next_row))
12933 {
12934 end_row = next_row;
12935 } else {
12936 break 'expand_downwards;
12937 }
12938 }
12939 }
12940
12941 let start = Point::new(start_row, 0);
12942 let start_offset = ToOffset::to_offset(&start, &buffer);
12943 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12944 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12945 let mut first_line_delimiter = None;
12946 let mut last_line_delimiter = None;
12947 let Some(lines_without_prefixes) = selection_text
12948 .lines()
12949 .enumerate()
12950 .map(|(ix, line)| {
12951 let line_trimmed = line.trim_start();
12952 if rewrap_prefix.is_some() && ix > 0 {
12953 Ok(line_trimmed)
12954 } else if let Some(
12955 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12956 start,
12957 prefix,
12958 end,
12959 tab_size,
12960 })
12961 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12962 start,
12963 prefix,
12964 end,
12965 tab_size,
12966 }),
12967 ) = &comment_prefix
12968 {
12969 let line_trimmed = line_trimmed
12970 .strip_prefix(start.as_ref())
12971 .map(|s| {
12972 let mut indent_size = indent_size;
12973 indent_size.len -= tab_size;
12974 let indent_prefix: String = indent_size.chars().collect();
12975 first_line_delimiter = Some((indent_prefix, start));
12976 s.trim_start()
12977 })
12978 .unwrap_or(line_trimmed);
12979 let line_trimmed = line_trimmed
12980 .strip_suffix(end.as_ref())
12981 .map(|s| {
12982 last_line_delimiter = Some(end);
12983 s.trim_end()
12984 })
12985 .unwrap_or(line_trimmed);
12986 let line_trimmed = line_trimmed
12987 .strip_prefix(prefix.as_ref())
12988 .unwrap_or(line_trimmed);
12989 Ok(line_trimmed)
12990 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12991 line_trimmed.strip_prefix(prefix).with_context(|| {
12992 format!("line did not start with prefix {prefix:?}: {line:?}")
12993 })
12994 } else {
12995 line_trimmed
12996 .strip_prefix(&line_prefix.trim_start())
12997 .with_context(|| {
12998 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12999 })
13000 }
13001 })
13002 .collect::<Result<Vec<_>, _>>()
13003 .log_err()
13004 else {
13005 continue;
13006 };
13007
13008 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13009 buffer
13010 .language_settings_at(Point::new(start_row, 0), cx)
13011 .preferred_line_length as usize
13012 });
13013
13014 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13015 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13016 } else {
13017 line_prefix.clone()
13018 };
13019
13020 let wrapped_text = {
13021 let mut wrapped_text = wrap_with_prefix(
13022 line_prefix,
13023 subsequent_lines_prefix,
13024 lines_without_prefixes.join("\n"),
13025 wrap_column,
13026 tab_size,
13027 options.preserve_existing_whitespace,
13028 );
13029
13030 if let Some((indent, delimiter)) = first_line_delimiter {
13031 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13032 }
13033 if let Some(last_line) = last_line_delimiter {
13034 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13035 }
13036
13037 wrapped_text
13038 };
13039
13040 // TODO: should always use char-based diff while still supporting cursor behavior that
13041 // matches vim.
13042 let mut diff_options = DiffOptions::default();
13043 if options.override_language_settings {
13044 diff_options.max_word_diff_len = 0;
13045 diff_options.max_word_diff_line_count = 0;
13046 } else {
13047 diff_options.max_word_diff_len = usize::MAX;
13048 diff_options.max_word_diff_line_count = usize::MAX;
13049 }
13050
13051 for (old_range, new_text) in
13052 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13053 {
13054 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13055 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13056 edits.push((edit_start..edit_end, new_text));
13057 }
13058
13059 rewrapped_row_ranges.push(start_row..=end_row);
13060 }
13061
13062 self.buffer
13063 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13064 }
13065
13066 pub fn cut_common(
13067 &mut self,
13068 cut_no_selection_line: bool,
13069 window: &mut Window,
13070 cx: &mut Context<Self>,
13071 ) -> ClipboardItem {
13072 let mut text = String::new();
13073 let buffer = self.buffer.read(cx).snapshot(cx);
13074 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13075 let mut clipboard_selections = Vec::with_capacity(selections.len());
13076 {
13077 let max_point = buffer.max_point();
13078 let mut is_first = true;
13079 let mut prev_selection_was_entire_line = false;
13080 for selection in &mut selections {
13081 let is_entire_line =
13082 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13083 if is_entire_line {
13084 selection.start = Point::new(selection.start.row, 0);
13085 if !selection.is_empty() && selection.end.column == 0 {
13086 selection.end = cmp::min(max_point, selection.end);
13087 } else {
13088 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13089 }
13090 selection.goal = SelectionGoal::None;
13091 }
13092 if is_first {
13093 is_first = false;
13094 } else if !prev_selection_was_entire_line {
13095 text += "\n";
13096 }
13097 prev_selection_was_entire_line = is_entire_line;
13098 let mut len = 0;
13099 for chunk in buffer.text_for_range(selection.start..selection.end) {
13100 text.push_str(chunk);
13101 len += chunk.len();
13102 }
13103
13104 clipboard_selections.push(ClipboardSelection::for_buffer(
13105 len,
13106 is_entire_line,
13107 selection.range(),
13108 &buffer,
13109 self.project.as_ref(),
13110 cx,
13111 ));
13112 }
13113 }
13114
13115 self.transact(window, cx, |this, window, cx| {
13116 this.change_selections(Default::default(), window, cx, |s| {
13117 s.select(selections);
13118 });
13119 this.insert("", window, cx);
13120 });
13121 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13122 }
13123
13124 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13125 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13126 let item = self.cut_common(true, window, cx);
13127 cx.write_to_clipboard(item);
13128 }
13129
13130 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13131 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13132 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13133 s.move_with(|snapshot, sel| {
13134 if sel.is_empty() {
13135 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13136 }
13137 if sel.is_empty() {
13138 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13139 }
13140 });
13141 });
13142 let item = self.cut_common(false, window, cx);
13143 cx.set_global(KillRing(item))
13144 }
13145
13146 pub fn kill_ring_yank(
13147 &mut self,
13148 _: &KillRingYank,
13149 window: &mut Window,
13150 cx: &mut Context<Self>,
13151 ) {
13152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13153 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13154 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13155 (kill_ring.text().to_string(), kill_ring.metadata_json())
13156 } else {
13157 return;
13158 }
13159 } else {
13160 return;
13161 };
13162 self.do_paste(&text, metadata, false, window, cx);
13163 }
13164
13165 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13166 self.do_copy(true, cx);
13167 }
13168
13169 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13170 self.do_copy(false, cx);
13171 }
13172
13173 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13174 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13175 let buffer = self.buffer.read(cx).read(cx);
13176 let mut text = String::new();
13177
13178 let mut clipboard_selections = Vec::with_capacity(selections.len());
13179 {
13180 let max_point = buffer.max_point();
13181 let mut is_first = true;
13182 let mut prev_selection_was_entire_line = false;
13183 for selection in &selections {
13184 let mut start = selection.start;
13185 let mut end = selection.end;
13186 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13187 let mut add_trailing_newline = false;
13188 if is_entire_line {
13189 start = Point::new(start.row, 0);
13190 let next_line_start = Point::new(end.row + 1, 0);
13191 if next_line_start <= max_point {
13192 end = next_line_start;
13193 } else {
13194 // We're on the last line without a trailing newline.
13195 // Copy to the end of the line and add a newline afterwards.
13196 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13197 add_trailing_newline = true;
13198 }
13199 }
13200
13201 let mut trimmed_selections = Vec::new();
13202 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13203 let row = MultiBufferRow(start.row);
13204 let first_indent = buffer.indent_size_for_line(row);
13205 if first_indent.len == 0 || start.column > first_indent.len {
13206 trimmed_selections.push(start..end);
13207 } else {
13208 trimmed_selections.push(
13209 Point::new(row.0, first_indent.len)
13210 ..Point::new(row.0, buffer.line_len(row)),
13211 );
13212 for row in start.row + 1..=end.row {
13213 let mut line_len = buffer.line_len(MultiBufferRow(row));
13214 if row == end.row {
13215 line_len = end.column;
13216 }
13217 if line_len == 0 {
13218 trimmed_selections
13219 .push(Point::new(row, 0)..Point::new(row, line_len));
13220 continue;
13221 }
13222 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13223 if row_indent_size.len >= first_indent.len {
13224 trimmed_selections.push(
13225 Point::new(row, first_indent.len)..Point::new(row, line_len),
13226 );
13227 } else {
13228 trimmed_selections.clear();
13229 trimmed_selections.push(start..end);
13230 break;
13231 }
13232 }
13233 }
13234 } else {
13235 trimmed_selections.push(start..end);
13236 }
13237
13238 for trimmed_range in trimmed_selections {
13239 if is_first {
13240 is_first = false;
13241 } else if !prev_selection_was_entire_line {
13242 text += "\n";
13243 }
13244 prev_selection_was_entire_line = is_entire_line;
13245 let mut len = 0;
13246 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13247 text.push_str(chunk);
13248 len += chunk.len();
13249 }
13250 if add_trailing_newline {
13251 text.push('\n');
13252 len += 1;
13253 }
13254 clipboard_selections.push(ClipboardSelection::for_buffer(
13255 len,
13256 is_entire_line,
13257 trimmed_range,
13258 &buffer,
13259 self.project.as_ref(),
13260 cx,
13261 ));
13262 }
13263 }
13264 }
13265
13266 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13267 text,
13268 clipboard_selections,
13269 ));
13270 }
13271
13272 pub fn do_paste(
13273 &mut self,
13274 text: &String,
13275 clipboard_selections: Option<Vec<ClipboardSelection>>,
13276 handle_entire_lines: bool,
13277 window: &mut Window,
13278 cx: &mut Context<Self>,
13279 ) {
13280 if self.read_only(cx) {
13281 return;
13282 }
13283
13284 let clipboard_text = Cow::Borrowed(text.as_str());
13285
13286 self.transact(window, cx, |this, window, cx| {
13287 let had_active_edit_prediction = this.has_active_edit_prediction();
13288 let display_map = this.display_snapshot(cx);
13289 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13290 let cursor_offset = this
13291 .selections
13292 .last::<MultiBufferOffset>(&display_map)
13293 .head();
13294
13295 if let Some(mut clipboard_selections) = clipboard_selections {
13296 let all_selections_were_entire_line =
13297 clipboard_selections.iter().all(|s| s.is_entire_line);
13298 let first_selection_indent_column =
13299 clipboard_selections.first().map(|s| s.first_line_indent);
13300 if clipboard_selections.len() != old_selections.len() {
13301 clipboard_selections.drain(..);
13302 }
13303 let mut auto_indent_on_paste = true;
13304
13305 this.buffer.update(cx, |buffer, cx| {
13306 let snapshot = buffer.read(cx);
13307 auto_indent_on_paste = snapshot
13308 .language_settings_at(cursor_offset, cx)
13309 .auto_indent_on_paste;
13310
13311 let mut start_offset = 0;
13312 let mut edits = Vec::new();
13313 let mut original_indent_columns = Vec::new();
13314 for (ix, selection) in old_selections.iter().enumerate() {
13315 let to_insert;
13316 let entire_line;
13317 let original_indent_column;
13318 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13319 let end_offset = start_offset + clipboard_selection.len;
13320 to_insert = &clipboard_text[start_offset..end_offset];
13321 entire_line = clipboard_selection.is_entire_line;
13322 start_offset = if entire_line {
13323 end_offset
13324 } else {
13325 end_offset + 1
13326 };
13327 original_indent_column = Some(clipboard_selection.first_line_indent);
13328 } else {
13329 to_insert = &*clipboard_text;
13330 entire_line = all_selections_were_entire_line;
13331 original_indent_column = first_selection_indent_column
13332 }
13333
13334 let (range, to_insert) =
13335 if selection.is_empty() && handle_entire_lines && entire_line {
13336 // If the corresponding selection was empty when this slice of the
13337 // clipboard text was written, then the entire line containing the
13338 // selection was copied. If this selection is also currently empty,
13339 // then paste the line before the current line of the buffer.
13340 let column = selection.start.to_point(&snapshot).column as usize;
13341 let line_start = selection.start - column;
13342 (line_start..line_start, Cow::Borrowed(to_insert))
13343 } else {
13344 let language = snapshot.language_at(selection.head());
13345 let range = selection.range();
13346 if let Some(language) = language
13347 && language.name() == "Markdown".into()
13348 {
13349 edit_for_markdown_paste(
13350 &snapshot,
13351 range,
13352 to_insert,
13353 url::Url::parse(to_insert).ok(),
13354 )
13355 } else {
13356 (range, Cow::Borrowed(to_insert))
13357 }
13358 };
13359
13360 edits.push((range, to_insert));
13361 original_indent_columns.push(original_indent_column);
13362 }
13363 drop(snapshot);
13364
13365 buffer.edit(
13366 edits,
13367 if auto_indent_on_paste {
13368 Some(AutoindentMode::Block {
13369 original_indent_columns,
13370 })
13371 } else {
13372 None
13373 },
13374 cx,
13375 );
13376 });
13377
13378 let selections = this
13379 .selections
13380 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13381 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13382 } else {
13383 let url = url::Url::parse(&clipboard_text).ok();
13384
13385 let auto_indent_mode = if !clipboard_text.is_empty() {
13386 Some(AutoindentMode::Block {
13387 original_indent_columns: Vec::new(),
13388 })
13389 } else {
13390 None
13391 };
13392
13393 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13394 let snapshot = buffer.snapshot(cx);
13395
13396 let anchors = old_selections
13397 .iter()
13398 .map(|s| {
13399 let anchor = snapshot.anchor_after(s.head());
13400 s.map(|_| anchor)
13401 })
13402 .collect::<Vec<_>>();
13403
13404 let mut edits = Vec::new();
13405
13406 for selection in old_selections.iter() {
13407 let language = snapshot.language_at(selection.head());
13408 let range = selection.range();
13409
13410 let (edit_range, edit_text) = if let Some(language) = language
13411 && language.name() == "Markdown".into()
13412 {
13413 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13414 } else {
13415 (range, clipboard_text.clone())
13416 };
13417
13418 edits.push((edit_range, edit_text));
13419 }
13420
13421 drop(snapshot);
13422 buffer.edit(edits, auto_indent_mode, cx);
13423
13424 anchors
13425 });
13426
13427 this.change_selections(Default::default(), window, cx, |s| {
13428 s.select_anchors(selection_anchors);
13429 });
13430 }
13431
13432 // 🤔 | .. | show_in_menu |
13433 // | .. | true true
13434 // | had_edit_prediction | false true
13435
13436 let trigger_in_words =
13437 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13438
13439 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13440 });
13441 }
13442
13443 pub fn diff_clipboard_with_selection(
13444 &mut self,
13445 _: &DiffClipboardWithSelection,
13446 window: &mut Window,
13447 cx: &mut Context<Self>,
13448 ) {
13449 let selections = self
13450 .selections
13451 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13452
13453 if selections.is_empty() {
13454 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13455 return;
13456 };
13457
13458 let clipboard_text = match cx.read_from_clipboard() {
13459 Some(item) => match item.entries().first() {
13460 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13461 _ => None,
13462 },
13463 None => None,
13464 };
13465
13466 let Some(clipboard_text) = clipboard_text else {
13467 log::warn!("Clipboard doesn't contain text.");
13468 return;
13469 };
13470
13471 window.dispatch_action(
13472 Box::new(DiffClipboardWithSelectionData {
13473 clipboard_text,
13474 editor: cx.entity(),
13475 }),
13476 cx,
13477 );
13478 }
13479
13480 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13481 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13482 if let Some(item) = cx.read_from_clipboard() {
13483 let entries = item.entries();
13484
13485 match entries.first() {
13486 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13487 // of all the pasted entries.
13488 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13489 .do_paste(
13490 clipboard_string.text(),
13491 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13492 true,
13493 window,
13494 cx,
13495 ),
13496 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13497 }
13498 }
13499 }
13500
13501 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13502 if self.read_only(cx) {
13503 return;
13504 }
13505
13506 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13507
13508 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13509 if let Some((selections, _)) =
13510 self.selection_history.transaction(transaction_id).cloned()
13511 {
13512 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13513 s.select_anchors(selections.to_vec());
13514 });
13515 } else {
13516 log::error!(
13517 "No entry in selection_history found for undo. \
13518 This may correspond to a bug where undo does not update the selection. \
13519 If this is occurring, please add details to \
13520 https://github.com/zed-industries/zed/issues/22692"
13521 );
13522 }
13523 self.request_autoscroll(Autoscroll::fit(), cx);
13524 self.unmark_text(window, cx);
13525 self.refresh_edit_prediction(true, false, window, cx);
13526 cx.emit(EditorEvent::Edited { transaction_id });
13527 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13528 }
13529 }
13530
13531 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13532 if self.read_only(cx) {
13533 return;
13534 }
13535
13536 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13537
13538 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13539 if let Some((_, Some(selections))) =
13540 self.selection_history.transaction(transaction_id).cloned()
13541 {
13542 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13543 s.select_anchors(selections.to_vec());
13544 });
13545 } else {
13546 log::error!(
13547 "No entry in selection_history found for redo. \
13548 This may correspond to a bug where undo does not update the selection. \
13549 If this is occurring, please add details to \
13550 https://github.com/zed-industries/zed/issues/22692"
13551 );
13552 }
13553 self.request_autoscroll(Autoscroll::fit(), cx);
13554 self.unmark_text(window, cx);
13555 self.refresh_edit_prediction(true, false, window, cx);
13556 cx.emit(EditorEvent::Edited { transaction_id });
13557 }
13558 }
13559
13560 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13561 self.buffer
13562 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13563 }
13564
13565 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13566 self.buffer
13567 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13568 }
13569
13570 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13571 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13572 self.change_selections(Default::default(), window, cx, |s| {
13573 s.move_with(|map, selection| {
13574 let cursor = if selection.is_empty() {
13575 movement::left(map, selection.start)
13576 } else {
13577 selection.start
13578 };
13579 selection.collapse_to(cursor, SelectionGoal::None);
13580 });
13581 })
13582 }
13583
13584 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13585 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13586 self.change_selections(Default::default(), window, cx, |s| {
13587 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13588 })
13589 }
13590
13591 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13593 self.change_selections(Default::default(), window, cx, |s| {
13594 s.move_with(|map, selection| {
13595 let cursor = if selection.is_empty() {
13596 movement::right(map, selection.end)
13597 } else {
13598 selection.end
13599 };
13600 selection.collapse_to(cursor, SelectionGoal::None)
13601 });
13602 })
13603 }
13604
13605 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13607 self.change_selections(Default::default(), window, cx, |s| {
13608 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13609 });
13610 }
13611
13612 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13613 if self.take_rename(true, window, cx).is_some() {
13614 return;
13615 }
13616
13617 if self.mode.is_single_line() {
13618 cx.propagate();
13619 return;
13620 }
13621
13622 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13623
13624 let text_layout_details = &self.text_layout_details(window);
13625 let selection_count = self.selections.count();
13626 let first_selection = self.selections.first_anchor();
13627
13628 self.change_selections(Default::default(), window, cx, |s| {
13629 s.move_with(|map, selection| {
13630 if !selection.is_empty() {
13631 selection.goal = SelectionGoal::None;
13632 }
13633 let (cursor, goal) = movement::up(
13634 map,
13635 selection.start,
13636 selection.goal,
13637 false,
13638 text_layout_details,
13639 );
13640 selection.collapse_to(cursor, goal);
13641 });
13642 });
13643
13644 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13645 {
13646 cx.propagate();
13647 }
13648 }
13649
13650 pub fn move_up_by_lines(
13651 &mut self,
13652 action: &MoveUpByLines,
13653 window: &mut Window,
13654 cx: &mut Context<Self>,
13655 ) {
13656 if self.take_rename(true, window, cx).is_some() {
13657 return;
13658 }
13659
13660 if self.mode.is_single_line() {
13661 cx.propagate();
13662 return;
13663 }
13664
13665 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13666
13667 let text_layout_details = &self.text_layout_details(window);
13668
13669 self.change_selections(Default::default(), window, cx, |s| {
13670 s.move_with(|map, selection| {
13671 if !selection.is_empty() {
13672 selection.goal = SelectionGoal::None;
13673 }
13674 let (cursor, goal) = movement::up_by_rows(
13675 map,
13676 selection.start,
13677 action.lines,
13678 selection.goal,
13679 false,
13680 text_layout_details,
13681 );
13682 selection.collapse_to(cursor, goal);
13683 });
13684 })
13685 }
13686
13687 pub fn move_down_by_lines(
13688 &mut self,
13689 action: &MoveDownByLines,
13690 window: &mut Window,
13691 cx: &mut Context<Self>,
13692 ) {
13693 if self.take_rename(true, window, cx).is_some() {
13694 return;
13695 }
13696
13697 if self.mode.is_single_line() {
13698 cx.propagate();
13699 return;
13700 }
13701
13702 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13703
13704 let text_layout_details = &self.text_layout_details(window);
13705
13706 self.change_selections(Default::default(), window, cx, |s| {
13707 s.move_with(|map, selection| {
13708 if !selection.is_empty() {
13709 selection.goal = SelectionGoal::None;
13710 }
13711 let (cursor, goal) = movement::down_by_rows(
13712 map,
13713 selection.start,
13714 action.lines,
13715 selection.goal,
13716 false,
13717 text_layout_details,
13718 );
13719 selection.collapse_to(cursor, goal);
13720 });
13721 })
13722 }
13723
13724 pub fn select_down_by_lines(
13725 &mut self,
13726 action: &SelectDownByLines,
13727 window: &mut Window,
13728 cx: &mut Context<Self>,
13729 ) {
13730 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13731 let text_layout_details = &self.text_layout_details(window);
13732 self.change_selections(Default::default(), window, cx, |s| {
13733 s.move_heads_with(|map, head, goal| {
13734 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13735 })
13736 })
13737 }
13738
13739 pub fn select_up_by_lines(
13740 &mut self,
13741 action: &SelectUpByLines,
13742 window: &mut Window,
13743 cx: &mut Context<Self>,
13744 ) {
13745 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13746 let text_layout_details = &self.text_layout_details(window);
13747 self.change_selections(Default::default(), window, cx, |s| {
13748 s.move_heads_with(|map, head, goal| {
13749 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13750 })
13751 })
13752 }
13753
13754 pub fn select_page_up(
13755 &mut self,
13756 _: &SelectPageUp,
13757 window: &mut Window,
13758 cx: &mut Context<Self>,
13759 ) {
13760 let Some(row_count) = self.visible_row_count() else {
13761 return;
13762 };
13763
13764 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13765
13766 let text_layout_details = &self.text_layout_details(window);
13767
13768 self.change_selections(Default::default(), window, cx, |s| {
13769 s.move_heads_with(|map, head, goal| {
13770 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13771 })
13772 })
13773 }
13774
13775 pub fn move_page_up(
13776 &mut self,
13777 action: &MovePageUp,
13778 window: &mut Window,
13779 cx: &mut Context<Self>,
13780 ) {
13781 if self.take_rename(true, window, cx).is_some() {
13782 return;
13783 }
13784
13785 if self
13786 .context_menu
13787 .borrow_mut()
13788 .as_mut()
13789 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13790 .unwrap_or(false)
13791 {
13792 return;
13793 }
13794
13795 if matches!(self.mode, EditorMode::SingleLine) {
13796 cx.propagate();
13797 return;
13798 }
13799
13800 let Some(row_count) = self.visible_row_count() else {
13801 return;
13802 };
13803
13804 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13805
13806 let effects = if action.center_cursor {
13807 SelectionEffects::scroll(Autoscroll::center())
13808 } else {
13809 SelectionEffects::default()
13810 };
13811
13812 let text_layout_details = &self.text_layout_details(window);
13813
13814 self.change_selections(effects, window, cx, |s| {
13815 s.move_with(|map, selection| {
13816 if !selection.is_empty() {
13817 selection.goal = SelectionGoal::None;
13818 }
13819 let (cursor, goal) = movement::up_by_rows(
13820 map,
13821 selection.end,
13822 row_count,
13823 selection.goal,
13824 false,
13825 text_layout_details,
13826 );
13827 selection.collapse_to(cursor, goal);
13828 });
13829 });
13830 }
13831
13832 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13833 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13834 let text_layout_details = &self.text_layout_details(window);
13835 self.change_selections(Default::default(), window, cx, |s| {
13836 s.move_heads_with(|map, head, goal| {
13837 movement::up(map, head, goal, false, text_layout_details)
13838 })
13839 })
13840 }
13841
13842 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13843 self.take_rename(true, window, cx);
13844
13845 if self.mode.is_single_line() {
13846 cx.propagate();
13847 return;
13848 }
13849
13850 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13851
13852 let text_layout_details = &self.text_layout_details(window);
13853 let selection_count = self.selections.count();
13854 let first_selection = self.selections.first_anchor();
13855
13856 self.change_selections(Default::default(), window, cx, |s| {
13857 s.move_with(|map, selection| {
13858 if !selection.is_empty() {
13859 selection.goal = SelectionGoal::None;
13860 }
13861 let (cursor, goal) = movement::down(
13862 map,
13863 selection.end,
13864 selection.goal,
13865 false,
13866 text_layout_details,
13867 );
13868 selection.collapse_to(cursor, goal);
13869 });
13870 });
13871
13872 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13873 {
13874 cx.propagate();
13875 }
13876 }
13877
13878 pub fn select_page_down(
13879 &mut self,
13880 _: &SelectPageDown,
13881 window: &mut Window,
13882 cx: &mut Context<Self>,
13883 ) {
13884 let Some(row_count) = self.visible_row_count() else {
13885 return;
13886 };
13887
13888 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13889
13890 let text_layout_details = &self.text_layout_details(window);
13891
13892 self.change_selections(Default::default(), window, cx, |s| {
13893 s.move_heads_with(|map, head, goal| {
13894 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13895 })
13896 })
13897 }
13898
13899 pub fn move_page_down(
13900 &mut self,
13901 action: &MovePageDown,
13902 window: &mut Window,
13903 cx: &mut Context<Self>,
13904 ) {
13905 if self.take_rename(true, window, cx).is_some() {
13906 return;
13907 }
13908
13909 if self
13910 .context_menu
13911 .borrow_mut()
13912 .as_mut()
13913 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13914 .unwrap_or(false)
13915 {
13916 return;
13917 }
13918
13919 if matches!(self.mode, EditorMode::SingleLine) {
13920 cx.propagate();
13921 return;
13922 }
13923
13924 let Some(row_count) = self.visible_row_count() else {
13925 return;
13926 };
13927
13928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13929
13930 let effects = if action.center_cursor {
13931 SelectionEffects::scroll(Autoscroll::center())
13932 } else {
13933 SelectionEffects::default()
13934 };
13935
13936 let text_layout_details = &self.text_layout_details(window);
13937 self.change_selections(effects, window, cx, |s| {
13938 s.move_with(|map, selection| {
13939 if !selection.is_empty() {
13940 selection.goal = SelectionGoal::None;
13941 }
13942 let (cursor, goal) = movement::down_by_rows(
13943 map,
13944 selection.end,
13945 row_count,
13946 selection.goal,
13947 false,
13948 text_layout_details,
13949 );
13950 selection.collapse_to(cursor, goal);
13951 });
13952 });
13953 }
13954
13955 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13956 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13957 let text_layout_details = &self.text_layout_details(window);
13958 self.change_selections(Default::default(), window, cx, |s| {
13959 s.move_heads_with(|map, head, goal| {
13960 movement::down(map, head, goal, false, text_layout_details)
13961 })
13962 });
13963 }
13964
13965 pub fn context_menu_first(
13966 &mut self,
13967 _: &ContextMenuFirst,
13968 window: &mut Window,
13969 cx: &mut Context<Self>,
13970 ) {
13971 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13972 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13973 }
13974 }
13975
13976 pub fn context_menu_prev(
13977 &mut self,
13978 _: &ContextMenuPrevious,
13979 window: &mut Window,
13980 cx: &mut Context<Self>,
13981 ) {
13982 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13983 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13984 }
13985 }
13986
13987 pub fn context_menu_next(
13988 &mut self,
13989 _: &ContextMenuNext,
13990 window: &mut Window,
13991 cx: &mut Context<Self>,
13992 ) {
13993 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13994 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13995 }
13996 }
13997
13998 pub fn context_menu_last(
13999 &mut self,
14000 _: &ContextMenuLast,
14001 window: &mut Window,
14002 cx: &mut Context<Self>,
14003 ) {
14004 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14005 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14006 }
14007 }
14008
14009 pub fn signature_help_prev(
14010 &mut self,
14011 _: &SignatureHelpPrevious,
14012 _: &mut Window,
14013 cx: &mut Context<Self>,
14014 ) {
14015 if let Some(popover) = self.signature_help_state.popover_mut() {
14016 if popover.current_signature == 0 {
14017 popover.current_signature = popover.signatures.len() - 1;
14018 } else {
14019 popover.current_signature -= 1;
14020 }
14021 cx.notify();
14022 }
14023 }
14024
14025 pub fn signature_help_next(
14026 &mut self,
14027 _: &SignatureHelpNext,
14028 _: &mut Window,
14029 cx: &mut Context<Self>,
14030 ) {
14031 if let Some(popover) = self.signature_help_state.popover_mut() {
14032 if popover.current_signature + 1 == popover.signatures.len() {
14033 popover.current_signature = 0;
14034 } else {
14035 popover.current_signature += 1;
14036 }
14037 cx.notify();
14038 }
14039 }
14040
14041 pub fn move_to_previous_word_start(
14042 &mut self,
14043 _: &MoveToPreviousWordStart,
14044 window: &mut Window,
14045 cx: &mut Context<Self>,
14046 ) {
14047 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14048 self.change_selections(Default::default(), window, cx, |s| {
14049 s.move_cursors_with(|map, head, _| {
14050 (
14051 movement::previous_word_start(map, head),
14052 SelectionGoal::None,
14053 )
14054 });
14055 })
14056 }
14057
14058 pub fn move_to_previous_subword_start(
14059 &mut self,
14060 _: &MoveToPreviousSubwordStart,
14061 window: &mut Window,
14062 cx: &mut Context<Self>,
14063 ) {
14064 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14065 self.change_selections(Default::default(), window, cx, |s| {
14066 s.move_cursors_with(|map, head, _| {
14067 (
14068 movement::previous_subword_start(map, head),
14069 SelectionGoal::None,
14070 )
14071 });
14072 })
14073 }
14074
14075 pub fn select_to_previous_word_start(
14076 &mut self,
14077 _: &SelectToPreviousWordStart,
14078 window: &mut Window,
14079 cx: &mut Context<Self>,
14080 ) {
14081 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14082 self.change_selections(Default::default(), window, cx, |s| {
14083 s.move_heads_with(|map, head, _| {
14084 (
14085 movement::previous_word_start(map, head),
14086 SelectionGoal::None,
14087 )
14088 });
14089 })
14090 }
14091
14092 pub fn select_to_previous_subword_start(
14093 &mut self,
14094 _: &SelectToPreviousSubwordStart,
14095 window: &mut Window,
14096 cx: &mut Context<Self>,
14097 ) {
14098 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14099 self.change_selections(Default::default(), window, cx, |s| {
14100 s.move_heads_with(|map, head, _| {
14101 (
14102 movement::previous_subword_start(map, head),
14103 SelectionGoal::None,
14104 )
14105 });
14106 })
14107 }
14108
14109 pub fn delete_to_previous_word_start(
14110 &mut self,
14111 action: &DeleteToPreviousWordStart,
14112 window: &mut Window,
14113 cx: &mut Context<Self>,
14114 ) {
14115 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14116 self.transact(window, cx, |this, window, cx| {
14117 this.select_autoclose_pair(window, cx);
14118 this.change_selections(Default::default(), window, cx, |s| {
14119 s.move_with(|map, selection| {
14120 if selection.is_empty() {
14121 let mut cursor = if action.ignore_newlines {
14122 movement::previous_word_start(map, selection.head())
14123 } else {
14124 movement::previous_word_start_or_newline(map, selection.head())
14125 };
14126 cursor = movement::adjust_greedy_deletion(
14127 map,
14128 selection.head(),
14129 cursor,
14130 action.ignore_brackets,
14131 );
14132 selection.set_head(cursor, SelectionGoal::None);
14133 }
14134 });
14135 });
14136 this.insert("", window, cx);
14137 });
14138 }
14139
14140 pub fn delete_to_previous_subword_start(
14141 &mut self,
14142 _: &DeleteToPreviousSubwordStart,
14143 window: &mut Window,
14144 cx: &mut Context<Self>,
14145 ) {
14146 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14147 self.transact(window, cx, |this, window, cx| {
14148 this.select_autoclose_pair(window, cx);
14149 this.change_selections(Default::default(), window, cx, |s| {
14150 s.move_with(|map, selection| {
14151 if selection.is_empty() {
14152 let mut cursor = movement::previous_subword_start(map, selection.head());
14153 cursor =
14154 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14155 selection.set_head(cursor, SelectionGoal::None);
14156 }
14157 });
14158 });
14159 this.insert("", window, cx);
14160 });
14161 }
14162
14163 pub fn move_to_next_word_end(
14164 &mut self,
14165 _: &MoveToNextWordEnd,
14166 window: &mut Window,
14167 cx: &mut Context<Self>,
14168 ) {
14169 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14170 self.change_selections(Default::default(), window, cx, |s| {
14171 s.move_cursors_with(|map, head, _| {
14172 (movement::next_word_end(map, head), SelectionGoal::None)
14173 });
14174 })
14175 }
14176
14177 pub fn move_to_next_subword_end(
14178 &mut self,
14179 _: &MoveToNextSubwordEnd,
14180 window: &mut Window,
14181 cx: &mut Context<Self>,
14182 ) {
14183 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14184 self.change_selections(Default::default(), window, cx, |s| {
14185 s.move_cursors_with(|map, head, _| {
14186 (movement::next_subword_end(map, head), SelectionGoal::None)
14187 });
14188 })
14189 }
14190
14191 pub fn select_to_next_word_end(
14192 &mut self,
14193 _: &SelectToNextWordEnd,
14194 window: &mut Window,
14195 cx: &mut Context<Self>,
14196 ) {
14197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14198 self.change_selections(Default::default(), window, cx, |s| {
14199 s.move_heads_with(|map, head, _| {
14200 (movement::next_word_end(map, head), SelectionGoal::None)
14201 });
14202 })
14203 }
14204
14205 pub fn select_to_next_subword_end(
14206 &mut self,
14207 _: &SelectToNextSubwordEnd,
14208 window: &mut Window,
14209 cx: &mut Context<Self>,
14210 ) {
14211 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14212 self.change_selections(Default::default(), window, cx, |s| {
14213 s.move_heads_with(|map, head, _| {
14214 (movement::next_subword_end(map, head), SelectionGoal::None)
14215 });
14216 })
14217 }
14218
14219 pub fn delete_to_next_word_end(
14220 &mut self,
14221 action: &DeleteToNextWordEnd,
14222 window: &mut Window,
14223 cx: &mut Context<Self>,
14224 ) {
14225 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14226 self.transact(window, cx, |this, window, cx| {
14227 this.change_selections(Default::default(), window, cx, |s| {
14228 s.move_with(|map, selection| {
14229 if selection.is_empty() {
14230 let mut cursor = if action.ignore_newlines {
14231 movement::next_word_end(map, selection.head())
14232 } else {
14233 movement::next_word_end_or_newline(map, selection.head())
14234 };
14235 cursor = movement::adjust_greedy_deletion(
14236 map,
14237 selection.head(),
14238 cursor,
14239 action.ignore_brackets,
14240 );
14241 selection.set_head(cursor, SelectionGoal::None);
14242 }
14243 });
14244 });
14245 this.insert("", window, cx);
14246 });
14247 }
14248
14249 pub fn delete_to_next_subword_end(
14250 &mut self,
14251 _: &DeleteToNextSubwordEnd,
14252 window: &mut Window,
14253 cx: &mut Context<Self>,
14254 ) {
14255 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14256 self.transact(window, cx, |this, window, cx| {
14257 this.change_selections(Default::default(), window, cx, |s| {
14258 s.move_with(|map, selection| {
14259 if selection.is_empty() {
14260 let mut cursor = movement::next_subword_end(map, selection.head());
14261 cursor =
14262 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14263 selection.set_head(cursor, SelectionGoal::None);
14264 }
14265 });
14266 });
14267 this.insert("", window, cx);
14268 });
14269 }
14270
14271 pub fn move_to_beginning_of_line(
14272 &mut self,
14273 action: &MoveToBeginningOfLine,
14274 window: &mut Window,
14275 cx: &mut Context<Self>,
14276 ) {
14277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14278 self.change_selections(Default::default(), window, cx, |s| {
14279 s.move_cursors_with(|map, head, _| {
14280 (
14281 movement::indented_line_beginning(
14282 map,
14283 head,
14284 action.stop_at_soft_wraps,
14285 action.stop_at_indent,
14286 ),
14287 SelectionGoal::None,
14288 )
14289 });
14290 })
14291 }
14292
14293 pub fn select_to_beginning_of_line(
14294 &mut self,
14295 action: &SelectToBeginningOfLine,
14296 window: &mut Window,
14297 cx: &mut Context<Self>,
14298 ) {
14299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14300 self.change_selections(Default::default(), window, cx, |s| {
14301 s.move_heads_with(|map, head, _| {
14302 (
14303 movement::indented_line_beginning(
14304 map,
14305 head,
14306 action.stop_at_soft_wraps,
14307 action.stop_at_indent,
14308 ),
14309 SelectionGoal::None,
14310 )
14311 });
14312 });
14313 }
14314
14315 pub fn delete_to_beginning_of_line(
14316 &mut self,
14317 action: &DeleteToBeginningOfLine,
14318 window: &mut Window,
14319 cx: &mut Context<Self>,
14320 ) {
14321 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14322 self.transact(window, cx, |this, window, cx| {
14323 this.change_selections(Default::default(), window, cx, |s| {
14324 s.move_with(|_, selection| {
14325 selection.reversed = true;
14326 });
14327 });
14328
14329 this.select_to_beginning_of_line(
14330 &SelectToBeginningOfLine {
14331 stop_at_soft_wraps: false,
14332 stop_at_indent: action.stop_at_indent,
14333 },
14334 window,
14335 cx,
14336 );
14337 this.backspace(&Backspace, window, cx);
14338 });
14339 }
14340
14341 pub fn move_to_end_of_line(
14342 &mut self,
14343 action: &MoveToEndOfLine,
14344 window: &mut Window,
14345 cx: &mut Context<Self>,
14346 ) {
14347 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14348 self.change_selections(Default::default(), window, cx, |s| {
14349 s.move_cursors_with(|map, head, _| {
14350 (
14351 movement::line_end(map, head, action.stop_at_soft_wraps),
14352 SelectionGoal::None,
14353 )
14354 });
14355 })
14356 }
14357
14358 pub fn select_to_end_of_line(
14359 &mut self,
14360 action: &SelectToEndOfLine,
14361 window: &mut Window,
14362 cx: &mut Context<Self>,
14363 ) {
14364 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14365 self.change_selections(Default::default(), window, cx, |s| {
14366 s.move_heads_with(|map, head, _| {
14367 (
14368 movement::line_end(map, head, action.stop_at_soft_wraps),
14369 SelectionGoal::None,
14370 )
14371 });
14372 })
14373 }
14374
14375 pub fn delete_to_end_of_line(
14376 &mut self,
14377 _: &DeleteToEndOfLine,
14378 window: &mut Window,
14379 cx: &mut Context<Self>,
14380 ) {
14381 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14382 self.transact(window, cx, |this, window, cx| {
14383 this.select_to_end_of_line(
14384 &SelectToEndOfLine {
14385 stop_at_soft_wraps: false,
14386 },
14387 window,
14388 cx,
14389 );
14390 this.delete(&Delete, window, cx);
14391 });
14392 }
14393
14394 pub fn cut_to_end_of_line(
14395 &mut self,
14396 action: &CutToEndOfLine,
14397 window: &mut Window,
14398 cx: &mut Context<Self>,
14399 ) {
14400 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14401 self.transact(window, cx, |this, window, cx| {
14402 this.select_to_end_of_line(
14403 &SelectToEndOfLine {
14404 stop_at_soft_wraps: false,
14405 },
14406 window,
14407 cx,
14408 );
14409 if !action.stop_at_newlines {
14410 this.change_selections(Default::default(), window, cx, |s| {
14411 s.move_with(|_, sel| {
14412 if sel.is_empty() {
14413 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14414 }
14415 });
14416 });
14417 }
14418 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14419 let item = this.cut_common(false, window, cx);
14420 cx.write_to_clipboard(item);
14421 });
14422 }
14423
14424 pub fn move_to_start_of_paragraph(
14425 &mut self,
14426 _: &MoveToStartOfParagraph,
14427 window: &mut Window,
14428 cx: &mut Context<Self>,
14429 ) {
14430 if matches!(self.mode, EditorMode::SingleLine) {
14431 cx.propagate();
14432 return;
14433 }
14434 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14435 self.change_selections(Default::default(), window, cx, |s| {
14436 s.move_with(|map, selection| {
14437 selection.collapse_to(
14438 movement::start_of_paragraph(map, selection.head(), 1),
14439 SelectionGoal::None,
14440 )
14441 });
14442 })
14443 }
14444
14445 pub fn move_to_end_of_paragraph(
14446 &mut self,
14447 _: &MoveToEndOfParagraph,
14448 window: &mut Window,
14449 cx: &mut Context<Self>,
14450 ) {
14451 if matches!(self.mode, EditorMode::SingleLine) {
14452 cx.propagate();
14453 return;
14454 }
14455 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14456 self.change_selections(Default::default(), window, cx, |s| {
14457 s.move_with(|map, selection| {
14458 selection.collapse_to(
14459 movement::end_of_paragraph(map, selection.head(), 1),
14460 SelectionGoal::None,
14461 )
14462 });
14463 })
14464 }
14465
14466 pub fn select_to_start_of_paragraph(
14467 &mut self,
14468 _: &SelectToStartOfParagraph,
14469 window: &mut Window,
14470 cx: &mut Context<Self>,
14471 ) {
14472 if matches!(self.mode, EditorMode::SingleLine) {
14473 cx.propagate();
14474 return;
14475 }
14476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14477 self.change_selections(Default::default(), window, cx, |s| {
14478 s.move_heads_with(|map, head, _| {
14479 (
14480 movement::start_of_paragraph(map, head, 1),
14481 SelectionGoal::None,
14482 )
14483 });
14484 })
14485 }
14486
14487 pub fn select_to_end_of_paragraph(
14488 &mut self,
14489 _: &SelectToEndOfParagraph,
14490 window: &mut Window,
14491 cx: &mut Context<Self>,
14492 ) {
14493 if matches!(self.mode, EditorMode::SingleLine) {
14494 cx.propagate();
14495 return;
14496 }
14497 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14498 self.change_selections(Default::default(), window, cx, |s| {
14499 s.move_heads_with(|map, head, _| {
14500 (
14501 movement::end_of_paragraph(map, head, 1),
14502 SelectionGoal::None,
14503 )
14504 });
14505 })
14506 }
14507
14508 pub fn move_to_start_of_excerpt(
14509 &mut self,
14510 _: &MoveToStartOfExcerpt,
14511 window: &mut Window,
14512 cx: &mut Context<Self>,
14513 ) {
14514 if matches!(self.mode, EditorMode::SingleLine) {
14515 cx.propagate();
14516 return;
14517 }
14518 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14519 self.change_selections(Default::default(), window, cx, |s| {
14520 s.move_with(|map, selection| {
14521 selection.collapse_to(
14522 movement::start_of_excerpt(
14523 map,
14524 selection.head(),
14525 workspace::searchable::Direction::Prev,
14526 ),
14527 SelectionGoal::None,
14528 )
14529 });
14530 })
14531 }
14532
14533 pub fn move_to_start_of_next_excerpt(
14534 &mut self,
14535 _: &MoveToStartOfNextExcerpt,
14536 window: &mut Window,
14537 cx: &mut Context<Self>,
14538 ) {
14539 if matches!(self.mode, EditorMode::SingleLine) {
14540 cx.propagate();
14541 return;
14542 }
14543
14544 self.change_selections(Default::default(), window, cx, |s| {
14545 s.move_with(|map, selection| {
14546 selection.collapse_to(
14547 movement::start_of_excerpt(
14548 map,
14549 selection.head(),
14550 workspace::searchable::Direction::Next,
14551 ),
14552 SelectionGoal::None,
14553 )
14554 });
14555 })
14556 }
14557
14558 pub fn move_to_end_of_excerpt(
14559 &mut self,
14560 _: &MoveToEndOfExcerpt,
14561 window: &mut Window,
14562 cx: &mut Context<Self>,
14563 ) {
14564 if matches!(self.mode, EditorMode::SingleLine) {
14565 cx.propagate();
14566 return;
14567 }
14568 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14569 self.change_selections(Default::default(), window, cx, |s| {
14570 s.move_with(|map, selection| {
14571 selection.collapse_to(
14572 movement::end_of_excerpt(
14573 map,
14574 selection.head(),
14575 workspace::searchable::Direction::Next,
14576 ),
14577 SelectionGoal::None,
14578 )
14579 });
14580 })
14581 }
14582
14583 pub fn move_to_end_of_previous_excerpt(
14584 &mut self,
14585 _: &MoveToEndOfPreviousExcerpt,
14586 window: &mut Window,
14587 cx: &mut Context<Self>,
14588 ) {
14589 if matches!(self.mode, EditorMode::SingleLine) {
14590 cx.propagate();
14591 return;
14592 }
14593 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14594 self.change_selections(Default::default(), window, cx, |s| {
14595 s.move_with(|map, selection| {
14596 selection.collapse_to(
14597 movement::end_of_excerpt(
14598 map,
14599 selection.head(),
14600 workspace::searchable::Direction::Prev,
14601 ),
14602 SelectionGoal::None,
14603 )
14604 });
14605 })
14606 }
14607
14608 pub fn select_to_start_of_excerpt(
14609 &mut self,
14610 _: &SelectToStartOfExcerpt,
14611 window: &mut Window,
14612 cx: &mut Context<Self>,
14613 ) {
14614 if matches!(self.mode, EditorMode::SingleLine) {
14615 cx.propagate();
14616 return;
14617 }
14618 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14619 self.change_selections(Default::default(), window, cx, |s| {
14620 s.move_heads_with(|map, head, _| {
14621 (
14622 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14623 SelectionGoal::None,
14624 )
14625 });
14626 })
14627 }
14628
14629 pub fn select_to_start_of_next_excerpt(
14630 &mut self,
14631 _: &SelectToStartOfNextExcerpt,
14632 window: &mut Window,
14633 cx: &mut Context<Self>,
14634 ) {
14635 if matches!(self.mode, EditorMode::SingleLine) {
14636 cx.propagate();
14637 return;
14638 }
14639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14640 self.change_selections(Default::default(), window, cx, |s| {
14641 s.move_heads_with(|map, head, _| {
14642 (
14643 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14644 SelectionGoal::None,
14645 )
14646 });
14647 })
14648 }
14649
14650 pub fn select_to_end_of_excerpt(
14651 &mut self,
14652 _: &SelectToEndOfExcerpt,
14653 window: &mut Window,
14654 cx: &mut Context<Self>,
14655 ) {
14656 if matches!(self.mode, EditorMode::SingleLine) {
14657 cx.propagate();
14658 return;
14659 }
14660 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14661 self.change_selections(Default::default(), window, cx, |s| {
14662 s.move_heads_with(|map, head, _| {
14663 (
14664 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14665 SelectionGoal::None,
14666 )
14667 });
14668 })
14669 }
14670
14671 pub fn select_to_end_of_previous_excerpt(
14672 &mut self,
14673 _: &SelectToEndOfPreviousExcerpt,
14674 window: &mut Window,
14675 cx: &mut Context<Self>,
14676 ) {
14677 if matches!(self.mode, EditorMode::SingleLine) {
14678 cx.propagate();
14679 return;
14680 }
14681 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14682 self.change_selections(Default::default(), window, cx, |s| {
14683 s.move_heads_with(|map, head, _| {
14684 (
14685 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14686 SelectionGoal::None,
14687 )
14688 });
14689 })
14690 }
14691
14692 pub fn move_to_beginning(
14693 &mut self,
14694 _: &MoveToBeginning,
14695 window: &mut Window,
14696 cx: &mut Context<Self>,
14697 ) {
14698 if matches!(self.mode, EditorMode::SingleLine) {
14699 cx.propagate();
14700 return;
14701 }
14702 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14703 self.change_selections(Default::default(), window, cx, |s| {
14704 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14705 });
14706 }
14707
14708 pub fn select_to_beginning(
14709 &mut self,
14710 _: &SelectToBeginning,
14711 window: &mut Window,
14712 cx: &mut Context<Self>,
14713 ) {
14714 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14715 selection.set_head(Point::zero(), SelectionGoal::None);
14716 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14717 self.change_selections(Default::default(), window, cx, |s| {
14718 s.select(vec![selection]);
14719 });
14720 }
14721
14722 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14723 if matches!(self.mode, EditorMode::SingleLine) {
14724 cx.propagate();
14725 return;
14726 }
14727 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14728 let cursor = self.buffer.read(cx).read(cx).len();
14729 self.change_selections(Default::default(), window, cx, |s| {
14730 s.select_ranges(vec![cursor..cursor])
14731 });
14732 }
14733
14734 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14735 self.nav_history = nav_history;
14736 }
14737
14738 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14739 self.nav_history.as_ref()
14740 }
14741
14742 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14743 self.push_to_nav_history(
14744 self.selections.newest_anchor().head(),
14745 None,
14746 false,
14747 true,
14748 cx,
14749 );
14750 }
14751
14752 fn push_to_nav_history(
14753 &mut self,
14754 cursor_anchor: Anchor,
14755 new_position: Option<Point>,
14756 is_deactivate: bool,
14757 always: bool,
14758 cx: &mut Context<Self>,
14759 ) {
14760 if let Some(nav_history) = self.nav_history.as_mut() {
14761 let buffer = self.buffer.read(cx).read(cx);
14762 let cursor_position = cursor_anchor.to_point(&buffer);
14763 let scroll_state = self.scroll_manager.anchor();
14764 let scroll_top_row = scroll_state.top_row(&buffer);
14765 drop(buffer);
14766
14767 if let Some(new_position) = new_position {
14768 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14769 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14770 return;
14771 }
14772 }
14773
14774 nav_history.push(
14775 Some(NavigationData {
14776 cursor_anchor,
14777 cursor_position,
14778 scroll_anchor: scroll_state,
14779 scroll_top_row,
14780 }),
14781 cx,
14782 );
14783 cx.emit(EditorEvent::PushedToNavHistory {
14784 anchor: cursor_anchor,
14785 is_deactivate,
14786 })
14787 }
14788 }
14789
14790 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14792 let buffer = self.buffer.read(cx).snapshot(cx);
14793 let mut selection = self
14794 .selections
14795 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14796 selection.set_head(buffer.len(), SelectionGoal::None);
14797 self.change_selections(Default::default(), window, cx, |s| {
14798 s.select(vec![selection]);
14799 });
14800 }
14801
14802 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14803 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14804 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14805 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14806 });
14807 }
14808
14809 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14810 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14811 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14812 let mut selections = self.selections.all::<Point>(&display_map);
14813 let max_point = display_map.buffer_snapshot().max_point();
14814 for selection in &mut selections {
14815 let rows = selection.spanned_rows(true, &display_map);
14816 selection.start = Point::new(rows.start.0, 0);
14817 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14818 selection.reversed = false;
14819 }
14820 self.change_selections(Default::default(), window, cx, |s| {
14821 s.select(selections);
14822 });
14823 }
14824
14825 pub fn split_selection_into_lines(
14826 &mut self,
14827 action: &SplitSelectionIntoLines,
14828 window: &mut Window,
14829 cx: &mut Context<Self>,
14830 ) {
14831 let selections = self
14832 .selections
14833 .all::<Point>(&self.display_snapshot(cx))
14834 .into_iter()
14835 .map(|selection| selection.start..selection.end)
14836 .collect::<Vec<_>>();
14837 self.unfold_ranges(&selections, true, true, cx);
14838
14839 let mut new_selection_ranges = Vec::new();
14840 {
14841 let buffer = self.buffer.read(cx).read(cx);
14842 for selection in selections {
14843 for row in selection.start.row..selection.end.row {
14844 let line_start = Point::new(row, 0);
14845 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14846
14847 if action.keep_selections {
14848 // Keep the selection range for each line
14849 let selection_start = if row == selection.start.row {
14850 selection.start
14851 } else {
14852 line_start
14853 };
14854 new_selection_ranges.push(selection_start..line_end);
14855 } else {
14856 // Collapse to cursor at end of line
14857 new_selection_ranges.push(line_end..line_end);
14858 }
14859 }
14860
14861 let is_multiline_selection = selection.start.row != selection.end.row;
14862 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14863 // so this action feels more ergonomic when paired with other selection operations
14864 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14865 if !should_skip_last {
14866 if action.keep_selections {
14867 if is_multiline_selection {
14868 let line_start = Point::new(selection.end.row, 0);
14869 new_selection_ranges.push(line_start..selection.end);
14870 } else {
14871 new_selection_ranges.push(selection.start..selection.end);
14872 }
14873 } else {
14874 new_selection_ranges.push(selection.end..selection.end);
14875 }
14876 }
14877 }
14878 }
14879 self.change_selections(Default::default(), window, cx, |s| {
14880 s.select_ranges(new_selection_ranges);
14881 });
14882 }
14883
14884 pub fn add_selection_above(
14885 &mut self,
14886 action: &AddSelectionAbove,
14887 window: &mut Window,
14888 cx: &mut Context<Self>,
14889 ) {
14890 self.add_selection(true, action.skip_soft_wrap, window, cx);
14891 }
14892
14893 pub fn add_selection_below(
14894 &mut self,
14895 action: &AddSelectionBelow,
14896 window: &mut Window,
14897 cx: &mut Context<Self>,
14898 ) {
14899 self.add_selection(false, action.skip_soft_wrap, window, cx);
14900 }
14901
14902 fn add_selection(
14903 &mut self,
14904 above: bool,
14905 skip_soft_wrap: bool,
14906 window: &mut Window,
14907 cx: &mut Context<Self>,
14908 ) {
14909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14910
14911 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14912 let all_selections = self.selections.all::<Point>(&display_map);
14913 let text_layout_details = self.text_layout_details(window);
14914
14915 let (mut columnar_selections, new_selections_to_columnarize) = {
14916 if let Some(state) = self.add_selections_state.as_ref() {
14917 let columnar_selection_ids: HashSet<_> = state
14918 .groups
14919 .iter()
14920 .flat_map(|group| group.stack.iter())
14921 .copied()
14922 .collect();
14923
14924 all_selections
14925 .into_iter()
14926 .partition(|s| columnar_selection_ids.contains(&s.id))
14927 } else {
14928 (Vec::new(), all_selections)
14929 }
14930 };
14931
14932 let mut state = self
14933 .add_selections_state
14934 .take()
14935 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14936
14937 for selection in new_selections_to_columnarize {
14938 let range = selection.display_range(&display_map).sorted();
14939 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14940 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14941 let positions = start_x.min(end_x)..start_x.max(end_x);
14942 let mut stack = Vec::new();
14943 for row in range.start.row().0..=range.end.row().0 {
14944 if let Some(selection) = self.selections.build_columnar_selection(
14945 &display_map,
14946 DisplayRow(row),
14947 &positions,
14948 selection.reversed,
14949 &text_layout_details,
14950 ) {
14951 stack.push(selection.id);
14952 columnar_selections.push(selection);
14953 }
14954 }
14955 if !stack.is_empty() {
14956 if above {
14957 stack.reverse();
14958 }
14959 state.groups.push(AddSelectionsGroup { above, stack });
14960 }
14961 }
14962
14963 let mut final_selections = Vec::new();
14964 let end_row = if above {
14965 DisplayRow(0)
14966 } else {
14967 display_map.max_point().row()
14968 };
14969
14970 let mut last_added_item_per_group = HashMap::default();
14971 for group in state.groups.iter_mut() {
14972 if let Some(last_id) = group.stack.last() {
14973 last_added_item_per_group.insert(*last_id, group);
14974 }
14975 }
14976
14977 for selection in columnar_selections {
14978 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14979 if above == group.above {
14980 let range = selection.display_range(&display_map).sorted();
14981 debug_assert_eq!(range.start.row(), range.end.row());
14982 let mut row = range.start.row();
14983 let positions =
14984 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14985 Pixels::from(start)..Pixels::from(end)
14986 } else {
14987 let start_x =
14988 display_map.x_for_display_point(range.start, &text_layout_details);
14989 let end_x =
14990 display_map.x_for_display_point(range.end, &text_layout_details);
14991 start_x.min(end_x)..start_x.max(end_x)
14992 };
14993
14994 let mut maybe_new_selection = None;
14995 let direction = if above { -1 } else { 1 };
14996
14997 while row != end_row {
14998 let new_buffer_row = if skip_soft_wrap {
14999 let new_row = display_map
15000 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction);
15001 row = new_row.row();
15002 Some(new_row.to_point(&display_map).row)
15003 } else {
15004 if above {
15005 row.0 -= 1;
15006 } else {
15007 row.0 += 1;
15008 }
15009 None
15010 };
15011
15012 let new_selection = if let Some(buffer_row) = new_buffer_row {
15013 let start_col = selection.start.column;
15014 let end_col = selection.end.column;
15015 let buffer_columns = start_col.min(end_col)..start_col.max(end_col);
15016
15017 self.selections
15018 .build_columnar_selection_from_buffer_columns(
15019 &display_map,
15020 buffer_row,
15021 &buffer_columns,
15022 selection.reversed,
15023 &text_layout_details,
15024 )
15025 } else {
15026 self.selections.build_columnar_selection(
15027 &display_map,
15028 row,
15029 &positions,
15030 selection.reversed,
15031 &text_layout_details,
15032 )
15033 };
15034
15035 if let Some(new_selection) = new_selection {
15036 maybe_new_selection = Some(new_selection);
15037 break;
15038 }
15039 }
15040
15041 if let Some(new_selection) = maybe_new_selection {
15042 group.stack.push(new_selection.id);
15043 if above {
15044 final_selections.push(new_selection);
15045 final_selections.push(selection);
15046 } else {
15047 final_selections.push(selection);
15048 final_selections.push(new_selection);
15049 }
15050 } else {
15051 final_selections.push(selection);
15052 }
15053 } else {
15054 group.stack.pop();
15055 }
15056 } else {
15057 final_selections.push(selection);
15058 }
15059 }
15060
15061 self.change_selections(Default::default(), window, cx, |s| {
15062 s.select(final_selections);
15063 });
15064
15065 let final_selection_ids: HashSet<_> = self
15066 .selections
15067 .all::<Point>(&display_map)
15068 .iter()
15069 .map(|s| s.id)
15070 .collect();
15071 state.groups.retain_mut(|group| {
15072 // selections might get merged above so we remove invalid items from stacks
15073 group.stack.retain(|id| final_selection_ids.contains(id));
15074
15075 // single selection in stack can be treated as initial state
15076 group.stack.len() > 1
15077 });
15078
15079 if !state.groups.is_empty() {
15080 self.add_selections_state = Some(state);
15081 }
15082 }
15083
15084 pub fn insert_snippet_at_selections(
15085 &mut self,
15086 action: &InsertSnippet,
15087 window: &mut Window,
15088 cx: &mut Context<Self>,
15089 ) {
15090 self.try_insert_snippet_at_selections(action, window, cx)
15091 .log_err();
15092 }
15093
15094 fn try_insert_snippet_at_selections(
15095 &mut self,
15096 action: &InsertSnippet,
15097 window: &mut Window,
15098 cx: &mut Context<Self>,
15099 ) -> Result<()> {
15100 let insertion_ranges = self
15101 .selections
15102 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15103 .into_iter()
15104 .map(|selection| selection.range())
15105 .collect_vec();
15106
15107 let snippet = if let Some(snippet_body) = &action.snippet {
15108 if action.language.is_none() && action.name.is_none() {
15109 Snippet::parse(snippet_body)?
15110 } else {
15111 bail!("`snippet` is mutually exclusive with `language` and `name`")
15112 }
15113 } else if let Some(name) = &action.name {
15114 let project = self.project().context("no project")?;
15115 let snippet_store = project.read(cx).snippets().read(cx);
15116 let snippet = snippet_store
15117 .snippets_for(action.language.clone(), cx)
15118 .into_iter()
15119 .find(|snippet| snippet.name == *name)
15120 .context("snippet not found")?;
15121 Snippet::parse(&snippet.body)?
15122 } else {
15123 // todo(andrew): open modal to select snippet
15124 bail!("`name` or `snippet` is required")
15125 };
15126
15127 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15128 }
15129
15130 fn select_match_ranges(
15131 &mut self,
15132 range: Range<MultiBufferOffset>,
15133 reversed: bool,
15134 replace_newest: bool,
15135 auto_scroll: Option<Autoscroll>,
15136 window: &mut Window,
15137 cx: &mut Context<Editor>,
15138 ) {
15139 self.unfold_ranges(
15140 std::slice::from_ref(&range),
15141 false,
15142 auto_scroll.is_some(),
15143 cx,
15144 );
15145 let effects = if let Some(scroll) = auto_scroll {
15146 SelectionEffects::scroll(scroll)
15147 } else {
15148 SelectionEffects::no_scroll()
15149 };
15150 self.change_selections(effects, window, cx, |s| {
15151 if replace_newest {
15152 s.delete(s.newest_anchor().id);
15153 }
15154 if reversed {
15155 s.insert_range(range.end..range.start);
15156 } else {
15157 s.insert_range(range);
15158 }
15159 });
15160 }
15161
15162 pub fn select_next_match_internal(
15163 &mut self,
15164 display_map: &DisplaySnapshot,
15165 replace_newest: bool,
15166 autoscroll: Option<Autoscroll>,
15167 window: &mut Window,
15168 cx: &mut Context<Self>,
15169 ) -> Result<()> {
15170 let buffer = display_map.buffer_snapshot();
15171 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15172 if let Some(mut select_next_state) = self.select_next_state.take() {
15173 let query = &select_next_state.query;
15174 if !select_next_state.done {
15175 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15176 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15177 let mut next_selected_range = None;
15178
15179 let bytes_after_last_selection =
15180 buffer.bytes_in_range(last_selection.end..buffer.len());
15181 let bytes_before_first_selection =
15182 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15183 let query_matches = query
15184 .stream_find_iter(bytes_after_last_selection)
15185 .map(|result| (last_selection.end, result))
15186 .chain(
15187 query
15188 .stream_find_iter(bytes_before_first_selection)
15189 .map(|result| (MultiBufferOffset(0), result)),
15190 );
15191
15192 for (start_offset, query_match) in query_matches {
15193 let query_match = query_match.unwrap(); // can only fail due to I/O
15194 let offset_range =
15195 start_offset + query_match.start()..start_offset + query_match.end();
15196
15197 if !select_next_state.wordwise
15198 || (!buffer.is_inside_word(offset_range.start, None)
15199 && !buffer.is_inside_word(offset_range.end, None))
15200 {
15201 let idx = selections
15202 .partition_point(|selection| selection.end <= offset_range.start);
15203 let overlaps = selections
15204 .get(idx)
15205 .map_or(false, |selection| selection.start < offset_range.end);
15206
15207 if !overlaps {
15208 next_selected_range = Some(offset_range);
15209 break;
15210 }
15211 }
15212 }
15213
15214 if let Some(next_selected_range) = next_selected_range {
15215 self.select_match_ranges(
15216 next_selected_range,
15217 last_selection.reversed,
15218 replace_newest,
15219 autoscroll,
15220 window,
15221 cx,
15222 );
15223 } else {
15224 select_next_state.done = true;
15225 }
15226 }
15227
15228 self.select_next_state = Some(select_next_state);
15229 } else {
15230 let mut only_carets = true;
15231 let mut same_text_selected = true;
15232 let mut selected_text = None;
15233
15234 let mut selections_iter = selections.iter().peekable();
15235 while let Some(selection) = selections_iter.next() {
15236 if selection.start != selection.end {
15237 only_carets = false;
15238 }
15239
15240 if same_text_selected {
15241 if selected_text.is_none() {
15242 selected_text =
15243 Some(buffer.text_for_range(selection.range()).collect::<String>());
15244 }
15245
15246 if let Some(next_selection) = selections_iter.peek() {
15247 if next_selection.len() == selection.len() {
15248 let next_selected_text = buffer
15249 .text_for_range(next_selection.range())
15250 .collect::<String>();
15251 if Some(next_selected_text) != selected_text {
15252 same_text_selected = false;
15253 selected_text = None;
15254 }
15255 } else {
15256 same_text_selected = false;
15257 selected_text = None;
15258 }
15259 }
15260 }
15261 }
15262
15263 if only_carets {
15264 for selection in &mut selections {
15265 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15266 selection.start = word_range.start;
15267 selection.end = word_range.end;
15268 selection.goal = SelectionGoal::None;
15269 selection.reversed = false;
15270 self.select_match_ranges(
15271 selection.start..selection.end,
15272 selection.reversed,
15273 replace_newest,
15274 autoscroll,
15275 window,
15276 cx,
15277 );
15278 }
15279
15280 if selections.len() == 1 {
15281 let selection = selections
15282 .last()
15283 .expect("ensured that there's only one selection");
15284 let query = buffer
15285 .text_for_range(selection.start..selection.end)
15286 .collect::<String>();
15287 let is_empty = query.is_empty();
15288 let select_state = SelectNextState {
15289 query: self.build_query(&[query], cx)?,
15290 wordwise: true,
15291 done: is_empty,
15292 };
15293 self.select_next_state = Some(select_state);
15294 } else {
15295 self.select_next_state = None;
15296 }
15297 } else if let Some(selected_text) = selected_text {
15298 self.select_next_state = Some(SelectNextState {
15299 query: self.build_query(&[selected_text], cx)?,
15300 wordwise: false,
15301 done: false,
15302 });
15303 self.select_next_match_internal(
15304 display_map,
15305 replace_newest,
15306 autoscroll,
15307 window,
15308 cx,
15309 )?;
15310 }
15311 }
15312 Ok(())
15313 }
15314
15315 pub fn select_all_matches(
15316 &mut self,
15317 _action: &SelectAllMatches,
15318 window: &mut Window,
15319 cx: &mut Context<Self>,
15320 ) -> Result<()> {
15321 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15322
15323 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15324
15325 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15326 let Some(select_next_state) = self.select_next_state.as_mut() else {
15327 return Ok(());
15328 };
15329 if select_next_state.done {
15330 return Ok(());
15331 }
15332
15333 let mut new_selections = Vec::new();
15334
15335 let reversed = self
15336 .selections
15337 .oldest::<MultiBufferOffset>(&display_map)
15338 .reversed;
15339 let buffer = display_map.buffer_snapshot();
15340 let query_matches = select_next_state
15341 .query
15342 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15343
15344 for query_match in query_matches.into_iter() {
15345 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15346 let offset_range = if reversed {
15347 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15348 } else {
15349 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15350 };
15351
15352 if !select_next_state.wordwise
15353 || (!buffer.is_inside_word(offset_range.start, None)
15354 && !buffer.is_inside_word(offset_range.end, None))
15355 {
15356 new_selections.push(offset_range.start..offset_range.end);
15357 }
15358 }
15359
15360 select_next_state.done = true;
15361
15362 if new_selections.is_empty() {
15363 log::error!("bug: new_selections is empty in select_all_matches");
15364 return Ok(());
15365 }
15366
15367 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15368 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15369 selections.select_ranges(new_selections)
15370 });
15371
15372 Ok(())
15373 }
15374
15375 pub fn select_next(
15376 &mut self,
15377 action: &SelectNext,
15378 window: &mut Window,
15379 cx: &mut Context<Self>,
15380 ) -> Result<()> {
15381 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15382 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15383 self.select_next_match_internal(
15384 &display_map,
15385 action.replace_newest,
15386 Some(Autoscroll::newest()),
15387 window,
15388 cx,
15389 )?;
15390 Ok(())
15391 }
15392
15393 pub fn select_previous(
15394 &mut self,
15395 action: &SelectPrevious,
15396 window: &mut Window,
15397 cx: &mut Context<Self>,
15398 ) -> Result<()> {
15399 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15400 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15401 let buffer = display_map.buffer_snapshot();
15402 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15403 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15404 let query = &select_prev_state.query;
15405 if !select_prev_state.done {
15406 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15407 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15408 let mut next_selected_range = None;
15409 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15410 let bytes_before_last_selection =
15411 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15412 let bytes_after_first_selection =
15413 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15414 let query_matches = query
15415 .stream_find_iter(bytes_before_last_selection)
15416 .map(|result| (last_selection.start, result))
15417 .chain(
15418 query
15419 .stream_find_iter(bytes_after_first_selection)
15420 .map(|result| (buffer.len(), result)),
15421 );
15422 for (end_offset, query_match) in query_matches {
15423 let query_match = query_match.unwrap(); // can only fail due to I/O
15424 let offset_range =
15425 end_offset - query_match.end()..end_offset - query_match.start();
15426
15427 if !select_prev_state.wordwise
15428 || (!buffer.is_inside_word(offset_range.start, None)
15429 && !buffer.is_inside_word(offset_range.end, None))
15430 {
15431 next_selected_range = Some(offset_range);
15432 break;
15433 }
15434 }
15435
15436 if let Some(next_selected_range) = next_selected_range {
15437 self.select_match_ranges(
15438 next_selected_range,
15439 last_selection.reversed,
15440 action.replace_newest,
15441 Some(Autoscroll::newest()),
15442 window,
15443 cx,
15444 );
15445 } else {
15446 select_prev_state.done = true;
15447 }
15448 }
15449
15450 self.select_prev_state = Some(select_prev_state);
15451 } else {
15452 let mut only_carets = true;
15453 let mut same_text_selected = true;
15454 let mut selected_text = None;
15455
15456 let mut selections_iter = selections.iter().peekable();
15457 while let Some(selection) = selections_iter.next() {
15458 if selection.start != selection.end {
15459 only_carets = false;
15460 }
15461
15462 if same_text_selected {
15463 if selected_text.is_none() {
15464 selected_text =
15465 Some(buffer.text_for_range(selection.range()).collect::<String>());
15466 }
15467
15468 if let Some(next_selection) = selections_iter.peek() {
15469 if next_selection.len() == selection.len() {
15470 let next_selected_text = buffer
15471 .text_for_range(next_selection.range())
15472 .collect::<String>();
15473 if Some(next_selected_text) != selected_text {
15474 same_text_selected = false;
15475 selected_text = None;
15476 }
15477 } else {
15478 same_text_selected = false;
15479 selected_text = None;
15480 }
15481 }
15482 }
15483 }
15484
15485 if only_carets {
15486 for selection in &mut selections {
15487 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15488 selection.start = word_range.start;
15489 selection.end = word_range.end;
15490 selection.goal = SelectionGoal::None;
15491 selection.reversed = false;
15492 self.select_match_ranges(
15493 selection.start..selection.end,
15494 selection.reversed,
15495 action.replace_newest,
15496 Some(Autoscroll::newest()),
15497 window,
15498 cx,
15499 );
15500 }
15501 if selections.len() == 1 {
15502 let selection = selections
15503 .last()
15504 .expect("ensured that there's only one selection");
15505 let query = buffer
15506 .text_for_range(selection.start..selection.end)
15507 .collect::<String>();
15508 let is_empty = query.is_empty();
15509 let select_state = SelectNextState {
15510 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15511 wordwise: true,
15512 done: is_empty,
15513 };
15514 self.select_prev_state = Some(select_state);
15515 } else {
15516 self.select_prev_state = None;
15517 }
15518 } else if let Some(selected_text) = selected_text {
15519 self.select_prev_state = Some(SelectNextState {
15520 query: self
15521 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15522 wordwise: false,
15523 done: false,
15524 });
15525 self.select_previous(action, window, cx)?;
15526 }
15527 }
15528 Ok(())
15529 }
15530
15531 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15532 /// setting the case sensitivity based on the global
15533 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15534 /// editor's settings.
15535 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15536 where
15537 I: IntoIterator<Item = P>,
15538 P: AsRef<[u8]>,
15539 {
15540 let case_sensitive = self
15541 .select_next_is_case_sensitive
15542 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15543
15544 let mut builder = AhoCorasickBuilder::new();
15545 builder.ascii_case_insensitive(!case_sensitive);
15546 builder.build(patterns)
15547 }
15548
15549 pub fn find_next_match(
15550 &mut self,
15551 _: &FindNextMatch,
15552 window: &mut Window,
15553 cx: &mut Context<Self>,
15554 ) -> Result<()> {
15555 let selections = self.selections.disjoint_anchors_arc();
15556 match selections.first() {
15557 Some(first) if selections.len() >= 2 => {
15558 self.change_selections(Default::default(), window, cx, |s| {
15559 s.select_ranges([first.range()]);
15560 });
15561 }
15562 _ => self.select_next(
15563 &SelectNext {
15564 replace_newest: true,
15565 },
15566 window,
15567 cx,
15568 )?,
15569 }
15570 Ok(())
15571 }
15572
15573 pub fn find_previous_match(
15574 &mut self,
15575 _: &FindPreviousMatch,
15576 window: &mut Window,
15577 cx: &mut Context<Self>,
15578 ) -> Result<()> {
15579 let selections = self.selections.disjoint_anchors_arc();
15580 match selections.last() {
15581 Some(last) if selections.len() >= 2 => {
15582 self.change_selections(Default::default(), window, cx, |s| {
15583 s.select_ranges([last.range()]);
15584 });
15585 }
15586 _ => self.select_previous(
15587 &SelectPrevious {
15588 replace_newest: true,
15589 },
15590 window,
15591 cx,
15592 )?,
15593 }
15594 Ok(())
15595 }
15596
15597 pub fn toggle_comments(
15598 &mut self,
15599 action: &ToggleComments,
15600 window: &mut Window,
15601 cx: &mut Context<Self>,
15602 ) {
15603 if self.read_only(cx) {
15604 return;
15605 }
15606 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15607 let text_layout_details = &self.text_layout_details(window);
15608 self.transact(window, cx, |this, window, cx| {
15609 let mut selections = this
15610 .selections
15611 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15612 let mut edits = Vec::new();
15613 let mut selection_edit_ranges = Vec::new();
15614 let mut last_toggled_row = None;
15615 let snapshot = this.buffer.read(cx).read(cx);
15616 let empty_str: Arc<str> = Arc::default();
15617 let mut suffixes_inserted = Vec::new();
15618 let ignore_indent = action.ignore_indent;
15619
15620 fn comment_prefix_range(
15621 snapshot: &MultiBufferSnapshot,
15622 row: MultiBufferRow,
15623 comment_prefix: &str,
15624 comment_prefix_whitespace: &str,
15625 ignore_indent: bool,
15626 ) -> Range<Point> {
15627 let indent_size = if ignore_indent {
15628 0
15629 } else {
15630 snapshot.indent_size_for_line(row).len
15631 };
15632
15633 let start = Point::new(row.0, indent_size);
15634
15635 let mut line_bytes = snapshot
15636 .bytes_in_range(start..snapshot.max_point())
15637 .flatten()
15638 .copied();
15639
15640 // If this line currently begins with the line comment prefix, then record
15641 // the range containing the prefix.
15642 if line_bytes
15643 .by_ref()
15644 .take(comment_prefix.len())
15645 .eq(comment_prefix.bytes())
15646 {
15647 // Include any whitespace that matches the comment prefix.
15648 let matching_whitespace_len = line_bytes
15649 .zip(comment_prefix_whitespace.bytes())
15650 .take_while(|(a, b)| a == b)
15651 .count() as u32;
15652 let end = Point::new(
15653 start.row,
15654 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15655 );
15656 start..end
15657 } else {
15658 start..start
15659 }
15660 }
15661
15662 fn comment_suffix_range(
15663 snapshot: &MultiBufferSnapshot,
15664 row: MultiBufferRow,
15665 comment_suffix: &str,
15666 comment_suffix_has_leading_space: bool,
15667 ) -> Range<Point> {
15668 let end = Point::new(row.0, snapshot.line_len(row));
15669 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15670
15671 let mut line_end_bytes = snapshot
15672 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15673 .flatten()
15674 .copied();
15675
15676 let leading_space_len = if suffix_start_column > 0
15677 && line_end_bytes.next() == Some(b' ')
15678 && comment_suffix_has_leading_space
15679 {
15680 1
15681 } else {
15682 0
15683 };
15684
15685 // If this line currently begins with the line comment prefix, then record
15686 // the range containing the prefix.
15687 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15688 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15689 start..end
15690 } else {
15691 end..end
15692 }
15693 }
15694
15695 // TODO: Handle selections that cross excerpts
15696 for selection in &mut selections {
15697 let start_column = snapshot
15698 .indent_size_for_line(MultiBufferRow(selection.start.row))
15699 .len;
15700 let language = if let Some(language) =
15701 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15702 {
15703 language
15704 } else {
15705 continue;
15706 };
15707
15708 selection_edit_ranges.clear();
15709
15710 // If multiple selections contain a given row, avoid processing that
15711 // row more than once.
15712 let mut start_row = MultiBufferRow(selection.start.row);
15713 if last_toggled_row == Some(start_row) {
15714 start_row = start_row.next_row();
15715 }
15716 let end_row =
15717 if selection.end.row > selection.start.row && selection.end.column == 0 {
15718 MultiBufferRow(selection.end.row - 1)
15719 } else {
15720 MultiBufferRow(selection.end.row)
15721 };
15722 last_toggled_row = Some(end_row);
15723
15724 if start_row > end_row {
15725 continue;
15726 }
15727
15728 // If the language has line comments, toggle those.
15729 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15730
15731 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15732 if ignore_indent {
15733 full_comment_prefixes = full_comment_prefixes
15734 .into_iter()
15735 .map(|s| Arc::from(s.trim_end()))
15736 .collect();
15737 }
15738
15739 if !full_comment_prefixes.is_empty() {
15740 let first_prefix = full_comment_prefixes
15741 .first()
15742 .expect("prefixes is non-empty");
15743 let prefix_trimmed_lengths = full_comment_prefixes
15744 .iter()
15745 .map(|p| p.trim_end_matches(' ').len())
15746 .collect::<SmallVec<[usize; 4]>>();
15747
15748 let mut all_selection_lines_are_comments = true;
15749
15750 for row in start_row.0..=end_row.0 {
15751 let row = MultiBufferRow(row);
15752 if start_row < end_row && snapshot.is_line_blank(row) {
15753 continue;
15754 }
15755
15756 let prefix_range = full_comment_prefixes
15757 .iter()
15758 .zip(prefix_trimmed_lengths.iter().copied())
15759 .map(|(prefix, trimmed_prefix_len)| {
15760 comment_prefix_range(
15761 snapshot.deref(),
15762 row,
15763 &prefix[..trimmed_prefix_len],
15764 &prefix[trimmed_prefix_len..],
15765 ignore_indent,
15766 )
15767 })
15768 .max_by_key(|range| range.end.column - range.start.column)
15769 .expect("prefixes is non-empty");
15770
15771 if prefix_range.is_empty() {
15772 all_selection_lines_are_comments = false;
15773 }
15774
15775 selection_edit_ranges.push(prefix_range);
15776 }
15777
15778 if all_selection_lines_are_comments {
15779 edits.extend(
15780 selection_edit_ranges
15781 .iter()
15782 .cloned()
15783 .map(|range| (range, empty_str.clone())),
15784 );
15785 } else {
15786 let min_column = selection_edit_ranges
15787 .iter()
15788 .map(|range| range.start.column)
15789 .min()
15790 .unwrap_or(0);
15791 edits.extend(selection_edit_ranges.iter().map(|range| {
15792 let position = Point::new(range.start.row, min_column);
15793 (position..position, first_prefix.clone())
15794 }));
15795 }
15796 } else if let Some(BlockCommentConfig {
15797 start: full_comment_prefix,
15798 end: comment_suffix,
15799 ..
15800 }) = language.block_comment()
15801 {
15802 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15803 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15804 let prefix_range = comment_prefix_range(
15805 snapshot.deref(),
15806 start_row,
15807 comment_prefix,
15808 comment_prefix_whitespace,
15809 ignore_indent,
15810 );
15811 let suffix_range = comment_suffix_range(
15812 snapshot.deref(),
15813 end_row,
15814 comment_suffix.trim_start_matches(' '),
15815 comment_suffix.starts_with(' '),
15816 );
15817
15818 if prefix_range.is_empty() || suffix_range.is_empty() {
15819 edits.push((
15820 prefix_range.start..prefix_range.start,
15821 full_comment_prefix.clone(),
15822 ));
15823 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15824 suffixes_inserted.push((end_row, comment_suffix.len()));
15825 } else {
15826 edits.push((prefix_range, empty_str.clone()));
15827 edits.push((suffix_range, empty_str.clone()));
15828 }
15829 } else {
15830 continue;
15831 }
15832 }
15833
15834 drop(snapshot);
15835 this.buffer.update(cx, |buffer, cx| {
15836 buffer.edit(edits, None, cx);
15837 });
15838
15839 // Adjust selections so that they end before any comment suffixes that
15840 // were inserted.
15841 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15842 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15843 let snapshot = this.buffer.read(cx).read(cx);
15844 for selection in &mut selections {
15845 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15846 match row.cmp(&MultiBufferRow(selection.end.row)) {
15847 Ordering::Less => {
15848 suffixes_inserted.next();
15849 continue;
15850 }
15851 Ordering::Greater => break,
15852 Ordering::Equal => {
15853 if selection.end.column == snapshot.line_len(row) {
15854 if selection.is_empty() {
15855 selection.start.column -= suffix_len as u32;
15856 }
15857 selection.end.column -= suffix_len as u32;
15858 }
15859 break;
15860 }
15861 }
15862 }
15863 }
15864
15865 drop(snapshot);
15866 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15867
15868 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15869 let selections_on_single_row = selections.windows(2).all(|selections| {
15870 selections[0].start.row == selections[1].start.row
15871 && selections[0].end.row == selections[1].end.row
15872 && selections[0].start.row == selections[0].end.row
15873 });
15874 let selections_selecting = selections
15875 .iter()
15876 .any(|selection| selection.start != selection.end);
15877 let advance_downwards = action.advance_downwards
15878 && selections_on_single_row
15879 && !selections_selecting
15880 && !matches!(this.mode, EditorMode::SingleLine);
15881
15882 if advance_downwards {
15883 let snapshot = this.buffer.read(cx).snapshot(cx);
15884
15885 this.change_selections(Default::default(), window, cx, |s| {
15886 s.move_cursors_with(|display_snapshot, display_point, _| {
15887 let mut point = display_point.to_point(display_snapshot);
15888 point.row += 1;
15889 point = snapshot.clip_point(point, Bias::Left);
15890 let display_point = point.to_display_point(display_snapshot);
15891 let goal = SelectionGoal::HorizontalPosition(
15892 display_snapshot
15893 .x_for_display_point(display_point, text_layout_details)
15894 .into(),
15895 );
15896 (display_point, goal)
15897 })
15898 });
15899 }
15900 });
15901 }
15902
15903 pub fn select_enclosing_symbol(
15904 &mut self,
15905 _: &SelectEnclosingSymbol,
15906 window: &mut Window,
15907 cx: &mut Context<Self>,
15908 ) {
15909 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15910
15911 let buffer = self.buffer.read(cx).snapshot(cx);
15912 let old_selections = self
15913 .selections
15914 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15915 .into_boxed_slice();
15916
15917 fn update_selection(
15918 selection: &Selection<MultiBufferOffset>,
15919 buffer_snap: &MultiBufferSnapshot,
15920 ) -> Option<Selection<MultiBufferOffset>> {
15921 let cursor = selection.head();
15922 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15923 for symbol in symbols.iter().rev() {
15924 let start = symbol.range.start.to_offset(buffer_snap);
15925 let end = symbol.range.end.to_offset(buffer_snap);
15926 let new_range = start..end;
15927 if start < selection.start || end > selection.end {
15928 return Some(Selection {
15929 id: selection.id,
15930 start: new_range.start,
15931 end: new_range.end,
15932 goal: SelectionGoal::None,
15933 reversed: selection.reversed,
15934 });
15935 }
15936 }
15937 None
15938 }
15939
15940 let mut selected_larger_symbol = false;
15941 let new_selections = old_selections
15942 .iter()
15943 .map(|selection| match update_selection(selection, &buffer) {
15944 Some(new_selection) => {
15945 if new_selection.range() != selection.range() {
15946 selected_larger_symbol = true;
15947 }
15948 new_selection
15949 }
15950 None => selection.clone(),
15951 })
15952 .collect::<Vec<_>>();
15953
15954 if selected_larger_symbol {
15955 self.change_selections(Default::default(), window, cx, |s| {
15956 s.select(new_selections);
15957 });
15958 }
15959 }
15960
15961 pub fn select_larger_syntax_node(
15962 &mut self,
15963 _: &SelectLargerSyntaxNode,
15964 window: &mut Window,
15965 cx: &mut Context<Self>,
15966 ) {
15967 let Some(visible_row_count) = self.visible_row_count() else {
15968 return;
15969 };
15970 let old_selections: Box<[_]> = self
15971 .selections
15972 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15973 .into();
15974 if old_selections.is_empty() {
15975 return;
15976 }
15977
15978 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15979
15980 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15981 let buffer = self.buffer.read(cx).snapshot(cx);
15982
15983 let mut selected_larger_node = false;
15984 let mut new_selections = old_selections
15985 .iter()
15986 .map(|selection| {
15987 let old_range = selection.start..selection.end;
15988
15989 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15990 // manually select word at selection
15991 if ["string_content", "inline"].contains(&node.kind()) {
15992 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15993 // ignore if word is already selected
15994 if !word_range.is_empty() && old_range != word_range {
15995 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15996 // only select word if start and end point belongs to same word
15997 if word_range == last_word_range {
15998 selected_larger_node = true;
15999 return Selection {
16000 id: selection.id,
16001 start: word_range.start,
16002 end: word_range.end,
16003 goal: SelectionGoal::None,
16004 reversed: selection.reversed,
16005 };
16006 }
16007 }
16008 }
16009 }
16010
16011 let mut new_range = old_range.clone();
16012 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16013 new_range = range;
16014 if !node.is_named() {
16015 continue;
16016 }
16017 if !display_map.intersects_fold(new_range.start)
16018 && !display_map.intersects_fold(new_range.end)
16019 {
16020 break;
16021 }
16022 }
16023
16024 selected_larger_node |= new_range != old_range;
16025 Selection {
16026 id: selection.id,
16027 start: new_range.start,
16028 end: new_range.end,
16029 goal: SelectionGoal::None,
16030 reversed: selection.reversed,
16031 }
16032 })
16033 .collect::<Vec<_>>();
16034
16035 if !selected_larger_node {
16036 return; // don't put this call in the history
16037 }
16038
16039 // scroll based on transformation done to the last selection created by the user
16040 let (last_old, last_new) = old_selections
16041 .last()
16042 .zip(new_selections.last().cloned())
16043 .expect("old_selections isn't empty");
16044
16045 // revert selection
16046 let is_selection_reversed = {
16047 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16048 new_selections.last_mut().expect("checked above").reversed =
16049 should_newest_selection_be_reversed;
16050 should_newest_selection_be_reversed
16051 };
16052
16053 if selected_larger_node {
16054 self.select_syntax_node_history.disable_clearing = true;
16055 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16056 s.select(new_selections.clone());
16057 });
16058 self.select_syntax_node_history.disable_clearing = false;
16059 }
16060
16061 let start_row = last_new.start.to_display_point(&display_map).row().0;
16062 let end_row = last_new.end.to_display_point(&display_map).row().0;
16063 let selection_height = end_row - start_row + 1;
16064 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16065
16066 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16067 let scroll_behavior = if fits_on_the_screen {
16068 self.request_autoscroll(Autoscroll::fit(), cx);
16069 SelectSyntaxNodeScrollBehavior::FitSelection
16070 } else if is_selection_reversed {
16071 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16072 SelectSyntaxNodeScrollBehavior::CursorTop
16073 } else {
16074 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16075 SelectSyntaxNodeScrollBehavior::CursorBottom
16076 };
16077
16078 self.select_syntax_node_history.push((
16079 old_selections,
16080 scroll_behavior,
16081 is_selection_reversed,
16082 ));
16083 }
16084
16085 pub fn select_smaller_syntax_node(
16086 &mut self,
16087 _: &SelectSmallerSyntaxNode,
16088 window: &mut Window,
16089 cx: &mut Context<Self>,
16090 ) {
16091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16092
16093 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16094 self.select_syntax_node_history.pop()
16095 {
16096 if let Some(selection) = selections.last_mut() {
16097 selection.reversed = is_selection_reversed;
16098 }
16099
16100 self.select_syntax_node_history.disable_clearing = true;
16101 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16102 s.select(selections.to_vec());
16103 });
16104 self.select_syntax_node_history.disable_clearing = false;
16105
16106 match scroll_behavior {
16107 SelectSyntaxNodeScrollBehavior::CursorTop => {
16108 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16109 }
16110 SelectSyntaxNodeScrollBehavior::FitSelection => {
16111 self.request_autoscroll(Autoscroll::fit(), cx);
16112 }
16113 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16114 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16115 }
16116 }
16117 }
16118 }
16119
16120 pub fn unwrap_syntax_node(
16121 &mut self,
16122 _: &UnwrapSyntaxNode,
16123 window: &mut Window,
16124 cx: &mut Context<Self>,
16125 ) {
16126 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16127
16128 let buffer = self.buffer.read(cx).snapshot(cx);
16129 let selections = self
16130 .selections
16131 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16132 .into_iter()
16133 // subtracting the offset requires sorting
16134 .sorted_by_key(|i| i.start);
16135
16136 let full_edits = selections
16137 .into_iter()
16138 .filter_map(|selection| {
16139 let child = if selection.is_empty()
16140 && let Some((_, ancestor_range)) =
16141 buffer.syntax_ancestor(selection.start..selection.end)
16142 {
16143 ancestor_range
16144 } else {
16145 selection.range()
16146 };
16147
16148 let mut parent = child.clone();
16149 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16150 parent = ancestor_range;
16151 if parent.start < child.start || parent.end > child.end {
16152 break;
16153 }
16154 }
16155
16156 if parent == child {
16157 return None;
16158 }
16159 let text = buffer.text_for_range(child).collect::<String>();
16160 Some((selection.id, parent, text))
16161 })
16162 .collect::<Vec<_>>();
16163 if full_edits.is_empty() {
16164 return;
16165 }
16166
16167 self.transact(window, cx, |this, window, cx| {
16168 this.buffer.update(cx, |buffer, cx| {
16169 buffer.edit(
16170 full_edits
16171 .iter()
16172 .map(|(_, p, t)| (p.clone(), t.clone()))
16173 .collect::<Vec<_>>(),
16174 None,
16175 cx,
16176 );
16177 });
16178 this.change_selections(Default::default(), window, cx, |s| {
16179 let mut offset = 0;
16180 let mut selections = vec![];
16181 for (id, parent, text) in full_edits {
16182 let start = parent.start - offset;
16183 offset += (parent.end - parent.start) - text.len();
16184 selections.push(Selection {
16185 id,
16186 start,
16187 end: start + text.len(),
16188 reversed: false,
16189 goal: Default::default(),
16190 });
16191 }
16192 s.select(selections);
16193 });
16194 });
16195 }
16196
16197 pub fn select_next_syntax_node(
16198 &mut self,
16199 _: &SelectNextSyntaxNode,
16200 window: &mut Window,
16201 cx: &mut Context<Self>,
16202 ) {
16203 let old_selections: Box<[_]> = self
16204 .selections
16205 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16206 .into();
16207 if old_selections.is_empty() {
16208 return;
16209 }
16210
16211 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16212
16213 let buffer = self.buffer.read(cx).snapshot(cx);
16214 let mut selected_sibling = false;
16215
16216 let new_selections = old_selections
16217 .iter()
16218 .map(|selection| {
16219 let old_range = selection.start..selection.end;
16220
16221 let old_range =
16222 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16223 let excerpt = buffer.excerpt_containing(old_range.clone());
16224
16225 if let Some(mut excerpt) = excerpt
16226 && let Some(node) = excerpt
16227 .buffer()
16228 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16229 {
16230 let new_range = excerpt.map_range_from_buffer(
16231 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16232 );
16233 selected_sibling = true;
16234 Selection {
16235 id: selection.id,
16236 start: new_range.start,
16237 end: new_range.end,
16238 goal: SelectionGoal::None,
16239 reversed: selection.reversed,
16240 }
16241 } else {
16242 selection.clone()
16243 }
16244 })
16245 .collect::<Vec<_>>();
16246
16247 if selected_sibling {
16248 self.change_selections(
16249 SelectionEffects::scroll(Autoscroll::fit()),
16250 window,
16251 cx,
16252 |s| {
16253 s.select(new_selections);
16254 },
16255 );
16256 }
16257 }
16258
16259 pub fn select_prev_syntax_node(
16260 &mut self,
16261 _: &SelectPreviousSyntaxNode,
16262 window: &mut Window,
16263 cx: &mut Context<Self>,
16264 ) {
16265 let old_selections: Box<[_]> = self
16266 .selections
16267 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16268 .into();
16269 if old_selections.is_empty() {
16270 return;
16271 }
16272
16273 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16274
16275 let buffer = self.buffer.read(cx).snapshot(cx);
16276 let mut selected_sibling = false;
16277
16278 let new_selections = old_selections
16279 .iter()
16280 .map(|selection| {
16281 let old_range = selection.start..selection.end;
16282 let old_range =
16283 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16284 let excerpt = buffer.excerpt_containing(old_range.clone());
16285
16286 if let Some(mut excerpt) = excerpt
16287 && let Some(node) = excerpt
16288 .buffer()
16289 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16290 {
16291 let new_range = excerpt.map_range_from_buffer(
16292 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16293 );
16294 selected_sibling = true;
16295 Selection {
16296 id: selection.id,
16297 start: new_range.start,
16298 end: new_range.end,
16299 goal: SelectionGoal::None,
16300 reversed: selection.reversed,
16301 }
16302 } else {
16303 selection.clone()
16304 }
16305 })
16306 .collect::<Vec<_>>();
16307
16308 if selected_sibling {
16309 self.change_selections(
16310 SelectionEffects::scroll(Autoscroll::fit()),
16311 window,
16312 cx,
16313 |s| {
16314 s.select(new_selections);
16315 },
16316 );
16317 }
16318 }
16319
16320 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16321 if !EditorSettings::get_global(cx).gutter.runnables {
16322 self.clear_tasks();
16323 return Task::ready(());
16324 }
16325 let project = self.project().map(Entity::downgrade);
16326 let task_sources = self.lsp_task_sources(cx);
16327 let multi_buffer = self.buffer.downgrade();
16328 cx.spawn_in(window, async move |editor, cx| {
16329 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16330 let Some(project) = project.and_then(|p| p.upgrade()) else {
16331 return;
16332 };
16333 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16334 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16335 }) else {
16336 return;
16337 };
16338
16339 let hide_runnables = project
16340 .update(cx, |project, _| project.is_via_collab())
16341 .unwrap_or(true);
16342 if hide_runnables {
16343 return;
16344 }
16345 let new_rows =
16346 cx.background_spawn({
16347 let snapshot = display_snapshot.clone();
16348 async move {
16349 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16350 }
16351 })
16352 .await;
16353 let Ok(lsp_tasks) =
16354 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16355 else {
16356 return;
16357 };
16358 let lsp_tasks = lsp_tasks.await;
16359
16360 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16361 lsp_tasks
16362 .into_iter()
16363 .flat_map(|(kind, tasks)| {
16364 tasks.into_iter().filter_map(move |(location, task)| {
16365 Some((kind.clone(), location?, task))
16366 })
16367 })
16368 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16369 let buffer = location.target.buffer;
16370 let buffer_snapshot = buffer.read(cx).snapshot();
16371 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16372 |(excerpt_id, snapshot, _)| {
16373 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16374 display_snapshot
16375 .buffer_snapshot()
16376 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16377 } else {
16378 None
16379 }
16380 },
16381 );
16382 if let Some(offset) = offset {
16383 let task_buffer_range =
16384 location.target.range.to_point(&buffer_snapshot);
16385 let context_buffer_range =
16386 task_buffer_range.to_offset(&buffer_snapshot);
16387 let context_range = BufferOffset(context_buffer_range.start)
16388 ..BufferOffset(context_buffer_range.end);
16389
16390 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16391 .or_insert_with(|| RunnableTasks {
16392 templates: Vec::new(),
16393 offset,
16394 column: task_buffer_range.start.column,
16395 extra_variables: HashMap::default(),
16396 context_range,
16397 })
16398 .templates
16399 .push((kind, task.original_task().clone()));
16400 }
16401
16402 acc
16403 })
16404 }) else {
16405 return;
16406 };
16407
16408 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16409 buffer.language_settings(cx).tasks.prefer_lsp
16410 }) else {
16411 return;
16412 };
16413
16414 let rows = Self::runnable_rows(
16415 project,
16416 display_snapshot,
16417 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16418 new_rows,
16419 cx.clone(),
16420 )
16421 .await;
16422 editor
16423 .update(cx, |editor, _| {
16424 editor.clear_tasks();
16425 for (key, mut value) in rows {
16426 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16427 value.templates.extend(lsp_tasks.templates);
16428 }
16429
16430 editor.insert_tasks(key, value);
16431 }
16432 for (key, value) in lsp_tasks_by_rows {
16433 editor.insert_tasks(key, value);
16434 }
16435 })
16436 .ok();
16437 })
16438 }
16439 fn fetch_runnable_ranges(
16440 snapshot: &DisplaySnapshot,
16441 range: Range<Anchor>,
16442 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16443 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16444 }
16445
16446 fn runnable_rows(
16447 project: Entity<Project>,
16448 snapshot: DisplaySnapshot,
16449 prefer_lsp: bool,
16450 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16451 cx: AsyncWindowContext,
16452 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16453 cx.spawn(async move |cx| {
16454 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16455 for (run_range, mut runnable) in runnable_ranges {
16456 let Some(tasks) = cx
16457 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16458 .ok()
16459 else {
16460 continue;
16461 };
16462 let mut tasks = tasks.await;
16463
16464 if prefer_lsp {
16465 tasks.retain(|(task_kind, _)| {
16466 !matches!(task_kind, TaskSourceKind::Language { .. })
16467 });
16468 }
16469 if tasks.is_empty() {
16470 continue;
16471 }
16472
16473 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16474 let Some(row) = snapshot
16475 .buffer_snapshot()
16476 .buffer_line_for_row(MultiBufferRow(point.row))
16477 .map(|(_, range)| range.start.row)
16478 else {
16479 continue;
16480 };
16481
16482 let context_range =
16483 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16484 runnable_rows.push((
16485 (runnable.buffer_id, row),
16486 RunnableTasks {
16487 templates: tasks,
16488 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16489 context_range,
16490 column: point.column,
16491 extra_variables: runnable.extra_captures,
16492 },
16493 ));
16494 }
16495 runnable_rows
16496 })
16497 }
16498
16499 fn templates_with_tags(
16500 project: &Entity<Project>,
16501 runnable: &mut Runnable,
16502 cx: &mut App,
16503 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16504 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16505 let (worktree_id, file) = project
16506 .buffer_for_id(runnable.buffer, cx)
16507 .and_then(|buffer| buffer.read(cx).file())
16508 .map(|file| (file.worktree_id(cx), file.clone()))
16509 .unzip();
16510
16511 (
16512 project.task_store().read(cx).task_inventory().cloned(),
16513 worktree_id,
16514 file,
16515 )
16516 });
16517
16518 let tags = mem::take(&mut runnable.tags);
16519 let language = runnable.language.clone();
16520 cx.spawn(async move |cx| {
16521 let mut templates_with_tags = Vec::new();
16522 if let Some(inventory) = inventory {
16523 for RunnableTag(tag) in tags {
16524 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16525 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16526 }) else {
16527 return templates_with_tags;
16528 };
16529 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16530 move |(_, template)| {
16531 template.tags.iter().any(|source_tag| source_tag == &tag)
16532 },
16533 ));
16534 }
16535 }
16536 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16537
16538 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16539 // Strongest source wins; if we have worktree tag binding, prefer that to
16540 // global and language bindings;
16541 // if we have a global binding, prefer that to language binding.
16542 let first_mismatch = templates_with_tags
16543 .iter()
16544 .position(|(tag_source, _)| tag_source != leading_tag_source);
16545 if let Some(index) = first_mismatch {
16546 templates_with_tags.truncate(index);
16547 }
16548 }
16549
16550 templates_with_tags
16551 })
16552 }
16553
16554 pub fn move_to_enclosing_bracket(
16555 &mut self,
16556 _: &MoveToEnclosingBracket,
16557 window: &mut Window,
16558 cx: &mut Context<Self>,
16559 ) {
16560 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16561 self.change_selections(Default::default(), window, cx, |s| {
16562 s.move_offsets_with(|snapshot, selection| {
16563 let Some(enclosing_bracket_ranges) =
16564 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16565 else {
16566 return;
16567 };
16568
16569 let mut best_length = usize::MAX;
16570 let mut best_inside = false;
16571 let mut best_in_bracket_range = false;
16572 let mut best_destination = None;
16573 for (open, close) in enclosing_bracket_ranges {
16574 let close = close.to_inclusive();
16575 let length = *close.end() - open.start;
16576 let inside = selection.start >= open.end && selection.end <= *close.start();
16577 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16578 || close.contains(&selection.head());
16579
16580 // If best is next to a bracket and current isn't, skip
16581 if !in_bracket_range && best_in_bracket_range {
16582 continue;
16583 }
16584
16585 // Prefer smaller lengths unless best is inside and current isn't
16586 if length > best_length && (best_inside || !inside) {
16587 continue;
16588 }
16589
16590 best_length = length;
16591 best_inside = inside;
16592 best_in_bracket_range = in_bracket_range;
16593 best_destination = Some(
16594 if close.contains(&selection.start) && close.contains(&selection.end) {
16595 if inside { open.end } else { open.start }
16596 } else if inside {
16597 *close.start()
16598 } else {
16599 *close.end()
16600 },
16601 );
16602 }
16603
16604 if let Some(destination) = best_destination {
16605 selection.collapse_to(destination, SelectionGoal::None);
16606 }
16607 })
16608 });
16609 }
16610
16611 pub fn undo_selection(
16612 &mut self,
16613 _: &UndoSelection,
16614 window: &mut Window,
16615 cx: &mut Context<Self>,
16616 ) {
16617 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16618 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16619 self.selection_history.mode = SelectionHistoryMode::Undoing;
16620 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16621 this.end_selection(window, cx);
16622 this.change_selections(
16623 SelectionEffects::scroll(Autoscroll::newest()),
16624 window,
16625 cx,
16626 |s| s.select_anchors(entry.selections.to_vec()),
16627 );
16628 });
16629 self.selection_history.mode = SelectionHistoryMode::Normal;
16630
16631 self.select_next_state = entry.select_next_state;
16632 self.select_prev_state = entry.select_prev_state;
16633 self.add_selections_state = entry.add_selections_state;
16634 }
16635 }
16636
16637 pub fn redo_selection(
16638 &mut self,
16639 _: &RedoSelection,
16640 window: &mut Window,
16641 cx: &mut Context<Self>,
16642 ) {
16643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16644 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16645 self.selection_history.mode = SelectionHistoryMode::Redoing;
16646 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16647 this.end_selection(window, cx);
16648 this.change_selections(
16649 SelectionEffects::scroll(Autoscroll::newest()),
16650 window,
16651 cx,
16652 |s| s.select_anchors(entry.selections.to_vec()),
16653 );
16654 });
16655 self.selection_history.mode = SelectionHistoryMode::Normal;
16656
16657 self.select_next_state = entry.select_next_state;
16658 self.select_prev_state = entry.select_prev_state;
16659 self.add_selections_state = entry.add_selections_state;
16660 }
16661 }
16662
16663 pub fn expand_excerpts(
16664 &mut self,
16665 action: &ExpandExcerpts,
16666 _: &mut Window,
16667 cx: &mut Context<Self>,
16668 ) {
16669 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16670 }
16671
16672 pub fn expand_excerpts_down(
16673 &mut self,
16674 action: &ExpandExcerptsDown,
16675 _: &mut Window,
16676 cx: &mut Context<Self>,
16677 ) {
16678 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16679 }
16680
16681 pub fn expand_excerpts_up(
16682 &mut self,
16683 action: &ExpandExcerptsUp,
16684 _: &mut Window,
16685 cx: &mut Context<Self>,
16686 ) {
16687 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16688 }
16689
16690 pub fn expand_excerpts_for_direction(
16691 &mut self,
16692 lines: u32,
16693 direction: ExpandExcerptDirection,
16694 cx: &mut Context<Self>,
16695 ) {
16696 let selections = self.selections.disjoint_anchors_arc();
16697
16698 let lines = if lines == 0 {
16699 EditorSettings::get_global(cx).expand_excerpt_lines
16700 } else {
16701 lines
16702 };
16703
16704 let snapshot = self.buffer.read(cx).snapshot(cx);
16705 let mut excerpt_ids = selections
16706 .iter()
16707 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16708 .collect::<Vec<_>>();
16709 excerpt_ids.sort();
16710 excerpt_ids.dedup();
16711
16712 if self.delegate_expand_excerpts {
16713 cx.emit(EditorEvent::ExpandExcerptsRequested {
16714 excerpt_ids,
16715 lines,
16716 direction,
16717 });
16718 return;
16719 }
16720
16721 self.buffer.update(cx, |buffer, cx| {
16722 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16723 })
16724 }
16725
16726 pub fn expand_excerpt(
16727 &mut self,
16728 excerpt: ExcerptId,
16729 direction: ExpandExcerptDirection,
16730 window: &mut Window,
16731 cx: &mut Context<Self>,
16732 ) {
16733 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16734
16735 if self.delegate_expand_excerpts {
16736 cx.emit(EditorEvent::ExpandExcerptsRequested {
16737 excerpt_ids: vec![excerpt],
16738 lines: lines_to_expand,
16739 direction,
16740 });
16741 return;
16742 }
16743
16744 let current_scroll_position = self.scroll_position(cx);
16745 let mut scroll = None;
16746
16747 if direction == ExpandExcerptDirection::Down {
16748 let multi_buffer = self.buffer.read(cx);
16749 let snapshot = multi_buffer.snapshot(cx);
16750 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16751 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16752 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16753 {
16754 let buffer_snapshot = buffer.read(cx).snapshot();
16755 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16756 let last_row = buffer_snapshot.max_point().row;
16757 let lines_below = last_row.saturating_sub(excerpt_end_row);
16758 if lines_below >= lines_to_expand {
16759 scroll = Some(
16760 current_scroll_position
16761 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16762 );
16763 }
16764 }
16765 }
16766 if direction == ExpandExcerptDirection::Up
16767 && self
16768 .buffer
16769 .read(cx)
16770 .snapshot(cx)
16771 .excerpt_before(excerpt)
16772 .is_none()
16773 {
16774 scroll = Some(current_scroll_position);
16775 }
16776
16777 self.buffer.update(cx, |buffer, cx| {
16778 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16779 });
16780
16781 if let Some(new_scroll_position) = scroll {
16782 self.set_scroll_position(new_scroll_position, window, cx);
16783 }
16784 }
16785
16786 pub fn go_to_singleton_buffer_point(
16787 &mut self,
16788 point: Point,
16789 window: &mut Window,
16790 cx: &mut Context<Self>,
16791 ) {
16792 self.go_to_singleton_buffer_range(point..point, window, cx);
16793 }
16794
16795 pub fn go_to_singleton_buffer_range(
16796 &mut self,
16797 range: Range<Point>,
16798 window: &mut Window,
16799 cx: &mut Context<Self>,
16800 ) {
16801 let multibuffer = self.buffer().read(cx);
16802 let Some(buffer) = multibuffer.as_singleton() else {
16803 return;
16804 };
16805 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16806 return;
16807 };
16808 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16809 return;
16810 };
16811 self.change_selections(
16812 SelectionEffects::default().nav_history(true),
16813 window,
16814 cx,
16815 |s| s.select_anchor_ranges([start..end]),
16816 );
16817 }
16818
16819 pub fn go_to_diagnostic(
16820 &mut self,
16821 action: &GoToDiagnostic,
16822 window: &mut Window,
16823 cx: &mut Context<Self>,
16824 ) {
16825 if !self.diagnostics_enabled() {
16826 return;
16827 }
16828 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16829 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16830 }
16831
16832 pub fn go_to_prev_diagnostic(
16833 &mut self,
16834 action: &GoToPreviousDiagnostic,
16835 window: &mut Window,
16836 cx: &mut Context<Self>,
16837 ) {
16838 if !self.diagnostics_enabled() {
16839 return;
16840 }
16841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16842 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16843 }
16844
16845 pub fn go_to_diagnostic_impl(
16846 &mut self,
16847 direction: Direction,
16848 severity: GoToDiagnosticSeverityFilter,
16849 window: &mut Window,
16850 cx: &mut Context<Self>,
16851 ) {
16852 let buffer = self.buffer.read(cx).snapshot(cx);
16853 let selection = self
16854 .selections
16855 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16856
16857 let mut active_group_id = None;
16858 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16859 && active_group.active_range.start.to_offset(&buffer) == selection.start
16860 {
16861 active_group_id = Some(active_group.group_id);
16862 }
16863
16864 fn filtered<'a>(
16865 severity: GoToDiagnosticSeverityFilter,
16866 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16867 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16868 diagnostics
16869 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16870 .filter(|entry| entry.range.start != entry.range.end)
16871 .filter(|entry| !entry.diagnostic.is_unnecessary)
16872 }
16873
16874 let before = filtered(
16875 severity,
16876 buffer
16877 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16878 .filter(|entry| entry.range.start <= selection.start),
16879 );
16880 let after = filtered(
16881 severity,
16882 buffer
16883 .diagnostics_in_range(selection.start..buffer.len())
16884 .filter(|entry| entry.range.start >= selection.start),
16885 );
16886
16887 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16888 if direction == Direction::Prev {
16889 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16890 {
16891 for diagnostic in prev_diagnostics.into_iter().rev() {
16892 if diagnostic.range.start != selection.start
16893 || active_group_id
16894 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16895 {
16896 found = Some(diagnostic);
16897 break 'outer;
16898 }
16899 }
16900 }
16901 } else {
16902 for diagnostic in after.chain(before) {
16903 if diagnostic.range.start != selection.start
16904 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16905 {
16906 found = Some(diagnostic);
16907 break;
16908 }
16909 }
16910 }
16911 let Some(next_diagnostic) = found else {
16912 return;
16913 };
16914
16915 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16916 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16917 return;
16918 };
16919 let snapshot = self.snapshot(window, cx);
16920 if snapshot.intersects_fold(next_diagnostic.range.start) {
16921 self.unfold_ranges(
16922 std::slice::from_ref(&next_diagnostic.range),
16923 true,
16924 false,
16925 cx,
16926 );
16927 }
16928 self.change_selections(Default::default(), window, cx, |s| {
16929 s.select_ranges(vec![
16930 next_diagnostic.range.start..next_diagnostic.range.start,
16931 ])
16932 });
16933 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16934 self.refresh_edit_prediction(false, true, window, cx);
16935 }
16936
16937 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16938 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16939 let snapshot = self.snapshot(window, cx);
16940 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16941 self.go_to_hunk_before_or_after_position(
16942 &snapshot,
16943 selection.head(),
16944 Direction::Next,
16945 window,
16946 cx,
16947 );
16948 }
16949
16950 pub fn go_to_hunk_before_or_after_position(
16951 &mut self,
16952 snapshot: &EditorSnapshot,
16953 position: Point,
16954 direction: Direction,
16955 window: &mut Window,
16956 cx: &mut Context<Editor>,
16957 ) {
16958 let row = if direction == Direction::Next {
16959 self.hunk_after_position(snapshot, position)
16960 .map(|hunk| hunk.row_range.start)
16961 } else {
16962 self.hunk_before_position(snapshot, position)
16963 };
16964
16965 if let Some(row) = row {
16966 let destination = Point::new(row.0, 0);
16967 let autoscroll = Autoscroll::center();
16968
16969 self.unfold_ranges(&[destination..destination], false, false, cx);
16970 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16971 s.select_ranges([destination..destination]);
16972 });
16973 }
16974 }
16975
16976 fn hunk_after_position(
16977 &mut self,
16978 snapshot: &EditorSnapshot,
16979 position: Point,
16980 ) -> Option<MultiBufferDiffHunk> {
16981 snapshot
16982 .buffer_snapshot()
16983 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16984 .find(|hunk| hunk.row_range.start.0 > position.row)
16985 .or_else(|| {
16986 snapshot
16987 .buffer_snapshot()
16988 .diff_hunks_in_range(Point::zero()..position)
16989 .find(|hunk| hunk.row_range.end.0 < position.row)
16990 })
16991 }
16992
16993 fn go_to_prev_hunk(
16994 &mut self,
16995 _: &GoToPreviousHunk,
16996 window: &mut Window,
16997 cx: &mut Context<Self>,
16998 ) {
16999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17000 let snapshot = self.snapshot(window, cx);
17001 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
17002 self.go_to_hunk_before_or_after_position(
17003 &snapshot,
17004 selection.head(),
17005 Direction::Prev,
17006 window,
17007 cx,
17008 );
17009 }
17010
17011 fn hunk_before_position(
17012 &mut self,
17013 snapshot: &EditorSnapshot,
17014 position: Point,
17015 ) -> Option<MultiBufferRow> {
17016 snapshot
17017 .buffer_snapshot()
17018 .diff_hunk_before(position)
17019 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
17020 }
17021
17022 fn go_to_next_change(
17023 &mut self,
17024 _: &GoToNextChange,
17025 window: &mut Window,
17026 cx: &mut Context<Self>,
17027 ) {
17028 if let Some(selections) = self
17029 .change_list
17030 .next_change(1, Direction::Next)
17031 .map(|s| s.to_vec())
17032 {
17033 self.change_selections(Default::default(), window, cx, |s| {
17034 let map = s.display_snapshot();
17035 s.select_display_ranges(selections.iter().map(|a| {
17036 let point = a.to_display_point(&map);
17037 point..point
17038 }))
17039 })
17040 }
17041 }
17042
17043 fn go_to_previous_change(
17044 &mut self,
17045 _: &GoToPreviousChange,
17046 window: &mut Window,
17047 cx: &mut Context<Self>,
17048 ) {
17049 if let Some(selections) = self
17050 .change_list
17051 .next_change(1, Direction::Prev)
17052 .map(|s| s.to_vec())
17053 {
17054 self.change_selections(Default::default(), window, cx, |s| {
17055 let map = s.display_snapshot();
17056 s.select_display_ranges(selections.iter().map(|a| {
17057 let point = a.to_display_point(&map);
17058 point..point
17059 }))
17060 })
17061 }
17062 }
17063
17064 pub fn go_to_next_document_highlight(
17065 &mut self,
17066 _: &GoToNextDocumentHighlight,
17067 window: &mut Window,
17068 cx: &mut Context<Self>,
17069 ) {
17070 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17071 }
17072
17073 pub fn go_to_prev_document_highlight(
17074 &mut self,
17075 _: &GoToPreviousDocumentHighlight,
17076 window: &mut Window,
17077 cx: &mut Context<Self>,
17078 ) {
17079 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17080 }
17081
17082 pub fn go_to_document_highlight_before_or_after_position(
17083 &mut self,
17084 direction: Direction,
17085 window: &mut Window,
17086 cx: &mut Context<Editor>,
17087 ) {
17088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17089 let snapshot = self.snapshot(window, cx);
17090 let buffer = &snapshot.buffer_snapshot();
17091 let position = self
17092 .selections
17093 .newest::<Point>(&snapshot.display_snapshot)
17094 .head();
17095 let anchor_position = buffer.anchor_after(position);
17096
17097 // Get all document highlights (both read and write)
17098 let mut all_highlights = Vec::new();
17099
17100 if let Some((_, read_highlights)) = self
17101 .background_highlights
17102 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17103 {
17104 all_highlights.extend(read_highlights.iter());
17105 }
17106
17107 if let Some((_, write_highlights)) = self
17108 .background_highlights
17109 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17110 {
17111 all_highlights.extend(write_highlights.iter());
17112 }
17113
17114 if all_highlights.is_empty() {
17115 return;
17116 }
17117
17118 // Sort highlights by position
17119 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17120
17121 let target_highlight = match direction {
17122 Direction::Next => {
17123 // Find the first highlight after the current position
17124 all_highlights
17125 .iter()
17126 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17127 }
17128 Direction::Prev => {
17129 // Find the last highlight before the current position
17130 all_highlights
17131 .iter()
17132 .rev()
17133 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17134 }
17135 };
17136
17137 if let Some(highlight) = target_highlight {
17138 let destination = highlight.start.to_point(buffer);
17139 let autoscroll = Autoscroll::center();
17140
17141 self.unfold_ranges(&[destination..destination], false, false, cx);
17142 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17143 s.select_ranges([destination..destination]);
17144 });
17145 }
17146 }
17147
17148 fn go_to_line<T: 'static>(
17149 &mut self,
17150 position: Anchor,
17151 highlight_color: Option<Hsla>,
17152 window: &mut Window,
17153 cx: &mut Context<Self>,
17154 ) {
17155 let snapshot = self.snapshot(window, cx).display_snapshot;
17156 let position = position.to_point(&snapshot.buffer_snapshot());
17157 let start = snapshot
17158 .buffer_snapshot()
17159 .clip_point(Point::new(position.row, 0), Bias::Left);
17160 let end = start + Point::new(1, 0);
17161 let start = snapshot.buffer_snapshot().anchor_before(start);
17162 let end = snapshot.buffer_snapshot().anchor_before(end);
17163
17164 self.highlight_rows::<T>(
17165 start..end,
17166 highlight_color
17167 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17168 Default::default(),
17169 cx,
17170 );
17171
17172 if self.buffer.read(cx).is_singleton() {
17173 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17174 }
17175 }
17176
17177 pub fn go_to_definition(
17178 &mut self,
17179 _: &GoToDefinition,
17180 window: &mut Window,
17181 cx: &mut Context<Self>,
17182 ) -> Task<Result<Navigated>> {
17183 let definition =
17184 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17185 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17186 cx.spawn_in(window, async move |editor, cx| {
17187 if definition.await? == Navigated::Yes {
17188 return Ok(Navigated::Yes);
17189 }
17190 match fallback_strategy {
17191 GoToDefinitionFallback::None => Ok(Navigated::No),
17192 GoToDefinitionFallback::FindAllReferences => {
17193 match editor.update_in(cx, |editor, window, cx| {
17194 editor.find_all_references(&FindAllReferences::default(), window, cx)
17195 })? {
17196 Some(references) => references.await,
17197 None => Ok(Navigated::No),
17198 }
17199 }
17200 }
17201 })
17202 }
17203
17204 pub fn go_to_declaration(
17205 &mut self,
17206 _: &GoToDeclaration,
17207 window: &mut Window,
17208 cx: &mut Context<Self>,
17209 ) -> Task<Result<Navigated>> {
17210 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17211 }
17212
17213 pub fn go_to_declaration_split(
17214 &mut self,
17215 _: &GoToDeclaration,
17216 window: &mut Window,
17217 cx: &mut Context<Self>,
17218 ) -> Task<Result<Navigated>> {
17219 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17220 }
17221
17222 pub fn go_to_implementation(
17223 &mut self,
17224 _: &GoToImplementation,
17225 window: &mut Window,
17226 cx: &mut Context<Self>,
17227 ) -> Task<Result<Navigated>> {
17228 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17229 }
17230
17231 pub fn go_to_implementation_split(
17232 &mut self,
17233 _: &GoToImplementationSplit,
17234 window: &mut Window,
17235 cx: &mut Context<Self>,
17236 ) -> Task<Result<Navigated>> {
17237 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17238 }
17239
17240 pub fn go_to_type_definition(
17241 &mut self,
17242 _: &GoToTypeDefinition,
17243 window: &mut Window,
17244 cx: &mut Context<Self>,
17245 ) -> Task<Result<Navigated>> {
17246 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17247 }
17248
17249 pub fn go_to_definition_split(
17250 &mut self,
17251 _: &GoToDefinitionSplit,
17252 window: &mut Window,
17253 cx: &mut Context<Self>,
17254 ) -> Task<Result<Navigated>> {
17255 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17256 }
17257
17258 pub fn go_to_type_definition_split(
17259 &mut self,
17260 _: &GoToTypeDefinitionSplit,
17261 window: &mut Window,
17262 cx: &mut Context<Self>,
17263 ) -> Task<Result<Navigated>> {
17264 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17265 }
17266
17267 fn go_to_definition_of_kind(
17268 &mut self,
17269 kind: GotoDefinitionKind,
17270 split: bool,
17271 window: &mut Window,
17272 cx: &mut Context<Self>,
17273 ) -> Task<Result<Navigated>> {
17274 let Some(provider) = self.semantics_provider.clone() else {
17275 return Task::ready(Ok(Navigated::No));
17276 };
17277 let head = self
17278 .selections
17279 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17280 .head();
17281 let buffer = self.buffer.read(cx);
17282 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17283 return Task::ready(Ok(Navigated::No));
17284 };
17285 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17286 return Task::ready(Ok(Navigated::No));
17287 };
17288
17289 cx.spawn_in(window, async move |editor, cx| {
17290 let Some(definitions) = definitions.await? else {
17291 return Ok(Navigated::No);
17292 };
17293 let navigated = editor
17294 .update_in(cx, |editor, window, cx| {
17295 editor.navigate_to_hover_links(
17296 Some(kind),
17297 definitions
17298 .into_iter()
17299 .filter(|location| {
17300 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17301 })
17302 .map(HoverLink::Text)
17303 .collect::<Vec<_>>(),
17304 split,
17305 window,
17306 cx,
17307 )
17308 })?
17309 .await?;
17310 anyhow::Ok(navigated)
17311 })
17312 }
17313
17314 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17315 let selection = self.selections.newest_anchor();
17316 let head = selection.head();
17317 let tail = selection.tail();
17318
17319 let Some((buffer, start_position)) =
17320 self.buffer.read(cx).text_anchor_for_position(head, cx)
17321 else {
17322 return;
17323 };
17324
17325 let end_position = if head != tail {
17326 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17327 return;
17328 };
17329 Some(pos)
17330 } else {
17331 None
17332 };
17333
17334 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17335 let url = if let Some(end_pos) = end_position {
17336 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17337 } else {
17338 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17339 };
17340
17341 if let Some(url) = url {
17342 cx.update(|window, cx| {
17343 if parse_zed_link(&url, cx).is_some() {
17344 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17345 } else {
17346 cx.open_url(&url);
17347 }
17348 })?;
17349 }
17350
17351 anyhow::Ok(())
17352 });
17353
17354 url_finder.detach();
17355 }
17356
17357 pub fn open_selected_filename(
17358 &mut self,
17359 _: &OpenSelectedFilename,
17360 window: &mut Window,
17361 cx: &mut Context<Self>,
17362 ) {
17363 let Some(workspace) = self.workspace() else {
17364 return;
17365 };
17366
17367 let position = self.selections.newest_anchor().head();
17368
17369 let Some((buffer, buffer_position)) =
17370 self.buffer.read(cx).text_anchor_for_position(position, cx)
17371 else {
17372 return;
17373 };
17374
17375 let project = self.project.clone();
17376
17377 cx.spawn_in(window, async move |_, cx| {
17378 let result = find_file(&buffer, project, buffer_position, cx).await;
17379
17380 if let Some((_, path)) = result {
17381 workspace
17382 .update_in(cx, |workspace, window, cx| {
17383 workspace.open_resolved_path(path, window, cx)
17384 })?
17385 .await?;
17386 }
17387 anyhow::Ok(())
17388 })
17389 .detach();
17390 }
17391
17392 pub(crate) fn navigate_to_hover_links(
17393 &mut self,
17394 kind: Option<GotoDefinitionKind>,
17395 definitions: Vec<HoverLink>,
17396 split: bool,
17397 window: &mut Window,
17398 cx: &mut Context<Editor>,
17399 ) -> Task<Result<Navigated>> {
17400 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17401 let mut first_url_or_file = None;
17402 let definitions: Vec<_> = definitions
17403 .into_iter()
17404 .filter_map(|def| match def {
17405 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17406 HoverLink::InlayHint(lsp_location, server_id) => {
17407 let computation =
17408 self.compute_target_location(lsp_location, server_id, window, cx);
17409 Some(cx.background_spawn(computation))
17410 }
17411 HoverLink::Url(url) => {
17412 first_url_or_file = Some(Either::Left(url));
17413 None
17414 }
17415 HoverLink::File(path) => {
17416 first_url_or_file = Some(Either::Right(path));
17417 None
17418 }
17419 })
17420 .collect();
17421
17422 let workspace = self.workspace();
17423
17424 cx.spawn_in(window, async move |editor, cx| {
17425 let locations: Vec<Location> = future::join_all(definitions)
17426 .await
17427 .into_iter()
17428 .filter_map(|location| location.transpose())
17429 .collect::<Result<_>>()
17430 .context("location tasks")?;
17431 let mut locations = cx.update(|_, cx| {
17432 locations
17433 .into_iter()
17434 .map(|location| {
17435 let buffer = location.buffer.read(cx);
17436 (location.buffer, location.range.to_point(buffer))
17437 })
17438 .into_group_map()
17439 })?;
17440 let mut num_locations = 0;
17441 for ranges in locations.values_mut() {
17442 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17443 ranges.dedup();
17444 num_locations += ranges.len();
17445 }
17446
17447 if num_locations > 1 {
17448 let tab_kind = match kind {
17449 Some(GotoDefinitionKind::Implementation) => "Implementations",
17450 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17451 Some(GotoDefinitionKind::Declaration) => "Declarations",
17452 Some(GotoDefinitionKind::Type) => "Types",
17453 };
17454 let title = editor
17455 .update_in(cx, |_, _, cx| {
17456 let target = locations
17457 .iter()
17458 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17459 .map(|(buffer, location)| {
17460 buffer
17461 .read(cx)
17462 .text_for_range(location.clone())
17463 .collect::<String>()
17464 })
17465 .filter(|text| !text.contains('\n'))
17466 .unique()
17467 .take(3)
17468 .join(", ");
17469 if target.is_empty() {
17470 tab_kind.to_owned()
17471 } else {
17472 format!("{tab_kind} for {target}")
17473 }
17474 })
17475 .context("buffer title")?;
17476
17477 let Some(workspace) = workspace else {
17478 return Ok(Navigated::No);
17479 };
17480
17481 let opened = workspace
17482 .update_in(cx, |workspace, window, cx| {
17483 let allow_preview = PreviewTabsSettings::get_global(cx)
17484 .enable_preview_multibuffer_from_code_navigation;
17485 Self::open_locations_in_multibuffer(
17486 workspace,
17487 locations,
17488 title,
17489 split,
17490 allow_preview,
17491 MultibufferSelectionMode::First,
17492 window,
17493 cx,
17494 )
17495 })
17496 .is_ok();
17497
17498 anyhow::Ok(Navigated::from_bool(opened))
17499 } else if num_locations == 0 {
17500 // If there is one url or file, open it directly
17501 match first_url_or_file {
17502 Some(Either::Left(url)) => {
17503 cx.update(|window, cx| {
17504 if parse_zed_link(&url, cx).is_some() {
17505 window
17506 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17507 } else {
17508 cx.open_url(&url);
17509 }
17510 })?;
17511 Ok(Navigated::Yes)
17512 }
17513 Some(Either::Right(path)) => {
17514 // TODO(andrew): respect preview tab settings
17515 // `enable_keep_preview_on_code_navigation` and
17516 // `enable_preview_file_from_code_navigation`
17517 let Some(workspace) = workspace else {
17518 return Ok(Navigated::No);
17519 };
17520 workspace
17521 .update_in(cx, |workspace, window, cx| {
17522 workspace.open_resolved_path(path, window, cx)
17523 })?
17524 .await?;
17525 Ok(Navigated::Yes)
17526 }
17527 None => Ok(Navigated::No),
17528 }
17529 } else {
17530 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17531 let target_range = target_ranges.first().unwrap().clone();
17532
17533 editor.update_in(cx, |editor, window, cx| {
17534 let range = target_range.to_point(target_buffer.read(cx));
17535 let range = editor.range_for_match(&range);
17536 let range = collapse_multiline_range(range);
17537
17538 if !split
17539 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17540 {
17541 editor.go_to_singleton_buffer_range(range, window, cx);
17542 } else {
17543 let Some(workspace) = workspace else {
17544 return Navigated::No;
17545 };
17546 let pane = workspace.read(cx).active_pane().clone();
17547 window.defer(cx, move |window, cx| {
17548 let target_editor: Entity<Self> =
17549 workspace.update(cx, |workspace, cx| {
17550 let pane = if split {
17551 workspace.adjacent_pane(window, cx)
17552 } else {
17553 workspace.active_pane().clone()
17554 };
17555
17556 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17557 let keep_old_preview = preview_tabs_settings
17558 .enable_keep_preview_on_code_navigation;
17559 let allow_new_preview = preview_tabs_settings
17560 .enable_preview_file_from_code_navigation;
17561
17562 workspace.open_project_item(
17563 pane,
17564 target_buffer.clone(),
17565 true,
17566 true,
17567 keep_old_preview,
17568 allow_new_preview,
17569 window,
17570 cx,
17571 )
17572 });
17573 target_editor.update(cx, |target_editor, cx| {
17574 // When selecting a definition in a different buffer, disable the nav history
17575 // to avoid creating a history entry at the previous cursor location.
17576 pane.update(cx, |pane, _| pane.disable_history());
17577 target_editor.go_to_singleton_buffer_range(range, window, cx);
17578 pane.update(cx, |pane, _| pane.enable_history());
17579 });
17580 });
17581 }
17582 Navigated::Yes
17583 })
17584 }
17585 })
17586 }
17587
17588 fn compute_target_location(
17589 &self,
17590 lsp_location: lsp::Location,
17591 server_id: LanguageServerId,
17592 window: &mut Window,
17593 cx: &mut Context<Self>,
17594 ) -> Task<anyhow::Result<Option<Location>>> {
17595 let Some(project) = self.project.clone() else {
17596 return Task::ready(Ok(None));
17597 };
17598
17599 cx.spawn_in(window, async move |editor, cx| {
17600 let location_task = editor.update(cx, |_, cx| {
17601 project.update(cx, |project, cx| {
17602 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17603 })
17604 })?;
17605 let location = Some({
17606 let target_buffer_handle = location_task.await.context("open local buffer")?;
17607 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17608 let target_start = target_buffer
17609 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17610 let target_end = target_buffer
17611 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17612 target_buffer.anchor_after(target_start)
17613 ..target_buffer.anchor_before(target_end)
17614 })?;
17615 Location {
17616 buffer: target_buffer_handle,
17617 range,
17618 }
17619 });
17620 Ok(location)
17621 })
17622 }
17623
17624 fn go_to_next_reference(
17625 &mut self,
17626 _: &GoToNextReference,
17627 window: &mut Window,
17628 cx: &mut Context<Self>,
17629 ) {
17630 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17631 if let Some(task) = task {
17632 task.detach();
17633 };
17634 }
17635
17636 fn go_to_prev_reference(
17637 &mut self,
17638 _: &GoToPreviousReference,
17639 window: &mut Window,
17640 cx: &mut Context<Self>,
17641 ) {
17642 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17643 if let Some(task) = task {
17644 task.detach();
17645 };
17646 }
17647
17648 pub fn go_to_reference_before_or_after_position(
17649 &mut self,
17650 direction: Direction,
17651 count: usize,
17652 window: &mut Window,
17653 cx: &mut Context<Self>,
17654 ) -> Option<Task<Result<()>>> {
17655 let selection = self.selections.newest_anchor();
17656 let head = selection.head();
17657
17658 let multi_buffer = self.buffer.read(cx);
17659
17660 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17661 let workspace = self.workspace()?;
17662 let project = workspace.read(cx).project().clone();
17663 let references =
17664 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17665 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17666 let Some(locations) = references.await? else {
17667 return Ok(());
17668 };
17669
17670 if locations.is_empty() {
17671 // totally normal - the cursor may be on something which is not
17672 // a symbol (e.g. a keyword)
17673 log::info!("no references found under cursor");
17674 return Ok(());
17675 }
17676
17677 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17678
17679 let (locations, current_location_index) =
17680 multi_buffer.update(cx, |multi_buffer, cx| {
17681 let mut locations = locations
17682 .into_iter()
17683 .filter_map(|loc| {
17684 let start = multi_buffer.buffer_anchor_to_anchor(
17685 &loc.buffer,
17686 loc.range.start,
17687 cx,
17688 )?;
17689 let end = multi_buffer.buffer_anchor_to_anchor(
17690 &loc.buffer,
17691 loc.range.end,
17692 cx,
17693 )?;
17694 Some(start..end)
17695 })
17696 .collect::<Vec<_>>();
17697
17698 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17699 // There is an O(n) implementation, but given this list will be
17700 // small (usually <100 items), the extra O(log(n)) factor isn't
17701 // worth the (surprisingly large amount of) extra complexity.
17702 locations
17703 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17704
17705 let head_offset = head.to_offset(&multi_buffer_snapshot);
17706
17707 let current_location_index = locations.iter().position(|loc| {
17708 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17709 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17710 });
17711
17712 (locations, current_location_index)
17713 })?;
17714
17715 let Some(current_location_index) = current_location_index else {
17716 // This indicates something has gone wrong, because we already
17717 // handle the "no references" case above
17718 log::error!(
17719 "failed to find current reference under cursor. Total references: {}",
17720 locations.len()
17721 );
17722 return Ok(());
17723 };
17724
17725 let destination_location_index = match direction {
17726 Direction::Next => (current_location_index + count) % locations.len(),
17727 Direction::Prev => {
17728 (current_location_index + locations.len() - count % locations.len())
17729 % locations.len()
17730 }
17731 };
17732
17733 // TODO(cameron): is this needed?
17734 // the thinking is to avoid "jumping to the current location" (avoid
17735 // polluting "jumplist" in vim terms)
17736 if current_location_index == destination_location_index {
17737 return Ok(());
17738 }
17739
17740 let Range { start, end } = locations[destination_location_index];
17741
17742 editor.update_in(cx, |editor, window, cx| {
17743 let effects = SelectionEffects::default();
17744
17745 editor.unfold_ranges(&[start..end], false, false, cx);
17746 editor.change_selections(effects, window, cx, |s| {
17747 s.select_ranges([start..start]);
17748 });
17749 })?;
17750
17751 Ok(())
17752 }))
17753 }
17754
17755 pub fn find_all_references(
17756 &mut self,
17757 action: &FindAllReferences,
17758 window: &mut Window,
17759 cx: &mut Context<Self>,
17760 ) -> Option<Task<Result<Navigated>>> {
17761 let always_open_multibuffer = action.always_open_multibuffer;
17762 let selection = self.selections.newest_anchor();
17763 let multi_buffer = self.buffer.read(cx);
17764 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17765 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17766 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17767 let head = selection_offset.head();
17768
17769 let head_anchor = multi_buffer_snapshot.anchor_at(
17770 head,
17771 if head < selection_offset.tail() {
17772 Bias::Right
17773 } else {
17774 Bias::Left
17775 },
17776 );
17777
17778 match self
17779 .find_all_references_task_sources
17780 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17781 {
17782 Ok(_) => {
17783 log::info!(
17784 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17785 );
17786 return None;
17787 }
17788 Err(i) => {
17789 self.find_all_references_task_sources.insert(i, head_anchor);
17790 }
17791 }
17792
17793 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17794 let workspace = self.workspace()?;
17795 let project = workspace.read(cx).project().clone();
17796 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17797 Some(cx.spawn_in(window, async move |editor, cx| {
17798 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17799 if let Ok(i) = editor
17800 .find_all_references_task_sources
17801 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17802 {
17803 editor.find_all_references_task_sources.remove(i);
17804 }
17805 });
17806
17807 let Some(locations) = references.await? else {
17808 return anyhow::Ok(Navigated::No);
17809 };
17810 let mut locations = cx.update(|_, cx| {
17811 locations
17812 .into_iter()
17813 .map(|location| {
17814 let buffer = location.buffer.read(cx);
17815 (location.buffer, location.range.to_point(buffer))
17816 })
17817 // if special-casing the single-match case, remove ranges
17818 // that intersect current selection
17819 .filter(|(location_buffer, location)| {
17820 if always_open_multibuffer || &buffer != location_buffer {
17821 return true;
17822 }
17823
17824 !location.contains_inclusive(&selection_point.range())
17825 })
17826 .into_group_map()
17827 })?;
17828 if locations.is_empty() {
17829 return anyhow::Ok(Navigated::No);
17830 }
17831 for ranges in locations.values_mut() {
17832 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17833 ranges.dedup();
17834 }
17835 let mut num_locations = 0;
17836 for ranges in locations.values_mut() {
17837 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17838 ranges.dedup();
17839 num_locations += ranges.len();
17840 }
17841
17842 if num_locations == 1 && !always_open_multibuffer {
17843 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17844 let target_range = target_ranges.first().unwrap().clone();
17845
17846 return editor.update_in(cx, |editor, window, cx| {
17847 let range = target_range.to_point(target_buffer.read(cx));
17848 let range = editor.range_for_match(&range);
17849 let range = range.start..range.start;
17850
17851 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17852 editor.go_to_singleton_buffer_range(range, window, cx);
17853 } else {
17854 let pane = workspace.read(cx).active_pane().clone();
17855 window.defer(cx, move |window, cx| {
17856 let target_editor: Entity<Self> =
17857 workspace.update(cx, |workspace, cx| {
17858 let pane = workspace.active_pane().clone();
17859
17860 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17861 let keep_old_preview = preview_tabs_settings
17862 .enable_keep_preview_on_code_navigation;
17863 let allow_new_preview = preview_tabs_settings
17864 .enable_preview_file_from_code_navigation;
17865
17866 workspace.open_project_item(
17867 pane,
17868 target_buffer.clone(),
17869 true,
17870 true,
17871 keep_old_preview,
17872 allow_new_preview,
17873 window,
17874 cx,
17875 )
17876 });
17877 target_editor.update(cx, |target_editor, cx| {
17878 // When selecting a definition in a different buffer, disable the nav history
17879 // to avoid creating a history entry at the previous cursor location.
17880 pane.update(cx, |pane, _| pane.disable_history());
17881 target_editor.go_to_singleton_buffer_range(range, window, cx);
17882 pane.update(cx, |pane, _| pane.enable_history());
17883 });
17884 });
17885 }
17886 Navigated::No
17887 });
17888 }
17889
17890 workspace.update_in(cx, |workspace, window, cx| {
17891 let target = locations
17892 .iter()
17893 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17894 .map(|(buffer, location)| {
17895 buffer
17896 .read(cx)
17897 .text_for_range(location.clone())
17898 .collect::<String>()
17899 })
17900 .filter(|text| !text.contains('\n'))
17901 .unique()
17902 .take(3)
17903 .join(", ");
17904 let title = if target.is_empty() {
17905 "References".to_owned()
17906 } else {
17907 format!("References to {target}")
17908 };
17909 let allow_preview = PreviewTabsSettings::get_global(cx)
17910 .enable_preview_multibuffer_from_code_navigation;
17911 Self::open_locations_in_multibuffer(
17912 workspace,
17913 locations,
17914 title,
17915 false,
17916 allow_preview,
17917 MultibufferSelectionMode::First,
17918 window,
17919 cx,
17920 );
17921 Navigated::Yes
17922 })
17923 }))
17924 }
17925
17926 /// Opens a multibuffer with the given project locations in it.
17927 pub fn open_locations_in_multibuffer(
17928 workspace: &mut Workspace,
17929 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17930 title: String,
17931 split: bool,
17932 allow_preview: bool,
17933 multibuffer_selection_mode: MultibufferSelectionMode,
17934 window: &mut Window,
17935 cx: &mut Context<Workspace>,
17936 ) {
17937 if locations.is_empty() {
17938 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17939 return;
17940 }
17941
17942 let capability = workspace.project().read(cx).capability();
17943 let mut ranges = <Vec<Range<Anchor>>>::new();
17944
17945 // a key to find existing multibuffer editors with the same set of locations
17946 // to prevent us from opening more and more multibuffer tabs for searches and the like
17947 let mut key = (title.clone(), vec![]);
17948 let excerpt_buffer = cx.new(|cx| {
17949 let key = &mut key.1;
17950 let mut multibuffer = MultiBuffer::new(capability);
17951 for (buffer, mut ranges_for_buffer) in locations {
17952 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17953 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17954 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17955 PathKey::for_buffer(&buffer, cx),
17956 buffer.clone(),
17957 ranges_for_buffer,
17958 multibuffer_context_lines(cx),
17959 cx,
17960 );
17961 ranges.extend(new_ranges)
17962 }
17963
17964 multibuffer.with_title(title)
17965 });
17966 let existing = workspace.active_pane().update(cx, |pane, cx| {
17967 pane.items()
17968 .filter_map(|item| item.downcast::<Editor>())
17969 .find(|editor| {
17970 editor
17971 .read(cx)
17972 .lookup_key
17973 .as_ref()
17974 .and_then(|it| {
17975 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17976 })
17977 .is_some_and(|it| *it == key)
17978 })
17979 });
17980 let was_existing = existing.is_some();
17981 let editor = existing.unwrap_or_else(|| {
17982 cx.new(|cx| {
17983 let mut editor = Editor::for_multibuffer(
17984 excerpt_buffer,
17985 Some(workspace.project().clone()),
17986 window,
17987 cx,
17988 );
17989 editor.lookup_key = Some(Box::new(key));
17990 editor
17991 })
17992 });
17993 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17994 MultibufferSelectionMode::First => {
17995 if let Some(first_range) = ranges.first() {
17996 editor.change_selections(
17997 SelectionEffects::no_scroll(),
17998 window,
17999 cx,
18000 |selections| {
18001 selections.clear_disjoint();
18002 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
18003 },
18004 );
18005 }
18006 editor.highlight_background::<Self>(
18007 &ranges,
18008 |_, theme| theme.colors().editor_highlighted_line_background,
18009 cx,
18010 );
18011 }
18012 MultibufferSelectionMode::All => {
18013 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
18014 selections.clear_disjoint();
18015 selections.select_anchor_ranges(ranges);
18016 });
18017 }
18018 });
18019
18020 let item = Box::new(editor);
18021
18022 let pane = if split {
18023 workspace.adjacent_pane(window, cx)
18024 } else {
18025 workspace.active_pane().clone()
18026 };
18027 let activate_pane = split;
18028
18029 let mut destination_index = None;
18030 pane.update(cx, |pane, cx| {
18031 if allow_preview && !was_existing {
18032 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18033 }
18034 if was_existing && !allow_preview {
18035 pane.unpreview_item_if_preview(item.item_id());
18036 }
18037 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18038 });
18039 }
18040
18041 pub fn rename(
18042 &mut self,
18043 _: &Rename,
18044 window: &mut Window,
18045 cx: &mut Context<Self>,
18046 ) -> Option<Task<Result<()>>> {
18047 use language::ToOffset as _;
18048
18049 let provider = self.semantics_provider.clone()?;
18050 let selection = self.selections.newest_anchor().clone();
18051 let (cursor_buffer, cursor_buffer_position) = self
18052 .buffer
18053 .read(cx)
18054 .text_anchor_for_position(selection.head(), cx)?;
18055 let (tail_buffer, cursor_buffer_position_end) = self
18056 .buffer
18057 .read(cx)
18058 .text_anchor_for_position(selection.tail(), cx)?;
18059 if tail_buffer != cursor_buffer {
18060 return None;
18061 }
18062
18063 let snapshot = cursor_buffer.read(cx).snapshot();
18064 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18065 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18066 let prepare_rename = provider
18067 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18068 .unwrap_or_else(|| Task::ready(Ok(None)));
18069 drop(snapshot);
18070
18071 Some(cx.spawn_in(window, async move |this, cx| {
18072 let rename_range = if let Some(range) = prepare_rename.await? {
18073 Some(range)
18074 } else {
18075 this.update(cx, |this, cx| {
18076 let buffer = this.buffer.read(cx).snapshot(cx);
18077 let mut buffer_highlights = this
18078 .document_highlights_for_position(selection.head(), &buffer)
18079 .filter(|highlight| {
18080 highlight.start.excerpt_id == selection.head().excerpt_id
18081 && highlight.end.excerpt_id == selection.head().excerpt_id
18082 });
18083 buffer_highlights
18084 .next()
18085 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18086 })?
18087 };
18088 if let Some(rename_range) = rename_range {
18089 this.update_in(cx, |this, window, cx| {
18090 let snapshot = cursor_buffer.read(cx).snapshot();
18091 let rename_buffer_range = rename_range.to_offset(&snapshot);
18092 let cursor_offset_in_rename_range =
18093 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18094 let cursor_offset_in_rename_range_end =
18095 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18096
18097 this.take_rename(false, window, cx);
18098 let buffer = this.buffer.read(cx).read(cx);
18099 let cursor_offset = selection.head().to_offset(&buffer);
18100 let rename_start =
18101 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18102 let rename_end = rename_start + rename_buffer_range.len();
18103 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18104 let mut old_highlight_id = None;
18105 let old_name: Arc<str> = buffer
18106 .chunks(rename_start..rename_end, true)
18107 .map(|chunk| {
18108 if old_highlight_id.is_none() {
18109 old_highlight_id = chunk.syntax_highlight_id;
18110 }
18111 chunk.text
18112 })
18113 .collect::<String>()
18114 .into();
18115
18116 drop(buffer);
18117
18118 // Position the selection in the rename editor so that it matches the current selection.
18119 this.show_local_selections = false;
18120 let rename_editor = cx.new(|cx| {
18121 let mut editor = Editor::single_line(window, cx);
18122 editor.buffer.update(cx, |buffer, cx| {
18123 buffer.edit(
18124 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18125 None,
18126 cx,
18127 )
18128 });
18129 let cursor_offset_in_rename_range =
18130 MultiBufferOffset(cursor_offset_in_rename_range);
18131 let cursor_offset_in_rename_range_end =
18132 MultiBufferOffset(cursor_offset_in_rename_range_end);
18133 let rename_selection_range = match cursor_offset_in_rename_range
18134 .cmp(&cursor_offset_in_rename_range_end)
18135 {
18136 Ordering::Equal => {
18137 editor.select_all(&SelectAll, window, cx);
18138 return editor;
18139 }
18140 Ordering::Less => {
18141 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18142 }
18143 Ordering::Greater => {
18144 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18145 }
18146 };
18147 if rename_selection_range.end.0 > old_name.len() {
18148 editor.select_all(&SelectAll, window, cx);
18149 } else {
18150 editor.change_selections(Default::default(), window, cx, |s| {
18151 s.select_ranges([rename_selection_range]);
18152 });
18153 }
18154 editor
18155 });
18156 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18157 if e == &EditorEvent::Focused {
18158 cx.emit(EditorEvent::FocusedIn)
18159 }
18160 })
18161 .detach();
18162
18163 let write_highlights =
18164 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18165 let read_highlights =
18166 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18167 let ranges = write_highlights
18168 .iter()
18169 .flat_map(|(_, ranges)| ranges.iter())
18170 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18171 .cloned()
18172 .collect();
18173
18174 this.highlight_text::<Rename>(
18175 ranges,
18176 HighlightStyle {
18177 fade_out: Some(0.6),
18178 ..Default::default()
18179 },
18180 cx,
18181 );
18182 let rename_focus_handle = rename_editor.focus_handle(cx);
18183 window.focus(&rename_focus_handle, cx);
18184 let block_id = this.insert_blocks(
18185 [BlockProperties {
18186 style: BlockStyle::Flex,
18187 placement: BlockPlacement::Below(range.start),
18188 height: Some(1),
18189 render: Arc::new({
18190 let rename_editor = rename_editor.clone();
18191 move |cx: &mut BlockContext| {
18192 let mut text_style = cx.editor_style.text.clone();
18193 if let Some(highlight_style) = old_highlight_id
18194 .and_then(|h| h.style(&cx.editor_style.syntax))
18195 {
18196 text_style = text_style.highlight(highlight_style);
18197 }
18198 div()
18199 .block_mouse_except_scroll()
18200 .pl(cx.anchor_x)
18201 .child(EditorElement::new(
18202 &rename_editor,
18203 EditorStyle {
18204 background: cx.theme().system().transparent,
18205 local_player: cx.editor_style.local_player,
18206 text: text_style,
18207 scrollbar_width: cx.editor_style.scrollbar_width,
18208 syntax: cx.editor_style.syntax.clone(),
18209 status: cx.editor_style.status.clone(),
18210 inlay_hints_style: HighlightStyle {
18211 font_weight: Some(FontWeight::BOLD),
18212 ..make_inlay_hints_style(cx.app)
18213 },
18214 edit_prediction_styles: make_suggestion_styles(
18215 cx.app,
18216 ),
18217 ..EditorStyle::default()
18218 },
18219 ))
18220 .into_any_element()
18221 }
18222 }),
18223 priority: 0,
18224 }],
18225 Some(Autoscroll::fit()),
18226 cx,
18227 )[0];
18228 this.pending_rename = Some(RenameState {
18229 range,
18230 old_name,
18231 editor: rename_editor,
18232 block_id,
18233 });
18234 })?;
18235 }
18236
18237 Ok(())
18238 }))
18239 }
18240
18241 pub fn confirm_rename(
18242 &mut self,
18243 _: &ConfirmRename,
18244 window: &mut Window,
18245 cx: &mut Context<Self>,
18246 ) -> Option<Task<Result<()>>> {
18247 let rename = self.take_rename(false, window, cx)?;
18248 let workspace = self.workspace()?.downgrade();
18249 let (buffer, start) = self
18250 .buffer
18251 .read(cx)
18252 .text_anchor_for_position(rename.range.start, cx)?;
18253 let (end_buffer, _) = self
18254 .buffer
18255 .read(cx)
18256 .text_anchor_for_position(rename.range.end, cx)?;
18257 if buffer != end_buffer {
18258 return None;
18259 }
18260
18261 let old_name = rename.old_name;
18262 let new_name = rename.editor.read(cx).text(cx);
18263
18264 let rename = self.semantics_provider.as_ref()?.perform_rename(
18265 &buffer,
18266 start,
18267 new_name.clone(),
18268 cx,
18269 )?;
18270
18271 Some(cx.spawn_in(window, async move |editor, cx| {
18272 let project_transaction = rename.await?;
18273 Self::open_project_transaction(
18274 &editor,
18275 workspace,
18276 project_transaction,
18277 format!("Rename: {} → {}", old_name, new_name),
18278 cx,
18279 )
18280 .await?;
18281
18282 editor.update(cx, |editor, cx| {
18283 editor.refresh_document_highlights(cx);
18284 })?;
18285 Ok(())
18286 }))
18287 }
18288
18289 fn take_rename(
18290 &mut self,
18291 moving_cursor: bool,
18292 window: &mut Window,
18293 cx: &mut Context<Self>,
18294 ) -> Option<RenameState> {
18295 let rename = self.pending_rename.take()?;
18296 if rename.editor.focus_handle(cx).is_focused(window) {
18297 window.focus(&self.focus_handle, cx);
18298 }
18299
18300 self.remove_blocks(
18301 [rename.block_id].into_iter().collect(),
18302 Some(Autoscroll::fit()),
18303 cx,
18304 );
18305 self.clear_highlights::<Rename>(cx);
18306 self.show_local_selections = true;
18307
18308 if moving_cursor {
18309 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18310 editor
18311 .selections
18312 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18313 .head()
18314 });
18315
18316 // Update the selection to match the position of the selection inside
18317 // the rename editor.
18318 let snapshot = self.buffer.read(cx).read(cx);
18319 let rename_range = rename.range.to_offset(&snapshot);
18320 let cursor_in_editor = snapshot
18321 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18322 .min(rename_range.end);
18323 drop(snapshot);
18324
18325 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18326 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18327 });
18328 } else {
18329 self.refresh_document_highlights(cx);
18330 }
18331
18332 Some(rename)
18333 }
18334
18335 pub fn pending_rename(&self) -> Option<&RenameState> {
18336 self.pending_rename.as_ref()
18337 }
18338
18339 fn format(
18340 &mut self,
18341 _: &Format,
18342 window: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) -> Option<Task<Result<()>>> {
18345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18346
18347 let project = match &self.project {
18348 Some(project) => project.clone(),
18349 None => return None,
18350 };
18351
18352 Some(self.perform_format(
18353 project,
18354 FormatTrigger::Manual,
18355 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18356 window,
18357 cx,
18358 ))
18359 }
18360
18361 fn format_selections(
18362 &mut self,
18363 _: &FormatSelections,
18364 window: &mut Window,
18365 cx: &mut Context<Self>,
18366 ) -> Option<Task<Result<()>>> {
18367 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18368
18369 let project = match &self.project {
18370 Some(project) => project.clone(),
18371 None => return None,
18372 };
18373
18374 let ranges = self
18375 .selections
18376 .all_adjusted(&self.display_snapshot(cx))
18377 .into_iter()
18378 .map(|selection| selection.range())
18379 .collect_vec();
18380
18381 Some(self.perform_format(
18382 project,
18383 FormatTrigger::Manual,
18384 FormatTarget::Ranges(ranges),
18385 window,
18386 cx,
18387 ))
18388 }
18389
18390 fn perform_format(
18391 &mut self,
18392 project: Entity<Project>,
18393 trigger: FormatTrigger,
18394 target: FormatTarget,
18395 window: &mut Window,
18396 cx: &mut Context<Self>,
18397 ) -> Task<Result<()>> {
18398 let buffer = self.buffer.clone();
18399 let (buffers, target) = match target {
18400 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18401 FormatTarget::Ranges(selection_ranges) => {
18402 let multi_buffer = buffer.read(cx);
18403 let snapshot = multi_buffer.read(cx);
18404 let mut buffers = HashSet::default();
18405 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18406 BTreeMap::new();
18407 for selection_range in selection_ranges {
18408 for (buffer, buffer_range, _) in
18409 snapshot.range_to_buffer_ranges(selection_range)
18410 {
18411 let buffer_id = buffer.remote_id();
18412 let start = buffer.anchor_before(buffer_range.start);
18413 let end = buffer.anchor_after(buffer_range.end);
18414 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18415 buffer_id_to_ranges
18416 .entry(buffer_id)
18417 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18418 .or_insert_with(|| vec![start..end]);
18419 }
18420 }
18421 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18422 }
18423 };
18424
18425 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18426 let selections_prev = transaction_id_prev
18427 .and_then(|transaction_id_prev| {
18428 // default to selections as they were after the last edit, if we have them,
18429 // instead of how they are now.
18430 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18431 // will take you back to where you made the last edit, instead of staying where you scrolled
18432 self.selection_history
18433 .transaction(transaction_id_prev)
18434 .map(|t| t.0.clone())
18435 })
18436 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18437
18438 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18439 let format = project.update(cx, |project, cx| {
18440 project.format(buffers, target, true, trigger, cx)
18441 });
18442
18443 cx.spawn_in(window, async move |editor, cx| {
18444 let transaction = futures::select_biased! {
18445 transaction = format.log_err().fuse() => transaction,
18446 () = timeout => {
18447 log::warn!("timed out waiting for formatting");
18448 None
18449 }
18450 };
18451
18452 buffer
18453 .update(cx, |buffer, cx| {
18454 if let Some(transaction) = transaction
18455 && !buffer.is_singleton()
18456 {
18457 buffer.push_transaction(&transaction.0, cx);
18458 }
18459 cx.notify();
18460 })
18461 .ok();
18462
18463 if let Some(transaction_id_now) =
18464 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18465 {
18466 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18467 if has_new_transaction {
18468 _ = editor.update(cx, |editor, _| {
18469 editor
18470 .selection_history
18471 .insert_transaction(transaction_id_now, selections_prev);
18472 });
18473 }
18474 }
18475
18476 Ok(())
18477 })
18478 }
18479
18480 fn organize_imports(
18481 &mut self,
18482 _: &OrganizeImports,
18483 window: &mut Window,
18484 cx: &mut Context<Self>,
18485 ) -> Option<Task<Result<()>>> {
18486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18487 let project = match &self.project {
18488 Some(project) => project.clone(),
18489 None => return None,
18490 };
18491 Some(self.perform_code_action_kind(
18492 project,
18493 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18494 window,
18495 cx,
18496 ))
18497 }
18498
18499 fn perform_code_action_kind(
18500 &mut self,
18501 project: Entity<Project>,
18502 kind: CodeActionKind,
18503 window: &mut Window,
18504 cx: &mut Context<Self>,
18505 ) -> Task<Result<()>> {
18506 let buffer = self.buffer.clone();
18507 let buffers = buffer.read(cx).all_buffers();
18508 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18509 let apply_action = project.update(cx, |project, cx| {
18510 project.apply_code_action_kind(buffers, kind, true, cx)
18511 });
18512 cx.spawn_in(window, async move |_, cx| {
18513 let transaction = futures::select_biased! {
18514 () = timeout => {
18515 log::warn!("timed out waiting for executing code action");
18516 None
18517 }
18518 transaction = apply_action.log_err().fuse() => transaction,
18519 };
18520 buffer
18521 .update(cx, |buffer, cx| {
18522 // check if we need this
18523 if let Some(transaction) = transaction
18524 && !buffer.is_singleton()
18525 {
18526 buffer.push_transaction(&transaction.0, cx);
18527 }
18528 cx.notify();
18529 })
18530 .ok();
18531 Ok(())
18532 })
18533 }
18534
18535 pub fn restart_language_server(
18536 &mut self,
18537 _: &RestartLanguageServer,
18538 _: &mut Window,
18539 cx: &mut Context<Self>,
18540 ) {
18541 if let Some(project) = self.project.clone() {
18542 self.buffer.update(cx, |multi_buffer, cx| {
18543 project.update(cx, |project, cx| {
18544 project.restart_language_servers_for_buffers(
18545 multi_buffer.all_buffers().into_iter().collect(),
18546 HashSet::default(),
18547 cx,
18548 );
18549 });
18550 })
18551 }
18552 }
18553
18554 pub fn stop_language_server(
18555 &mut self,
18556 _: &StopLanguageServer,
18557 _: &mut Window,
18558 cx: &mut Context<Self>,
18559 ) {
18560 if let Some(project) = self.project.clone() {
18561 self.buffer.update(cx, |multi_buffer, cx| {
18562 project.update(cx, |project, cx| {
18563 project.stop_language_servers_for_buffers(
18564 multi_buffer.all_buffers().into_iter().collect(),
18565 HashSet::default(),
18566 cx,
18567 );
18568 });
18569 });
18570 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18571 }
18572 }
18573
18574 fn cancel_language_server_work(
18575 workspace: &mut Workspace,
18576 _: &actions::CancelLanguageServerWork,
18577 _: &mut Window,
18578 cx: &mut Context<Workspace>,
18579 ) {
18580 let project = workspace.project();
18581 let buffers = workspace
18582 .active_item(cx)
18583 .and_then(|item| item.act_as::<Editor>(cx))
18584 .map_or(HashSet::default(), |editor| {
18585 editor.read(cx).buffer.read(cx).all_buffers()
18586 });
18587 project.update(cx, |project, cx| {
18588 project.cancel_language_server_work_for_buffers(buffers, cx);
18589 });
18590 }
18591
18592 fn show_character_palette(
18593 &mut self,
18594 _: &ShowCharacterPalette,
18595 window: &mut Window,
18596 _: &mut Context<Self>,
18597 ) {
18598 window.show_character_palette();
18599 }
18600
18601 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18602 if !self.diagnostics_enabled() {
18603 return;
18604 }
18605
18606 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18607 let buffer = self.buffer.read(cx).snapshot(cx);
18608 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18609 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18610 let is_valid = buffer
18611 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18612 .any(|entry| {
18613 entry.diagnostic.is_primary
18614 && !entry.range.is_empty()
18615 && entry.range.start == primary_range_start
18616 && entry.diagnostic.message == active_diagnostics.active_message
18617 });
18618
18619 if !is_valid {
18620 self.dismiss_diagnostics(cx);
18621 }
18622 }
18623 }
18624
18625 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18626 match &self.active_diagnostics {
18627 ActiveDiagnostic::Group(group) => Some(group),
18628 _ => None,
18629 }
18630 }
18631
18632 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18633 if !self.diagnostics_enabled() {
18634 return;
18635 }
18636 self.dismiss_diagnostics(cx);
18637 self.active_diagnostics = ActiveDiagnostic::All;
18638 }
18639
18640 fn activate_diagnostics(
18641 &mut self,
18642 buffer_id: BufferId,
18643 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18644 window: &mut Window,
18645 cx: &mut Context<Self>,
18646 ) {
18647 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18648 return;
18649 }
18650 self.dismiss_diagnostics(cx);
18651 let snapshot = self.snapshot(window, cx);
18652 let buffer = self.buffer.read(cx).snapshot(cx);
18653 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18654 return;
18655 };
18656
18657 let diagnostic_group = buffer
18658 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18659 .collect::<Vec<_>>();
18660
18661 let language_registry = self
18662 .project()
18663 .map(|project| project.read(cx).languages().clone());
18664
18665 let blocks = renderer.render_group(
18666 diagnostic_group,
18667 buffer_id,
18668 snapshot,
18669 cx.weak_entity(),
18670 language_registry,
18671 cx,
18672 );
18673
18674 let blocks = self.display_map.update(cx, |display_map, cx| {
18675 display_map.insert_blocks(blocks, cx).into_iter().collect()
18676 });
18677 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18678 active_range: buffer.anchor_before(diagnostic.range.start)
18679 ..buffer.anchor_after(diagnostic.range.end),
18680 active_message: diagnostic.diagnostic.message.clone(),
18681 group_id: diagnostic.diagnostic.group_id,
18682 blocks,
18683 });
18684 cx.notify();
18685 }
18686
18687 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18688 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18689 return;
18690 };
18691
18692 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18693 if let ActiveDiagnostic::Group(group) = prev {
18694 self.display_map.update(cx, |display_map, cx| {
18695 display_map.remove_blocks(group.blocks, cx);
18696 });
18697 cx.notify();
18698 }
18699 }
18700
18701 /// Disable inline diagnostics rendering for this editor.
18702 pub fn disable_inline_diagnostics(&mut self) {
18703 self.inline_diagnostics_enabled = false;
18704 self.inline_diagnostics_update = Task::ready(());
18705 self.inline_diagnostics.clear();
18706 }
18707
18708 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18709 self.diagnostics_enabled = false;
18710 self.dismiss_diagnostics(cx);
18711 self.inline_diagnostics_update = Task::ready(());
18712 self.inline_diagnostics.clear();
18713 }
18714
18715 pub fn disable_word_completions(&mut self) {
18716 self.word_completions_enabled = false;
18717 }
18718
18719 pub fn diagnostics_enabled(&self) -> bool {
18720 self.diagnostics_enabled && self.mode.is_full()
18721 }
18722
18723 pub fn inline_diagnostics_enabled(&self) -> bool {
18724 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18725 }
18726
18727 pub fn show_inline_diagnostics(&self) -> bool {
18728 self.show_inline_diagnostics
18729 }
18730
18731 pub fn toggle_inline_diagnostics(
18732 &mut self,
18733 _: &ToggleInlineDiagnostics,
18734 window: &mut Window,
18735 cx: &mut Context<Editor>,
18736 ) {
18737 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18738 self.refresh_inline_diagnostics(false, window, cx);
18739 }
18740
18741 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18742 self.diagnostics_max_severity = severity;
18743 self.display_map.update(cx, |display_map, _| {
18744 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18745 });
18746 }
18747
18748 pub fn toggle_diagnostics(
18749 &mut self,
18750 _: &ToggleDiagnostics,
18751 window: &mut Window,
18752 cx: &mut Context<Editor>,
18753 ) {
18754 if !self.diagnostics_enabled() {
18755 return;
18756 }
18757
18758 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18759 EditorSettings::get_global(cx)
18760 .diagnostics_max_severity
18761 .filter(|severity| severity != &DiagnosticSeverity::Off)
18762 .unwrap_or(DiagnosticSeverity::Hint)
18763 } else {
18764 DiagnosticSeverity::Off
18765 };
18766 self.set_max_diagnostics_severity(new_severity, cx);
18767 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18768 self.active_diagnostics = ActiveDiagnostic::None;
18769 self.inline_diagnostics_update = Task::ready(());
18770 self.inline_diagnostics.clear();
18771 } else {
18772 self.refresh_inline_diagnostics(false, window, cx);
18773 }
18774
18775 cx.notify();
18776 }
18777
18778 pub fn toggle_minimap(
18779 &mut self,
18780 _: &ToggleMinimap,
18781 window: &mut Window,
18782 cx: &mut Context<Editor>,
18783 ) {
18784 if self.supports_minimap(cx) {
18785 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18786 }
18787 }
18788
18789 fn refresh_inline_diagnostics(
18790 &mut self,
18791 debounce: bool,
18792 window: &mut Window,
18793 cx: &mut Context<Self>,
18794 ) {
18795 let max_severity = ProjectSettings::get_global(cx)
18796 .diagnostics
18797 .inline
18798 .max_severity
18799 .unwrap_or(self.diagnostics_max_severity);
18800
18801 if !self.inline_diagnostics_enabled()
18802 || !self.diagnostics_enabled()
18803 || !self.show_inline_diagnostics
18804 || max_severity == DiagnosticSeverity::Off
18805 {
18806 self.inline_diagnostics_update = Task::ready(());
18807 self.inline_diagnostics.clear();
18808 return;
18809 }
18810
18811 let debounce_ms = ProjectSettings::get_global(cx)
18812 .diagnostics
18813 .inline
18814 .update_debounce_ms;
18815 let debounce = if debounce && debounce_ms > 0 {
18816 Some(Duration::from_millis(debounce_ms))
18817 } else {
18818 None
18819 };
18820 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18821 if let Some(debounce) = debounce {
18822 cx.background_executor().timer(debounce).await;
18823 }
18824 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18825 editor
18826 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18827 .ok()
18828 }) else {
18829 return;
18830 };
18831
18832 let new_inline_diagnostics = cx
18833 .background_spawn(async move {
18834 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18835 for diagnostic_entry in
18836 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18837 {
18838 let message = diagnostic_entry
18839 .diagnostic
18840 .message
18841 .split_once('\n')
18842 .map(|(line, _)| line)
18843 .map(SharedString::new)
18844 .unwrap_or_else(|| {
18845 SharedString::new(&*diagnostic_entry.diagnostic.message)
18846 });
18847 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18848 let (Ok(i) | Err(i)) = inline_diagnostics
18849 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18850 inline_diagnostics.insert(
18851 i,
18852 (
18853 start_anchor,
18854 InlineDiagnostic {
18855 message,
18856 group_id: diagnostic_entry.diagnostic.group_id,
18857 start: diagnostic_entry.range.start.to_point(&snapshot),
18858 is_primary: diagnostic_entry.diagnostic.is_primary,
18859 severity: diagnostic_entry.diagnostic.severity,
18860 },
18861 ),
18862 );
18863 }
18864 inline_diagnostics
18865 })
18866 .await;
18867
18868 editor
18869 .update(cx, |editor, cx| {
18870 editor.inline_diagnostics = new_inline_diagnostics;
18871 cx.notify();
18872 })
18873 .ok();
18874 });
18875 }
18876
18877 fn pull_diagnostics(
18878 &mut self,
18879 buffer_id: Option<BufferId>,
18880 window: &Window,
18881 cx: &mut Context<Self>,
18882 ) -> Option<()> {
18883 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18884 return None;
18885 }
18886 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18887 .diagnostics
18888 .lsp_pull_diagnostics;
18889 if !pull_diagnostics_settings.enabled {
18890 return None;
18891 }
18892 let project = self.project()?.downgrade();
18893
18894 let mut edited_buffer_ids = HashSet::default();
18895 let mut edited_worktree_ids = HashSet::default();
18896 let edited_buffers = match buffer_id {
18897 Some(buffer_id) => {
18898 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18899 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18900 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18901 edited_worktree_ids.insert(worktree_id);
18902 vec![buffer]
18903 }
18904 None => self
18905 .buffer()
18906 .read(cx)
18907 .all_buffers()
18908 .into_iter()
18909 .filter(|buffer| {
18910 let buffer = buffer.read(cx);
18911 match buffer.file().map(|f| f.worktree_id(cx)) {
18912 Some(worktree_id) => {
18913 edited_buffer_ids.insert(buffer.remote_id());
18914 edited_worktree_ids.insert(worktree_id);
18915 true
18916 }
18917 None => false,
18918 }
18919 })
18920 .collect::<Vec<_>>(),
18921 };
18922
18923 if edited_buffers.is_empty() {
18924 self.pull_diagnostics_task = Task::ready(());
18925 self.pull_diagnostics_background_task = Task::ready(());
18926 return None;
18927 }
18928
18929 let mut already_used_buffers = HashSet::default();
18930 let related_open_buffers = self
18931 .workspace
18932 .as_ref()
18933 .and_then(|(workspace, _)| workspace.upgrade())
18934 .into_iter()
18935 .flat_map(|workspace| workspace.read(cx).panes())
18936 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18937 .filter(|editor| editor != &cx.entity())
18938 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18939 .filter(|buffer| {
18940 let buffer = buffer.read(cx);
18941 let buffer_id = buffer.remote_id();
18942 if already_used_buffers.insert(buffer_id) {
18943 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18944 return !edited_buffer_ids.contains(&buffer_id)
18945 && !edited_worktree_ids.contains(&worktree_id);
18946 }
18947 }
18948 false
18949 })
18950 .collect::<Vec<_>>();
18951
18952 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18953 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18954 if buffers.is_empty() {
18955 return Task::ready(());
18956 }
18957 let project_weak = project.clone();
18958 cx.spawn_in(window, async move |_, cx| {
18959 cx.background_executor().timer(delay).await;
18960
18961 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18962 buffers
18963 .into_iter()
18964 .filter_map(|buffer| {
18965 project_weak
18966 .update(cx, |project, cx| {
18967 project.lsp_store().update(cx, |lsp_store, cx| {
18968 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18969 })
18970 })
18971 .ok()
18972 })
18973 .collect::<FuturesUnordered<_>>()
18974 }) else {
18975 return;
18976 };
18977
18978 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18979 if let Err(e) = pull_task {
18980 log::error!("Failed to update project diagnostics: {e:#}");
18981 }
18982 }
18983 })
18984 };
18985
18986 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18987 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18988
18989 Some(())
18990 }
18991
18992 pub fn set_selections_from_remote(
18993 &mut self,
18994 selections: Vec<Selection<Anchor>>,
18995 pending_selection: Option<Selection<Anchor>>,
18996 window: &mut Window,
18997 cx: &mut Context<Self>,
18998 ) {
18999 let old_cursor_position = self.selections.newest_anchor().head();
19000 self.selections
19001 .change_with(&self.display_snapshot(cx), |s| {
19002 s.select_anchors(selections);
19003 if let Some(pending_selection) = pending_selection {
19004 s.set_pending(pending_selection, SelectMode::Character);
19005 } else {
19006 s.clear_pending();
19007 }
19008 });
19009 self.selections_did_change(
19010 false,
19011 &old_cursor_position,
19012 SelectionEffects::default(),
19013 window,
19014 cx,
19015 );
19016 }
19017
19018 pub fn transact(
19019 &mut self,
19020 window: &mut Window,
19021 cx: &mut Context<Self>,
19022 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19023 ) -> Option<TransactionId> {
19024 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19025 this.start_transaction_at(Instant::now(), window, cx);
19026 update(this, window, cx);
19027 this.end_transaction_at(Instant::now(), cx)
19028 })
19029 }
19030
19031 pub fn start_transaction_at(
19032 &mut self,
19033 now: Instant,
19034 window: &mut Window,
19035 cx: &mut Context<Self>,
19036 ) -> Option<TransactionId> {
19037 self.end_selection(window, cx);
19038 if let Some(tx_id) = self
19039 .buffer
19040 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19041 {
19042 self.selection_history
19043 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19044 cx.emit(EditorEvent::TransactionBegun {
19045 transaction_id: tx_id,
19046 });
19047 Some(tx_id)
19048 } else {
19049 None
19050 }
19051 }
19052
19053 pub fn end_transaction_at(
19054 &mut self,
19055 now: Instant,
19056 cx: &mut Context<Self>,
19057 ) -> Option<TransactionId> {
19058 if let Some(transaction_id) = self
19059 .buffer
19060 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19061 {
19062 if let Some((_, end_selections)) =
19063 self.selection_history.transaction_mut(transaction_id)
19064 {
19065 *end_selections = Some(self.selections.disjoint_anchors_arc());
19066 } else {
19067 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19068 }
19069
19070 cx.emit(EditorEvent::Edited { transaction_id });
19071 Some(transaction_id)
19072 } else {
19073 None
19074 }
19075 }
19076
19077 pub fn modify_transaction_selection_history(
19078 &mut self,
19079 transaction_id: TransactionId,
19080 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19081 ) -> bool {
19082 self.selection_history
19083 .transaction_mut(transaction_id)
19084 .map(modify)
19085 .is_some()
19086 }
19087
19088 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19089 if self.selection_mark_mode {
19090 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19091 s.move_with(|_, sel| {
19092 sel.collapse_to(sel.head(), SelectionGoal::None);
19093 });
19094 })
19095 }
19096 self.selection_mark_mode = true;
19097 cx.notify();
19098 }
19099
19100 pub fn swap_selection_ends(
19101 &mut self,
19102 _: &actions::SwapSelectionEnds,
19103 window: &mut Window,
19104 cx: &mut Context<Self>,
19105 ) {
19106 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19107 s.move_with(|_, sel| {
19108 if sel.start != sel.end {
19109 sel.reversed = !sel.reversed
19110 }
19111 });
19112 });
19113 self.request_autoscroll(Autoscroll::newest(), cx);
19114 cx.notify();
19115 }
19116
19117 pub fn toggle_focus(
19118 workspace: &mut Workspace,
19119 _: &actions::ToggleFocus,
19120 window: &mut Window,
19121 cx: &mut Context<Workspace>,
19122 ) {
19123 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19124 return;
19125 };
19126 workspace.activate_item(&item, true, true, window, cx);
19127 }
19128
19129 pub fn toggle_fold(
19130 &mut self,
19131 _: &actions::ToggleFold,
19132 window: &mut Window,
19133 cx: &mut Context<Self>,
19134 ) {
19135 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19136 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19137 let selection = self.selections.newest::<Point>(&display_map);
19138
19139 let range = if selection.is_empty() {
19140 let point = selection.head().to_display_point(&display_map);
19141 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19142 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19143 .to_point(&display_map);
19144 start..end
19145 } else {
19146 selection.range()
19147 };
19148 if display_map.folds_in_range(range).next().is_some() {
19149 self.unfold_lines(&Default::default(), window, cx)
19150 } else {
19151 self.fold(&Default::default(), window, cx)
19152 }
19153 } else {
19154 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19155 let buffer_ids: HashSet<_> = self
19156 .selections
19157 .disjoint_anchor_ranges()
19158 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19159 .collect();
19160
19161 let should_unfold = buffer_ids
19162 .iter()
19163 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19164
19165 for buffer_id in buffer_ids {
19166 if should_unfold {
19167 self.unfold_buffer(buffer_id, cx);
19168 } else {
19169 self.fold_buffer(buffer_id, cx);
19170 }
19171 }
19172 }
19173 }
19174
19175 pub fn toggle_fold_recursive(
19176 &mut self,
19177 _: &actions::ToggleFoldRecursive,
19178 window: &mut Window,
19179 cx: &mut Context<Self>,
19180 ) {
19181 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19182
19183 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19184 let range = if selection.is_empty() {
19185 let point = selection.head().to_display_point(&display_map);
19186 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19187 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19188 .to_point(&display_map);
19189 start..end
19190 } else {
19191 selection.range()
19192 };
19193 if display_map.folds_in_range(range).next().is_some() {
19194 self.unfold_recursive(&Default::default(), window, cx)
19195 } else {
19196 self.fold_recursive(&Default::default(), window, cx)
19197 }
19198 }
19199
19200 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19201 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19202 let mut to_fold = Vec::new();
19203 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19204 let selections = self.selections.all_adjusted(&display_map);
19205
19206 for selection in selections {
19207 let range = selection.range().sorted();
19208 let buffer_start_row = range.start.row;
19209
19210 if range.start.row != range.end.row {
19211 let mut found = false;
19212 let mut row = range.start.row;
19213 while row <= range.end.row {
19214 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19215 {
19216 found = true;
19217 row = crease.range().end.row + 1;
19218 to_fold.push(crease);
19219 } else {
19220 row += 1
19221 }
19222 }
19223 if found {
19224 continue;
19225 }
19226 }
19227
19228 for row in (0..=range.start.row).rev() {
19229 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19230 && crease.range().end.row >= buffer_start_row
19231 {
19232 to_fold.push(crease);
19233 if row <= range.start.row {
19234 break;
19235 }
19236 }
19237 }
19238 }
19239
19240 self.fold_creases(to_fold, true, window, cx);
19241 } else {
19242 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19243 let buffer_ids = self
19244 .selections
19245 .disjoint_anchor_ranges()
19246 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19247 .collect::<HashSet<_>>();
19248 for buffer_id in buffer_ids {
19249 self.fold_buffer(buffer_id, cx);
19250 }
19251 }
19252 }
19253
19254 pub fn toggle_fold_all(
19255 &mut self,
19256 _: &actions::ToggleFoldAll,
19257 window: &mut Window,
19258 cx: &mut Context<Self>,
19259 ) {
19260 if self.buffer.read(cx).is_singleton() {
19261 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19262 let has_folds = display_map
19263 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19264 .next()
19265 .is_some();
19266
19267 if has_folds {
19268 self.unfold_all(&actions::UnfoldAll, window, cx);
19269 } else {
19270 self.fold_all(&actions::FoldAll, window, cx);
19271 }
19272 } else {
19273 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19274 let should_unfold = buffer_ids
19275 .iter()
19276 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19277
19278 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19279 editor
19280 .update_in(cx, |editor, _, cx| {
19281 for buffer_id in buffer_ids {
19282 if should_unfold {
19283 editor.unfold_buffer(buffer_id, cx);
19284 } else {
19285 editor.fold_buffer(buffer_id, cx);
19286 }
19287 }
19288 })
19289 .ok();
19290 });
19291 }
19292 }
19293
19294 fn fold_at_level(
19295 &mut self,
19296 fold_at: &FoldAtLevel,
19297 window: &mut Window,
19298 cx: &mut Context<Self>,
19299 ) {
19300 if !self.buffer.read(cx).is_singleton() {
19301 return;
19302 }
19303
19304 let fold_at_level = fold_at.0;
19305 let snapshot = self.buffer.read(cx).snapshot(cx);
19306 let mut to_fold = Vec::new();
19307 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19308
19309 let row_ranges_to_keep: Vec<Range<u32>> = self
19310 .selections
19311 .all::<Point>(&self.display_snapshot(cx))
19312 .into_iter()
19313 .map(|sel| sel.start.row..sel.end.row)
19314 .collect();
19315
19316 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19317 while start_row < end_row {
19318 match self
19319 .snapshot(window, cx)
19320 .crease_for_buffer_row(MultiBufferRow(start_row))
19321 {
19322 Some(crease) => {
19323 let nested_start_row = crease.range().start.row + 1;
19324 let nested_end_row = crease.range().end.row;
19325
19326 if current_level < fold_at_level {
19327 stack.push((nested_start_row, nested_end_row, current_level + 1));
19328 } else if current_level == fold_at_level {
19329 // Fold iff there is no selection completely contained within the fold region
19330 if !row_ranges_to_keep.iter().any(|selection| {
19331 selection.end >= nested_start_row
19332 && selection.start <= nested_end_row
19333 }) {
19334 to_fold.push(crease);
19335 }
19336 }
19337
19338 start_row = nested_end_row + 1;
19339 }
19340 None => start_row += 1,
19341 }
19342 }
19343 }
19344
19345 self.fold_creases(to_fold, true, window, cx);
19346 }
19347
19348 pub fn fold_at_level_1(
19349 &mut self,
19350 _: &actions::FoldAtLevel1,
19351 window: &mut Window,
19352 cx: &mut Context<Self>,
19353 ) {
19354 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19355 }
19356
19357 pub fn fold_at_level_2(
19358 &mut self,
19359 _: &actions::FoldAtLevel2,
19360 window: &mut Window,
19361 cx: &mut Context<Self>,
19362 ) {
19363 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19364 }
19365
19366 pub fn fold_at_level_3(
19367 &mut self,
19368 _: &actions::FoldAtLevel3,
19369 window: &mut Window,
19370 cx: &mut Context<Self>,
19371 ) {
19372 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19373 }
19374
19375 pub fn fold_at_level_4(
19376 &mut self,
19377 _: &actions::FoldAtLevel4,
19378 window: &mut Window,
19379 cx: &mut Context<Self>,
19380 ) {
19381 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19382 }
19383
19384 pub fn fold_at_level_5(
19385 &mut self,
19386 _: &actions::FoldAtLevel5,
19387 window: &mut Window,
19388 cx: &mut Context<Self>,
19389 ) {
19390 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19391 }
19392
19393 pub fn fold_at_level_6(
19394 &mut self,
19395 _: &actions::FoldAtLevel6,
19396 window: &mut Window,
19397 cx: &mut Context<Self>,
19398 ) {
19399 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19400 }
19401
19402 pub fn fold_at_level_7(
19403 &mut self,
19404 _: &actions::FoldAtLevel7,
19405 window: &mut Window,
19406 cx: &mut Context<Self>,
19407 ) {
19408 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19409 }
19410
19411 pub fn fold_at_level_8(
19412 &mut self,
19413 _: &actions::FoldAtLevel8,
19414 window: &mut Window,
19415 cx: &mut Context<Self>,
19416 ) {
19417 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19418 }
19419
19420 pub fn fold_at_level_9(
19421 &mut self,
19422 _: &actions::FoldAtLevel9,
19423 window: &mut Window,
19424 cx: &mut Context<Self>,
19425 ) {
19426 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19427 }
19428
19429 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19430 if self.buffer.read(cx).is_singleton() {
19431 let mut fold_ranges = Vec::new();
19432 let snapshot = self.buffer.read(cx).snapshot(cx);
19433
19434 for row in 0..snapshot.max_row().0 {
19435 if let Some(foldable_range) = self
19436 .snapshot(window, cx)
19437 .crease_for_buffer_row(MultiBufferRow(row))
19438 {
19439 fold_ranges.push(foldable_range);
19440 }
19441 }
19442
19443 self.fold_creases(fold_ranges, true, window, cx);
19444 } else {
19445 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19446 editor
19447 .update_in(cx, |editor, _, cx| {
19448 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19449 editor.fold_buffer(buffer_id, cx);
19450 }
19451 })
19452 .ok();
19453 });
19454 }
19455 }
19456
19457 pub fn fold_function_bodies(
19458 &mut self,
19459 _: &actions::FoldFunctionBodies,
19460 window: &mut Window,
19461 cx: &mut Context<Self>,
19462 ) {
19463 let snapshot = self.buffer.read(cx).snapshot(cx);
19464
19465 let ranges = snapshot
19466 .text_object_ranges(
19467 MultiBufferOffset(0)..snapshot.len(),
19468 TreeSitterOptions::default(),
19469 )
19470 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19471 .collect::<Vec<_>>();
19472
19473 let creases = ranges
19474 .into_iter()
19475 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19476 .collect();
19477
19478 self.fold_creases(creases, true, window, cx);
19479 }
19480
19481 pub fn fold_recursive(
19482 &mut self,
19483 _: &actions::FoldRecursive,
19484 window: &mut Window,
19485 cx: &mut Context<Self>,
19486 ) {
19487 let mut to_fold = Vec::new();
19488 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19489 let selections = self.selections.all_adjusted(&display_map);
19490
19491 for selection in selections {
19492 let range = selection.range().sorted();
19493 let buffer_start_row = range.start.row;
19494
19495 if range.start.row != range.end.row {
19496 let mut found = false;
19497 for row in range.start.row..=range.end.row {
19498 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19499 found = true;
19500 to_fold.push(crease);
19501 }
19502 }
19503 if found {
19504 continue;
19505 }
19506 }
19507
19508 for row in (0..=range.start.row).rev() {
19509 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19510 if crease.range().end.row >= buffer_start_row {
19511 to_fold.push(crease);
19512 } else {
19513 break;
19514 }
19515 }
19516 }
19517 }
19518
19519 self.fold_creases(to_fold, true, window, cx);
19520 }
19521
19522 pub fn fold_at(
19523 &mut self,
19524 buffer_row: MultiBufferRow,
19525 window: &mut Window,
19526 cx: &mut Context<Self>,
19527 ) {
19528 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19529
19530 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19531 let autoscroll = self
19532 .selections
19533 .all::<Point>(&display_map)
19534 .iter()
19535 .any(|selection| crease.range().overlaps(&selection.range()));
19536
19537 self.fold_creases(vec![crease], autoscroll, window, cx);
19538 }
19539 }
19540
19541 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19542 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19543 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19544 let buffer = display_map.buffer_snapshot();
19545 let selections = self.selections.all::<Point>(&display_map);
19546 let ranges = selections
19547 .iter()
19548 .map(|s| {
19549 let range = s.display_range(&display_map).sorted();
19550 let mut start = range.start.to_point(&display_map);
19551 let mut end = range.end.to_point(&display_map);
19552 start.column = 0;
19553 end.column = buffer.line_len(MultiBufferRow(end.row));
19554 start..end
19555 })
19556 .collect::<Vec<_>>();
19557
19558 self.unfold_ranges(&ranges, true, true, cx);
19559 } else {
19560 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19561 let buffer_ids = self
19562 .selections
19563 .disjoint_anchor_ranges()
19564 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19565 .collect::<HashSet<_>>();
19566 for buffer_id in buffer_ids {
19567 self.unfold_buffer(buffer_id, cx);
19568 }
19569 }
19570 }
19571
19572 pub fn unfold_recursive(
19573 &mut self,
19574 _: &UnfoldRecursive,
19575 _window: &mut Window,
19576 cx: &mut Context<Self>,
19577 ) {
19578 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19579 let selections = self.selections.all::<Point>(&display_map);
19580 let ranges = selections
19581 .iter()
19582 .map(|s| {
19583 let mut range = s.display_range(&display_map).sorted();
19584 *range.start.column_mut() = 0;
19585 *range.end.column_mut() = display_map.line_len(range.end.row());
19586 let start = range.start.to_point(&display_map);
19587 let end = range.end.to_point(&display_map);
19588 start..end
19589 })
19590 .collect::<Vec<_>>();
19591
19592 self.unfold_ranges(&ranges, true, true, cx);
19593 }
19594
19595 pub fn unfold_at(
19596 &mut self,
19597 buffer_row: MultiBufferRow,
19598 _window: &mut Window,
19599 cx: &mut Context<Self>,
19600 ) {
19601 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19602
19603 let intersection_range = Point::new(buffer_row.0, 0)
19604 ..Point::new(
19605 buffer_row.0,
19606 display_map.buffer_snapshot().line_len(buffer_row),
19607 );
19608
19609 let autoscroll = self
19610 .selections
19611 .all::<Point>(&display_map)
19612 .iter()
19613 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19614
19615 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19616 }
19617
19618 pub fn unfold_all(
19619 &mut self,
19620 _: &actions::UnfoldAll,
19621 _window: &mut Window,
19622 cx: &mut Context<Self>,
19623 ) {
19624 if self.buffer.read(cx).is_singleton() {
19625 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19626 self.unfold_ranges(
19627 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19628 true,
19629 true,
19630 cx,
19631 );
19632 } else {
19633 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19634 editor
19635 .update(cx, |editor, cx| {
19636 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19637 editor.unfold_buffer(buffer_id, cx);
19638 }
19639 })
19640 .ok();
19641 });
19642 }
19643 }
19644
19645 pub fn fold_selected_ranges(
19646 &mut self,
19647 _: &FoldSelectedRanges,
19648 window: &mut Window,
19649 cx: &mut Context<Self>,
19650 ) {
19651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19652 let selections = self.selections.all_adjusted(&display_map);
19653 let ranges = selections
19654 .into_iter()
19655 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19656 .collect::<Vec<_>>();
19657 self.fold_creases(ranges, true, window, cx);
19658 }
19659
19660 pub fn fold_ranges<T: ToOffset + Clone>(
19661 &mut self,
19662 ranges: Vec<Range<T>>,
19663 auto_scroll: bool,
19664 window: &mut Window,
19665 cx: &mut Context<Self>,
19666 ) {
19667 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19668 let ranges = ranges
19669 .into_iter()
19670 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19671 .collect::<Vec<_>>();
19672 self.fold_creases(ranges, auto_scroll, window, cx);
19673 }
19674
19675 pub fn fold_creases<T: ToOffset + Clone>(
19676 &mut self,
19677 creases: Vec<Crease<T>>,
19678 auto_scroll: bool,
19679 _window: &mut Window,
19680 cx: &mut Context<Self>,
19681 ) {
19682 if creases.is_empty() {
19683 return;
19684 }
19685
19686 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19687
19688 if auto_scroll {
19689 self.request_autoscroll(Autoscroll::fit(), cx);
19690 }
19691
19692 cx.notify();
19693
19694 self.scrollbar_marker_state.dirty = true;
19695 self.folds_did_change(cx);
19696 }
19697
19698 /// Removes any folds whose ranges intersect any of the given ranges.
19699 pub fn unfold_ranges<T: ToOffset + Clone>(
19700 &mut self,
19701 ranges: &[Range<T>],
19702 inclusive: bool,
19703 auto_scroll: bool,
19704 cx: &mut Context<Self>,
19705 ) {
19706 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19707 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19708 });
19709 self.folds_did_change(cx);
19710 }
19711
19712 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19713 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19714 return;
19715 }
19716
19717 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19718 self.display_map.update(cx, |display_map, cx| {
19719 display_map.fold_buffers([buffer_id], cx)
19720 });
19721
19722 let snapshot = self.display_snapshot(cx);
19723 self.selections.change_with(&snapshot, |selections| {
19724 selections.remove_selections_from_buffer(buffer_id);
19725 });
19726
19727 cx.emit(EditorEvent::BufferFoldToggled {
19728 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19729 folded: true,
19730 });
19731 cx.notify();
19732 }
19733
19734 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19735 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19736 return;
19737 }
19738 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19739 self.display_map.update(cx, |display_map, cx| {
19740 display_map.unfold_buffers([buffer_id], cx);
19741 });
19742 cx.emit(EditorEvent::BufferFoldToggled {
19743 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19744 folded: false,
19745 });
19746 cx.notify();
19747 }
19748
19749 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19750 self.display_map.read(cx).is_buffer_folded(buffer)
19751 }
19752
19753 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19754 self.display_map.read(cx).folded_buffers()
19755 }
19756
19757 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19758 self.display_map.update(cx, |display_map, cx| {
19759 display_map.disable_header_for_buffer(buffer_id, cx);
19760 });
19761 cx.notify();
19762 }
19763
19764 /// Removes any folds with the given ranges.
19765 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19766 &mut self,
19767 ranges: &[Range<T>],
19768 type_id: TypeId,
19769 auto_scroll: bool,
19770 cx: &mut Context<Self>,
19771 ) {
19772 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19773 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19774 });
19775 self.folds_did_change(cx);
19776 }
19777
19778 fn remove_folds_with<T: ToOffset + Clone>(
19779 &mut self,
19780 ranges: &[Range<T>],
19781 auto_scroll: bool,
19782 cx: &mut Context<Self>,
19783 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19784 ) {
19785 if ranges.is_empty() {
19786 return;
19787 }
19788
19789 let mut buffers_affected = HashSet::default();
19790 let multi_buffer = self.buffer().read(cx);
19791 for range in ranges {
19792 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19793 buffers_affected.insert(buffer.read(cx).remote_id());
19794 };
19795 }
19796
19797 self.display_map.update(cx, update);
19798
19799 if auto_scroll {
19800 self.request_autoscroll(Autoscroll::fit(), cx);
19801 }
19802
19803 cx.notify();
19804 self.scrollbar_marker_state.dirty = true;
19805 self.active_indent_guides_state.dirty = true;
19806 }
19807
19808 pub fn update_renderer_widths(
19809 &mut self,
19810 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19811 cx: &mut Context<Self>,
19812 ) -> bool {
19813 self.display_map
19814 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19815 }
19816
19817 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19818 self.display_map.read(cx).fold_placeholder.clone()
19819 }
19820
19821 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19822 self.buffer.update(cx, |buffer, cx| {
19823 buffer.set_all_diff_hunks_expanded(cx);
19824 });
19825 }
19826
19827 pub fn expand_all_diff_hunks(
19828 &mut self,
19829 _: &ExpandAllDiffHunks,
19830 _window: &mut Window,
19831 cx: &mut Context<Self>,
19832 ) {
19833 self.buffer.update(cx, |buffer, cx| {
19834 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19835 });
19836 }
19837
19838 pub fn collapse_all_diff_hunks(
19839 &mut self,
19840 _: &CollapseAllDiffHunks,
19841 _window: &mut Window,
19842 cx: &mut Context<Self>,
19843 ) {
19844 self.buffer.update(cx, |buffer, cx| {
19845 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19846 });
19847 }
19848
19849 pub fn toggle_selected_diff_hunks(
19850 &mut self,
19851 _: &ToggleSelectedDiffHunks,
19852 _window: &mut Window,
19853 cx: &mut Context<Self>,
19854 ) {
19855 let ranges: Vec<_> = self
19856 .selections
19857 .disjoint_anchors()
19858 .iter()
19859 .map(|s| s.range())
19860 .collect();
19861 self.toggle_diff_hunks_in_ranges(ranges, cx);
19862 }
19863
19864 pub fn diff_hunks_in_ranges<'a>(
19865 &'a self,
19866 ranges: &'a [Range<Anchor>],
19867 buffer: &'a MultiBufferSnapshot,
19868 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19869 ranges.iter().flat_map(move |range| {
19870 let end_excerpt_id = range.end.excerpt_id;
19871 let range = range.to_point(buffer);
19872 let mut peek_end = range.end;
19873 if range.end.row < buffer.max_row().0 {
19874 peek_end = Point::new(range.end.row + 1, 0);
19875 }
19876 buffer
19877 .diff_hunks_in_range(range.start..peek_end)
19878 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19879 })
19880 }
19881
19882 pub fn has_stageable_diff_hunks_in_ranges(
19883 &self,
19884 ranges: &[Range<Anchor>],
19885 snapshot: &MultiBufferSnapshot,
19886 ) -> bool {
19887 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19888 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19889 }
19890
19891 pub fn toggle_staged_selected_diff_hunks(
19892 &mut self,
19893 _: &::git::ToggleStaged,
19894 _: &mut Window,
19895 cx: &mut Context<Self>,
19896 ) {
19897 let snapshot = self.buffer.read(cx).snapshot(cx);
19898 let ranges: Vec<_> = self
19899 .selections
19900 .disjoint_anchors()
19901 .iter()
19902 .map(|s| s.range())
19903 .collect();
19904 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19905 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19906 }
19907
19908 pub fn set_render_diff_hunk_controls(
19909 &mut self,
19910 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19911 cx: &mut Context<Self>,
19912 ) {
19913 self.render_diff_hunk_controls = render_diff_hunk_controls;
19914 cx.notify();
19915 }
19916
19917 pub fn stage_and_next(
19918 &mut self,
19919 _: &::git::StageAndNext,
19920 window: &mut Window,
19921 cx: &mut Context<Self>,
19922 ) {
19923 self.do_stage_or_unstage_and_next(true, window, cx);
19924 }
19925
19926 pub fn unstage_and_next(
19927 &mut self,
19928 _: &::git::UnstageAndNext,
19929 window: &mut Window,
19930 cx: &mut Context<Self>,
19931 ) {
19932 self.do_stage_or_unstage_and_next(false, window, cx);
19933 }
19934
19935 pub fn stage_or_unstage_diff_hunks(
19936 &mut self,
19937 stage: bool,
19938 ranges: Vec<Range<Anchor>>,
19939 cx: &mut Context<Self>,
19940 ) {
19941 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19942 cx.spawn(async move |this, cx| {
19943 task.await?;
19944 this.update(cx, |this, cx| {
19945 let snapshot = this.buffer.read(cx).snapshot(cx);
19946 let chunk_by = this
19947 .diff_hunks_in_ranges(&ranges, &snapshot)
19948 .chunk_by(|hunk| hunk.buffer_id);
19949 for (buffer_id, hunks) in &chunk_by {
19950 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19951 }
19952 })
19953 })
19954 .detach_and_log_err(cx);
19955 }
19956
19957 fn save_buffers_for_ranges_if_needed(
19958 &mut self,
19959 ranges: &[Range<Anchor>],
19960 cx: &mut Context<Editor>,
19961 ) -> Task<Result<()>> {
19962 let multibuffer = self.buffer.read(cx);
19963 let snapshot = multibuffer.read(cx);
19964 let buffer_ids: HashSet<_> = ranges
19965 .iter()
19966 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19967 .collect();
19968 drop(snapshot);
19969
19970 let mut buffers = HashSet::default();
19971 for buffer_id in buffer_ids {
19972 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19973 let buffer = buffer_entity.read(cx);
19974 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19975 {
19976 buffers.insert(buffer_entity);
19977 }
19978 }
19979 }
19980
19981 if let Some(project) = &self.project {
19982 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19983 } else {
19984 Task::ready(Ok(()))
19985 }
19986 }
19987
19988 fn do_stage_or_unstage_and_next(
19989 &mut self,
19990 stage: bool,
19991 window: &mut Window,
19992 cx: &mut Context<Self>,
19993 ) {
19994 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19995
19996 if ranges.iter().any(|range| range.start != range.end) {
19997 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19998 return;
19999 }
20000
20001 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
20002 let snapshot = self.snapshot(window, cx);
20003 let position = self
20004 .selections
20005 .newest::<Point>(&snapshot.display_snapshot)
20006 .head();
20007 let mut row = snapshot
20008 .buffer_snapshot()
20009 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
20010 .find(|hunk| hunk.row_range.start.0 > position.row)
20011 .map(|hunk| hunk.row_range.start);
20012
20013 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
20014 // Outside of the project diff editor, wrap around to the beginning.
20015 if !all_diff_hunks_expanded {
20016 row = row.or_else(|| {
20017 snapshot
20018 .buffer_snapshot()
20019 .diff_hunks_in_range(Point::zero()..position)
20020 .find(|hunk| hunk.row_range.end.0 < position.row)
20021 .map(|hunk| hunk.row_range.start)
20022 });
20023 }
20024
20025 if let Some(row) = row {
20026 let destination = Point::new(row.0, 0);
20027 let autoscroll = Autoscroll::center();
20028
20029 self.unfold_ranges(&[destination..destination], false, false, cx);
20030 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20031 s.select_ranges([destination..destination]);
20032 });
20033 }
20034 }
20035
20036 fn do_stage_or_unstage(
20037 &self,
20038 stage: bool,
20039 buffer_id: BufferId,
20040 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20041 cx: &mut App,
20042 ) -> Option<()> {
20043 let project = self.project()?;
20044 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20045 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20046 let buffer_snapshot = buffer.read(cx).snapshot();
20047 let file_exists = buffer_snapshot
20048 .file()
20049 .is_some_and(|file| file.disk_state().exists());
20050 diff.update(cx, |diff, cx| {
20051 diff.stage_or_unstage_hunks(
20052 stage,
20053 &hunks
20054 .map(|hunk| buffer_diff::DiffHunk {
20055 buffer_range: hunk.buffer_range,
20056 // We don't need to pass in word diffs here because they're only used for rendering and
20057 // this function changes internal state
20058 base_word_diffs: Vec::default(),
20059 buffer_word_diffs: Vec::default(),
20060 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20061 ..hunk.diff_base_byte_range.end.0,
20062 secondary_status: hunk.status.secondary,
20063 range: Point::zero()..Point::zero(), // unused
20064 })
20065 .collect::<Vec<_>>(),
20066 &buffer_snapshot,
20067 file_exists,
20068 cx,
20069 )
20070 });
20071 None
20072 }
20073
20074 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20075 let ranges: Vec<_> = self
20076 .selections
20077 .disjoint_anchors()
20078 .iter()
20079 .map(|s| s.range())
20080 .collect();
20081 self.buffer
20082 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20083 }
20084
20085 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20086 self.buffer.update(cx, |buffer, cx| {
20087 let ranges = vec![Anchor::min()..Anchor::max()];
20088 if !buffer.all_diff_hunks_expanded()
20089 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20090 {
20091 buffer.collapse_diff_hunks(ranges, cx);
20092 true
20093 } else {
20094 false
20095 }
20096 })
20097 }
20098
20099 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20100 if self.buffer.read(cx).all_diff_hunks_expanded() {
20101 return true;
20102 }
20103 let ranges = vec![Anchor::min()..Anchor::max()];
20104 self.buffer
20105 .read(cx)
20106 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20107 }
20108
20109 fn toggle_diff_hunks_in_ranges(
20110 &mut self,
20111 ranges: Vec<Range<Anchor>>,
20112 cx: &mut Context<Editor>,
20113 ) {
20114 self.buffer.update(cx, |buffer, cx| {
20115 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20116 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20117 })
20118 }
20119
20120 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20121 self.buffer.update(cx, |buffer, cx| {
20122 let snapshot = buffer.snapshot(cx);
20123 let excerpt_id = range.end.excerpt_id;
20124 let point_range = range.to_point(&snapshot);
20125 let expand = !buffer.single_hunk_is_expanded(range, cx);
20126 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20127 })
20128 }
20129
20130 pub(crate) fn apply_all_diff_hunks(
20131 &mut self,
20132 _: &ApplyAllDiffHunks,
20133 window: &mut Window,
20134 cx: &mut Context<Self>,
20135 ) {
20136 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20137
20138 let buffers = self.buffer.read(cx).all_buffers();
20139 for branch_buffer in buffers {
20140 branch_buffer.update(cx, |branch_buffer, cx| {
20141 branch_buffer.merge_into_base(Vec::new(), cx);
20142 });
20143 }
20144
20145 if let Some(project) = self.project.clone() {
20146 self.save(
20147 SaveOptions {
20148 format: true,
20149 autosave: false,
20150 },
20151 project,
20152 window,
20153 cx,
20154 )
20155 .detach_and_log_err(cx);
20156 }
20157 }
20158
20159 pub(crate) fn apply_selected_diff_hunks(
20160 &mut self,
20161 _: &ApplyDiffHunk,
20162 window: &mut Window,
20163 cx: &mut Context<Self>,
20164 ) {
20165 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20166 let snapshot = self.snapshot(window, cx);
20167 let hunks = snapshot.hunks_for_ranges(
20168 self.selections
20169 .all(&snapshot.display_snapshot)
20170 .into_iter()
20171 .map(|selection| selection.range()),
20172 );
20173 let mut ranges_by_buffer = HashMap::default();
20174 self.transact(window, cx, |editor, _window, cx| {
20175 for hunk in hunks {
20176 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20177 ranges_by_buffer
20178 .entry(buffer.clone())
20179 .or_insert_with(Vec::new)
20180 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20181 }
20182 }
20183
20184 for (buffer, ranges) in ranges_by_buffer {
20185 buffer.update(cx, |buffer, cx| {
20186 buffer.merge_into_base(ranges, cx);
20187 });
20188 }
20189 });
20190
20191 if let Some(project) = self.project.clone() {
20192 self.save(
20193 SaveOptions {
20194 format: true,
20195 autosave: false,
20196 },
20197 project,
20198 window,
20199 cx,
20200 )
20201 .detach_and_log_err(cx);
20202 }
20203 }
20204
20205 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20206 if hovered != self.gutter_hovered {
20207 self.gutter_hovered = hovered;
20208 cx.notify();
20209 }
20210 }
20211
20212 pub fn insert_blocks(
20213 &mut self,
20214 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20215 autoscroll: Option<Autoscroll>,
20216 cx: &mut Context<Self>,
20217 ) -> Vec<CustomBlockId> {
20218 let blocks = self
20219 .display_map
20220 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20221 if let Some(autoscroll) = autoscroll {
20222 self.request_autoscroll(autoscroll, cx);
20223 }
20224 cx.notify();
20225 blocks
20226 }
20227
20228 pub fn resize_blocks(
20229 &mut self,
20230 heights: HashMap<CustomBlockId, u32>,
20231 autoscroll: Option<Autoscroll>,
20232 cx: &mut Context<Self>,
20233 ) {
20234 self.display_map
20235 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20236 if let Some(autoscroll) = autoscroll {
20237 self.request_autoscroll(autoscroll, cx);
20238 }
20239 cx.notify();
20240 }
20241
20242 pub fn replace_blocks(
20243 &mut self,
20244 renderers: HashMap<CustomBlockId, RenderBlock>,
20245 autoscroll: Option<Autoscroll>,
20246 cx: &mut Context<Self>,
20247 ) {
20248 self.display_map
20249 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20250 if let Some(autoscroll) = autoscroll {
20251 self.request_autoscroll(autoscroll, cx);
20252 }
20253 cx.notify();
20254 }
20255
20256 pub fn remove_blocks(
20257 &mut self,
20258 block_ids: HashSet<CustomBlockId>,
20259 autoscroll: Option<Autoscroll>,
20260 cx: &mut Context<Self>,
20261 ) {
20262 self.display_map.update(cx, |display_map, cx| {
20263 display_map.remove_blocks(block_ids, cx)
20264 });
20265 if let Some(autoscroll) = autoscroll {
20266 self.request_autoscroll(autoscroll, cx);
20267 }
20268 cx.notify();
20269 }
20270
20271 pub fn row_for_block(
20272 &self,
20273 block_id: CustomBlockId,
20274 cx: &mut Context<Self>,
20275 ) -> Option<DisplayRow> {
20276 self.display_map
20277 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20278 }
20279
20280 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20281 self.focused_block = Some(focused_block);
20282 }
20283
20284 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20285 self.focused_block.take()
20286 }
20287
20288 pub fn insert_creases(
20289 &mut self,
20290 creases: impl IntoIterator<Item = Crease<Anchor>>,
20291 cx: &mut Context<Self>,
20292 ) -> Vec<CreaseId> {
20293 self.display_map
20294 .update(cx, |map, cx| map.insert_creases(creases, cx))
20295 }
20296
20297 pub fn remove_creases(
20298 &mut self,
20299 ids: impl IntoIterator<Item = CreaseId>,
20300 cx: &mut Context<Self>,
20301 ) -> Vec<(CreaseId, Range<Anchor>)> {
20302 self.display_map
20303 .update(cx, |map, cx| map.remove_creases(ids, cx))
20304 }
20305
20306 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20307 self.display_map
20308 .update(cx, |map, cx| map.snapshot(cx))
20309 .longest_row()
20310 }
20311
20312 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20313 self.display_map
20314 .update(cx, |map, cx| map.snapshot(cx))
20315 .max_point()
20316 }
20317
20318 pub fn text(&self, cx: &App) -> String {
20319 self.buffer.read(cx).read(cx).text()
20320 }
20321
20322 pub fn is_empty(&self, cx: &App) -> bool {
20323 self.buffer.read(cx).read(cx).is_empty()
20324 }
20325
20326 pub fn text_option(&self, cx: &App) -> Option<String> {
20327 let text = self.text(cx);
20328 let text = text.trim();
20329
20330 if text.is_empty() {
20331 return None;
20332 }
20333
20334 Some(text.to_string())
20335 }
20336
20337 pub fn set_text(
20338 &mut self,
20339 text: impl Into<Arc<str>>,
20340 window: &mut Window,
20341 cx: &mut Context<Self>,
20342 ) {
20343 self.transact(window, cx, |this, _, cx| {
20344 this.buffer
20345 .read(cx)
20346 .as_singleton()
20347 .expect("you can only call set_text on editors for singleton buffers")
20348 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20349 });
20350 }
20351
20352 pub fn display_text(&self, cx: &mut App) -> String {
20353 self.display_map
20354 .update(cx, |map, cx| map.snapshot(cx))
20355 .text()
20356 }
20357
20358 fn create_minimap(
20359 &self,
20360 minimap_settings: MinimapSettings,
20361 window: &mut Window,
20362 cx: &mut Context<Self>,
20363 ) -> Option<Entity<Self>> {
20364 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20365 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20366 }
20367
20368 fn initialize_new_minimap(
20369 &self,
20370 minimap_settings: MinimapSettings,
20371 window: &mut Window,
20372 cx: &mut Context<Self>,
20373 ) -> Entity<Self> {
20374 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20375 const MINIMAP_FONT_FAMILY: SharedString = SharedString::new_static(".ZedMono");
20376
20377 let mut minimap = Editor::new_internal(
20378 EditorMode::Minimap {
20379 parent: cx.weak_entity(),
20380 },
20381 self.buffer.clone(),
20382 None,
20383 Some(self.display_map.clone()),
20384 window,
20385 cx,
20386 );
20387 minimap.scroll_manager.clone_state(&self.scroll_manager);
20388 minimap.set_text_style_refinement(TextStyleRefinement {
20389 font_size: Some(MINIMAP_FONT_SIZE),
20390 font_weight: Some(MINIMAP_FONT_WEIGHT),
20391 font_family: Some(MINIMAP_FONT_FAMILY),
20392 ..Default::default()
20393 });
20394 minimap.update_minimap_configuration(minimap_settings, cx);
20395 cx.new(|_| minimap)
20396 }
20397
20398 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20399 let current_line_highlight = minimap_settings
20400 .current_line_highlight
20401 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20402 self.set_current_line_highlight(Some(current_line_highlight));
20403 }
20404
20405 pub fn minimap(&self) -> Option<&Entity<Self>> {
20406 self.minimap
20407 .as_ref()
20408 .filter(|_| self.minimap_visibility.visible())
20409 }
20410
20411 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20412 let mut wrap_guides = smallvec![];
20413
20414 if self.show_wrap_guides == Some(false) {
20415 return wrap_guides;
20416 }
20417
20418 let settings = self.buffer.read(cx).language_settings(cx);
20419 if settings.show_wrap_guides {
20420 match self.soft_wrap_mode(cx) {
20421 SoftWrap::Column(soft_wrap) => {
20422 wrap_guides.push((soft_wrap as usize, true));
20423 }
20424 SoftWrap::Bounded(soft_wrap) => {
20425 wrap_guides.push((soft_wrap as usize, true));
20426 }
20427 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20428 }
20429 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20430 }
20431
20432 wrap_guides
20433 }
20434
20435 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20436 let settings = self.buffer.read(cx).language_settings(cx);
20437 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20438 match mode {
20439 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20440 SoftWrap::None
20441 }
20442 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20443 language_settings::SoftWrap::PreferredLineLength => {
20444 SoftWrap::Column(settings.preferred_line_length)
20445 }
20446 language_settings::SoftWrap::Bounded => {
20447 SoftWrap::Bounded(settings.preferred_line_length)
20448 }
20449 }
20450 }
20451
20452 pub fn set_soft_wrap_mode(
20453 &mut self,
20454 mode: language_settings::SoftWrap,
20455
20456 cx: &mut Context<Self>,
20457 ) {
20458 self.soft_wrap_mode_override = Some(mode);
20459 cx.notify();
20460 }
20461
20462 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20463 self.hard_wrap = hard_wrap;
20464 cx.notify();
20465 }
20466
20467 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20468 self.text_style_refinement = Some(style);
20469 }
20470
20471 /// called by the Element so we know what style we were most recently rendered with.
20472 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20473 // We intentionally do not inform the display map about the minimap style
20474 // so that wrapping is not recalculated and stays consistent for the editor
20475 // and its linked minimap.
20476 if !self.mode.is_minimap() {
20477 let font = style.text.font();
20478 let font_size = style.text.font_size.to_pixels(window.rem_size());
20479 let display_map = self
20480 .placeholder_display_map
20481 .as_ref()
20482 .filter(|_| self.is_empty(cx))
20483 .unwrap_or(&self.display_map);
20484
20485 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20486 }
20487 self.style = Some(style);
20488 }
20489
20490 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20491 if self.style.is_none() {
20492 self.style = Some(self.create_style(cx));
20493 }
20494 self.style.as_ref().unwrap()
20495 }
20496
20497 // Called by the element. This method is not designed to be called outside of the editor
20498 // element's layout code because it does not notify when rewrapping is computed synchronously.
20499 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20500 if self.is_empty(cx) {
20501 self.placeholder_display_map
20502 .as_ref()
20503 .map_or(false, |display_map| {
20504 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20505 })
20506 } else {
20507 self.display_map
20508 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20509 }
20510 }
20511
20512 pub fn set_soft_wrap(&mut self) {
20513 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20514 }
20515
20516 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20517 if self.soft_wrap_mode_override.is_some() {
20518 self.soft_wrap_mode_override.take();
20519 } else {
20520 let soft_wrap = match self.soft_wrap_mode(cx) {
20521 SoftWrap::GitDiff => return,
20522 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20523 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20524 language_settings::SoftWrap::None
20525 }
20526 };
20527 self.soft_wrap_mode_override = Some(soft_wrap);
20528 }
20529 cx.notify();
20530 }
20531
20532 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20533 let Some(workspace) = self.workspace() else {
20534 return;
20535 };
20536 let fs = workspace.read(cx).app_state().fs.clone();
20537 let current_show = TabBarSettings::get_global(cx).show;
20538 update_settings_file(fs, cx, move |setting, _| {
20539 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20540 });
20541 }
20542
20543 pub fn toggle_indent_guides(
20544 &mut self,
20545 _: &ToggleIndentGuides,
20546 _: &mut Window,
20547 cx: &mut Context<Self>,
20548 ) {
20549 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20550 self.buffer
20551 .read(cx)
20552 .language_settings(cx)
20553 .indent_guides
20554 .enabled
20555 });
20556 self.show_indent_guides = Some(!currently_enabled);
20557 cx.notify();
20558 }
20559
20560 fn should_show_indent_guides(&self) -> Option<bool> {
20561 self.show_indent_guides
20562 }
20563
20564 pub fn disable_indent_guides_for_buffer(
20565 &mut self,
20566 buffer_id: BufferId,
20567 cx: &mut Context<Self>,
20568 ) {
20569 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20570 cx.notify();
20571 }
20572
20573 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20574 self.buffers_with_disabled_indent_guides
20575 .contains(&buffer_id)
20576 }
20577
20578 pub fn toggle_line_numbers(
20579 &mut self,
20580 _: &ToggleLineNumbers,
20581 _: &mut Window,
20582 cx: &mut Context<Self>,
20583 ) {
20584 let mut editor_settings = EditorSettings::get_global(cx).clone();
20585 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20586 EditorSettings::override_global(editor_settings, cx);
20587 }
20588
20589 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20590 if let Some(show_line_numbers) = self.show_line_numbers {
20591 return show_line_numbers;
20592 }
20593 EditorSettings::get_global(cx).gutter.line_numbers
20594 }
20595
20596 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
20597 match (
20598 self.use_relative_line_numbers,
20599 EditorSettings::get_global(cx).relative_line_numbers,
20600 ) {
20601 (None, setting) => setting,
20602 (Some(false), _) => RelativeLineNumbers::Disabled,
20603 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20604 (Some(true), _) => RelativeLineNumbers::Enabled,
20605 }
20606 }
20607
20608 pub fn toggle_relative_line_numbers(
20609 &mut self,
20610 _: &ToggleRelativeLineNumbers,
20611 _: &mut Window,
20612 cx: &mut Context<Self>,
20613 ) {
20614 let is_relative = self.relative_line_numbers(cx);
20615 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20616 }
20617
20618 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20619 self.use_relative_line_numbers = is_relative;
20620 cx.notify();
20621 }
20622
20623 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20624 self.show_gutter = show_gutter;
20625 cx.notify();
20626 }
20627
20628 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20629 self.show_scrollbars = ScrollbarAxes {
20630 horizontal: show,
20631 vertical: show,
20632 };
20633 cx.notify();
20634 }
20635
20636 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20637 self.show_scrollbars.vertical = show;
20638 cx.notify();
20639 }
20640
20641 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20642 self.show_scrollbars.horizontal = show;
20643 cx.notify();
20644 }
20645
20646 pub fn set_minimap_visibility(
20647 &mut self,
20648 minimap_visibility: MinimapVisibility,
20649 window: &mut Window,
20650 cx: &mut Context<Self>,
20651 ) {
20652 if self.minimap_visibility != minimap_visibility {
20653 if minimap_visibility.visible() && self.minimap.is_none() {
20654 let minimap_settings = EditorSettings::get_global(cx).minimap;
20655 self.minimap =
20656 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20657 }
20658 self.minimap_visibility = minimap_visibility;
20659 cx.notify();
20660 }
20661 }
20662
20663 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20664 self.set_show_scrollbars(false, cx);
20665 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20666 }
20667
20668 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20669 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20670 }
20671
20672 /// Normally the text in full mode and auto height editors is padded on the
20673 /// left side by roughly half a character width for improved hit testing.
20674 ///
20675 /// Use this method to disable this for cases where this is not wanted (e.g.
20676 /// if you want to align the editor text with some other text above or below)
20677 /// or if you want to add this padding to single-line editors.
20678 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20679 self.offset_content = offset_content;
20680 cx.notify();
20681 }
20682
20683 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20684 self.show_line_numbers = Some(show_line_numbers);
20685 cx.notify();
20686 }
20687
20688 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20689 self.disable_expand_excerpt_buttons = true;
20690 cx.notify();
20691 }
20692
20693 pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
20694 self.delegate_expand_excerpts = delegate;
20695 }
20696
20697 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20698 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20699 cx.notify();
20700 }
20701
20702 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20703 self.show_code_actions = Some(show_code_actions);
20704 cx.notify();
20705 }
20706
20707 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20708 self.show_runnables = Some(show_runnables);
20709 cx.notify();
20710 }
20711
20712 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20713 self.show_breakpoints = Some(show_breakpoints);
20714 cx.notify();
20715 }
20716
20717 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20718 if self.display_map.read(cx).masked != masked {
20719 self.display_map.update(cx, |map, _| map.masked = masked);
20720 }
20721 cx.notify()
20722 }
20723
20724 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20725 self.show_wrap_guides = Some(show_wrap_guides);
20726 cx.notify();
20727 }
20728
20729 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20730 self.show_indent_guides = Some(show_indent_guides);
20731 cx.notify();
20732 }
20733
20734 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20735 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20736 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20737 && let Some(dir) = file.abs_path(cx).parent()
20738 {
20739 return Some(dir.to_owned());
20740 }
20741 }
20742
20743 None
20744 }
20745
20746 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20747 self.active_excerpt(cx)?
20748 .1
20749 .read(cx)
20750 .file()
20751 .and_then(|f| f.as_local())
20752 }
20753
20754 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20755 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20756 let buffer = buffer.read(cx);
20757 if let Some(project_path) = buffer.project_path(cx) {
20758 let project = self.project()?.read(cx);
20759 project.absolute_path(&project_path, cx)
20760 } else {
20761 buffer
20762 .file()
20763 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20764 }
20765 })
20766 }
20767
20768 pub fn reveal_in_finder(
20769 &mut self,
20770 _: &RevealInFileManager,
20771 _window: &mut Window,
20772 cx: &mut Context<Self>,
20773 ) {
20774 if let Some(target) = self.target_file(cx) {
20775 cx.reveal_path(&target.abs_path(cx));
20776 }
20777 }
20778
20779 pub fn copy_path(
20780 &mut self,
20781 _: &zed_actions::workspace::CopyPath,
20782 _window: &mut Window,
20783 cx: &mut Context<Self>,
20784 ) {
20785 if let Some(path) = self.target_file_abs_path(cx)
20786 && let Some(path) = path.to_str()
20787 {
20788 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20789 } else {
20790 cx.propagate();
20791 }
20792 }
20793
20794 pub fn copy_relative_path(
20795 &mut self,
20796 _: &zed_actions::workspace::CopyRelativePath,
20797 _window: &mut Window,
20798 cx: &mut Context<Self>,
20799 ) {
20800 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20801 let project = self.project()?.read(cx);
20802 let path = buffer.read(cx).file()?.path();
20803 let path = path.display(project.path_style(cx));
20804 Some(path)
20805 }) {
20806 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20807 } else {
20808 cx.propagate();
20809 }
20810 }
20811
20812 /// Returns the project path for the editor's buffer, if any buffer is
20813 /// opened in the editor.
20814 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20815 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20816 buffer.read(cx).project_path(cx)
20817 } else {
20818 None
20819 }
20820 }
20821
20822 // Returns true if the editor handled a go-to-line request
20823 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20824 maybe!({
20825 let breakpoint_store = self.breakpoint_store.as_ref()?;
20826
20827 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20828 else {
20829 self.clear_row_highlights::<ActiveDebugLine>();
20830 return None;
20831 };
20832
20833 let position = active_stack_frame.position;
20834 let buffer_id = position.buffer_id?;
20835 let snapshot = self
20836 .project
20837 .as_ref()?
20838 .read(cx)
20839 .buffer_for_id(buffer_id, cx)?
20840 .read(cx)
20841 .snapshot();
20842
20843 let mut handled = false;
20844 for (id, ExcerptRange { context, .. }) in
20845 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20846 {
20847 if context.start.cmp(&position, &snapshot).is_ge()
20848 || context.end.cmp(&position, &snapshot).is_lt()
20849 {
20850 continue;
20851 }
20852 let snapshot = self.buffer.read(cx).snapshot(cx);
20853 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20854
20855 handled = true;
20856 self.clear_row_highlights::<ActiveDebugLine>();
20857
20858 self.go_to_line::<ActiveDebugLine>(
20859 multibuffer_anchor,
20860 Some(cx.theme().colors().editor_debugger_active_line_background),
20861 window,
20862 cx,
20863 );
20864
20865 cx.notify();
20866 }
20867
20868 handled.then_some(())
20869 })
20870 .is_some()
20871 }
20872
20873 pub fn copy_file_name_without_extension(
20874 &mut self,
20875 _: &CopyFileNameWithoutExtension,
20876 _: &mut Window,
20877 cx: &mut Context<Self>,
20878 ) {
20879 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20880 let file = buffer.read(cx).file()?;
20881 file.path().file_stem()
20882 }) {
20883 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20884 }
20885 }
20886
20887 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20888 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20889 let file = buffer.read(cx).file()?;
20890 Some(file.file_name(cx))
20891 }) {
20892 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20893 }
20894 }
20895
20896 pub fn toggle_git_blame(
20897 &mut self,
20898 _: &::git::Blame,
20899 window: &mut Window,
20900 cx: &mut Context<Self>,
20901 ) {
20902 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20903
20904 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20905 self.start_git_blame(true, window, cx);
20906 }
20907
20908 cx.notify();
20909 }
20910
20911 pub fn toggle_git_blame_inline(
20912 &mut self,
20913 _: &ToggleGitBlameInline,
20914 window: &mut Window,
20915 cx: &mut Context<Self>,
20916 ) {
20917 self.toggle_git_blame_inline_internal(true, window, cx);
20918 cx.notify();
20919 }
20920
20921 pub fn open_git_blame_commit(
20922 &mut self,
20923 _: &OpenGitBlameCommit,
20924 window: &mut Window,
20925 cx: &mut Context<Self>,
20926 ) {
20927 self.open_git_blame_commit_internal(window, cx);
20928 }
20929
20930 fn open_git_blame_commit_internal(
20931 &mut self,
20932 window: &mut Window,
20933 cx: &mut Context<Self>,
20934 ) -> Option<()> {
20935 let blame = self.blame.as_ref()?;
20936 let snapshot = self.snapshot(window, cx);
20937 let cursor = self
20938 .selections
20939 .newest::<Point>(&snapshot.display_snapshot)
20940 .head();
20941 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20942 let (_, blame_entry) = blame
20943 .update(cx, |blame, cx| {
20944 blame
20945 .blame_for_rows(
20946 &[RowInfo {
20947 buffer_id: Some(buffer.remote_id()),
20948 buffer_row: Some(point.row),
20949 ..Default::default()
20950 }],
20951 cx,
20952 )
20953 .next()
20954 })
20955 .flatten()?;
20956 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20957 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20958 let workspace = self.workspace()?.downgrade();
20959 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20960 None
20961 }
20962
20963 pub fn git_blame_inline_enabled(&self) -> bool {
20964 self.git_blame_inline_enabled
20965 }
20966
20967 pub fn toggle_selection_menu(
20968 &mut self,
20969 _: &ToggleSelectionMenu,
20970 _: &mut Window,
20971 cx: &mut Context<Self>,
20972 ) {
20973 self.show_selection_menu = self
20974 .show_selection_menu
20975 .map(|show_selections_menu| !show_selections_menu)
20976 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20977
20978 cx.notify();
20979 }
20980
20981 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20982 self.show_selection_menu
20983 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20984 }
20985
20986 fn start_git_blame(
20987 &mut self,
20988 user_triggered: bool,
20989 window: &mut Window,
20990 cx: &mut Context<Self>,
20991 ) {
20992 if let Some(project) = self.project() {
20993 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20994 && buffer.read(cx).file().is_none()
20995 {
20996 return;
20997 }
20998
20999 let focused = self.focus_handle(cx).contains_focused(window, cx);
21000
21001 let project = project.clone();
21002 let blame = cx
21003 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
21004 self.blame_subscription =
21005 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
21006 self.blame = Some(blame);
21007 }
21008 }
21009
21010 fn toggle_git_blame_inline_internal(
21011 &mut self,
21012 user_triggered: bool,
21013 window: &mut Window,
21014 cx: &mut Context<Self>,
21015 ) {
21016 if self.git_blame_inline_enabled {
21017 self.git_blame_inline_enabled = false;
21018 self.show_git_blame_inline = false;
21019 self.show_git_blame_inline_delay_task.take();
21020 } else {
21021 self.git_blame_inline_enabled = true;
21022 self.start_git_blame_inline(user_triggered, window, cx);
21023 }
21024
21025 cx.notify();
21026 }
21027
21028 fn start_git_blame_inline(
21029 &mut self,
21030 user_triggered: bool,
21031 window: &mut Window,
21032 cx: &mut Context<Self>,
21033 ) {
21034 self.start_git_blame(user_triggered, window, cx);
21035
21036 if ProjectSettings::get_global(cx)
21037 .git
21038 .inline_blame_delay()
21039 .is_some()
21040 {
21041 self.start_inline_blame_timer(window, cx);
21042 } else {
21043 self.show_git_blame_inline = true
21044 }
21045 }
21046
21047 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
21048 self.blame.as_ref()
21049 }
21050
21051 pub fn show_git_blame_gutter(&self) -> bool {
21052 self.show_git_blame_gutter
21053 }
21054
21055 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
21056 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
21057 }
21058
21059 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
21060 self.show_git_blame_inline
21061 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
21062 && !self.newest_selection_head_on_empty_line(cx)
21063 && self.has_blame_entries(cx)
21064 }
21065
21066 fn has_blame_entries(&self, cx: &App) -> bool {
21067 self.blame()
21068 .is_some_and(|blame| blame.read(cx).has_generated_entries())
21069 }
21070
21071 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
21072 let cursor_anchor = self.selections.newest_anchor().head();
21073
21074 let snapshot = self.buffer.read(cx).snapshot(cx);
21075 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
21076
21077 snapshot.line_len(buffer_row) == 0
21078 }
21079
21080 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
21081 let buffer_and_selection = maybe!({
21082 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
21083 let selection_range = selection.range();
21084
21085 let multi_buffer = self.buffer().read(cx);
21086 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21087 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
21088
21089 let (buffer, range, _) = if selection.reversed {
21090 buffer_ranges.first()
21091 } else {
21092 buffer_ranges.last()
21093 }?;
21094
21095 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
21096 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
21097
21098 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
21099 let selection = start_row_in_buffer..end_row_in_buffer;
21100
21101 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
21102 };
21103
21104 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
21105
21106 Some((
21107 multi_buffer.buffer(buffer.remote_id()).unwrap(),
21108 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, Bias::Left, buffer)
21109 ..buffer_diff_snapshot.row_to_base_text_row(
21110 end_row_in_buffer,
21111 Bias::Left,
21112 buffer,
21113 ),
21114 ))
21115 });
21116
21117 let Some((buffer, selection)) = buffer_and_selection else {
21118 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
21119 };
21120
21121 let Some(project) = self.project() else {
21122 return Task::ready(Err(anyhow!("editor does not have project")));
21123 };
21124
21125 project.update(cx, |project, cx| {
21126 project.get_permalink_to_line(&buffer, selection, cx)
21127 })
21128 }
21129
21130 pub fn copy_permalink_to_line(
21131 &mut self,
21132 _: &CopyPermalinkToLine,
21133 window: &mut Window,
21134 cx: &mut Context<Self>,
21135 ) {
21136 let permalink_task = self.get_permalink_to_line(cx);
21137 let workspace = self.workspace();
21138
21139 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21140 Ok(permalink) => {
21141 cx.update(|_, cx| {
21142 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21143 })
21144 .ok();
21145 }
21146 Err(err) => {
21147 let message = format!("Failed to copy permalink: {err}");
21148
21149 anyhow::Result::<()>::Err(err).log_err();
21150
21151 if let Some(workspace) = workspace {
21152 workspace
21153 .update_in(cx, |workspace, _, cx| {
21154 struct CopyPermalinkToLine;
21155
21156 workspace.show_toast(
21157 Toast::new(
21158 NotificationId::unique::<CopyPermalinkToLine>(),
21159 message,
21160 ),
21161 cx,
21162 )
21163 })
21164 .ok();
21165 }
21166 }
21167 })
21168 .detach();
21169 }
21170
21171 pub fn copy_file_location(
21172 &mut self,
21173 _: &CopyFileLocation,
21174 _: &mut Window,
21175 cx: &mut Context<Self>,
21176 ) {
21177 let selection = self
21178 .selections
21179 .newest::<Point>(&self.display_snapshot(cx))
21180 .start
21181 .row
21182 + 1;
21183 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21184 let project = self.project()?.read(cx);
21185 let file = buffer.read(cx).file()?;
21186 let path = file.path().display(project.path_style(cx));
21187
21188 Some(format!("{path}:{selection}"))
21189 }) {
21190 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21191 }
21192 }
21193
21194 pub fn open_permalink_to_line(
21195 &mut self,
21196 _: &OpenPermalinkToLine,
21197 window: &mut Window,
21198 cx: &mut Context<Self>,
21199 ) {
21200 let permalink_task = self.get_permalink_to_line(cx);
21201 let workspace = self.workspace();
21202
21203 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21204 Ok(permalink) => {
21205 cx.update(|_, cx| {
21206 cx.open_url(permalink.as_ref());
21207 })
21208 .ok();
21209 }
21210 Err(err) => {
21211 let message = format!("Failed to open permalink: {err}");
21212
21213 anyhow::Result::<()>::Err(err).log_err();
21214
21215 if let Some(workspace) = workspace {
21216 workspace
21217 .update(cx, |workspace, cx| {
21218 struct OpenPermalinkToLine;
21219
21220 workspace.show_toast(
21221 Toast::new(
21222 NotificationId::unique::<OpenPermalinkToLine>(),
21223 message,
21224 ),
21225 cx,
21226 )
21227 })
21228 .ok();
21229 }
21230 }
21231 })
21232 .detach();
21233 }
21234
21235 pub fn insert_uuid_v4(
21236 &mut self,
21237 _: &InsertUuidV4,
21238 window: &mut Window,
21239 cx: &mut Context<Self>,
21240 ) {
21241 self.insert_uuid(UuidVersion::V4, window, cx);
21242 }
21243
21244 pub fn insert_uuid_v7(
21245 &mut self,
21246 _: &InsertUuidV7,
21247 window: &mut Window,
21248 cx: &mut Context<Self>,
21249 ) {
21250 self.insert_uuid(UuidVersion::V7, window, cx);
21251 }
21252
21253 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21254 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21255 self.transact(window, cx, |this, window, cx| {
21256 let edits = this
21257 .selections
21258 .all::<Point>(&this.display_snapshot(cx))
21259 .into_iter()
21260 .map(|selection| {
21261 let uuid = match version {
21262 UuidVersion::V4 => uuid::Uuid::new_v4(),
21263 UuidVersion::V7 => uuid::Uuid::now_v7(),
21264 };
21265
21266 (selection.range(), uuid.to_string())
21267 });
21268 this.edit(edits, cx);
21269 this.refresh_edit_prediction(true, false, window, cx);
21270 });
21271 }
21272
21273 pub fn open_selections_in_multibuffer(
21274 &mut self,
21275 _: &OpenSelectionsInMultibuffer,
21276 window: &mut Window,
21277 cx: &mut Context<Self>,
21278 ) {
21279 let multibuffer = self.buffer.read(cx);
21280
21281 let Some(buffer) = multibuffer.as_singleton() else {
21282 return;
21283 };
21284
21285 let Some(workspace) = self.workspace() else {
21286 return;
21287 };
21288
21289 let title = multibuffer.title(cx).to_string();
21290
21291 let locations = self
21292 .selections
21293 .all_anchors(&self.display_snapshot(cx))
21294 .iter()
21295 .map(|selection| {
21296 (
21297 buffer.clone(),
21298 (selection.start.text_anchor..selection.end.text_anchor)
21299 .to_point(buffer.read(cx)),
21300 )
21301 })
21302 .into_group_map();
21303
21304 cx.spawn_in(window, async move |_, cx| {
21305 workspace.update_in(cx, |workspace, window, cx| {
21306 Self::open_locations_in_multibuffer(
21307 workspace,
21308 locations,
21309 format!("Selections for '{title}'"),
21310 false,
21311 false,
21312 MultibufferSelectionMode::All,
21313 window,
21314 cx,
21315 );
21316 })
21317 })
21318 .detach();
21319 }
21320
21321 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21322 /// last highlight added will be used.
21323 ///
21324 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21325 pub fn highlight_rows<T: 'static>(
21326 &mut self,
21327 range: Range<Anchor>,
21328 color: Hsla,
21329 options: RowHighlightOptions,
21330 cx: &mut Context<Self>,
21331 ) {
21332 let snapshot = self.buffer().read(cx).snapshot(cx);
21333 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21334 let ix = row_highlights.binary_search_by(|highlight| {
21335 Ordering::Equal
21336 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21337 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21338 });
21339
21340 if let Err(mut ix) = ix {
21341 let index = post_inc(&mut self.highlight_order);
21342
21343 // If this range intersects with the preceding highlight, then merge it with
21344 // the preceding highlight. Otherwise insert a new highlight.
21345 let mut merged = false;
21346 if ix > 0 {
21347 let prev_highlight = &mut row_highlights[ix - 1];
21348 if prev_highlight
21349 .range
21350 .end
21351 .cmp(&range.start, &snapshot)
21352 .is_ge()
21353 {
21354 ix -= 1;
21355 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21356 prev_highlight.range.end = range.end;
21357 }
21358 merged = true;
21359 prev_highlight.index = index;
21360 prev_highlight.color = color;
21361 prev_highlight.options = options;
21362 }
21363 }
21364
21365 if !merged {
21366 row_highlights.insert(
21367 ix,
21368 RowHighlight {
21369 range,
21370 index,
21371 color,
21372 options,
21373 type_id: TypeId::of::<T>(),
21374 },
21375 );
21376 }
21377
21378 // If any of the following highlights intersect with this one, merge them.
21379 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21380 let highlight = &row_highlights[ix];
21381 if next_highlight
21382 .range
21383 .start
21384 .cmp(&highlight.range.end, &snapshot)
21385 .is_le()
21386 {
21387 if next_highlight
21388 .range
21389 .end
21390 .cmp(&highlight.range.end, &snapshot)
21391 .is_gt()
21392 {
21393 row_highlights[ix].range.end = next_highlight.range.end;
21394 }
21395 row_highlights.remove(ix + 1);
21396 } else {
21397 break;
21398 }
21399 }
21400 }
21401 }
21402
21403 /// Remove any highlighted row ranges of the given type that intersect the
21404 /// given ranges.
21405 pub fn remove_highlighted_rows<T: 'static>(
21406 &mut self,
21407 ranges_to_remove: Vec<Range<Anchor>>,
21408 cx: &mut Context<Self>,
21409 ) {
21410 let snapshot = self.buffer().read(cx).snapshot(cx);
21411 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21412 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21413 row_highlights.retain(|highlight| {
21414 while let Some(range_to_remove) = ranges_to_remove.peek() {
21415 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21416 Ordering::Less | Ordering::Equal => {
21417 ranges_to_remove.next();
21418 }
21419 Ordering::Greater => {
21420 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21421 Ordering::Less | Ordering::Equal => {
21422 return false;
21423 }
21424 Ordering::Greater => break,
21425 }
21426 }
21427 }
21428 }
21429
21430 true
21431 })
21432 }
21433
21434 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21435 pub fn clear_row_highlights<T: 'static>(&mut self) {
21436 self.highlighted_rows.remove(&TypeId::of::<T>());
21437 }
21438
21439 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21440 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21441 self.highlighted_rows
21442 .get(&TypeId::of::<T>())
21443 .map_or(&[] as &[_], |vec| vec.as_slice())
21444 .iter()
21445 .map(|highlight| (highlight.range.clone(), highlight.color))
21446 }
21447
21448 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21449 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21450 /// Allows to ignore certain kinds of highlights.
21451 pub fn highlighted_display_rows(
21452 &self,
21453 window: &mut Window,
21454 cx: &mut App,
21455 ) -> BTreeMap<DisplayRow, LineHighlight> {
21456 let snapshot = self.snapshot(window, cx);
21457 let mut used_highlight_orders = HashMap::default();
21458 self.highlighted_rows
21459 .iter()
21460 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21461 .fold(
21462 BTreeMap::<DisplayRow, LineHighlight>::new(),
21463 |mut unique_rows, highlight| {
21464 let start = highlight.range.start.to_display_point(&snapshot);
21465 let end = highlight.range.end.to_display_point(&snapshot);
21466 let start_row = start.row().0;
21467 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21468 {
21469 end.row().0.saturating_sub(1)
21470 } else {
21471 end.row().0
21472 };
21473 for row in start_row..=end_row {
21474 let used_index =
21475 used_highlight_orders.entry(row).or_insert(highlight.index);
21476 if highlight.index >= *used_index {
21477 *used_index = highlight.index;
21478 unique_rows.insert(
21479 DisplayRow(row),
21480 LineHighlight {
21481 include_gutter: highlight.options.include_gutter,
21482 border: None,
21483 background: highlight.color.into(),
21484 type_id: Some(highlight.type_id),
21485 },
21486 );
21487 }
21488 }
21489 unique_rows
21490 },
21491 )
21492 }
21493
21494 pub fn highlighted_display_row_for_autoscroll(
21495 &self,
21496 snapshot: &DisplaySnapshot,
21497 ) -> Option<DisplayRow> {
21498 self.highlighted_rows
21499 .values()
21500 .flat_map(|highlighted_rows| highlighted_rows.iter())
21501 .filter_map(|highlight| {
21502 if highlight.options.autoscroll {
21503 Some(highlight.range.start.to_display_point(snapshot).row())
21504 } else {
21505 None
21506 }
21507 })
21508 .min()
21509 }
21510
21511 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21512 self.highlight_background::<SearchWithinRange>(
21513 ranges,
21514 |_, colors| colors.colors().editor_document_highlight_read_background,
21515 cx,
21516 )
21517 }
21518
21519 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21520 self.breadcrumb_header = Some(new_header);
21521 }
21522
21523 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21524 self.clear_background_highlights::<SearchWithinRange>(cx);
21525 }
21526
21527 pub fn highlight_background<T: 'static>(
21528 &mut self,
21529 ranges: &[Range<Anchor>],
21530 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21531 cx: &mut Context<Self>,
21532 ) {
21533 self.background_highlights.insert(
21534 HighlightKey::Type(TypeId::of::<T>()),
21535 (Arc::new(color_fetcher), Arc::from(ranges)),
21536 );
21537 self.scrollbar_marker_state.dirty = true;
21538 cx.notify();
21539 }
21540
21541 pub fn highlight_background_key<T: 'static>(
21542 &mut self,
21543 key: usize,
21544 ranges: &[Range<Anchor>],
21545 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21546 cx: &mut Context<Self>,
21547 ) {
21548 self.background_highlights.insert(
21549 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21550 (Arc::new(color_fetcher), Arc::from(ranges)),
21551 );
21552 self.scrollbar_marker_state.dirty = true;
21553 cx.notify();
21554 }
21555
21556 pub fn clear_background_highlights<T: 'static>(
21557 &mut self,
21558 cx: &mut Context<Self>,
21559 ) -> Option<BackgroundHighlight> {
21560 let text_highlights = self
21561 .background_highlights
21562 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21563 if !text_highlights.1.is_empty() {
21564 self.scrollbar_marker_state.dirty = true;
21565 cx.notify();
21566 }
21567 Some(text_highlights)
21568 }
21569
21570 pub fn highlight_gutter<T: 'static>(
21571 &mut self,
21572 ranges: impl Into<Vec<Range<Anchor>>>,
21573 color_fetcher: fn(&App) -> Hsla,
21574 cx: &mut Context<Self>,
21575 ) {
21576 self.gutter_highlights
21577 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21578 cx.notify();
21579 }
21580
21581 pub fn clear_gutter_highlights<T: 'static>(
21582 &mut self,
21583 cx: &mut Context<Self>,
21584 ) -> Option<GutterHighlight> {
21585 cx.notify();
21586 self.gutter_highlights.remove(&TypeId::of::<T>())
21587 }
21588
21589 pub fn insert_gutter_highlight<T: 'static>(
21590 &mut self,
21591 range: Range<Anchor>,
21592 color_fetcher: fn(&App) -> Hsla,
21593 cx: &mut Context<Self>,
21594 ) {
21595 let snapshot = self.buffer().read(cx).snapshot(cx);
21596 let mut highlights = self
21597 .gutter_highlights
21598 .remove(&TypeId::of::<T>())
21599 .map(|(_, highlights)| highlights)
21600 .unwrap_or_default();
21601 let ix = highlights.binary_search_by(|highlight| {
21602 Ordering::Equal
21603 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21604 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21605 });
21606 if let Err(ix) = ix {
21607 highlights.insert(ix, range);
21608 }
21609 self.gutter_highlights
21610 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21611 }
21612
21613 pub fn remove_gutter_highlights<T: 'static>(
21614 &mut self,
21615 ranges_to_remove: Vec<Range<Anchor>>,
21616 cx: &mut Context<Self>,
21617 ) {
21618 let snapshot = self.buffer().read(cx).snapshot(cx);
21619 let Some((color_fetcher, mut gutter_highlights)) =
21620 self.gutter_highlights.remove(&TypeId::of::<T>())
21621 else {
21622 return;
21623 };
21624 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21625 gutter_highlights.retain(|highlight| {
21626 while let Some(range_to_remove) = ranges_to_remove.peek() {
21627 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21628 Ordering::Less | Ordering::Equal => {
21629 ranges_to_remove.next();
21630 }
21631 Ordering::Greater => {
21632 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21633 Ordering::Less | Ordering::Equal => {
21634 return false;
21635 }
21636 Ordering::Greater => break,
21637 }
21638 }
21639 }
21640 }
21641
21642 true
21643 });
21644 self.gutter_highlights
21645 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21646 }
21647
21648 #[cfg(feature = "test-support")]
21649 pub fn all_text_highlights(
21650 &self,
21651 window: &mut Window,
21652 cx: &mut Context<Self>,
21653 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21654 let snapshot = self.snapshot(window, cx);
21655 self.display_map.update(cx, |display_map, _| {
21656 display_map
21657 .all_text_highlights()
21658 .map(|highlight| {
21659 let (style, ranges) = highlight.as_ref();
21660 (
21661 *style,
21662 ranges
21663 .iter()
21664 .map(|range| range.clone().to_display_points(&snapshot))
21665 .collect(),
21666 )
21667 })
21668 .collect()
21669 })
21670 }
21671
21672 #[cfg(feature = "test-support")]
21673 pub fn all_text_background_highlights(
21674 &self,
21675 window: &mut Window,
21676 cx: &mut Context<Self>,
21677 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21678 let snapshot = self.snapshot(window, cx);
21679 let buffer = &snapshot.buffer_snapshot();
21680 let start = buffer.anchor_before(MultiBufferOffset(0));
21681 let end = buffer.anchor_after(buffer.len());
21682 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21683 }
21684
21685 #[cfg(any(test, feature = "test-support"))]
21686 pub fn sorted_background_highlights_in_range(
21687 &self,
21688 search_range: Range<Anchor>,
21689 display_snapshot: &DisplaySnapshot,
21690 theme: &Theme,
21691 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21692 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21693 res.sort_by(|a, b| {
21694 a.0.start
21695 .cmp(&b.0.start)
21696 .then_with(|| a.0.end.cmp(&b.0.end))
21697 .then_with(|| a.1.cmp(&b.1))
21698 });
21699 res
21700 }
21701
21702 #[cfg(feature = "test-support")]
21703 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21704 let snapshot = self.buffer().read(cx).snapshot(cx);
21705
21706 let highlights = self
21707 .background_highlights
21708 .get(&HighlightKey::Type(TypeId::of::<
21709 items::BufferSearchHighlights,
21710 >()));
21711
21712 if let Some((_color, ranges)) = highlights {
21713 ranges
21714 .iter()
21715 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21716 .collect_vec()
21717 } else {
21718 vec![]
21719 }
21720 }
21721
21722 fn document_highlights_for_position<'a>(
21723 &'a self,
21724 position: Anchor,
21725 buffer: &'a MultiBufferSnapshot,
21726 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21727 let read_highlights = self
21728 .background_highlights
21729 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21730 .map(|h| &h.1);
21731 let write_highlights = self
21732 .background_highlights
21733 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21734 .map(|h| &h.1);
21735 let left_position = position.bias_left(buffer);
21736 let right_position = position.bias_right(buffer);
21737 read_highlights
21738 .into_iter()
21739 .chain(write_highlights)
21740 .flat_map(move |ranges| {
21741 let start_ix = match ranges.binary_search_by(|probe| {
21742 let cmp = probe.end.cmp(&left_position, buffer);
21743 if cmp.is_ge() {
21744 Ordering::Greater
21745 } else {
21746 Ordering::Less
21747 }
21748 }) {
21749 Ok(i) | Err(i) => i,
21750 };
21751
21752 ranges[start_ix..]
21753 .iter()
21754 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21755 })
21756 }
21757
21758 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21759 self.background_highlights
21760 .get(&HighlightKey::Type(TypeId::of::<T>()))
21761 .is_some_and(|(_, highlights)| !highlights.is_empty())
21762 }
21763
21764 /// Returns all background highlights for a given range.
21765 ///
21766 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21767 pub fn background_highlights_in_range(
21768 &self,
21769 search_range: Range<Anchor>,
21770 display_snapshot: &DisplaySnapshot,
21771 theme: &Theme,
21772 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21773 let mut results = Vec::new();
21774 for (color_fetcher, ranges) in self.background_highlights.values() {
21775 let start_ix = match ranges.binary_search_by(|probe| {
21776 let cmp = probe
21777 .end
21778 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21779 if cmp.is_gt() {
21780 Ordering::Greater
21781 } else {
21782 Ordering::Less
21783 }
21784 }) {
21785 Ok(i) | Err(i) => i,
21786 };
21787 for (index, range) in ranges[start_ix..].iter().enumerate() {
21788 if range
21789 .start
21790 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21791 .is_ge()
21792 {
21793 break;
21794 }
21795
21796 let color = color_fetcher(&(start_ix + index), theme);
21797 let start = range.start.to_display_point(display_snapshot);
21798 let end = range.end.to_display_point(display_snapshot);
21799 results.push((start..end, color))
21800 }
21801 }
21802 results
21803 }
21804
21805 pub fn gutter_highlights_in_range(
21806 &self,
21807 search_range: Range<Anchor>,
21808 display_snapshot: &DisplaySnapshot,
21809 cx: &App,
21810 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21811 let mut results = Vec::new();
21812 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21813 let color = color_fetcher(cx);
21814 let start_ix = match ranges.binary_search_by(|probe| {
21815 let cmp = probe
21816 .end
21817 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21818 if cmp.is_gt() {
21819 Ordering::Greater
21820 } else {
21821 Ordering::Less
21822 }
21823 }) {
21824 Ok(i) | Err(i) => i,
21825 };
21826 for range in &ranges[start_ix..] {
21827 if range
21828 .start
21829 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21830 .is_ge()
21831 {
21832 break;
21833 }
21834
21835 let start = range.start.to_display_point(display_snapshot);
21836 let end = range.end.to_display_point(display_snapshot);
21837 results.push((start..end, color))
21838 }
21839 }
21840 results
21841 }
21842
21843 /// Get the text ranges corresponding to the redaction query
21844 pub fn redacted_ranges(
21845 &self,
21846 search_range: Range<Anchor>,
21847 display_snapshot: &DisplaySnapshot,
21848 cx: &App,
21849 ) -> Vec<Range<DisplayPoint>> {
21850 display_snapshot
21851 .buffer_snapshot()
21852 .redacted_ranges(search_range, |file| {
21853 if let Some(file) = file {
21854 file.is_private()
21855 && EditorSettings::get(
21856 Some(SettingsLocation {
21857 worktree_id: file.worktree_id(cx),
21858 path: file.path().as_ref(),
21859 }),
21860 cx,
21861 )
21862 .redact_private_values
21863 } else {
21864 false
21865 }
21866 })
21867 .map(|range| {
21868 range.start.to_display_point(display_snapshot)
21869 ..range.end.to_display_point(display_snapshot)
21870 })
21871 .collect()
21872 }
21873
21874 pub fn highlight_text_key<T: 'static>(
21875 &mut self,
21876 key: usize,
21877 ranges: Vec<Range<Anchor>>,
21878 style: HighlightStyle,
21879 merge: bool,
21880 cx: &mut Context<Self>,
21881 ) {
21882 self.display_map.update(cx, |map, cx| {
21883 map.highlight_text(
21884 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21885 ranges,
21886 style,
21887 merge,
21888 cx,
21889 );
21890 });
21891 cx.notify();
21892 }
21893
21894 pub fn highlight_text<T: 'static>(
21895 &mut self,
21896 ranges: Vec<Range<Anchor>>,
21897 style: HighlightStyle,
21898 cx: &mut Context<Self>,
21899 ) {
21900 self.display_map.update(cx, |map, cx| {
21901 map.highlight_text(
21902 HighlightKey::Type(TypeId::of::<T>()),
21903 ranges,
21904 style,
21905 false,
21906 cx,
21907 )
21908 });
21909 cx.notify();
21910 }
21911
21912 pub fn text_highlights<'a, T: 'static>(
21913 &'a self,
21914 cx: &'a App,
21915 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21916 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21917 }
21918
21919 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21920 let cleared = self
21921 .display_map
21922 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21923 if cleared {
21924 cx.notify();
21925 }
21926 }
21927
21928 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21929 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21930 && self.focus_handle.is_focused(window)
21931 }
21932
21933 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21934 self.show_cursor_when_unfocused = is_enabled;
21935 cx.notify();
21936 }
21937
21938 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21939 cx.notify();
21940 }
21941
21942 fn on_debug_session_event(
21943 &mut self,
21944 _session: Entity<Session>,
21945 event: &SessionEvent,
21946 cx: &mut Context<Self>,
21947 ) {
21948 if let SessionEvent::InvalidateInlineValue = event {
21949 self.refresh_inline_values(cx);
21950 }
21951 }
21952
21953 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21954 let Some(project) = self.project.clone() else {
21955 return;
21956 };
21957
21958 if !self.inline_value_cache.enabled {
21959 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21960 self.splice_inlays(&inlays, Vec::new(), cx);
21961 return;
21962 }
21963
21964 let current_execution_position = self
21965 .highlighted_rows
21966 .get(&TypeId::of::<ActiveDebugLine>())
21967 .and_then(|lines| lines.last().map(|line| line.range.end));
21968
21969 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21970 let inline_values = editor
21971 .update(cx, |editor, cx| {
21972 let Some(current_execution_position) = current_execution_position else {
21973 return Some(Task::ready(Ok(Vec::new())));
21974 };
21975
21976 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21977 let snapshot = buffer.snapshot(cx);
21978
21979 let excerpt = snapshot.excerpt_containing(
21980 current_execution_position..current_execution_position,
21981 )?;
21982
21983 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21984 })?;
21985
21986 let range =
21987 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21988
21989 project.inline_values(buffer, range, cx)
21990 })
21991 .ok()
21992 .flatten()?
21993 .await
21994 .context("refreshing debugger inlays")
21995 .log_err()?;
21996
21997 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21998
21999 for (buffer_id, inline_value) in inline_values
22000 .into_iter()
22001 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
22002 {
22003 buffer_inline_values
22004 .entry(buffer_id)
22005 .or_default()
22006 .push(inline_value);
22007 }
22008
22009 editor
22010 .update(cx, |editor, cx| {
22011 let snapshot = editor.buffer.read(cx).snapshot(cx);
22012 let mut new_inlays = Vec::default();
22013
22014 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
22015 let buffer_id = buffer_snapshot.remote_id();
22016 buffer_inline_values
22017 .get(&buffer_id)
22018 .into_iter()
22019 .flatten()
22020 .for_each(|hint| {
22021 let inlay = Inlay::debugger(
22022 post_inc(&mut editor.next_inlay_id),
22023 Anchor::in_buffer(excerpt_id, hint.position),
22024 hint.text(),
22025 );
22026 if !inlay.text().chars().contains(&'\n') {
22027 new_inlays.push(inlay);
22028 }
22029 });
22030 }
22031
22032 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
22033 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
22034
22035 editor.splice_inlays(&inlay_ids, new_inlays, cx);
22036 })
22037 .ok()?;
22038 Some(())
22039 });
22040 }
22041
22042 fn on_buffer_event(
22043 &mut self,
22044 multibuffer: &Entity<MultiBuffer>,
22045 event: &multi_buffer::Event,
22046 window: &mut Window,
22047 cx: &mut Context<Self>,
22048 ) {
22049 match event {
22050 multi_buffer::Event::Edited { edited_buffer } => {
22051 self.scrollbar_marker_state.dirty = true;
22052 self.active_indent_guides_state.dirty = true;
22053 self.refresh_active_diagnostics(cx);
22054 self.refresh_code_actions(window, cx);
22055 self.refresh_single_line_folds(window, cx);
22056 self.refresh_matching_bracket_highlights(window, cx);
22057 if self.has_active_edit_prediction() {
22058 self.update_visible_edit_prediction(window, cx);
22059 }
22060
22061 if let Some(buffer) = edited_buffer {
22062 if buffer.read(cx).file().is_none() {
22063 cx.emit(EditorEvent::TitleChanged);
22064 }
22065
22066 if self.project.is_some() {
22067 let buffer_id = buffer.read(cx).remote_id();
22068 self.register_buffer(buffer_id, cx);
22069 self.update_lsp_data(Some(buffer_id), window, cx);
22070 self.refresh_inlay_hints(
22071 InlayHintRefreshReason::BufferEdited(buffer_id),
22072 cx,
22073 );
22074 }
22075 }
22076
22077 cx.emit(EditorEvent::BufferEdited);
22078 cx.emit(SearchEvent::MatchesInvalidated);
22079
22080 let Some(project) = &self.project else { return };
22081 let (telemetry, is_via_ssh) = {
22082 let project = project.read(cx);
22083 let telemetry = project.client().telemetry().clone();
22084 let is_via_ssh = project.is_via_remote_server();
22085 (telemetry, is_via_ssh)
22086 };
22087 telemetry.log_edit_event("editor", is_via_ssh);
22088 }
22089 multi_buffer::Event::ExcerptsAdded {
22090 buffer,
22091 predecessor,
22092 excerpts,
22093 } => {
22094 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22095 let buffer_id = buffer.read(cx).remote_id();
22096 if self.buffer.read(cx).diff_for(buffer_id).is_none()
22097 && let Some(project) = &self.project
22098 {
22099 update_uncommitted_diff_for_buffer(
22100 cx.entity(),
22101 project,
22102 [buffer.clone()],
22103 self.buffer.clone(),
22104 cx,
22105 )
22106 .detach();
22107 }
22108 self.update_lsp_data(Some(buffer_id), window, cx);
22109 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22110 self.colorize_brackets(false, cx);
22111 cx.emit(EditorEvent::ExcerptsAdded {
22112 buffer: buffer.clone(),
22113 predecessor: *predecessor,
22114 excerpts: excerpts.clone(),
22115 });
22116 }
22117 multi_buffer::Event::ExcerptsRemoved {
22118 ids,
22119 removed_buffer_ids,
22120 } => {
22121 if let Some(inlay_hints) = &mut self.inlay_hints {
22122 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
22123 }
22124 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
22125 for buffer_id in removed_buffer_ids {
22126 self.registered_buffers.remove(buffer_id);
22127 }
22128 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22129 cx.emit(EditorEvent::ExcerptsRemoved {
22130 ids: ids.clone(),
22131 removed_buffer_ids: removed_buffer_ids.clone(),
22132 });
22133 }
22134 multi_buffer::Event::ExcerptsEdited {
22135 excerpt_ids,
22136 buffer_ids,
22137 } => {
22138 self.display_map.update(cx, |map, cx| {
22139 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22140 });
22141 cx.emit(EditorEvent::ExcerptsEdited {
22142 ids: excerpt_ids.clone(),
22143 });
22144 }
22145 multi_buffer::Event::ExcerptsExpanded { ids } => {
22146 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22147 self.refresh_document_highlights(cx);
22148 for id in ids {
22149 self.fetched_tree_sitter_chunks.remove(id);
22150 }
22151 self.colorize_brackets(false, cx);
22152 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22153 }
22154 multi_buffer::Event::Reparsed(buffer_id) => {
22155 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22156 self.refresh_selected_text_highlights(true, window, cx);
22157 self.colorize_brackets(true, cx);
22158 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22159
22160 cx.emit(EditorEvent::Reparsed(*buffer_id));
22161 }
22162 multi_buffer::Event::DiffHunksToggled => {
22163 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22164 }
22165 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22166 if !is_fresh_language {
22167 self.registered_buffers.remove(&buffer_id);
22168 }
22169 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22170 cx.emit(EditorEvent::Reparsed(*buffer_id));
22171 cx.notify();
22172 }
22173 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22174 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22175 multi_buffer::Event::FileHandleChanged
22176 | multi_buffer::Event::Reloaded
22177 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22178 multi_buffer::Event::DiagnosticsUpdated => {
22179 self.update_diagnostics_state(window, cx);
22180 }
22181 _ => {}
22182 };
22183 }
22184
22185 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22186 if !self.diagnostics_enabled() {
22187 return;
22188 }
22189 self.refresh_active_diagnostics(cx);
22190 self.refresh_inline_diagnostics(true, window, cx);
22191 self.scrollbar_marker_state.dirty = true;
22192 cx.notify();
22193 }
22194
22195 pub fn start_temporary_diff_override(&mut self) {
22196 self.load_diff_task.take();
22197 self.temporary_diff_override = true;
22198 }
22199
22200 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22201 self.temporary_diff_override = false;
22202 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22203 self.buffer.update(cx, |buffer, cx| {
22204 buffer.set_all_diff_hunks_collapsed(cx);
22205 });
22206
22207 if let Some(project) = self.project.clone() {
22208 self.load_diff_task = Some(
22209 update_uncommitted_diff_for_buffer(
22210 cx.entity(),
22211 &project,
22212 self.buffer.read(cx).all_buffers(),
22213 self.buffer.clone(),
22214 cx,
22215 )
22216 .shared(),
22217 );
22218 }
22219 }
22220
22221 fn on_display_map_changed(
22222 &mut self,
22223 _: Entity<DisplayMap>,
22224 _: &mut Window,
22225 cx: &mut Context<Self>,
22226 ) {
22227 cx.notify();
22228 }
22229
22230 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22231 if !self.mode.is_full() {
22232 return None;
22233 }
22234
22235 let theme_settings = theme::ThemeSettings::get_global(cx);
22236 let theme = cx.theme();
22237 let accent_colors = theme.accents().clone();
22238
22239 let accent_overrides = theme_settings
22240 .theme_overrides
22241 .get(theme.name.as_ref())
22242 .map(|theme_style| &theme_style.accents)
22243 .into_iter()
22244 .flatten()
22245 .chain(
22246 theme_settings
22247 .experimental_theme_overrides
22248 .as_ref()
22249 .map(|overrides| &overrides.accents)
22250 .into_iter()
22251 .flatten(),
22252 )
22253 .flat_map(|accent| accent.0.clone())
22254 .collect();
22255
22256 Some(AccentData {
22257 colors: accent_colors,
22258 overrides: accent_overrides,
22259 })
22260 }
22261
22262 fn fetch_applicable_language_settings(
22263 &self,
22264 cx: &App,
22265 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22266 if !self.mode.is_full() {
22267 return HashMap::default();
22268 }
22269
22270 self.buffer().read(cx).all_buffers().into_iter().fold(
22271 HashMap::default(),
22272 |mut acc, buffer| {
22273 let buffer = buffer.read(cx);
22274 let language = buffer.language().map(|language| language.name());
22275 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22276 let file = buffer.file();
22277 v.insert(language_settings(language, file, cx).into_owned());
22278 }
22279 acc
22280 },
22281 )
22282 }
22283
22284 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22285 let new_language_settings = self.fetch_applicable_language_settings(cx);
22286 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22287 self.applicable_language_settings = new_language_settings;
22288
22289 let new_accents = self.fetch_accent_data(cx);
22290 let accents_changed = new_accents != self.accent_data;
22291 self.accent_data = new_accents;
22292
22293 if self.diagnostics_enabled() {
22294 let new_severity = EditorSettings::get_global(cx)
22295 .diagnostics_max_severity
22296 .unwrap_or(DiagnosticSeverity::Hint);
22297 self.set_max_diagnostics_severity(new_severity, cx);
22298 }
22299 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22300 self.update_edit_prediction_settings(cx);
22301 self.refresh_edit_prediction(true, false, window, cx);
22302 self.refresh_inline_values(cx);
22303 self.refresh_inlay_hints(
22304 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22305 self.selections.newest_anchor().head(),
22306 &self.buffer.read(cx).snapshot(cx),
22307 cx,
22308 )),
22309 cx,
22310 );
22311
22312 let old_cursor_shape = self.cursor_shape;
22313 let old_show_breadcrumbs = self.show_breadcrumbs;
22314
22315 {
22316 let editor_settings = EditorSettings::get_global(cx);
22317 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22318 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22319 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22320 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22321 }
22322
22323 if old_cursor_shape != self.cursor_shape {
22324 cx.emit(EditorEvent::CursorShapeChanged);
22325 }
22326
22327 if old_show_breadcrumbs != self.show_breadcrumbs {
22328 cx.emit(EditorEvent::BreadcrumbsChanged);
22329 }
22330
22331 let project_settings = ProjectSettings::get_global(cx);
22332 self.buffer_serialization = self
22333 .should_serialize_buffer()
22334 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22335
22336 if self.mode.is_full() {
22337 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22338 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22339 if self.show_inline_diagnostics != show_inline_diagnostics {
22340 self.show_inline_diagnostics = show_inline_diagnostics;
22341 self.refresh_inline_diagnostics(false, window, cx);
22342 }
22343
22344 if self.git_blame_inline_enabled != inline_blame_enabled {
22345 self.toggle_git_blame_inline_internal(false, window, cx);
22346 }
22347
22348 let minimap_settings = EditorSettings::get_global(cx).minimap;
22349 if self.minimap_visibility != MinimapVisibility::Disabled {
22350 if self.minimap_visibility.settings_visibility()
22351 != minimap_settings.minimap_enabled()
22352 {
22353 self.set_minimap_visibility(
22354 MinimapVisibility::for_mode(self.mode(), cx),
22355 window,
22356 cx,
22357 );
22358 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22359 minimap_entity.update(cx, |minimap_editor, cx| {
22360 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22361 })
22362 }
22363 }
22364
22365 if language_settings_changed || accents_changed {
22366 self.colorize_brackets(true, cx);
22367 }
22368
22369 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22370 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22371 }) {
22372 if !inlay_splice.is_empty() {
22373 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22374 }
22375 self.refresh_colors_for_visible_range(None, window, cx);
22376 }
22377 }
22378
22379 cx.notify();
22380 }
22381
22382 pub fn set_searchable(&mut self, searchable: bool) {
22383 self.searchable = searchable;
22384 }
22385
22386 pub fn searchable(&self) -> bool {
22387 self.searchable
22388 }
22389
22390 pub fn open_excerpts_in_split(
22391 &mut self,
22392 _: &OpenExcerptsSplit,
22393 window: &mut Window,
22394 cx: &mut Context<Self>,
22395 ) {
22396 self.open_excerpts_common(None, true, window, cx)
22397 }
22398
22399 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22400 self.open_excerpts_common(None, false, window, cx)
22401 }
22402
22403 fn open_excerpts_common(
22404 &mut self,
22405 jump_data: Option<JumpData>,
22406 split: bool,
22407 window: &mut Window,
22408 cx: &mut Context<Self>,
22409 ) {
22410 let Some(workspace) = self.workspace() else {
22411 cx.propagate();
22412 return;
22413 };
22414
22415 if self.buffer.read(cx).is_singleton() {
22416 cx.propagate();
22417 return;
22418 }
22419
22420 let mut new_selections_by_buffer = HashMap::default();
22421 match &jump_data {
22422 Some(JumpData::MultiBufferPoint {
22423 excerpt_id,
22424 position,
22425 anchor,
22426 line_offset_from_top,
22427 }) => {
22428 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22429 if let Some(buffer) = multi_buffer_snapshot
22430 .buffer_id_for_excerpt(*excerpt_id)
22431 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22432 {
22433 let buffer_snapshot = buffer.read(cx).snapshot();
22434 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22435 language::ToPoint::to_point(anchor, &buffer_snapshot)
22436 } else {
22437 buffer_snapshot.clip_point(*position, Bias::Left)
22438 };
22439 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22440 new_selections_by_buffer.insert(
22441 buffer,
22442 (
22443 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22444 Some(*line_offset_from_top),
22445 ),
22446 );
22447 }
22448 }
22449 Some(JumpData::MultiBufferRow {
22450 row,
22451 line_offset_from_top,
22452 }) => {
22453 let point = MultiBufferPoint::new(row.0, 0);
22454 if let Some((buffer, buffer_point, _)) =
22455 self.buffer.read(cx).point_to_buffer_point(point, cx)
22456 {
22457 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22458 new_selections_by_buffer
22459 .entry(buffer)
22460 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22461 .0
22462 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22463 }
22464 }
22465 None => {
22466 let selections = self
22467 .selections
22468 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22469 let multi_buffer = self.buffer.read(cx);
22470 for selection in selections {
22471 for (snapshot, range, _, anchor) in multi_buffer
22472 .snapshot(cx)
22473 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22474 {
22475 if let Some(anchor) = anchor {
22476 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22477 else {
22478 continue;
22479 };
22480 let offset = text::ToOffset::to_offset(
22481 &anchor.text_anchor,
22482 &buffer_handle.read(cx).snapshot(),
22483 );
22484 let range = BufferOffset(offset)..BufferOffset(offset);
22485 new_selections_by_buffer
22486 .entry(buffer_handle)
22487 .or_insert((Vec::new(), None))
22488 .0
22489 .push(range)
22490 } else {
22491 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22492 else {
22493 continue;
22494 };
22495 new_selections_by_buffer
22496 .entry(buffer_handle)
22497 .or_insert((Vec::new(), None))
22498 .0
22499 .push(range)
22500 }
22501 }
22502 }
22503 }
22504 }
22505
22506 new_selections_by_buffer
22507 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22508
22509 if new_selections_by_buffer.is_empty() {
22510 return;
22511 }
22512
22513 // We defer the pane interaction because we ourselves are a workspace item
22514 // and activating a new item causes the pane to call a method on us reentrantly,
22515 // which panics if we're on the stack.
22516 window.defer(cx, move |window, cx| {
22517 workspace.update(cx, |workspace, cx| {
22518 let pane = if split {
22519 workspace.adjacent_pane(window, cx)
22520 } else {
22521 workspace.active_pane().clone()
22522 };
22523
22524 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22525 let buffer_read = buffer.read(cx);
22526 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22527 (true, project::File::from_dyn(Some(file)).is_some())
22528 } else {
22529 (false, false)
22530 };
22531
22532 // If project file is none workspace.open_project_item will fail to open the excerpt
22533 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22534 // so we check if there's a tab match in that case first
22535 let editor = (!has_file || !is_project_file)
22536 .then(|| {
22537 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22538 // so `workspace.open_project_item` will never find them, always opening a new editor.
22539 // Instead, we try to activate the existing editor in the pane first.
22540 let (editor, pane_item_index, pane_item_id) =
22541 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22542 let editor = item.downcast::<Editor>()?;
22543 let singleton_buffer =
22544 editor.read(cx).buffer().read(cx).as_singleton()?;
22545 if singleton_buffer == buffer {
22546 Some((editor, i, item.item_id()))
22547 } else {
22548 None
22549 }
22550 })?;
22551 pane.update(cx, |pane, cx| {
22552 pane.activate_item(pane_item_index, true, true, window, cx);
22553 if !PreviewTabsSettings::get_global(cx)
22554 .enable_preview_from_multibuffer
22555 {
22556 pane.unpreview_item_if_preview(pane_item_id);
22557 }
22558 });
22559 Some(editor)
22560 })
22561 .flatten()
22562 .unwrap_or_else(|| {
22563 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22564 .enable_keep_preview_on_code_navigation;
22565 let allow_new_preview =
22566 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22567 workspace.open_project_item::<Self>(
22568 pane.clone(),
22569 buffer,
22570 true,
22571 true,
22572 keep_old_preview,
22573 allow_new_preview,
22574 window,
22575 cx,
22576 )
22577 });
22578
22579 editor.update(cx, |editor, cx| {
22580 if has_file && !is_project_file {
22581 editor.set_read_only(true);
22582 }
22583 let autoscroll = match scroll_offset {
22584 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22585 None => Autoscroll::newest(),
22586 };
22587 let nav_history = editor.nav_history.take();
22588 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22589 let Some((&excerpt_id, _, buffer_snapshot)) =
22590 multibuffer_snapshot.as_singleton()
22591 else {
22592 return;
22593 };
22594 editor.change_selections(
22595 SelectionEffects::scroll(autoscroll),
22596 window,
22597 cx,
22598 |s| {
22599 s.select_ranges(ranges.into_iter().map(|range| {
22600 let range = buffer_snapshot.anchor_before(range.start)
22601 ..buffer_snapshot.anchor_after(range.end);
22602 multibuffer_snapshot
22603 .anchor_range_in_excerpt(excerpt_id, range)
22604 .unwrap()
22605 }));
22606 },
22607 );
22608 editor.nav_history = nav_history;
22609 });
22610 }
22611 })
22612 });
22613 }
22614
22615 // Allow opening excerpts for buffers that either belong to the current project
22616 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22617 // are also supported so tests and other in-memory views keep working.
22618 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22619 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22620 }
22621
22622 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22623 let snapshot = self.buffer.read(cx).read(cx);
22624 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22625 Some(
22626 ranges
22627 .iter()
22628 .map(move |range| {
22629 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22630 })
22631 .collect(),
22632 )
22633 }
22634
22635 fn selection_replacement_ranges(
22636 &self,
22637 range: Range<MultiBufferOffsetUtf16>,
22638 cx: &mut App,
22639 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22640 let selections = self
22641 .selections
22642 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22643 let newest_selection = selections
22644 .iter()
22645 .max_by_key(|selection| selection.id)
22646 .unwrap();
22647 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22648 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22649 let snapshot = self.buffer.read(cx).read(cx);
22650 selections
22651 .into_iter()
22652 .map(|mut selection| {
22653 selection.start.0.0 =
22654 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22655 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22656 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22657 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22658 })
22659 .collect()
22660 }
22661
22662 fn report_editor_event(
22663 &self,
22664 reported_event: ReportEditorEvent,
22665 file_extension: Option<String>,
22666 cx: &App,
22667 ) {
22668 if cfg!(any(test, feature = "test-support")) {
22669 return;
22670 }
22671
22672 let Some(project) = &self.project else { return };
22673
22674 // If None, we are in a file without an extension
22675 let file = self
22676 .buffer
22677 .read(cx)
22678 .as_singleton()
22679 .and_then(|b| b.read(cx).file());
22680 let file_extension = file_extension.or(file
22681 .as_ref()
22682 .and_then(|file| Path::new(file.file_name(cx)).extension())
22683 .and_then(|e| e.to_str())
22684 .map(|a| a.to_string()));
22685
22686 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22687 .map(|vim_mode| vim_mode.0)
22688 .unwrap_or(false);
22689
22690 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22691 let copilot_enabled = edit_predictions_provider
22692 == language::language_settings::EditPredictionProvider::Copilot;
22693 let copilot_enabled_for_language = self
22694 .buffer
22695 .read(cx)
22696 .language_settings(cx)
22697 .show_edit_predictions;
22698
22699 let project = project.read(cx);
22700 let event_type = reported_event.event_type();
22701
22702 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22703 telemetry::event!(
22704 event_type,
22705 type = if auto_saved {"autosave"} else {"manual"},
22706 file_extension,
22707 vim_mode,
22708 copilot_enabled,
22709 copilot_enabled_for_language,
22710 edit_predictions_provider,
22711 is_via_ssh = project.is_via_remote_server(),
22712 );
22713 } else {
22714 telemetry::event!(
22715 event_type,
22716 file_extension,
22717 vim_mode,
22718 copilot_enabled,
22719 copilot_enabled_for_language,
22720 edit_predictions_provider,
22721 is_via_ssh = project.is_via_remote_server(),
22722 );
22723 };
22724 }
22725
22726 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22727 /// with each line being an array of {text, highlight} objects.
22728 fn copy_highlight_json(
22729 &mut self,
22730 _: &CopyHighlightJson,
22731 window: &mut Window,
22732 cx: &mut Context<Self>,
22733 ) {
22734 #[derive(Serialize)]
22735 struct Chunk<'a> {
22736 text: String,
22737 highlight: Option<&'a str>,
22738 }
22739
22740 let snapshot = self.buffer.read(cx).snapshot(cx);
22741 let range = self
22742 .selected_text_range(false, window, cx)
22743 .and_then(|selection| {
22744 if selection.range.is_empty() {
22745 None
22746 } else {
22747 Some(
22748 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22749 selection.range.start,
22750 )))
22751 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22752 selection.range.end,
22753 ))),
22754 )
22755 }
22756 })
22757 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22758
22759 let chunks = snapshot.chunks(range, true);
22760 let mut lines = Vec::new();
22761 let mut line: VecDeque<Chunk> = VecDeque::new();
22762
22763 let Some(style) = self.style.as_ref() else {
22764 return;
22765 };
22766
22767 for chunk in chunks {
22768 let highlight = chunk
22769 .syntax_highlight_id
22770 .and_then(|id| id.name(&style.syntax));
22771 let mut chunk_lines = chunk.text.split('\n').peekable();
22772 while let Some(text) = chunk_lines.next() {
22773 let mut merged_with_last_token = false;
22774 if let Some(last_token) = line.back_mut()
22775 && last_token.highlight == highlight
22776 {
22777 last_token.text.push_str(text);
22778 merged_with_last_token = true;
22779 }
22780
22781 if !merged_with_last_token {
22782 line.push_back(Chunk {
22783 text: text.into(),
22784 highlight,
22785 });
22786 }
22787
22788 if chunk_lines.peek().is_some() {
22789 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22790 line.pop_front();
22791 }
22792 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22793 line.pop_back();
22794 }
22795
22796 lines.push(mem::take(&mut line));
22797 }
22798 }
22799 }
22800
22801 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22802 return;
22803 };
22804 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22805 }
22806
22807 pub fn open_context_menu(
22808 &mut self,
22809 _: &OpenContextMenu,
22810 window: &mut Window,
22811 cx: &mut Context<Self>,
22812 ) {
22813 self.request_autoscroll(Autoscroll::newest(), cx);
22814 let position = self
22815 .selections
22816 .newest_display(&self.display_snapshot(cx))
22817 .start;
22818 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22819 }
22820
22821 pub fn replay_insert_event(
22822 &mut self,
22823 text: &str,
22824 relative_utf16_range: Option<Range<isize>>,
22825 window: &mut Window,
22826 cx: &mut Context<Self>,
22827 ) {
22828 if !self.input_enabled {
22829 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22830 return;
22831 }
22832 if let Some(relative_utf16_range) = relative_utf16_range {
22833 let selections = self
22834 .selections
22835 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22836 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22837 let new_ranges = selections.into_iter().map(|range| {
22838 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22839 range
22840 .head()
22841 .0
22842 .0
22843 .saturating_add_signed(relative_utf16_range.start),
22844 ));
22845 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22846 range
22847 .head()
22848 .0
22849 .0
22850 .saturating_add_signed(relative_utf16_range.end),
22851 ));
22852 start..end
22853 });
22854 s.select_ranges(new_ranges);
22855 });
22856 }
22857
22858 self.handle_input(text, window, cx);
22859 }
22860
22861 pub fn is_focused(&self, window: &Window) -> bool {
22862 self.focus_handle.is_focused(window)
22863 }
22864
22865 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22866 cx.emit(EditorEvent::Focused);
22867
22868 if let Some(descendant) = self
22869 .last_focused_descendant
22870 .take()
22871 .and_then(|descendant| descendant.upgrade())
22872 {
22873 window.focus(&descendant, cx);
22874 } else {
22875 if let Some(blame) = self.blame.as_ref() {
22876 blame.update(cx, GitBlame::focus)
22877 }
22878
22879 self.blink_manager.update(cx, BlinkManager::enable);
22880 self.show_cursor_names(window, cx);
22881 self.buffer.update(cx, |buffer, cx| {
22882 buffer.finalize_last_transaction(cx);
22883 if self.leader_id.is_none() {
22884 buffer.set_active_selections(
22885 &self.selections.disjoint_anchors_arc(),
22886 self.selections.line_mode(),
22887 self.cursor_shape,
22888 cx,
22889 );
22890 }
22891 });
22892
22893 if let Some(position_map) = self.last_position_map.clone() {
22894 EditorElement::mouse_moved(
22895 self,
22896 &MouseMoveEvent {
22897 position: window.mouse_position(),
22898 pressed_button: None,
22899 modifiers: window.modifiers(),
22900 },
22901 &position_map,
22902 window,
22903 cx,
22904 );
22905 }
22906 }
22907 }
22908
22909 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22910 cx.emit(EditorEvent::FocusedIn)
22911 }
22912
22913 fn handle_focus_out(
22914 &mut self,
22915 event: FocusOutEvent,
22916 _window: &mut Window,
22917 cx: &mut Context<Self>,
22918 ) {
22919 if event.blurred != self.focus_handle {
22920 self.last_focused_descendant = Some(event.blurred);
22921 }
22922 self.selection_drag_state = SelectionDragState::None;
22923 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22924 }
22925
22926 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22927 self.blink_manager.update(cx, BlinkManager::disable);
22928 self.buffer
22929 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22930
22931 if let Some(blame) = self.blame.as_ref() {
22932 blame.update(cx, GitBlame::blur)
22933 }
22934 if !self.hover_state.focused(window, cx) {
22935 hide_hover(self, cx);
22936 }
22937 if !self
22938 .context_menu
22939 .borrow()
22940 .as_ref()
22941 .is_some_and(|context_menu| context_menu.focused(window, cx))
22942 {
22943 self.hide_context_menu(window, cx);
22944 }
22945 self.take_active_edit_prediction(cx);
22946 cx.emit(EditorEvent::Blurred);
22947 cx.notify();
22948 }
22949
22950 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22951 let mut pending: String = window
22952 .pending_input_keystrokes()
22953 .into_iter()
22954 .flatten()
22955 .filter_map(|keystroke| keystroke.key_char.clone())
22956 .collect();
22957
22958 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22959 pending = "".to_string();
22960 }
22961
22962 let existing_pending = self
22963 .text_highlights::<PendingInput>(cx)
22964 .map(|(_, ranges)| ranges.to_vec());
22965 if existing_pending.is_none() && pending.is_empty() {
22966 return;
22967 }
22968 let transaction =
22969 self.transact(window, cx, |this, window, cx| {
22970 let selections = this
22971 .selections
22972 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22973 let edits = selections
22974 .iter()
22975 .map(|selection| (selection.end..selection.end, pending.clone()));
22976 this.edit(edits, cx);
22977 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22978 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22979 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22980 }));
22981 });
22982 if let Some(existing_ranges) = existing_pending {
22983 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22984 this.edit(edits, cx);
22985 }
22986 });
22987
22988 let snapshot = self.snapshot(window, cx);
22989 let ranges = self
22990 .selections
22991 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22992 .into_iter()
22993 .map(|selection| {
22994 snapshot.buffer_snapshot().anchor_after(selection.end)
22995 ..snapshot
22996 .buffer_snapshot()
22997 .anchor_before(selection.end + pending.len())
22998 })
22999 .collect();
23000
23001 if pending.is_empty() {
23002 self.clear_highlights::<PendingInput>(cx);
23003 } else {
23004 self.highlight_text::<PendingInput>(
23005 ranges,
23006 HighlightStyle {
23007 underline: Some(UnderlineStyle {
23008 thickness: px(1.),
23009 color: None,
23010 wavy: false,
23011 }),
23012 ..Default::default()
23013 },
23014 cx,
23015 );
23016 }
23017
23018 self.ime_transaction = self.ime_transaction.or(transaction);
23019 if let Some(transaction) = self.ime_transaction {
23020 self.buffer.update(cx, |buffer, cx| {
23021 buffer.group_until_transaction(transaction, cx);
23022 });
23023 }
23024
23025 if self.text_highlights::<PendingInput>(cx).is_none() {
23026 self.ime_transaction.take();
23027 }
23028 }
23029
23030 pub fn register_action_renderer(
23031 &mut self,
23032 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
23033 ) -> Subscription {
23034 let id = self.next_editor_action_id.post_inc();
23035 self.editor_actions
23036 .borrow_mut()
23037 .insert(id, Box::new(listener));
23038
23039 let editor_actions = self.editor_actions.clone();
23040 Subscription::new(move || {
23041 editor_actions.borrow_mut().remove(&id);
23042 })
23043 }
23044
23045 pub fn register_action<A: Action>(
23046 &mut self,
23047 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
23048 ) -> Subscription {
23049 let id = self.next_editor_action_id.post_inc();
23050 let listener = Arc::new(listener);
23051 self.editor_actions.borrow_mut().insert(
23052 id,
23053 Box::new(move |_, window, _| {
23054 let listener = listener.clone();
23055 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
23056 let action = action.downcast_ref().unwrap();
23057 if phase == DispatchPhase::Bubble {
23058 listener(action, window, cx)
23059 }
23060 })
23061 }),
23062 );
23063
23064 let editor_actions = self.editor_actions.clone();
23065 Subscription::new(move || {
23066 editor_actions.borrow_mut().remove(&id);
23067 })
23068 }
23069
23070 pub fn file_header_size(&self) -> u32 {
23071 FILE_HEADER_HEIGHT
23072 }
23073
23074 pub fn restore(
23075 &mut self,
23076 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
23077 window: &mut Window,
23078 cx: &mut Context<Self>,
23079 ) {
23080 self.buffer().update(cx, |multi_buffer, cx| {
23081 for (buffer_id, changes) in revert_changes {
23082 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
23083 buffer.update(cx, |buffer, cx| {
23084 buffer.edit(
23085 changes
23086 .into_iter()
23087 .map(|(range, text)| (range, text.to_string())),
23088 None,
23089 cx,
23090 );
23091 });
23092 }
23093 }
23094 });
23095 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23096 selections.refresh()
23097 });
23098 }
23099
23100 pub fn to_pixel_point(
23101 &mut self,
23102 source: multi_buffer::Anchor,
23103 editor_snapshot: &EditorSnapshot,
23104 window: &mut Window,
23105 cx: &App,
23106 ) -> Option<gpui::Point<Pixels>> {
23107 let source_point = source.to_display_point(editor_snapshot);
23108 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23109 }
23110
23111 pub fn display_to_pixel_point(
23112 &mut self,
23113 source: DisplayPoint,
23114 editor_snapshot: &EditorSnapshot,
23115 window: &mut Window,
23116 cx: &App,
23117 ) -> Option<gpui::Point<Pixels>> {
23118 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23119 let text_layout_details = self.text_layout_details(window);
23120 let scroll_top = text_layout_details
23121 .scroll_anchor
23122 .scroll_position(editor_snapshot)
23123 .y;
23124
23125 if source.row().as_f64() < scroll_top.floor() {
23126 return None;
23127 }
23128 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23129 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23130 Some(gpui::Point::new(source_x, source_y))
23131 }
23132
23133 pub fn has_visible_completions_menu(&self) -> bool {
23134 !self.edit_prediction_preview_is_active()
23135 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23136 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23137 })
23138 }
23139
23140 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23141 if self.mode.is_minimap() {
23142 return;
23143 }
23144 self.addons
23145 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23146 }
23147
23148 pub fn unregister_addon<T: Addon>(&mut self) {
23149 self.addons.remove(&std::any::TypeId::of::<T>());
23150 }
23151
23152 pub fn addon<T: Addon>(&self) -> Option<&T> {
23153 let type_id = std::any::TypeId::of::<T>();
23154 self.addons
23155 .get(&type_id)
23156 .and_then(|item| item.to_any().downcast_ref::<T>())
23157 }
23158
23159 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23160 let type_id = std::any::TypeId::of::<T>();
23161 self.addons
23162 .get_mut(&type_id)
23163 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23164 }
23165
23166 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23167 let text_layout_details = self.text_layout_details(window);
23168 let style = &text_layout_details.editor_style;
23169 let font_id = window.text_system().resolve_font(&style.text.font());
23170 let font_size = style.text.font_size.to_pixels(window.rem_size());
23171 let line_height = style.text.line_height_in_pixels(window.rem_size());
23172 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23173 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23174
23175 CharacterDimensions {
23176 em_width,
23177 em_advance,
23178 line_height,
23179 }
23180 }
23181
23182 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23183 self.load_diff_task.clone()
23184 }
23185
23186 fn read_metadata_from_db(
23187 &mut self,
23188 item_id: u64,
23189 workspace_id: WorkspaceId,
23190 window: &mut Window,
23191 cx: &mut Context<Editor>,
23192 ) {
23193 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23194 && !self.mode.is_minimap()
23195 && WorkspaceSettings::get(None, cx).restore_on_startup
23196 != RestoreOnStartupBehavior::EmptyTab
23197 {
23198 let buffer_snapshot = OnceCell::new();
23199
23200 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23201 && !folds.is_empty()
23202 {
23203 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23204 self.fold_ranges(
23205 folds
23206 .into_iter()
23207 .map(|(start, end)| {
23208 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23209 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23210 })
23211 .collect(),
23212 false,
23213 window,
23214 cx,
23215 );
23216 }
23217
23218 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23219 && !selections.is_empty()
23220 {
23221 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23222 // skip adding the initial selection to selection history
23223 self.selection_history.mode = SelectionHistoryMode::Skipping;
23224 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23225 s.select_ranges(selections.into_iter().map(|(start, end)| {
23226 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23227 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23228 }));
23229 });
23230 self.selection_history.mode = SelectionHistoryMode::Normal;
23231 };
23232 }
23233
23234 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23235 }
23236
23237 fn update_lsp_data(
23238 &mut self,
23239 for_buffer: Option<BufferId>,
23240 window: &mut Window,
23241 cx: &mut Context<'_, Self>,
23242 ) {
23243 self.pull_diagnostics(for_buffer, window, cx);
23244 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23245 }
23246
23247 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23248 if self.ignore_lsp_data() {
23249 return;
23250 }
23251 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23252 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23253 }
23254 }
23255
23256 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23257 if self.ignore_lsp_data() {
23258 return;
23259 }
23260
23261 if !self.registered_buffers.contains_key(&buffer_id)
23262 && let Some(project) = self.project.as_ref()
23263 {
23264 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23265 project.update(cx, |project, cx| {
23266 self.registered_buffers.insert(
23267 buffer_id,
23268 project.register_buffer_with_language_servers(&buffer, cx),
23269 );
23270 });
23271 } else {
23272 self.registered_buffers.remove(&buffer_id);
23273 }
23274 }
23275 }
23276
23277 fn ignore_lsp_data(&self) -> bool {
23278 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23279 // skip any LSP updates for it.
23280 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23281 }
23282
23283 fn create_style(&self, cx: &App) -> EditorStyle {
23284 let settings = ThemeSettings::get_global(cx);
23285
23286 let mut text_style = match self.mode {
23287 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23288 color: cx.theme().colors().editor_foreground,
23289 font_family: settings.ui_font.family.clone(),
23290 font_features: settings.ui_font.features.clone(),
23291 font_fallbacks: settings.ui_font.fallbacks.clone(),
23292 font_size: rems(0.875).into(),
23293 font_weight: settings.ui_font.weight,
23294 line_height: relative(settings.buffer_line_height.value()),
23295 ..Default::default()
23296 },
23297 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23298 color: cx.theme().colors().editor_foreground,
23299 font_family: settings.buffer_font.family.clone(),
23300 font_features: settings.buffer_font.features.clone(),
23301 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23302 font_size: settings.buffer_font_size(cx).into(),
23303 font_weight: settings.buffer_font.weight,
23304 line_height: relative(settings.buffer_line_height.value()),
23305 ..Default::default()
23306 },
23307 };
23308 if let Some(text_style_refinement) = &self.text_style_refinement {
23309 text_style.refine(text_style_refinement)
23310 }
23311
23312 let background = match self.mode {
23313 EditorMode::SingleLine => cx.theme().system().transparent,
23314 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23315 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23316 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23317 };
23318
23319 EditorStyle {
23320 background,
23321 border: cx.theme().colors().border,
23322 local_player: cx.theme().players().local(),
23323 text: text_style,
23324 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23325 syntax: cx.theme().syntax().clone(),
23326 status: cx.theme().status().clone(),
23327 inlay_hints_style: make_inlay_hints_style(cx),
23328 edit_prediction_styles: make_suggestion_styles(cx),
23329 unnecessary_code_fade: settings.unnecessary_code_fade,
23330 show_underlines: self.diagnostics_enabled(),
23331 }
23332 }
23333}
23334
23335fn edit_for_markdown_paste<'a>(
23336 buffer: &MultiBufferSnapshot,
23337 range: Range<MultiBufferOffset>,
23338 to_insert: &'a str,
23339 url: Option<url::Url>,
23340) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23341 if url.is_none() {
23342 return (range, Cow::Borrowed(to_insert));
23343 };
23344
23345 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23346
23347 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23348 Cow::Borrowed(to_insert)
23349 } else {
23350 Cow::Owned(format!("[{old_text}]({to_insert})"))
23351 };
23352 (range, new_text)
23353}
23354
23355fn process_completion_for_edit(
23356 completion: &Completion,
23357 intent: CompletionIntent,
23358 buffer: &Entity<Buffer>,
23359 cursor_position: &text::Anchor,
23360 cx: &mut Context<Editor>,
23361) -> CompletionEdit {
23362 let buffer = buffer.read(cx);
23363 let buffer_snapshot = buffer.snapshot();
23364 let (snippet, new_text) = if completion.is_snippet() {
23365 let mut snippet_source = completion.new_text.clone();
23366 // Workaround for typescript language server issues so that methods don't expand within
23367 // strings and functions with type expressions. The previous point is used because the query
23368 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23369 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23370 let previous_point = if previous_point.column > 0 {
23371 cursor_position.to_previous_offset(&buffer_snapshot)
23372 } else {
23373 cursor_position.to_offset(&buffer_snapshot)
23374 };
23375 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23376 && scope.prefers_label_for_snippet_in_completion()
23377 && let Some(label) = completion.label()
23378 && matches!(
23379 completion.kind(),
23380 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23381 )
23382 {
23383 snippet_source = label;
23384 }
23385 match Snippet::parse(&snippet_source).log_err() {
23386 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23387 None => (None, completion.new_text.clone()),
23388 }
23389 } else {
23390 (None, completion.new_text.clone())
23391 };
23392
23393 let mut range_to_replace = {
23394 let replace_range = &completion.replace_range;
23395 if let CompletionSource::Lsp {
23396 insert_range: Some(insert_range),
23397 ..
23398 } = &completion.source
23399 {
23400 debug_assert_eq!(
23401 insert_range.start, replace_range.start,
23402 "insert_range and replace_range should start at the same position"
23403 );
23404 debug_assert!(
23405 insert_range
23406 .start
23407 .cmp(cursor_position, &buffer_snapshot)
23408 .is_le(),
23409 "insert_range should start before or at cursor position"
23410 );
23411 debug_assert!(
23412 replace_range
23413 .start
23414 .cmp(cursor_position, &buffer_snapshot)
23415 .is_le(),
23416 "replace_range should start before or at cursor position"
23417 );
23418
23419 let should_replace = match intent {
23420 CompletionIntent::CompleteWithInsert => false,
23421 CompletionIntent::CompleteWithReplace => true,
23422 CompletionIntent::Complete | CompletionIntent::Compose => {
23423 let insert_mode =
23424 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23425 .completions
23426 .lsp_insert_mode;
23427 match insert_mode {
23428 LspInsertMode::Insert => false,
23429 LspInsertMode::Replace => true,
23430 LspInsertMode::ReplaceSubsequence => {
23431 let mut text_to_replace = buffer.chars_for_range(
23432 buffer.anchor_before(replace_range.start)
23433 ..buffer.anchor_after(replace_range.end),
23434 );
23435 let mut current_needle = text_to_replace.next();
23436 for haystack_ch in completion.label.text.chars() {
23437 if let Some(needle_ch) = current_needle
23438 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23439 {
23440 current_needle = text_to_replace.next();
23441 }
23442 }
23443 current_needle.is_none()
23444 }
23445 LspInsertMode::ReplaceSuffix => {
23446 if replace_range
23447 .end
23448 .cmp(cursor_position, &buffer_snapshot)
23449 .is_gt()
23450 {
23451 let range_after_cursor = *cursor_position..replace_range.end;
23452 let text_after_cursor = buffer
23453 .text_for_range(
23454 buffer.anchor_before(range_after_cursor.start)
23455 ..buffer.anchor_after(range_after_cursor.end),
23456 )
23457 .collect::<String>()
23458 .to_ascii_lowercase();
23459 completion
23460 .label
23461 .text
23462 .to_ascii_lowercase()
23463 .ends_with(&text_after_cursor)
23464 } else {
23465 true
23466 }
23467 }
23468 }
23469 }
23470 };
23471
23472 if should_replace {
23473 replace_range.clone()
23474 } else {
23475 insert_range.clone()
23476 }
23477 } else {
23478 replace_range.clone()
23479 }
23480 };
23481
23482 if range_to_replace
23483 .end
23484 .cmp(cursor_position, &buffer_snapshot)
23485 .is_lt()
23486 {
23487 range_to_replace.end = *cursor_position;
23488 }
23489
23490 let replace_range = range_to_replace.to_offset(buffer);
23491 CompletionEdit {
23492 new_text,
23493 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23494 snippet,
23495 }
23496}
23497
23498struct CompletionEdit {
23499 new_text: String,
23500 replace_range: Range<BufferOffset>,
23501 snippet: Option<Snippet>,
23502}
23503
23504fn comment_delimiter_for_newline(
23505 start_point: &Point,
23506 buffer: &MultiBufferSnapshot,
23507 language: &LanguageScope,
23508) -> Option<Arc<str>> {
23509 let delimiters = language.line_comment_prefixes();
23510 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
23511 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23512
23513 let num_of_whitespaces = snapshot
23514 .chars_for_range(range.clone())
23515 .take_while(|c| c.is_whitespace())
23516 .count();
23517 let comment_candidate = snapshot
23518 .chars_for_range(range.clone())
23519 .skip(num_of_whitespaces)
23520 .take(max_len_of_delimiter)
23521 .collect::<String>();
23522 let (delimiter, trimmed_len) = delimiters
23523 .iter()
23524 .filter_map(|delimiter| {
23525 let prefix = delimiter.trim_end();
23526 if comment_candidate.starts_with(prefix) {
23527 Some((delimiter, prefix.len()))
23528 } else {
23529 None
23530 }
23531 })
23532 .max_by_key(|(_, len)| *len)?;
23533
23534 if let Some(BlockCommentConfig {
23535 start: block_start, ..
23536 }) = language.block_comment()
23537 {
23538 let block_start_trimmed = block_start.trim_end();
23539 if block_start_trimmed.starts_with(delimiter.trim_end()) {
23540 let line_content = snapshot
23541 .chars_for_range(range)
23542 .skip(num_of_whitespaces)
23543 .take(block_start_trimmed.len())
23544 .collect::<String>();
23545
23546 if line_content.starts_with(block_start_trimmed) {
23547 return None;
23548 }
23549 }
23550 }
23551
23552 let cursor_is_placed_after_comment_marker =
23553 num_of_whitespaces + trimmed_len <= start_point.column as usize;
23554 if cursor_is_placed_after_comment_marker {
23555 Some(delimiter.clone())
23556 } else {
23557 None
23558 }
23559}
23560
23561fn documentation_delimiter_for_newline(
23562 start_point: &Point,
23563 buffer: &MultiBufferSnapshot,
23564 language: &LanguageScope,
23565 newline_config: &mut NewlineConfig,
23566) -> Option<Arc<str>> {
23567 let BlockCommentConfig {
23568 start: start_tag,
23569 end: end_tag,
23570 prefix: delimiter,
23571 tab_size: len,
23572 } = language.documentation_comment()?;
23573 let is_within_block_comment = buffer
23574 .language_scope_at(*start_point)
23575 .is_some_and(|scope| scope.override_name() == Some("comment"));
23576 if !is_within_block_comment {
23577 return None;
23578 }
23579
23580 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23581
23582 let num_of_whitespaces = snapshot
23583 .chars_for_range(range.clone())
23584 .take_while(|c| c.is_whitespace())
23585 .count();
23586
23587 // 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.
23588 let column = start_point.column;
23589 let cursor_is_after_start_tag = {
23590 let start_tag_len = start_tag.len();
23591 let start_tag_line = snapshot
23592 .chars_for_range(range.clone())
23593 .skip(num_of_whitespaces)
23594 .take(start_tag_len)
23595 .collect::<String>();
23596 if start_tag_line.starts_with(start_tag.as_ref()) {
23597 num_of_whitespaces + start_tag_len <= column as usize
23598 } else {
23599 false
23600 }
23601 };
23602
23603 let cursor_is_after_delimiter = {
23604 let delimiter_trim = delimiter.trim_end();
23605 let delimiter_line = snapshot
23606 .chars_for_range(range.clone())
23607 .skip(num_of_whitespaces)
23608 .take(delimiter_trim.len())
23609 .collect::<String>();
23610 if delimiter_line.starts_with(delimiter_trim) {
23611 num_of_whitespaces + delimiter_trim.len() <= column as usize
23612 } else {
23613 false
23614 }
23615 };
23616
23617 let mut needs_extra_line = false;
23618 let mut extra_line_additional_indent = IndentSize::spaces(0);
23619
23620 let cursor_is_before_end_tag_if_exists = {
23621 let mut char_position = 0u32;
23622 let mut end_tag_offset = None;
23623
23624 'outer: for chunk in snapshot.text_for_range(range) {
23625 if let Some(byte_pos) = chunk.find(&**end_tag) {
23626 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
23627 end_tag_offset = Some(char_position + chars_before_match);
23628 break 'outer;
23629 }
23630 char_position += chunk.chars().count() as u32;
23631 }
23632
23633 if let Some(end_tag_offset) = end_tag_offset {
23634 let cursor_is_before_end_tag = column <= end_tag_offset;
23635 if cursor_is_after_start_tag {
23636 if cursor_is_before_end_tag {
23637 needs_extra_line = true;
23638 }
23639 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
23640 if cursor_is_at_start_of_end_tag {
23641 extra_line_additional_indent.len = *len;
23642 }
23643 }
23644 cursor_is_before_end_tag
23645 } else {
23646 true
23647 }
23648 };
23649
23650 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
23651 && cursor_is_before_end_tag_if_exists
23652 {
23653 let additional_indent = if cursor_is_after_start_tag {
23654 IndentSize::spaces(*len)
23655 } else {
23656 IndentSize::spaces(0)
23657 };
23658
23659 *newline_config = NewlineConfig::Newline {
23660 additional_indent,
23661 extra_line_additional_indent: if needs_extra_line {
23662 Some(extra_line_additional_indent)
23663 } else {
23664 None
23665 },
23666 prevent_auto_indent: true,
23667 };
23668 Some(delimiter.clone())
23669 } else {
23670 None
23671 }
23672}
23673
23674const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
23675
23676fn list_delimiter_for_newline(
23677 start_point: &Point,
23678 buffer: &MultiBufferSnapshot,
23679 language: &LanguageScope,
23680 newline_config: &mut NewlineConfig,
23681) -> Option<Arc<str>> {
23682 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23683
23684 let num_of_whitespaces = snapshot
23685 .chars_for_range(range.clone())
23686 .take_while(|c| c.is_whitespace())
23687 .count();
23688
23689 let task_list_entries: Vec<_> = language
23690 .task_list()
23691 .into_iter()
23692 .flat_map(|config| {
23693 config
23694 .prefixes
23695 .iter()
23696 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
23697 })
23698 .collect();
23699 let unordered_list_entries: Vec<_> = language
23700 .unordered_list()
23701 .iter()
23702 .map(|marker| (marker.as_ref(), marker.as_ref()))
23703 .collect();
23704
23705 let all_entries: Vec<_> = task_list_entries
23706 .into_iter()
23707 .chain(unordered_list_entries)
23708 .collect();
23709
23710 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
23711 let candidate: String = snapshot
23712 .chars_for_range(range.clone())
23713 .skip(num_of_whitespaces)
23714 .take(max_prefix_len)
23715 .collect();
23716
23717 if let Some((prefix, continuation)) = all_entries
23718 .iter()
23719 .filter(|(prefix, _)| candidate.starts_with(*prefix))
23720 .max_by_key(|(prefix, _)| prefix.len())
23721 {
23722 let end_of_prefix = num_of_whitespaces + prefix.len();
23723 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
23724 let has_content_after_marker = snapshot
23725 .chars_for_range(range)
23726 .skip(end_of_prefix)
23727 .any(|c| !c.is_whitespace());
23728
23729 if has_content_after_marker && cursor_is_after_prefix {
23730 return Some((*continuation).into());
23731 }
23732
23733 if start_point.column as usize == end_of_prefix {
23734 if num_of_whitespaces == 0 {
23735 *newline_config = NewlineConfig::ClearCurrentLine;
23736 } else {
23737 *newline_config = NewlineConfig::UnindentCurrentLine {
23738 continuation: (*continuation).into(),
23739 };
23740 }
23741 }
23742
23743 return None;
23744 }
23745 }
23746
23747 let candidate: String = snapshot
23748 .chars_for_range(range.clone())
23749 .skip(num_of_whitespaces)
23750 .take(ORDERED_LIST_MAX_MARKER_LEN)
23751 .collect();
23752
23753 for ordered_config in language.ordered_list() {
23754 let regex = match Regex::new(&ordered_config.pattern) {
23755 Ok(r) => r,
23756 Err(_) => continue,
23757 };
23758
23759 if let Some(captures) = regex.captures(&candidate) {
23760 let full_match = captures.get(0)?;
23761 let marker_len = full_match.len();
23762 let end_of_prefix = num_of_whitespaces + marker_len;
23763 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
23764
23765 let has_content_after_marker = snapshot
23766 .chars_for_range(range)
23767 .skip(end_of_prefix)
23768 .any(|c| !c.is_whitespace());
23769
23770 if has_content_after_marker && cursor_is_after_prefix {
23771 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
23772 let continuation = ordered_config
23773 .format
23774 .replace("{1}", &(number + 1).to_string());
23775 return Some(continuation.into());
23776 }
23777
23778 if start_point.column as usize == end_of_prefix {
23779 let continuation = ordered_config.format.replace("{1}", "1");
23780 if num_of_whitespaces == 0 {
23781 *newline_config = NewlineConfig::ClearCurrentLine;
23782 } else {
23783 *newline_config = NewlineConfig::UnindentCurrentLine {
23784 continuation: continuation.into(),
23785 };
23786 }
23787 }
23788
23789 return None;
23790 }
23791 }
23792
23793 None
23794}
23795
23796fn is_list_prefix_row(
23797 row: MultiBufferRow,
23798 buffer: &MultiBufferSnapshot,
23799 language: &LanguageScope,
23800) -> bool {
23801 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
23802 return false;
23803 };
23804
23805 let num_of_whitespaces = snapshot
23806 .chars_for_range(range.clone())
23807 .take_while(|c| c.is_whitespace())
23808 .count();
23809
23810 let task_list_prefixes: Vec<_> = language
23811 .task_list()
23812 .into_iter()
23813 .flat_map(|config| {
23814 config
23815 .prefixes
23816 .iter()
23817 .map(|p| p.as_ref())
23818 .collect::<Vec<_>>()
23819 })
23820 .collect();
23821 let unordered_list_markers: Vec<_> = language
23822 .unordered_list()
23823 .iter()
23824 .map(|marker| marker.as_ref())
23825 .collect();
23826 let all_prefixes: Vec<_> = task_list_prefixes
23827 .into_iter()
23828 .chain(unordered_list_markers)
23829 .collect();
23830 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
23831 let candidate: String = snapshot
23832 .chars_for_range(range.clone())
23833 .skip(num_of_whitespaces)
23834 .take(max_prefix_len)
23835 .collect();
23836 if all_prefixes
23837 .iter()
23838 .any(|prefix| candidate.starts_with(*prefix))
23839 {
23840 return true;
23841 }
23842 }
23843
23844 let ordered_list_candidate: String = snapshot
23845 .chars_for_range(range)
23846 .skip(num_of_whitespaces)
23847 .take(ORDERED_LIST_MAX_MARKER_LEN)
23848 .collect();
23849 for ordered_config in language.ordered_list() {
23850 let regex = match Regex::new(&ordered_config.pattern) {
23851 Ok(r) => r,
23852 Err(_) => continue,
23853 };
23854 if let Some(captures) = regex.captures(&ordered_list_candidate) {
23855 return captures.get(0).is_some();
23856 }
23857 }
23858
23859 false
23860}
23861
23862#[derive(Debug)]
23863enum NewlineConfig {
23864 /// Insert newline with optional additional indent and optional extra blank line
23865 Newline {
23866 additional_indent: IndentSize,
23867 extra_line_additional_indent: Option<IndentSize>,
23868 prevent_auto_indent: bool,
23869 },
23870 /// Clear the current line
23871 ClearCurrentLine,
23872 /// Unindent the current line and add continuation
23873 UnindentCurrentLine { continuation: Arc<str> },
23874}
23875
23876impl NewlineConfig {
23877 fn has_extra_line(&self) -> bool {
23878 matches!(
23879 self,
23880 Self::Newline {
23881 extra_line_additional_indent: Some(_),
23882 ..
23883 }
23884 )
23885 }
23886
23887 fn insert_extra_newline_brackets(
23888 buffer: &MultiBufferSnapshot,
23889 range: Range<MultiBufferOffset>,
23890 language: &language::LanguageScope,
23891 ) -> bool {
23892 let leading_whitespace_len = buffer
23893 .reversed_chars_at(range.start)
23894 .take_while(|c| c.is_whitespace() && *c != '\n')
23895 .map(|c| c.len_utf8())
23896 .sum::<usize>();
23897 let trailing_whitespace_len = buffer
23898 .chars_at(range.end)
23899 .take_while(|c| c.is_whitespace() && *c != '\n')
23900 .map(|c| c.len_utf8())
23901 .sum::<usize>();
23902 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23903
23904 language.brackets().any(|(pair, enabled)| {
23905 let pair_start = pair.start.trim_end();
23906 let pair_end = pair.end.trim_start();
23907
23908 enabled
23909 && pair.newline
23910 && buffer.contains_str_at(range.end, pair_end)
23911 && buffer.contains_str_at(
23912 range.start.saturating_sub_usize(pair_start.len()),
23913 pair_start,
23914 )
23915 })
23916 }
23917
23918 fn insert_extra_newline_tree_sitter(
23919 buffer: &MultiBufferSnapshot,
23920 range: Range<MultiBufferOffset>,
23921 ) -> bool {
23922 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23923 [(buffer, range, _)] => (*buffer, range.clone()),
23924 _ => return false,
23925 };
23926 let pair = {
23927 let mut result: Option<BracketMatch<usize>> = None;
23928
23929 for pair in buffer
23930 .all_bracket_ranges(range.start.0..range.end.0)
23931 .filter(move |pair| {
23932 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23933 })
23934 {
23935 let len = pair.close_range.end - pair.open_range.start;
23936
23937 if let Some(existing) = &result {
23938 let existing_len = existing.close_range.end - existing.open_range.start;
23939 if len > existing_len {
23940 continue;
23941 }
23942 }
23943
23944 result = Some(pair);
23945 }
23946
23947 result
23948 };
23949 let Some(pair) = pair else {
23950 return false;
23951 };
23952 pair.newline_only
23953 && buffer
23954 .chars_for_range(pair.open_range.end..range.start.0)
23955 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23956 .all(|c| c.is_whitespace() && c != '\n')
23957 }
23958}
23959
23960fn update_uncommitted_diff_for_buffer(
23961 editor: Entity<Editor>,
23962 project: &Entity<Project>,
23963 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23964 buffer: Entity<MultiBuffer>,
23965 cx: &mut App,
23966) -> Task<()> {
23967 let mut tasks = Vec::new();
23968 project.update(cx, |project, cx| {
23969 for buffer in buffers {
23970 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23971 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23972 }
23973 }
23974 });
23975 cx.spawn(async move |cx| {
23976 let diffs = future::join_all(tasks).await;
23977 if editor
23978 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23979 .unwrap_or(false)
23980 {
23981 return;
23982 }
23983
23984 buffer
23985 .update(cx, |buffer, cx| {
23986 for diff in diffs.into_iter().flatten() {
23987 buffer.add_diff(diff, cx);
23988 }
23989 })
23990 .ok();
23991 })
23992}
23993
23994fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23995 let tab_size = tab_size.get() as usize;
23996 let mut width = offset;
23997
23998 for ch in text.chars() {
23999 width += if ch == '\t' {
24000 tab_size - (width % tab_size)
24001 } else {
24002 1
24003 };
24004 }
24005
24006 width - offset
24007}
24008
24009#[cfg(test)]
24010mod tests {
24011 use super::*;
24012
24013 #[test]
24014 fn test_string_size_with_expanded_tabs() {
24015 let nz = |val| NonZeroU32::new(val).unwrap();
24016 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
24017 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
24018 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
24019 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
24020 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
24021 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
24022 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
24023 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
24024 }
24025}
24026
24027/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
24028struct WordBreakingTokenizer<'a> {
24029 input: &'a str,
24030}
24031
24032impl<'a> WordBreakingTokenizer<'a> {
24033 fn new(input: &'a str) -> Self {
24034 Self { input }
24035 }
24036}
24037
24038fn is_char_ideographic(ch: char) -> bool {
24039 use unicode_script::Script::*;
24040 use unicode_script::UnicodeScript;
24041 matches!(ch.script(), Han | Tangut | Yi)
24042}
24043
24044fn is_grapheme_ideographic(text: &str) -> bool {
24045 text.chars().any(is_char_ideographic)
24046}
24047
24048fn is_grapheme_whitespace(text: &str) -> bool {
24049 text.chars().any(|x| x.is_whitespace())
24050}
24051
24052fn should_stay_with_preceding_ideograph(text: &str) -> bool {
24053 text.chars()
24054 .next()
24055 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
24056}
24057
24058#[derive(PartialEq, Eq, Debug, Clone, Copy)]
24059enum WordBreakToken<'a> {
24060 Word { token: &'a str, grapheme_len: usize },
24061 InlineWhitespace { token: &'a str, grapheme_len: usize },
24062 Newline,
24063}
24064
24065impl<'a> Iterator for WordBreakingTokenizer<'a> {
24066 /// Yields a span, the count of graphemes in the token, and whether it was
24067 /// whitespace. Note that it also breaks at word boundaries.
24068 type Item = WordBreakToken<'a>;
24069
24070 fn next(&mut self) -> Option<Self::Item> {
24071 use unicode_segmentation::UnicodeSegmentation;
24072 if self.input.is_empty() {
24073 return None;
24074 }
24075
24076 let mut iter = self.input.graphemes(true).peekable();
24077 let mut offset = 0;
24078 let mut grapheme_len = 0;
24079 if let Some(first_grapheme) = iter.next() {
24080 let is_newline = first_grapheme == "\n";
24081 let is_whitespace = is_grapheme_whitespace(first_grapheme);
24082 offset += first_grapheme.len();
24083 grapheme_len += 1;
24084 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
24085 if let Some(grapheme) = iter.peek().copied()
24086 && should_stay_with_preceding_ideograph(grapheme)
24087 {
24088 offset += grapheme.len();
24089 grapheme_len += 1;
24090 }
24091 } else {
24092 let mut words = self.input[offset..].split_word_bound_indices().peekable();
24093 let mut next_word_bound = words.peek().copied();
24094 if next_word_bound.is_some_and(|(i, _)| i == 0) {
24095 next_word_bound = words.next();
24096 }
24097 while let Some(grapheme) = iter.peek().copied() {
24098 if next_word_bound.is_some_and(|(i, _)| i == offset) {
24099 break;
24100 };
24101 if is_grapheme_whitespace(grapheme) != is_whitespace
24102 || (grapheme == "\n") != is_newline
24103 {
24104 break;
24105 };
24106 offset += grapheme.len();
24107 grapheme_len += 1;
24108 iter.next();
24109 }
24110 }
24111 let token = &self.input[..offset];
24112 self.input = &self.input[offset..];
24113 if token == "\n" {
24114 Some(WordBreakToken::Newline)
24115 } else if is_whitespace {
24116 Some(WordBreakToken::InlineWhitespace {
24117 token,
24118 grapheme_len,
24119 })
24120 } else {
24121 Some(WordBreakToken::Word {
24122 token,
24123 grapheme_len,
24124 })
24125 }
24126 } else {
24127 None
24128 }
24129 }
24130}
24131
24132#[test]
24133fn test_word_breaking_tokenizer() {
24134 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
24135 ("", &[]),
24136 (" ", &[whitespace(" ", 2)]),
24137 ("Ʒ", &[word("Ʒ", 1)]),
24138 ("Ǽ", &[word("Ǽ", 1)]),
24139 ("⋑", &[word("⋑", 1)]),
24140 ("⋑⋑", &[word("⋑⋑", 2)]),
24141 (
24142 "原理,进而",
24143 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
24144 ),
24145 (
24146 "hello world",
24147 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
24148 ),
24149 (
24150 "hello, world",
24151 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
24152 ),
24153 (
24154 " hello world",
24155 &[
24156 whitespace(" ", 2),
24157 word("hello", 5),
24158 whitespace(" ", 1),
24159 word("world", 5),
24160 ],
24161 ),
24162 (
24163 "这是什么 \n 钢笔",
24164 &[
24165 word("这", 1),
24166 word("是", 1),
24167 word("什", 1),
24168 word("么", 1),
24169 whitespace(" ", 1),
24170 newline(),
24171 whitespace(" ", 1),
24172 word("钢", 1),
24173 word("笔", 1),
24174 ],
24175 ),
24176 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
24177 ];
24178
24179 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24180 WordBreakToken::Word {
24181 token,
24182 grapheme_len,
24183 }
24184 }
24185
24186 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24187 WordBreakToken::InlineWhitespace {
24188 token,
24189 grapheme_len,
24190 }
24191 }
24192
24193 fn newline() -> WordBreakToken<'static> {
24194 WordBreakToken::Newline
24195 }
24196
24197 for (input, result) in tests {
24198 assert_eq!(
24199 WordBreakingTokenizer::new(input)
24200 .collect::<Vec<_>>()
24201 .as_slice(),
24202 *result,
24203 );
24204 }
24205}
24206
24207fn wrap_with_prefix(
24208 first_line_prefix: String,
24209 subsequent_lines_prefix: String,
24210 unwrapped_text: String,
24211 wrap_column: usize,
24212 tab_size: NonZeroU32,
24213 preserve_existing_whitespace: bool,
24214) -> String {
24215 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
24216 let subsequent_lines_prefix_len =
24217 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
24218 let mut wrapped_text = String::new();
24219 let mut current_line = first_line_prefix;
24220 let mut is_first_line = true;
24221
24222 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
24223 let mut current_line_len = first_line_prefix_len;
24224 let mut in_whitespace = false;
24225 for token in tokenizer {
24226 let have_preceding_whitespace = in_whitespace;
24227 match token {
24228 WordBreakToken::Word {
24229 token,
24230 grapheme_len,
24231 } => {
24232 in_whitespace = false;
24233 let current_prefix_len = if is_first_line {
24234 first_line_prefix_len
24235 } else {
24236 subsequent_lines_prefix_len
24237 };
24238 if current_line_len + grapheme_len > wrap_column
24239 && current_line_len != current_prefix_len
24240 {
24241 wrapped_text.push_str(current_line.trim_end());
24242 wrapped_text.push('\n');
24243 is_first_line = false;
24244 current_line = subsequent_lines_prefix.clone();
24245 current_line_len = subsequent_lines_prefix_len;
24246 }
24247 current_line.push_str(token);
24248 current_line_len += grapheme_len;
24249 }
24250 WordBreakToken::InlineWhitespace {
24251 mut token,
24252 mut grapheme_len,
24253 } => {
24254 in_whitespace = true;
24255 if have_preceding_whitespace && !preserve_existing_whitespace {
24256 continue;
24257 }
24258 if !preserve_existing_whitespace {
24259 // Keep a single whitespace grapheme as-is
24260 if let Some(first) =
24261 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
24262 {
24263 token = first;
24264 } else {
24265 token = " ";
24266 }
24267 grapheme_len = 1;
24268 }
24269 let current_prefix_len = if is_first_line {
24270 first_line_prefix_len
24271 } else {
24272 subsequent_lines_prefix_len
24273 };
24274 if current_line_len + grapheme_len > wrap_column {
24275 wrapped_text.push_str(current_line.trim_end());
24276 wrapped_text.push('\n');
24277 is_first_line = false;
24278 current_line = subsequent_lines_prefix.clone();
24279 current_line_len = subsequent_lines_prefix_len;
24280 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
24281 current_line.push_str(token);
24282 current_line_len += grapheme_len;
24283 }
24284 }
24285 WordBreakToken::Newline => {
24286 in_whitespace = true;
24287 let current_prefix_len = if is_first_line {
24288 first_line_prefix_len
24289 } else {
24290 subsequent_lines_prefix_len
24291 };
24292 if preserve_existing_whitespace {
24293 wrapped_text.push_str(current_line.trim_end());
24294 wrapped_text.push('\n');
24295 is_first_line = false;
24296 current_line = subsequent_lines_prefix.clone();
24297 current_line_len = subsequent_lines_prefix_len;
24298 } else if have_preceding_whitespace {
24299 continue;
24300 } else if current_line_len + 1 > wrap_column
24301 && current_line_len != current_prefix_len
24302 {
24303 wrapped_text.push_str(current_line.trim_end());
24304 wrapped_text.push('\n');
24305 is_first_line = false;
24306 current_line = subsequent_lines_prefix.clone();
24307 current_line_len = subsequent_lines_prefix_len;
24308 } else if current_line_len != current_prefix_len {
24309 current_line.push(' ');
24310 current_line_len += 1;
24311 }
24312 }
24313 }
24314 }
24315
24316 if !current_line.is_empty() {
24317 wrapped_text.push_str(¤t_line);
24318 }
24319 wrapped_text
24320}
24321
24322#[test]
24323fn test_wrap_with_prefix() {
24324 assert_eq!(
24325 wrap_with_prefix(
24326 "# ".to_string(),
24327 "# ".to_string(),
24328 "abcdefg".to_string(),
24329 4,
24330 NonZeroU32::new(4).unwrap(),
24331 false,
24332 ),
24333 "# abcdefg"
24334 );
24335 assert_eq!(
24336 wrap_with_prefix(
24337 "".to_string(),
24338 "".to_string(),
24339 "\thello world".to_string(),
24340 8,
24341 NonZeroU32::new(4).unwrap(),
24342 false,
24343 ),
24344 "hello\nworld"
24345 );
24346 assert_eq!(
24347 wrap_with_prefix(
24348 "// ".to_string(),
24349 "// ".to_string(),
24350 "xx \nyy zz aa bb cc".to_string(),
24351 12,
24352 NonZeroU32::new(4).unwrap(),
24353 false,
24354 ),
24355 "// xx yy zz\n// aa bb cc"
24356 );
24357 assert_eq!(
24358 wrap_with_prefix(
24359 String::new(),
24360 String::new(),
24361 "这是什么 \n 钢笔".to_string(),
24362 3,
24363 NonZeroU32::new(4).unwrap(),
24364 false,
24365 ),
24366 "这是什\n么 钢\n笔"
24367 );
24368 assert_eq!(
24369 wrap_with_prefix(
24370 String::new(),
24371 String::new(),
24372 format!("foo{}bar", '\u{2009}'), // thin space
24373 80,
24374 NonZeroU32::new(4).unwrap(),
24375 false,
24376 ),
24377 format!("foo{}bar", '\u{2009}')
24378 );
24379}
24380
24381pub trait CollaborationHub {
24382 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
24383 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
24384 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
24385}
24386
24387impl CollaborationHub for Entity<Project> {
24388 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
24389 self.read(cx).collaborators()
24390 }
24391
24392 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
24393 self.read(cx).user_store().read(cx).participant_indices()
24394 }
24395
24396 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
24397 let this = self.read(cx);
24398 let user_ids = this.collaborators().values().map(|c| c.user_id);
24399 this.user_store().read(cx).participant_names(user_ids, cx)
24400 }
24401}
24402
24403pub trait SemanticsProvider {
24404 fn hover(
24405 &self,
24406 buffer: &Entity<Buffer>,
24407 position: text::Anchor,
24408 cx: &mut App,
24409 ) -> Option<Task<Option<Vec<project::Hover>>>>;
24410
24411 fn inline_values(
24412 &self,
24413 buffer_handle: Entity<Buffer>,
24414 range: Range<text::Anchor>,
24415 cx: &mut App,
24416 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24417
24418 fn applicable_inlay_chunks(
24419 &self,
24420 buffer: &Entity<Buffer>,
24421 ranges: &[Range<text::Anchor>],
24422 cx: &mut App,
24423 ) -> Vec<Range<BufferRow>>;
24424
24425 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24426
24427 fn inlay_hints(
24428 &self,
24429 invalidate: InvalidationStrategy,
24430 buffer: Entity<Buffer>,
24431 ranges: Vec<Range<text::Anchor>>,
24432 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24433 cx: &mut App,
24434 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24435
24436 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24437
24438 fn document_highlights(
24439 &self,
24440 buffer: &Entity<Buffer>,
24441 position: text::Anchor,
24442 cx: &mut App,
24443 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24444
24445 fn definitions(
24446 &self,
24447 buffer: &Entity<Buffer>,
24448 position: text::Anchor,
24449 kind: GotoDefinitionKind,
24450 cx: &mut App,
24451 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24452
24453 fn range_for_rename(
24454 &self,
24455 buffer: &Entity<Buffer>,
24456 position: text::Anchor,
24457 cx: &mut App,
24458 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24459
24460 fn perform_rename(
24461 &self,
24462 buffer: &Entity<Buffer>,
24463 position: text::Anchor,
24464 new_name: String,
24465 cx: &mut App,
24466 ) -> Option<Task<Result<ProjectTransaction>>>;
24467}
24468
24469pub trait CompletionProvider {
24470 fn completions(
24471 &self,
24472 excerpt_id: ExcerptId,
24473 buffer: &Entity<Buffer>,
24474 buffer_position: text::Anchor,
24475 trigger: CompletionContext,
24476 window: &mut Window,
24477 cx: &mut Context<Editor>,
24478 ) -> Task<Result<Vec<CompletionResponse>>>;
24479
24480 fn resolve_completions(
24481 &self,
24482 _buffer: Entity<Buffer>,
24483 _completion_indices: Vec<usize>,
24484 _completions: Rc<RefCell<Box<[Completion]>>>,
24485 _cx: &mut Context<Editor>,
24486 ) -> Task<Result<bool>> {
24487 Task::ready(Ok(false))
24488 }
24489
24490 fn apply_additional_edits_for_completion(
24491 &self,
24492 _buffer: Entity<Buffer>,
24493 _completions: Rc<RefCell<Box<[Completion]>>>,
24494 _completion_index: usize,
24495 _push_to_history: bool,
24496 _cx: &mut Context<Editor>,
24497 ) -> Task<Result<Option<language::Transaction>>> {
24498 Task::ready(Ok(None))
24499 }
24500
24501 fn is_completion_trigger(
24502 &self,
24503 buffer: &Entity<Buffer>,
24504 position: language::Anchor,
24505 text: &str,
24506 trigger_in_words: bool,
24507 cx: &mut Context<Editor>,
24508 ) -> bool;
24509
24510 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24511
24512 fn sort_completions(&self) -> bool {
24513 true
24514 }
24515
24516 fn filter_completions(&self) -> bool {
24517 true
24518 }
24519
24520 fn show_snippets(&self) -> bool {
24521 false
24522 }
24523}
24524
24525pub trait CodeActionProvider {
24526 fn id(&self) -> Arc<str>;
24527
24528 fn code_actions(
24529 &self,
24530 buffer: &Entity<Buffer>,
24531 range: Range<text::Anchor>,
24532 window: &mut Window,
24533 cx: &mut App,
24534 ) -> Task<Result<Vec<CodeAction>>>;
24535
24536 fn apply_code_action(
24537 &self,
24538 buffer_handle: Entity<Buffer>,
24539 action: CodeAction,
24540 excerpt_id: ExcerptId,
24541 push_to_history: bool,
24542 window: &mut Window,
24543 cx: &mut App,
24544 ) -> Task<Result<ProjectTransaction>>;
24545}
24546
24547impl CodeActionProvider for Entity<Project> {
24548 fn id(&self) -> Arc<str> {
24549 "project".into()
24550 }
24551
24552 fn code_actions(
24553 &self,
24554 buffer: &Entity<Buffer>,
24555 range: Range<text::Anchor>,
24556 _window: &mut Window,
24557 cx: &mut App,
24558 ) -> Task<Result<Vec<CodeAction>>> {
24559 self.update(cx, |project, cx| {
24560 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24561 let code_actions = project.code_actions(buffer, range, None, cx);
24562 cx.background_spawn(async move {
24563 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24564 Ok(code_lens_actions
24565 .context("code lens fetch")?
24566 .into_iter()
24567 .flatten()
24568 .chain(
24569 code_actions
24570 .context("code action fetch")?
24571 .into_iter()
24572 .flatten(),
24573 )
24574 .collect())
24575 })
24576 })
24577 }
24578
24579 fn apply_code_action(
24580 &self,
24581 buffer_handle: Entity<Buffer>,
24582 action: CodeAction,
24583 _excerpt_id: ExcerptId,
24584 push_to_history: bool,
24585 _window: &mut Window,
24586 cx: &mut App,
24587 ) -> Task<Result<ProjectTransaction>> {
24588 self.update(cx, |project, cx| {
24589 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24590 })
24591 }
24592}
24593
24594fn snippet_completions(
24595 project: &Project,
24596 buffer: &Entity<Buffer>,
24597 buffer_anchor: text::Anchor,
24598 classifier: CharClassifier,
24599 cx: &mut App,
24600) -> Task<Result<CompletionResponse>> {
24601 let languages = buffer.read(cx).languages_at(buffer_anchor);
24602 let snippet_store = project.snippets().read(cx);
24603
24604 let scopes: Vec<_> = languages
24605 .iter()
24606 .filter_map(|language| {
24607 let language_name = language.lsp_id();
24608 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24609
24610 if snippets.is_empty() {
24611 None
24612 } else {
24613 Some((language.default_scope(), snippets))
24614 }
24615 })
24616 .collect();
24617
24618 if scopes.is_empty() {
24619 return Task::ready(Ok(CompletionResponse {
24620 completions: vec![],
24621 display_options: CompletionDisplayOptions::default(),
24622 is_incomplete: false,
24623 }));
24624 }
24625
24626 let snapshot = buffer.read(cx).text_snapshot();
24627 let executor = cx.background_executor().clone();
24628
24629 cx.background_spawn(async move {
24630 let is_word_char = |c| classifier.is_word(c);
24631
24632 let mut is_incomplete = false;
24633 let mut completions: Vec<Completion> = Vec::new();
24634
24635 const MAX_PREFIX_LEN: usize = 128;
24636 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24637 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24638 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24639
24640 let max_buffer_window: String = snapshot
24641 .text_for_range(window_start..buffer_offset)
24642 .collect();
24643
24644 if max_buffer_window.is_empty() {
24645 return Ok(CompletionResponse {
24646 completions: vec![],
24647 display_options: CompletionDisplayOptions::default(),
24648 is_incomplete: true,
24649 });
24650 }
24651
24652 for (_scope, snippets) in scopes.into_iter() {
24653 // Sort snippets by word count to match longer snippet prefixes first.
24654 let mut sorted_snippet_candidates = snippets
24655 .iter()
24656 .enumerate()
24657 .flat_map(|(snippet_ix, snippet)| {
24658 snippet
24659 .prefix
24660 .iter()
24661 .enumerate()
24662 .map(move |(prefix_ix, prefix)| {
24663 let word_count =
24664 snippet_candidate_suffixes(prefix, is_word_char).count();
24665 ((snippet_ix, prefix_ix), prefix, word_count)
24666 })
24667 })
24668 .collect_vec();
24669 sorted_snippet_candidates
24670 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24671
24672 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24673
24674 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24675 .take(
24676 sorted_snippet_candidates
24677 .first()
24678 .map(|(_, _, word_count)| *word_count)
24679 .unwrap_or_default(),
24680 )
24681 .collect_vec();
24682
24683 const MAX_RESULTS: usize = 100;
24684 // Each match also remembers how many characters from the buffer it consumed
24685 let mut matches: Vec<(StringMatch, usize)> = vec![];
24686
24687 let mut snippet_list_cutoff_index = 0;
24688 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24689 let word_count = buffer_index + 1;
24690 // Increase `snippet_list_cutoff_index` until we have all of the
24691 // snippets with sufficiently many words.
24692 while sorted_snippet_candidates
24693 .get(snippet_list_cutoff_index)
24694 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24695 *snippet_word_count >= word_count
24696 })
24697 {
24698 snippet_list_cutoff_index += 1;
24699 }
24700
24701 // Take only the candidates with at least `word_count` many words
24702 let snippet_candidates_at_word_len =
24703 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24704
24705 let candidates = snippet_candidates_at_word_len
24706 .iter()
24707 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24708 .enumerate() // index in `sorted_snippet_candidates`
24709 // First char must match
24710 .filter(|(_ix, prefix)| {
24711 itertools::equal(
24712 prefix
24713 .chars()
24714 .next()
24715 .into_iter()
24716 .flat_map(|c| c.to_lowercase()),
24717 buffer_window
24718 .chars()
24719 .next()
24720 .into_iter()
24721 .flat_map(|c| c.to_lowercase()),
24722 )
24723 })
24724 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24725 .collect::<Vec<StringMatchCandidate>>();
24726
24727 matches.extend(
24728 fuzzy::match_strings(
24729 &candidates,
24730 &buffer_window,
24731 buffer_window.chars().any(|c| c.is_uppercase()),
24732 true,
24733 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24734 &Default::default(),
24735 executor.clone(),
24736 )
24737 .await
24738 .into_iter()
24739 .map(|string_match| (string_match, buffer_window.len())),
24740 );
24741
24742 if matches.len() >= MAX_RESULTS {
24743 break;
24744 }
24745 }
24746
24747 let to_lsp = |point: &text::Anchor| {
24748 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24749 point_to_lsp(end)
24750 };
24751 let lsp_end = to_lsp(&buffer_anchor);
24752
24753 if matches.len() >= MAX_RESULTS {
24754 is_incomplete = true;
24755 }
24756
24757 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24758 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24759 sorted_snippet_candidates[string_match.candidate_id];
24760 let snippet = &snippets[snippet_index];
24761 let start = buffer_offset - buffer_window_len;
24762 let start = snapshot.anchor_before(start);
24763 let range = start..buffer_anchor;
24764 let lsp_start = to_lsp(&start);
24765 let lsp_range = lsp::Range {
24766 start: lsp_start,
24767 end: lsp_end,
24768 };
24769 Completion {
24770 replace_range: range,
24771 new_text: snippet.body.clone(),
24772 source: CompletionSource::Lsp {
24773 insert_range: None,
24774 server_id: LanguageServerId(usize::MAX),
24775 resolved: true,
24776 lsp_completion: Box::new(lsp::CompletionItem {
24777 label: snippet.prefix.first().unwrap().clone(),
24778 kind: Some(CompletionItemKind::SNIPPET),
24779 label_details: snippet.description.as_ref().map(|description| {
24780 lsp::CompletionItemLabelDetails {
24781 detail: Some(description.clone()),
24782 description: None,
24783 }
24784 }),
24785 insert_text_format: Some(InsertTextFormat::SNIPPET),
24786 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24787 lsp::InsertReplaceEdit {
24788 new_text: snippet.body.clone(),
24789 insert: lsp_range,
24790 replace: lsp_range,
24791 },
24792 )),
24793 filter_text: Some(snippet.body.clone()),
24794 sort_text: Some(char::MAX.to_string()),
24795 ..lsp::CompletionItem::default()
24796 }),
24797 lsp_defaults: None,
24798 },
24799 label: CodeLabel {
24800 text: matching_prefix.clone(),
24801 runs: Vec::new(),
24802 filter_range: 0..matching_prefix.len(),
24803 },
24804 icon_path: None,
24805 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24806 single_line: snippet.name.clone().into(),
24807 plain_text: snippet
24808 .description
24809 .clone()
24810 .map(|description| description.into()),
24811 }),
24812 insert_text_mode: None,
24813 confirm: None,
24814 match_start: Some(start),
24815 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24816 }
24817 }));
24818 }
24819
24820 Ok(CompletionResponse {
24821 completions,
24822 display_options: CompletionDisplayOptions::default(),
24823 is_incomplete,
24824 })
24825 })
24826}
24827
24828impl CompletionProvider for Entity<Project> {
24829 fn completions(
24830 &self,
24831 _excerpt_id: ExcerptId,
24832 buffer: &Entity<Buffer>,
24833 buffer_position: text::Anchor,
24834 options: CompletionContext,
24835 _window: &mut Window,
24836 cx: &mut Context<Editor>,
24837 ) -> Task<Result<Vec<CompletionResponse>>> {
24838 self.update(cx, |project, cx| {
24839 let task = project.completions(buffer, buffer_position, options, cx);
24840 cx.background_spawn(task)
24841 })
24842 }
24843
24844 fn resolve_completions(
24845 &self,
24846 buffer: Entity<Buffer>,
24847 completion_indices: Vec<usize>,
24848 completions: Rc<RefCell<Box<[Completion]>>>,
24849 cx: &mut Context<Editor>,
24850 ) -> Task<Result<bool>> {
24851 self.update(cx, |project, cx| {
24852 project.lsp_store().update(cx, |lsp_store, cx| {
24853 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24854 })
24855 })
24856 }
24857
24858 fn apply_additional_edits_for_completion(
24859 &self,
24860 buffer: Entity<Buffer>,
24861 completions: Rc<RefCell<Box<[Completion]>>>,
24862 completion_index: usize,
24863 push_to_history: bool,
24864 cx: &mut Context<Editor>,
24865 ) -> Task<Result<Option<language::Transaction>>> {
24866 self.update(cx, |project, cx| {
24867 project.lsp_store().update(cx, |lsp_store, cx| {
24868 lsp_store.apply_additional_edits_for_completion(
24869 buffer,
24870 completions,
24871 completion_index,
24872 push_to_history,
24873 cx,
24874 )
24875 })
24876 })
24877 }
24878
24879 fn is_completion_trigger(
24880 &self,
24881 buffer: &Entity<Buffer>,
24882 position: language::Anchor,
24883 text: &str,
24884 trigger_in_words: bool,
24885 cx: &mut Context<Editor>,
24886 ) -> bool {
24887 let mut chars = text.chars();
24888 let char = if let Some(char) = chars.next() {
24889 char
24890 } else {
24891 return false;
24892 };
24893 if chars.next().is_some() {
24894 return false;
24895 }
24896
24897 let buffer = buffer.read(cx);
24898 let snapshot = buffer.snapshot();
24899 let classifier = snapshot
24900 .char_classifier_at(position)
24901 .scope_context(Some(CharScopeContext::Completion));
24902 if trigger_in_words && classifier.is_word(char) {
24903 return true;
24904 }
24905
24906 buffer.completion_triggers().contains(text)
24907 }
24908
24909 fn show_snippets(&self) -> bool {
24910 true
24911 }
24912}
24913
24914impl SemanticsProvider for Entity<Project> {
24915 fn hover(
24916 &self,
24917 buffer: &Entity<Buffer>,
24918 position: text::Anchor,
24919 cx: &mut App,
24920 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24921 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24922 }
24923
24924 fn document_highlights(
24925 &self,
24926 buffer: &Entity<Buffer>,
24927 position: text::Anchor,
24928 cx: &mut App,
24929 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24930 Some(self.update(cx, |project, cx| {
24931 project.document_highlights(buffer, position, cx)
24932 }))
24933 }
24934
24935 fn definitions(
24936 &self,
24937 buffer: &Entity<Buffer>,
24938 position: text::Anchor,
24939 kind: GotoDefinitionKind,
24940 cx: &mut App,
24941 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24942 Some(self.update(cx, |project, cx| match kind {
24943 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24944 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24945 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24946 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24947 }))
24948 }
24949
24950 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24951 self.update(cx, |project, cx| {
24952 if project
24953 .active_debug_session(cx)
24954 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24955 {
24956 return true;
24957 }
24958
24959 buffer.update(cx, |buffer, cx| {
24960 project.any_language_server_supports_inlay_hints(buffer, cx)
24961 })
24962 })
24963 }
24964
24965 fn inline_values(
24966 &self,
24967 buffer_handle: Entity<Buffer>,
24968 range: Range<text::Anchor>,
24969 cx: &mut App,
24970 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24971 self.update(cx, |project, cx| {
24972 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24973
24974 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24975 })
24976 }
24977
24978 fn applicable_inlay_chunks(
24979 &self,
24980 buffer: &Entity<Buffer>,
24981 ranges: &[Range<text::Anchor>],
24982 cx: &mut App,
24983 ) -> Vec<Range<BufferRow>> {
24984 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24985 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24986 })
24987 }
24988
24989 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24990 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24991 lsp_store.invalidate_inlay_hints(for_buffers)
24992 });
24993 }
24994
24995 fn inlay_hints(
24996 &self,
24997 invalidate: InvalidationStrategy,
24998 buffer: Entity<Buffer>,
24999 ranges: Vec<Range<text::Anchor>>,
25000 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
25001 cx: &mut App,
25002 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
25003 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
25004 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
25005 }))
25006 }
25007
25008 fn range_for_rename(
25009 &self,
25010 buffer: &Entity<Buffer>,
25011 position: text::Anchor,
25012 cx: &mut App,
25013 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
25014 Some(self.update(cx, |project, cx| {
25015 let buffer = buffer.clone();
25016 let task = project.prepare_rename(buffer.clone(), position, cx);
25017 cx.spawn(async move |_, cx| {
25018 Ok(match task.await? {
25019 PrepareRenameResponse::Success(range) => Some(range),
25020 PrepareRenameResponse::InvalidPosition => None,
25021 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
25022 // Fallback on using TreeSitter info to determine identifier range
25023 buffer.read_with(cx, |buffer, _| {
25024 let snapshot = buffer.snapshot();
25025 let (range, kind) = snapshot.surrounding_word(position, None);
25026 if kind != Some(CharKind::Word) {
25027 return None;
25028 }
25029 Some(
25030 snapshot.anchor_before(range.start)
25031 ..snapshot.anchor_after(range.end),
25032 )
25033 })?
25034 }
25035 })
25036 })
25037 }))
25038 }
25039
25040 fn perform_rename(
25041 &self,
25042 buffer: &Entity<Buffer>,
25043 position: text::Anchor,
25044 new_name: String,
25045 cx: &mut App,
25046 ) -> Option<Task<Result<ProjectTransaction>>> {
25047 Some(self.update(cx, |project, cx| {
25048 project.perform_rename(buffer.clone(), position, new_name, cx)
25049 }))
25050 }
25051}
25052
25053fn consume_contiguous_rows(
25054 contiguous_row_selections: &mut Vec<Selection<Point>>,
25055 selection: &Selection<Point>,
25056 display_map: &DisplaySnapshot,
25057 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
25058) -> (MultiBufferRow, MultiBufferRow) {
25059 contiguous_row_selections.push(selection.clone());
25060 let start_row = starting_row(selection, display_map);
25061 let mut end_row = ending_row(selection, display_map);
25062
25063 while let Some(next_selection) = selections.peek() {
25064 if next_selection.start.row <= end_row.0 {
25065 end_row = ending_row(next_selection, display_map);
25066 contiguous_row_selections.push(selections.next().unwrap().clone());
25067 } else {
25068 break;
25069 }
25070 }
25071 (start_row, end_row)
25072}
25073
25074fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
25075 if selection.start.column > 0 {
25076 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
25077 } else {
25078 MultiBufferRow(selection.start.row)
25079 }
25080}
25081
25082fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
25083 if next_selection.end.column > 0 || next_selection.is_empty() {
25084 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
25085 } else {
25086 MultiBufferRow(next_selection.end.row)
25087 }
25088}
25089
25090impl EditorSnapshot {
25091 pub fn remote_selections_in_range<'a>(
25092 &'a self,
25093 range: &'a Range<Anchor>,
25094 collaboration_hub: &dyn CollaborationHub,
25095 cx: &'a App,
25096 ) -> impl 'a + Iterator<Item = RemoteSelection> {
25097 let participant_names = collaboration_hub.user_names(cx);
25098 let participant_indices = collaboration_hub.user_participant_indices(cx);
25099 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
25100 let collaborators_by_replica_id = collaborators_by_peer_id
25101 .values()
25102 .map(|collaborator| (collaborator.replica_id, collaborator))
25103 .collect::<HashMap<_, _>>();
25104 self.buffer_snapshot()
25105 .selections_in_range(range, false)
25106 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
25107 if replica_id == ReplicaId::AGENT {
25108 Some(RemoteSelection {
25109 replica_id,
25110 selection,
25111 cursor_shape,
25112 line_mode,
25113 collaborator_id: CollaboratorId::Agent,
25114 user_name: Some("Agent".into()),
25115 color: cx.theme().players().agent(),
25116 })
25117 } else {
25118 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
25119 let participant_index = participant_indices.get(&collaborator.user_id).copied();
25120 let user_name = participant_names.get(&collaborator.user_id).cloned();
25121 Some(RemoteSelection {
25122 replica_id,
25123 selection,
25124 cursor_shape,
25125 line_mode,
25126 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
25127 user_name,
25128 color: if let Some(index) = participant_index {
25129 cx.theme().players().color_for_participant(index.0)
25130 } else {
25131 cx.theme().players().absent()
25132 },
25133 })
25134 }
25135 })
25136 }
25137
25138 pub fn hunks_for_ranges(
25139 &self,
25140 ranges: impl IntoIterator<Item = Range<Point>>,
25141 ) -> Vec<MultiBufferDiffHunk> {
25142 let mut hunks = Vec::new();
25143 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
25144 HashMap::default();
25145 for query_range in ranges {
25146 let query_rows =
25147 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
25148 for hunk in self.buffer_snapshot().diff_hunks_in_range(
25149 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
25150 ) {
25151 // Include deleted hunks that are adjacent to the query range, because
25152 // otherwise they would be missed.
25153 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
25154 if hunk.status().is_deleted() {
25155 intersects_range |= hunk.row_range.start == query_rows.end;
25156 intersects_range |= hunk.row_range.end == query_rows.start;
25157 }
25158 if intersects_range {
25159 if !processed_buffer_rows
25160 .entry(hunk.buffer_id)
25161 .or_default()
25162 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
25163 {
25164 continue;
25165 }
25166 hunks.push(hunk);
25167 }
25168 }
25169 }
25170
25171 hunks
25172 }
25173
25174 fn display_diff_hunks_for_rows<'a>(
25175 &'a self,
25176 display_rows: Range<DisplayRow>,
25177 folded_buffers: &'a HashSet<BufferId>,
25178 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
25179 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
25180 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
25181
25182 self.buffer_snapshot()
25183 .diff_hunks_in_range(buffer_start..buffer_end)
25184 .filter_map(|hunk| {
25185 if folded_buffers.contains(&hunk.buffer_id) {
25186 return None;
25187 }
25188
25189 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
25190 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
25191
25192 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
25193 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
25194
25195 let display_hunk = if hunk_display_start.column() != 0 {
25196 DisplayDiffHunk::Folded {
25197 display_row: hunk_display_start.row(),
25198 }
25199 } else {
25200 let mut end_row = hunk_display_end.row();
25201 if hunk_display_end.column() > 0 {
25202 end_row.0 += 1;
25203 }
25204 let is_created_file = hunk.is_created_file();
25205
25206 DisplayDiffHunk::Unfolded {
25207 status: hunk.status(),
25208 diff_base_byte_range: hunk.diff_base_byte_range.start.0
25209 ..hunk.diff_base_byte_range.end.0,
25210 word_diffs: hunk.word_diffs,
25211 display_row_range: hunk_display_start.row()..end_row,
25212 multi_buffer_range: Anchor::range_in_buffer(
25213 hunk.excerpt_id,
25214 hunk.buffer_range,
25215 ),
25216 is_created_file,
25217 }
25218 };
25219
25220 Some(display_hunk)
25221 })
25222 }
25223
25224 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
25225 self.display_snapshot
25226 .buffer_snapshot()
25227 .language_at(position)
25228 }
25229
25230 pub fn is_focused(&self) -> bool {
25231 self.is_focused
25232 }
25233
25234 pub fn placeholder_text(&self) -> Option<String> {
25235 self.placeholder_display_snapshot
25236 .as_ref()
25237 .map(|display_map| display_map.text())
25238 }
25239
25240 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
25241 self.scroll_anchor.scroll_position(&self.display_snapshot)
25242 }
25243
25244 pub fn gutter_dimensions(
25245 &self,
25246 font_id: FontId,
25247 font_size: Pixels,
25248 style: &EditorStyle,
25249 window: &mut Window,
25250 cx: &App,
25251 ) -> GutterDimensions {
25252 if self.show_gutter
25253 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
25254 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
25255 {
25256 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
25257 matches!(
25258 ProjectSettings::get_global(cx).git.git_gutter,
25259 GitGutterSetting::TrackedFiles
25260 )
25261 });
25262 let gutter_settings = EditorSettings::get_global(cx).gutter;
25263 let show_line_numbers = self
25264 .show_line_numbers
25265 .unwrap_or(gutter_settings.line_numbers);
25266 let line_gutter_width = if show_line_numbers {
25267 // Avoid flicker-like gutter resizes when the line number gains another digit by
25268 // only resizing the gutter on files with > 10**min_line_number_digits lines.
25269 let min_width_for_number_on_gutter =
25270 ch_advance * gutter_settings.min_line_number_digits as f32;
25271 self.max_line_number_width(style, window)
25272 .max(min_width_for_number_on_gutter)
25273 } else {
25274 0.0.into()
25275 };
25276
25277 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
25278 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
25279
25280 let git_blame_entries_width =
25281 self.git_blame_gutter_max_author_length
25282 .map(|max_author_length| {
25283 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
25284 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
25285
25286 /// The number of characters to dedicate to gaps and margins.
25287 const SPACING_WIDTH: usize = 4;
25288
25289 let max_char_count = max_author_length.min(renderer.max_author_length())
25290 + ::git::SHORT_SHA_LENGTH
25291 + MAX_RELATIVE_TIMESTAMP.len()
25292 + SPACING_WIDTH;
25293
25294 ch_advance * max_char_count
25295 });
25296
25297 let is_singleton = self.buffer_snapshot().is_singleton();
25298
25299 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
25300 left_padding += if !is_singleton {
25301 ch_width * 4.0
25302 } else if show_runnables || show_breakpoints {
25303 ch_width * 3.0
25304 } else if show_git_gutter && show_line_numbers {
25305 ch_width * 2.0
25306 } else if show_git_gutter || show_line_numbers {
25307 ch_width
25308 } else {
25309 px(0.)
25310 };
25311
25312 let shows_folds = is_singleton && gutter_settings.folds;
25313
25314 let right_padding = if shows_folds && show_line_numbers {
25315 ch_width * 4.0
25316 } else if shows_folds || (!is_singleton && show_line_numbers) {
25317 ch_width * 3.0
25318 } else if show_line_numbers {
25319 ch_width
25320 } else {
25321 px(0.)
25322 };
25323
25324 GutterDimensions {
25325 left_padding,
25326 right_padding,
25327 width: line_gutter_width + left_padding + right_padding,
25328 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
25329 git_blame_entries_width,
25330 }
25331 } else if self.offset_content {
25332 GutterDimensions::default_with_margin(font_id, font_size, cx)
25333 } else {
25334 GutterDimensions::default()
25335 }
25336 }
25337
25338 pub fn render_crease_toggle(
25339 &self,
25340 buffer_row: MultiBufferRow,
25341 row_contains_cursor: bool,
25342 editor: Entity<Editor>,
25343 window: &mut Window,
25344 cx: &mut App,
25345 ) -> Option<AnyElement> {
25346 let folded = self.is_line_folded(buffer_row);
25347 let mut is_foldable = false;
25348
25349 if let Some(crease) = self
25350 .crease_snapshot
25351 .query_row(buffer_row, self.buffer_snapshot())
25352 {
25353 is_foldable = true;
25354 match crease {
25355 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
25356 if let Some(render_toggle) = render_toggle {
25357 let toggle_callback =
25358 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
25359 if folded {
25360 editor.update(cx, |editor, cx| {
25361 editor.fold_at(buffer_row, window, cx)
25362 });
25363 } else {
25364 editor.update(cx, |editor, cx| {
25365 editor.unfold_at(buffer_row, window, cx)
25366 });
25367 }
25368 });
25369 return Some((render_toggle)(
25370 buffer_row,
25371 folded,
25372 toggle_callback,
25373 window,
25374 cx,
25375 ));
25376 }
25377 }
25378 }
25379 }
25380
25381 is_foldable |= self.starts_indent(buffer_row);
25382
25383 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
25384 Some(
25385 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
25386 .toggle_state(folded)
25387 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
25388 if folded {
25389 this.unfold_at(buffer_row, window, cx);
25390 } else {
25391 this.fold_at(buffer_row, window, cx);
25392 }
25393 }))
25394 .into_any_element(),
25395 )
25396 } else {
25397 None
25398 }
25399 }
25400
25401 pub fn render_crease_trailer(
25402 &self,
25403 buffer_row: MultiBufferRow,
25404 window: &mut Window,
25405 cx: &mut App,
25406 ) -> Option<AnyElement> {
25407 let folded = self.is_line_folded(buffer_row);
25408 if let Crease::Inline { render_trailer, .. } = self
25409 .crease_snapshot
25410 .query_row(buffer_row, self.buffer_snapshot())?
25411 {
25412 let render_trailer = render_trailer.as_ref()?;
25413 Some(render_trailer(buffer_row, folded, window, cx))
25414 } else {
25415 None
25416 }
25417 }
25418
25419 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25420 let digit_count = self.widest_line_number().ilog10() + 1;
25421 column_pixels(style, digit_count as usize, window)
25422 }
25423
25424 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
25425 ///
25426 /// This is positive if `base` is before `line`.
25427 fn relative_line_delta(
25428 &self,
25429 base: DisplayRow,
25430 line: DisplayRow,
25431 consider_wrapped_lines: bool,
25432 ) -> i64 {
25433 let point = DisplayPoint::new(line, 0).to_point(self);
25434 self.relative_line_delta_to_point(base, point, consider_wrapped_lines)
25435 }
25436
25437 /// Returns the line delta from `base` to `point` in the multibuffer.
25438 ///
25439 /// This is positive if `base` is before `point`.
25440 pub fn relative_line_delta_to_point(
25441 &self,
25442 base: DisplayRow,
25443 point: Point,
25444 consider_wrapped_lines: bool,
25445 ) -> i64 {
25446 let base_point = DisplayPoint::new(base, 0).to_point(self);
25447 if consider_wrapped_lines {
25448 let wrap_snapshot = self.wrap_snapshot();
25449 let base_wrap_row = wrap_snapshot.make_wrap_point(base_point, Bias::Left).row();
25450 let wrap_row = wrap_snapshot.make_wrap_point(point, Bias::Left).row();
25451 wrap_row.0 as i64 - base_wrap_row.0 as i64
25452 } else {
25453 point.row as i64 - base_point.row as i64
25454 }
25455 }
25456
25457 /// Returns the unsigned relative line number to display for each row in `rows`.
25458 ///
25459 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
25460 pub fn calculate_relative_line_numbers(
25461 &self,
25462 rows: &Range<DisplayRow>,
25463 relative_to: DisplayRow,
25464 count_wrapped_lines: bool,
25465 ) -> HashMap<DisplayRow, u32> {
25466 let initial_offset = self.relative_line_delta(relative_to, rows.start, count_wrapped_lines);
25467
25468 self.row_infos(rows.start)
25469 .take(rows.len())
25470 .enumerate()
25471 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info))
25472 .filter(|(_row, row_info)| {
25473 row_info.buffer_row.is_some()
25474 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
25475 })
25476 .enumerate()
25477 .flat_map(|(i, (row, _row_info))| {
25478 (row != relative_to)
25479 .then_some((row, (initial_offset + i as i64).unsigned_abs() as u32))
25480 })
25481 .collect()
25482 }
25483}
25484
25485pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25486 let font_size = style.text.font_size.to_pixels(window.rem_size());
25487 let layout = window.text_system().shape_line(
25488 SharedString::from(" ".repeat(column)),
25489 font_size,
25490 &[TextRun {
25491 len: column,
25492 font: style.text.font(),
25493 color: Hsla::default(),
25494 ..Default::default()
25495 }],
25496 None,
25497 );
25498
25499 layout.width
25500}
25501
25502impl Deref for EditorSnapshot {
25503 type Target = DisplaySnapshot;
25504
25505 fn deref(&self) -> &Self::Target {
25506 &self.display_snapshot
25507 }
25508}
25509
25510#[derive(Clone, Debug, PartialEq, Eq)]
25511pub enum EditorEvent {
25512 InputIgnored {
25513 text: Arc<str>,
25514 },
25515 InputHandled {
25516 utf16_range_to_replace: Option<Range<isize>>,
25517 text: Arc<str>,
25518 },
25519 ExcerptsAdded {
25520 buffer: Entity<Buffer>,
25521 predecessor: ExcerptId,
25522 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25523 },
25524 ExcerptsRemoved {
25525 ids: Vec<ExcerptId>,
25526 removed_buffer_ids: Vec<BufferId>,
25527 },
25528 BufferFoldToggled {
25529 ids: Vec<ExcerptId>,
25530 folded: bool,
25531 },
25532 ExcerptsEdited {
25533 ids: Vec<ExcerptId>,
25534 },
25535 ExcerptsExpanded {
25536 ids: Vec<ExcerptId>,
25537 },
25538 ExpandExcerptsRequested {
25539 excerpt_ids: Vec<ExcerptId>,
25540 lines: u32,
25541 direction: ExpandExcerptDirection,
25542 },
25543 BufferEdited,
25544 Edited {
25545 transaction_id: clock::Lamport,
25546 },
25547 Reparsed(BufferId),
25548 Focused,
25549 FocusedIn,
25550 Blurred,
25551 DirtyChanged,
25552 Saved,
25553 TitleChanged,
25554 SelectionsChanged {
25555 local: bool,
25556 },
25557 ScrollPositionChanged {
25558 local: bool,
25559 autoscroll: bool,
25560 },
25561 TransactionUndone {
25562 transaction_id: clock::Lamport,
25563 },
25564 TransactionBegun {
25565 transaction_id: clock::Lamport,
25566 },
25567 CursorShapeChanged,
25568 BreadcrumbsChanged,
25569 PushedToNavHistory {
25570 anchor: Anchor,
25571 is_deactivate: bool,
25572 },
25573}
25574
25575impl EventEmitter<EditorEvent> for Editor {}
25576
25577impl Focusable for Editor {
25578 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25579 self.focus_handle.clone()
25580 }
25581}
25582
25583impl Render for Editor {
25584 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25585 EditorElement::new(&cx.entity(), self.create_style(cx))
25586 }
25587}
25588
25589impl EntityInputHandler for Editor {
25590 fn text_for_range(
25591 &mut self,
25592 range_utf16: Range<usize>,
25593 adjusted_range: &mut Option<Range<usize>>,
25594 _: &mut Window,
25595 cx: &mut Context<Self>,
25596 ) -> Option<String> {
25597 let snapshot = self.buffer.read(cx).read(cx);
25598 let start = snapshot.clip_offset_utf16(
25599 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25600 Bias::Left,
25601 );
25602 let end = snapshot.clip_offset_utf16(
25603 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25604 Bias::Right,
25605 );
25606 if (start.0.0..end.0.0) != range_utf16 {
25607 adjusted_range.replace(start.0.0..end.0.0);
25608 }
25609 Some(snapshot.text_for_range(start..end).collect())
25610 }
25611
25612 fn selected_text_range(
25613 &mut self,
25614 ignore_disabled_input: bool,
25615 _: &mut Window,
25616 cx: &mut Context<Self>,
25617 ) -> Option<UTF16Selection> {
25618 // Prevent the IME menu from appearing when holding down an alphabetic key
25619 // while input is disabled.
25620 if !ignore_disabled_input && !self.input_enabled {
25621 return None;
25622 }
25623
25624 let selection = self
25625 .selections
25626 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25627 let range = selection.range();
25628
25629 Some(UTF16Selection {
25630 range: range.start.0.0..range.end.0.0,
25631 reversed: selection.reversed,
25632 })
25633 }
25634
25635 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25636 let snapshot = self.buffer.read(cx).read(cx);
25637 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25638 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25639 }
25640
25641 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25642 self.clear_highlights::<InputComposition>(cx);
25643 self.ime_transaction.take();
25644 }
25645
25646 fn replace_text_in_range(
25647 &mut self,
25648 range_utf16: Option<Range<usize>>,
25649 text: &str,
25650 window: &mut Window,
25651 cx: &mut Context<Self>,
25652 ) {
25653 if !self.input_enabled {
25654 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25655 return;
25656 }
25657
25658 self.transact(window, cx, |this, window, cx| {
25659 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25660 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25661 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25662 Some(this.selection_replacement_ranges(range_utf16, cx))
25663 } else {
25664 this.marked_text_ranges(cx)
25665 };
25666
25667 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25668 let newest_selection_id = this.selections.newest_anchor().id;
25669 this.selections
25670 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25671 .iter()
25672 .zip(ranges_to_replace.iter())
25673 .find_map(|(selection, range)| {
25674 if selection.id == newest_selection_id {
25675 Some(
25676 (range.start.0.0 as isize - selection.head().0.0 as isize)
25677 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25678 )
25679 } else {
25680 None
25681 }
25682 })
25683 });
25684
25685 cx.emit(EditorEvent::InputHandled {
25686 utf16_range_to_replace: range_to_replace,
25687 text: text.into(),
25688 });
25689
25690 if let Some(new_selected_ranges) = new_selected_ranges {
25691 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25692 selections.select_ranges(new_selected_ranges)
25693 });
25694 this.backspace(&Default::default(), window, cx);
25695 }
25696
25697 this.handle_input(text, window, cx);
25698 });
25699
25700 if let Some(transaction) = self.ime_transaction {
25701 self.buffer.update(cx, |buffer, cx| {
25702 buffer.group_until_transaction(transaction, cx);
25703 });
25704 }
25705
25706 self.unmark_text(window, cx);
25707 }
25708
25709 fn replace_and_mark_text_in_range(
25710 &mut self,
25711 range_utf16: Option<Range<usize>>,
25712 text: &str,
25713 new_selected_range_utf16: Option<Range<usize>>,
25714 window: &mut Window,
25715 cx: &mut Context<Self>,
25716 ) {
25717 if !self.input_enabled {
25718 return;
25719 }
25720
25721 let transaction = self.transact(window, cx, |this, window, cx| {
25722 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25723 let snapshot = this.buffer.read(cx).read(cx);
25724 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25725 for marked_range in &mut marked_ranges {
25726 marked_range.end = marked_range.start + relative_range_utf16.end;
25727 marked_range.start += relative_range_utf16.start;
25728 marked_range.start =
25729 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25730 marked_range.end =
25731 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25732 }
25733 }
25734 Some(marked_ranges)
25735 } else if let Some(range_utf16) = range_utf16 {
25736 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25737 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25738 Some(this.selection_replacement_ranges(range_utf16, cx))
25739 } else {
25740 None
25741 };
25742
25743 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25744 let newest_selection_id = this.selections.newest_anchor().id;
25745 this.selections
25746 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25747 .iter()
25748 .zip(ranges_to_replace.iter())
25749 .find_map(|(selection, range)| {
25750 if selection.id == newest_selection_id {
25751 Some(
25752 (range.start.0.0 as isize - selection.head().0.0 as isize)
25753 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25754 )
25755 } else {
25756 None
25757 }
25758 })
25759 });
25760
25761 cx.emit(EditorEvent::InputHandled {
25762 utf16_range_to_replace: range_to_replace,
25763 text: text.into(),
25764 });
25765
25766 if let Some(ranges) = ranges_to_replace {
25767 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25768 s.select_ranges(ranges)
25769 });
25770 }
25771
25772 let marked_ranges = {
25773 let snapshot = this.buffer.read(cx).read(cx);
25774 this.selections
25775 .disjoint_anchors_arc()
25776 .iter()
25777 .map(|selection| {
25778 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25779 })
25780 .collect::<Vec<_>>()
25781 };
25782
25783 if text.is_empty() {
25784 this.unmark_text(window, cx);
25785 } else {
25786 this.highlight_text::<InputComposition>(
25787 marked_ranges.clone(),
25788 HighlightStyle {
25789 underline: Some(UnderlineStyle {
25790 thickness: px(1.),
25791 color: None,
25792 wavy: false,
25793 }),
25794 ..Default::default()
25795 },
25796 cx,
25797 );
25798 }
25799
25800 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25801 let use_autoclose = this.use_autoclose;
25802 let use_auto_surround = this.use_auto_surround;
25803 this.set_use_autoclose(false);
25804 this.set_use_auto_surround(false);
25805 this.handle_input(text, window, cx);
25806 this.set_use_autoclose(use_autoclose);
25807 this.set_use_auto_surround(use_auto_surround);
25808
25809 if let Some(new_selected_range) = new_selected_range_utf16 {
25810 let snapshot = this.buffer.read(cx).read(cx);
25811 let new_selected_ranges = marked_ranges
25812 .into_iter()
25813 .map(|marked_range| {
25814 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25815 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25816 insertion_start.0 + new_selected_range.start,
25817 ));
25818 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25819 insertion_start.0 + new_selected_range.end,
25820 ));
25821 snapshot.clip_offset_utf16(new_start, Bias::Left)
25822 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25823 })
25824 .collect::<Vec<_>>();
25825
25826 drop(snapshot);
25827 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25828 selections.select_ranges(new_selected_ranges)
25829 });
25830 }
25831 });
25832
25833 self.ime_transaction = self.ime_transaction.or(transaction);
25834 if let Some(transaction) = self.ime_transaction {
25835 self.buffer.update(cx, |buffer, cx| {
25836 buffer.group_until_transaction(transaction, cx);
25837 });
25838 }
25839
25840 if self.text_highlights::<InputComposition>(cx).is_none() {
25841 self.ime_transaction.take();
25842 }
25843 }
25844
25845 fn bounds_for_range(
25846 &mut self,
25847 range_utf16: Range<usize>,
25848 element_bounds: gpui::Bounds<Pixels>,
25849 window: &mut Window,
25850 cx: &mut Context<Self>,
25851 ) -> Option<gpui::Bounds<Pixels>> {
25852 let text_layout_details = self.text_layout_details(window);
25853 let CharacterDimensions {
25854 em_width,
25855 em_advance,
25856 line_height,
25857 } = self.character_dimensions(window);
25858
25859 let snapshot = self.snapshot(window, cx);
25860 let scroll_position = snapshot.scroll_position();
25861 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25862
25863 let start =
25864 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25865 let x = Pixels::from(
25866 ScrollOffset::from(
25867 snapshot.x_for_display_point(start, &text_layout_details)
25868 + self.gutter_dimensions.full_width(),
25869 ) - scroll_left,
25870 );
25871 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25872
25873 Some(Bounds {
25874 origin: element_bounds.origin + point(x, y),
25875 size: size(em_width, line_height),
25876 })
25877 }
25878
25879 fn character_index_for_point(
25880 &mut self,
25881 point: gpui::Point<Pixels>,
25882 _window: &mut Window,
25883 _cx: &mut Context<Self>,
25884 ) -> Option<usize> {
25885 let position_map = self.last_position_map.as_ref()?;
25886 if !position_map.text_hitbox.contains(&point) {
25887 return None;
25888 }
25889 let display_point = position_map.point_for_position(point).previous_valid;
25890 let anchor = position_map
25891 .snapshot
25892 .display_point_to_anchor(display_point, Bias::Left);
25893 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25894 Some(utf16_offset.0.0)
25895 }
25896
25897 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25898 self.input_enabled
25899 }
25900}
25901
25902trait SelectionExt {
25903 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25904 fn spanned_rows(
25905 &self,
25906 include_end_if_at_line_start: bool,
25907 map: &DisplaySnapshot,
25908 ) -> Range<MultiBufferRow>;
25909}
25910
25911impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25912 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25913 let start = self
25914 .start
25915 .to_point(map.buffer_snapshot())
25916 .to_display_point(map);
25917 let end = self
25918 .end
25919 .to_point(map.buffer_snapshot())
25920 .to_display_point(map);
25921 if self.reversed {
25922 end..start
25923 } else {
25924 start..end
25925 }
25926 }
25927
25928 fn spanned_rows(
25929 &self,
25930 include_end_if_at_line_start: bool,
25931 map: &DisplaySnapshot,
25932 ) -> Range<MultiBufferRow> {
25933 let start = self.start.to_point(map.buffer_snapshot());
25934 let mut end = self.end.to_point(map.buffer_snapshot());
25935 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25936 end.row -= 1;
25937 }
25938
25939 let buffer_start = map.prev_line_boundary(start).0;
25940 let buffer_end = map.next_line_boundary(end).0;
25941 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25942 }
25943}
25944
25945impl<T: InvalidationRegion> InvalidationStack<T> {
25946 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25947 where
25948 S: Clone + ToOffset,
25949 {
25950 while let Some(region) = self.last() {
25951 let all_selections_inside_invalidation_ranges =
25952 if selections.len() == region.ranges().len() {
25953 selections
25954 .iter()
25955 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25956 .all(|(selection, invalidation_range)| {
25957 let head = selection.head().to_offset(buffer);
25958 invalidation_range.start <= head && invalidation_range.end >= head
25959 })
25960 } else {
25961 false
25962 };
25963
25964 if all_selections_inside_invalidation_ranges {
25965 break;
25966 } else {
25967 self.pop();
25968 }
25969 }
25970 }
25971}
25972
25973impl<T> Default for InvalidationStack<T> {
25974 fn default() -> Self {
25975 Self(Default::default())
25976 }
25977}
25978
25979impl<T> Deref for InvalidationStack<T> {
25980 type Target = Vec<T>;
25981
25982 fn deref(&self) -> &Self::Target {
25983 &self.0
25984 }
25985}
25986
25987impl<T> DerefMut for InvalidationStack<T> {
25988 fn deref_mut(&mut self) -> &mut Self::Target {
25989 &mut self.0
25990 }
25991}
25992
25993impl InvalidationRegion for SnippetState {
25994 fn ranges(&self) -> &[Range<Anchor>] {
25995 &self.ranges[self.active_index]
25996 }
25997}
25998
25999fn edit_prediction_edit_text(
26000 current_snapshot: &BufferSnapshot,
26001 edits: &[(Range<Anchor>, impl AsRef<str>)],
26002 edit_preview: &EditPreview,
26003 include_deletions: bool,
26004 cx: &App,
26005) -> HighlightedText {
26006 let edits = edits
26007 .iter()
26008 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
26009 .collect::<Vec<_>>();
26010
26011 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
26012}
26013
26014fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
26015 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
26016 // Just show the raw edit text with basic styling
26017 let mut text = String::new();
26018 let mut highlights = Vec::new();
26019
26020 let insertion_highlight_style = HighlightStyle {
26021 color: Some(cx.theme().colors().text),
26022 ..Default::default()
26023 };
26024
26025 for (_, edit_text) in edits {
26026 let start_offset = text.len();
26027 text.push_str(edit_text);
26028 let end_offset = text.len();
26029
26030 if start_offset < end_offset {
26031 highlights.push((start_offset..end_offset, insertion_highlight_style));
26032 }
26033 }
26034
26035 HighlightedText {
26036 text: text.into(),
26037 highlights,
26038 }
26039}
26040
26041pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
26042 match severity {
26043 lsp::DiagnosticSeverity::ERROR => colors.error,
26044 lsp::DiagnosticSeverity::WARNING => colors.warning,
26045 lsp::DiagnosticSeverity::INFORMATION => colors.info,
26046 lsp::DiagnosticSeverity::HINT => colors.info,
26047 _ => colors.ignored,
26048 }
26049}
26050
26051pub fn styled_runs_for_code_label<'a>(
26052 label: &'a CodeLabel,
26053 syntax_theme: &'a theme::SyntaxTheme,
26054 local_player: &'a theme::PlayerColor,
26055) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
26056 let fade_out = HighlightStyle {
26057 fade_out: Some(0.35),
26058 ..Default::default()
26059 };
26060
26061 let mut prev_end = label.filter_range.end;
26062 label
26063 .runs
26064 .iter()
26065 .enumerate()
26066 .flat_map(move |(ix, (range, highlight_id))| {
26067 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
26068 HighlightStyle {
26069 color: Some(local_player.cursor),
26070 ..Default::default()
26071 }
26072 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
26073 HighlightStyle {
26074 background_color: Some(local_player.selection),
26075 ..Default::default()
26076 }
26077 } else if let Some(style) = highlight_id.style(syntax_theme) {
26078 style
26079 } else {
26080 return Default::default();
26081 };
26082 let muted_style = style.highlight(fade_out);
26083
26084 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
26085 if range.start >= label.filter_range.end {
26086 if range.start > prev_end {
26087 runs.push((prev_end..range.start, fade_out));
26088 }
26089 runs.push((range.clone(), muted_style));
26090 } else if range.end <= label.filter_range.end {
26091 runs.push((range.clone(), style));
26092 } else {
26093 runs.push((range.start..label.filter_range.end, style));
26094 runs.push((label.filter_range.end..range.end, muted_style));
26095 }
26096 prev_end = cmp::max(prev_end, range.end);
26097
26098 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
26099 runs.push((prev_end..label.text.len(), fade_out));
26100 }
26101
26102 runs
26103 })
26104}
26105
26106pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
26107 let mut prev_index = 0;
26108 let mut prev_codepoint: Option<char> = None;
26109 text.char_indices()
26110 .chain([(text.len(), '\0')])
26111 .filter_map(move |(index, codepoint)| {
26112 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26113 let is_boundary = index == text.len()
26114 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
26115 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
26116 if is_boundary {
26117 let chunk = &text[prev_index..index];
26118 prev_index = index;
26119 Some(chunk)
26120 } else {
26121 None
26122 }
26123 })
26124}
26125
26126/// Given a string of text immediately before the cursor, iterates over possible
26127/// strings a snippet could match to. More precisely: returns an iterator over
26128/// suffixes of `text` created by splitting at word boundaries (before & after
26129/// every non-word character).
26130///
26131/// Shorter suffixes are returned first.
26132pub(crate) fn snippet_candidate_suffixes(
26133 text: &str,
26134 is_word_char: impl Fn(char) -> bool,
26135) -> impl std::iter::Iterator<Item = &str> {
26136 let mut prev_index = text.len();
26137 let mut prev_codepoint = None;
26138 text.char_indices()
26139 .rev()
26140 .chain([(0, '\0')])
26141 .filter_map(move |(index, codepoint)| {
26142 let prev_index = std::mem::replace(&mut prev_index, index);
26143 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26144 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
26145 None
26146 } else {
26147 let chunk = &text[prev_index..]; // go to end of string
26148 Some(chunk)
26149 }
26150 })
26151}
26152
26153pub trait RangeToAnchorExt: Sized {
26154 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
26155
26156 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
26157 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
26158 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
26159 }
26160}
26161
26162impl<T: ToOffset> RangeToAnchorExt for Range<T> {
26163 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
26164 let start_offset = self.start.to_offset(snapshot);
26165 let end_offset = self.end.to_offset(snapshot);
26166 if start_offset == end_offset {
26167 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
26168 } else {
26169 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
26170 }
26171 }
26172}
26173
26174pub trait RowExt {
26175 fn as_f64(&self) -> f64;
26176
26177 fn next_row(&self) -> Self;
26178
26179 fn previous_row(&self) -> Self;
26180
26181 fn minus(&self, other: Self) -> u32;
26182}
26183
26184impl RowExt for DisplayRow {
26185 fn as_f64(&self) -> f64 {
26186 self.0 as _
26187 }
26188
26189 fn next_row(&self) -> Self {
26190 Self(self.0 + 1)
26191 }
26192
26193 fn previous_row(&self) -> Self {
26194 Self(self.0.saturating_sub(1))
26195 }
26196
26197 fn minus(&self, other: Self) -> u32 {
26198 self.0 - other.0
26199 }
26200}
26201
26202impl RowExt for MultiBufferRow {
26203 fn as_f64(&self) -> f64 {
26204 self.0 as _
26205 }
26206
26207 fn next_row(&self) -> Self {
26208 Self(self.0 + 1)
26209 }
26210
26211 fn previous_row(&self) -> Self {
26212 Self(self.0.saturating_sub(1))
26213 }
26214
26215 fn minus(&self, other: Self) -> u32 {
26216 self.0 - other.0
26217 }
26218}
26219
26220trait RowRangeExt {
26221 type Row;
26222
26223 fn len(&self) -> usize;
26224
26225 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
26226}
26227
26228impl RowRangeExt for Range<MultiBufferRow> {
26229 type Row = MultiBufferRow;
26230
26231 fn len(&self) -> usize {
26232 (self.end.0 - self.start.0) as usize
26233 }
26234
26235 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
26236 (self.start.0..self.end.0).map(MultiBufferRow)
26237 }
26238}
26239
26240impl RowRangeExt for Range<DisplayRow> {
26241 type Row = DisplayRow;
26242
26243 fn len(&self) -> usize {
26244 (self.end.0 - self.start.0) as usize
26245 }
26246
26247 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
26248 (self.start.0..self.end.0).map(DisplayRow)
26249 }
26250}
26251
26252/// If select range has more than one line, we
26253/// just point the cursor to range.start.
26254fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
26255 if range.start.row == range.end.row {
26256 range
26257 } else {
26258 range.start..range.start
26259 }
26260}
26261pub struct KillRing(ClipboardItem);
26262impl Global for KillRing {}
26263
26264const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
26265
26266enum BreakpointPromptEditAction {
26267 Log,
26268 Condition,
26269 HitCondition,
26270}
26271
26272struct BreakpointPromptEditor {
26273 pub(crate) prompt: Entity<Editor>,
26274 editor: WeakEntity<Editor>,
26275 breakpoint_anchor: Anchor,
26276 breakpoint: Breakpoint,
26277 edit_action: BreakpointPromptEditAction,
26278 block_ids: HashSet<CustomBlockId>,
26279 editor_margins: Arc<Mutex<EditorMargins>>,
26280 _subscriptions: Vec<Subscription>,
26281}
26282
26283impl BreakpointPromptEditor {
26284 const MAX_LINES: u8 = 4;
26285
26286 fn new(
26287 editor: WeakEntity<Editor>,
26288 breakpoint_anchor: Anchor,
26289 breakpoint: Breakpoint,
26290 edit_action: BreakpointPromptEditAction,
26291 window: &mut Window,
26292 cx: &mut Context<Self>,
26293 ) -> Self {
26294 let base_text = match edit_action {
26295 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
26296 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
26297 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
26298 }
26299 .map(|msg| msg.to_string())
26300 .unwrap_or_default();
26301
26302 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
26303 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26304
26305 let prompt = cx.new(|cx| {
26306 let mut prompt = Editor::new(
26307 EditorMode::AutoHeight {
26308 min_lines: 1,
26309 max_lines: Some(Self::MAX_LINES as usize),
26310 },
26311 buffer,
26312 None,
26313 window,
26314 cx,
26315 );
26316 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
26317 prompt.set_show_cursor_when_unfocused(false, cx);
26318 prompt.set_placeholder_text(
26319 match edit_action {
26320 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
26321 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
26322 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
26323 },
26324 window,
26325 cx,
26326 );
26327
26328 prompt
26329 });
26330
26331 Self {
26332 prompt,
26333 editor,
26334 breakpoint_anchor,
26335 breakpoint,
26336 edit_action,
26337 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
26338 block_ids: Default::default(),
26339 _subscriptions: vec![],
26340 }
26341 }
26342
26343 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
26344 self.block_ids.extend(block_ids)
26345 }
26346
26347 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
26348 if let Some(editor) = self.editor.upgrade() {
26349 let message = self
26350 .prompt
26351 .read(cx)
26352 .buffer
26353 .read(cx)
26354 .as_singleton()
26355 .expect("A multi buffer in breakpoint prompt isn't possible")
26356 .read(cx)
26357 .as_rope()
26358 .to_string();
26359
26360 editor.update(cx, |editor, cx| {
26361 editor.edit_breakpoint_at_anchor(
26362 self.breakpoint_anchor,
26363 self.breakpoint.clone(),
26364 match self.edit_action {
26365 BreakpointPromptEditAction::Log => {
26366 BreakpointEditAction::EditLogMessage(message.into())
26367 }
26368 BreakpointPromptEditAction::Condition => {
26369 BreakpointEditAction::EditCondition(message.into())
26370 }
26371 BreakpointPromptEditAction::HitCondition => {
26372 BreakpointEditAction::EditHitCondition(message.into())
26373 }
26374 },
26375 cx,
26376 );
26377
26378 editor.remove_blocks(self.block_ids.clone(), None, cx);
26379 cx.focus_self(window);
26380 });
26381 }
26382 }
26383
26384 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
26385 self.editor
26386 .update(cx, |editor, cx| {
26387 editor.remove_blocks(self.block_ids.clone(), None, cx);
26388 window.focus(&editor.focus_handle, cx);
26389 })
26390 .log_err();
26391 }
26392
26393 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
26394 let settings = ThemeSettings::get_global(cx);
26395 let text_style = TextStyle {
26396 color: if self.prompt.read(cx).read_only(cx) {
26397 cx.theme().colors().text_disabled
26398 } else {
26399 cx.theme().colors().text
26400 },
26401 font_family: settings.buffer_font.family.clone(),
26402 font_fallbacks: settings.buffer_font.fallbacks.clone(),
26403 font_size: settings.buffer_font_size(cx).into(),
26404 font_weight: settings.buffer_font.weight,
26405 line_height: relative(settings.buffer_line_height.value()),
26406 ..Default::default()
26407 };
26408 EditorElement::new(
26409 &self.prompt,
26410 EditorStyle {
26411 background: cx.theme().colors().editor_background,
26412 local_player: cx.theme().players().local(),
26413 text: text_style,
26414 ..Default::default()
26415 },
26416 )
26417 }
26418}
26419
26420impl Render for BreakpointPromptEditor {
26421 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26422 let editor_margins = *self.editor_margins.lock();
26423 let gutter_dimensions = editor_margins.gutter;
26424 h_flex()
26425 .key_context("Editor")
26426 .bg(cx.theme().colors().editor_background)
26427 .border_y_1()
26428 .border_color(cx.theme().status().info_border)
26429 .size_full()
26430 .py(window.line_height() / 2.5)
26431 .on_action(cx.listener(Self::confirm))
26432 .on_action(cx.listener(Self::cancel))
26433 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
26434 .child(div().flex_1().child(self.render_prompt_editor(cx)))
26435 }
26436}
26437
26438impl Focusable for BreakpointPromptEditor {
26439 fn focus_handle(&self, cx: &App) -> FocusHandle {
26440 self.prompt.focus_handle(cx)
26441 }
26442}
26443
26444fn all_edits_insertions_or_deletions(
26445 edits: &Vec<(Range<Anchor>, Arc<str>)>,
26446 snapshot: &MultiBufferSnapshot,
26447) -> bool {
26448 let mut all_insertions = true;
26449 let mut all_deletions = true;
26450
26451 for (range, new_text) in edits.iter() {
26452 let range_is_empty = range.to_offset(snapshot).is_empty();
26453 let text_is_empty = new_text.is_empty();
26454
26455 if range_is_empty != text_is_empty {
26456 if range_is_empty {
26457 all_deletions = false;
26458 } else {
26459 all_insertions = false;
26460 }
26461 } else {
26462 return false;
26463 }
26464
26465 if !all_insertions && !all_deletions {
26466 return false;
26467 }
26468 }
26469 all_insertions || all_deletions
26470}
26471
26472struct MissingEditPredictionKeybindingTooltip;
26473
26474impl Render for MissingEditPredictionKeybindingTooltip {
26475 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26476 ui::tooltip_container(cx, |container, cx| {
26477 container
26478 .flex_shrink_0()
26479 .max_w_80()
26480 .min_h(rems_from_px(124.))
26481 .justify_between()
26482 .child(
26483 v_flex()
26484 .flex_1()
26485 .text_ui_sm(cx)
26486 .child(Label::new("Conflict with Accept Keybinding"))
26487 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26488 )
26489 .child(
26490 h_flex()
26491 .pb_1()
26492 .gap_1()
26493 .items_end()
26494 .w_full()
26495 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26496 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26497 }))
26498 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26499 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26500 })),
26501 )
26502 })
26503 }
26504}
26505
26506#[derive(Debug, Clone, Copy, PartialEq)]
26507pub struct LineHighlight {
26508 pub background: Background,
26509 pub border: Option<gpui::Hsla>,
26510 pub include_gutter: bool,
26511 pub type_id: Option<TypeId>,
26512}
26513
26514struct LineManipulationResult {
26515 pub new_text: String,
26516 pub line_count_before: usize,
26517 pub line_count_after: usize,
26518}
26519
26520fn render_diff_hunk_controls(
26521 row: u32,
26522 status: &DiffHunkStatus,
26523 hunk_range: Range<Anchor>,
26524 is_created_file: bool,
26525 line_height: Pixels,
26526 editor: &Entity<Editor>,
26527 _window: &mut Window,
26528 cx: &mut App,
26529) -> AnyElement {
26530 h_flex()
26531 .h(line_height)
26532 .mr_1()
26533 .gap_1()
26534 .px_0p5()
26535 .pb_1()
26536 .border_x_1()
26537 .border_b_1()
26538 .border_color(cx.theme().colors().border_variant)
26539 .rounded_b_lg()
26540 .bg(cx.theme().colors().editor_background)
26541 .gap_1()
26542 .block_mouse_except_scroll()
26543 .shadow_md()
26544 .child(if status.has_secondary_hunk() {
26545 Button::new(("stage", row as u64), "Stage")
26546 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26547 .tooltip({
26548 let focus_handle = editor.focus_handle(cx);
26549 move |_window, cx| {
26550 Tooltip::for_action_in(
26551 "Stage Hunk",
26552 &::git::ToggleStaged,
26553 &focus_handle,
26554 cx,
26555 )
26556 }
26557 })
26558 .on_click({
26559 let editor = editor.clone();
26560 move |_event, _window, cx| {
26561 editor.update(cx, |editor, cx| {
26562 editor.stage_or_unstage_diff_hunks(
26563 true,
26564 vec![hunk_range.start..hunk_range.start],
26565 cx,
26566 );
26567 });
26568 }
26569 })
26570 } else {
26571 Button::new(("unstage", row as u64), "Unstage")
26572 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26573 .tooltip({
26574 let focus_handle = editor.focus_handle(cx);
26575 move |_window, cx| {
26576 Tooltip::for_action_in(
26577 "Unstage Hunk",
26578 &::git::ToggleStaged,
26579 &focus_handle,
26580 cx,
26581 )
26582 }
26583 })
26584 .on_click({
26585 let editor = editor.clone();
26586 move |_event, _window, cx| {
26587 editor.update(cx, |editor, cx| {
26588 editor.stage_or_unstage_diff_hunks(
26589 false,
26590 vec![hunk_range.start..hunk_range.start],
26591 cx,
26592 );
26593 });
26594 }
26595 })
26596 })
26597 .child(
26598 Button::new(("restore", row as u64), "Restore")
26599 .tooltip({
26600 let focus_handle = editor.focus_handle(cx);
26601 move |_window, cx| {
26602 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26603 }
26604 })
26605 .on_click({
26606 let editor = editor.clone();
26607 move |_event, window, cx| {
26608 editor.update(cx, |editor, cx| {
26609 let snapshot = editor.snapshot(window, cx);
26610 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26611 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26612 });
26613 }
26614 })
26615 .disabled(is_created_file),
26616 )
26617 .when(
26618 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26619 |el| {
26620 el.child(
26621 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26622 .shape(IconButtonShape::Square)
26623 .icon_size(IconSize::Small)
26624 // .disabled(!has_multiple_hunks)
26625 .tooltip({
26626 let focus_handle = editor.focus_handle(cx);
26627 move |_window, cx| {
26628 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26629 }
26630 })
26631 .on_click({
26632 let editor = editor.clone();
26633 move |_event, window, cx| {
26634 editor.update(cx, |editor, cx| {
26635 let snapshot = editor.snapshot(window, cx);
26636 let position =
26637 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26638 editor.go_to_hunk_before_or_after_position(
26639 &snapshot,
26640 position,
26641 Direction::Next,
26642 window,
26643 cx,
26644 );
26645 editor.expand_selected_diff_hunks(cx);
26646 });
26647 }
26648 }),
26649 )
26650 .child(
26651 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26652 .shape(IconButtonShape::Square)
26653 .icon_size(IconSize::Small)
26654 // .disabled(!has_multiple_hunks)
26655 .tooltip({
26656 let focus_handle = editor.focus_handle(cx);
26657 move |_window, cx| {
26658 Tooltip::for_action_in(
26659 "Previous Hunk",
26660 &GoToPreviousHunk,
26661 &focus_handle,
26662 cx,
26663 )
26664 }
26665 })
26666 .on_click({
26667 let editor = editor.clone();
26668 move |_event, window, cx| {
26669 editor.update(cx, |editor, cx| {
26670 let snapshot = editor.snapshot(window, cx);
26671 let point =
26672 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26673 editor.go_to_hunk_before_or_after_position(
26674 &snapshot,
26675 point,
26676 Direction::Prev,
26677 window,
26678 cx,
26679 );
26680 editor.expand_selected_diff_hunks(cx);
26681 });
26682 }
26683 }),
26684 )
26685 },
26686 )
26687 .into_any_element()
26688}
26689
26690pub fn multibuffer_context_lines(cx: &App) -> u32 {
26691 EditorSettings::try_get(cx)
26692 .map(|settings| settings.excerpt_context_lines)
26693 .unwrap_or(2)
26694 .min(32)
26695}