1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39mod split;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction_types::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use inlays::Inlay;
65pub use items::MAX_TAB_TITLE_LEN;
66pub use lsp::CompletionContext;
67pub use lsp_ext::lsp_tasks;
68pub use multi_buffer::{
69 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
70 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
71 ToPoint,
72};
73pub use split::SplittableEditor;
74pub use text::Bias;
75
76use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
77use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
78use anyhow::{Context as _, Result, anyhow, bail};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction_types::{
92 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionGranularity,
93};
94use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
95use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
96use futures::{
97 FutureExt, StreamExt as _,
98 future::{self, Shared, join},
99 stream::FuturesUnordered,
100};
101use fuzzy::{StringMatch, StringMatchCandidate};
102use git::blame::{GitBlame, GlobalBlameRenderer};
103use gpui::{
104 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
105 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
106 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
107 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
108 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
109 Render, ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextRun,
110 TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
111 WeakEntity, WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative,
112 size,
113};
114use hover_links::{HoverLink, HoveredLinkState, find_file};
115use hover_popover::{HoverState, hide_hover};
116use indent_guides::ActiveIndentGuidesState;
117use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
118use itertools::{Either, Itertools};
119use language::{
120 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
121 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
122 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
123 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
124 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
125 TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143};
144use parking_lot::Mutex;
145use persistence::DB;
146use project::{
147 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
148 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
149 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
150 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
151 debugger::{
152 breakpoint_store::{
153 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
154 BreakpointStore, BreakpointStoreEvent,
155 },
156 session::{Session, SessionEvent},
157 },
158 git_store::GitStoreEvent,
159 lsp_store::{
160 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
161 OpenLspBufferHandle,
162 },
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use 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 show_line_numbers: Option<bool>,
1076 use_relative_line_numbers: Option<bool>,
1077 show_git_diff_gutter: Option<bool>,
1078 show_code_actions: Option<bool>,
1079 show_runnables: Option<bool>,
1080 show_breakpoints: Option<bool>,
1081 show_wrap_guides: Option<bool>,
1082 show_indent_guides: Option<bool>,
1083 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1084 highlight_order: usize,
1085 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1086 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1087 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1088 scrollbar_marker_state: ScrollbarMarkerState,
1089 active_indent_guides_state: ActiveIndentGuidesState,
1090 nav_history: Option<ItemNavHistory>,
1091 context_menu: RefCell<Option<CodeContextMenu>>,
1092 context_menu_options: Option<ContextMenuOptions>,
1093 mouse_context_menu: Option<MouseContextMenu>,
1094 completion_tasks: Vec<(CompletionId, Task<()>)>,
1095 inline_blame_popover: Option<InlineBlamePopover>,
1096 inline_blame_popover_show_task: Option<Task<()>>,
1097 signature_help_state: SignatureHelpState,
1098 auto_signature_help: Option<bool>,
1099 find_all_references_task_sources: Vec<Anchor>,
1100 next_completion_id: CompletionId,
1101 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1102 code_actions_task: Option<Task<Result<()>>>,
1103 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1104 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1105 document_highlights_task: Option<Task<()>>,
1106 linked_editing_range_task: Option<Task<Option<()>>>,
1107 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1108 pending_rename: Option<RenameState>,
1109 searchable: bool,
1110 cursor_shape: CursorShape,
1111 /// Whether the cursor is offset one character to the left when something is
1112 /// selected (needed for vim visual mode)
1113 cursor_offset_on_selection: bool,
1114 current_line_highlight: Option<CurrentLineHighlight>,
1115 pub collapse_matches: bool,
1116 autoindent_mode: Option<AutoindentMode>,
1117 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1118 input_enabled: bool,
1119 use_modal_editing: bool,
1120 read_only: bool,
1121 leader_id: Option<CollaboratorId>,
1122 remote_id: Option<ViewId>,
1123 pub hover_state: HoverState,
1124 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1125 prev_pressure_stage: Option<PressureStage>,
1126 gutter_hovered: bool,
1127 hovered_link_state: Option<HoveredLinkState>,
1128 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1129 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1130 active_edit_prediction: Option<EditPredictionState>,
1131 /// Used to prevent flickering as the user types while the menu is open
1132 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1133 edit_prediction_settings: EditPredictionSettings,
1134 edit_predictions_hidden_for_vim_mode: bool,
1135 show_edit_predictions_override: Option<bool>,
1136 show_completions_on_input_override: Option<bool>,
1137 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1138 edit_prediction_preview: EditPredictionPreview,
1139 edit_prediction_indent_conflict: bool,
1140 edit_prediction_requires_modifier_in_indent_conflict: bool,
1141 next_inlay_id: usize,
1142 next_color_inlay_id: usize,
1143 _subscriptions: Vec<Subscription>,
1144 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1145 gutter_dimensions: GutterDimensions,
1146 style: Option<EditorStyle>,
1147 text_style_refinement: Option<TextStyleRefinement>,
1148 next_editor_action_id: EditorActionId,
1149 editor_actions: Rc<
1150 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1151 >,
1152 use_autoclose: bool,
1153 use_auto_surround: bool,
1154 auto_replace_emoji_shortcode: bool,
1155 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1156 show_git_blame_gutter: bool,
1157 show_git_blame_inline: bool,
1158 show_git_blame_inline_delay_task: Option<Task<()>>,
1159 git_blame_inline_enabled: bool,
1160 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1161 buffer_serialization: Option<BufferSerialization>,
1162 show_selection_menu: Option<bool>,
1163 blame: Option<Entity<GitBlame>>,
1164 blame_subscription: Option<Subscription>,
1165 custom_context_menu: Option<
1166 Box<
1167 dyn 'static
1168 + Fn(
1169 &mut Self,
1170 DisplayPoint,
1171 &mut Window,
1172 &mut Context<Self>,
1173 ) -> Option<Entity<ui::ContextMenu>>,
1174 >,
1175 >,
1176 last_bounds: Option<Bounds<Pixels>>,
1177 last_position_map: Option<Rc<PositionMap>>,
1178 expect_bounds_change: Option<Bounds<Pixels>>,
1179 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1180 tasks_update_task: Option<Task<()>>,
1181 breakpoint_store: Option<Entity<BreakpointStore>>,
1182 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1183 hovered_diff_hunk_row: Option<DisplayRow>,
1184 pull_diagnostics_task: Task<()>,
1185 pull_diagnostics_background_task: Task<()>,
1186 in_project_search: bool,
1187 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1188 breadcrumb_header: Option<String>,
1189 focused_block: Option<FocusedBlock>,
1190 next_scroll_position: NextScrollCursorCenterTopBottom,
1191 addons: HashMap<TypeId, Box<dyn Addon>>,
1192 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1193 load_diff_task: Option<Shared<Task<()>>>,
1194 /// Whether we are temporarily displaying a diff other than git's
1195 temporary_diff_override: bool,
1196 selection_mark_mode: bool,
1197 toggle_fold_multiple_buffers: Task<()>,
1198 _scroll_cursor_center_top_bottom_task: Task<()>,
1199 serialize_selections: Task<()>,
1200 serialize_folds: Task<()>,
1201 mouse_cursor_hidden: bool,
1202 minimap: Option<Entity<Self>>,
1203 hide_mouse_mode: HideMouseMode,
1204 pub change_list: ChangeList,
1205 inline_value_cache: InlineValueCache,
1206
1207 selection_drag_state: SelectionDragState,
1208 colors: Option<LspColorData>,
1209 post_scroll_update: Task<()>,
1210 refresh_colors_task: Task<()>,
1211 inlay_hints: Option<LspInlayHintData>,
1212 folding_newlines: Task<()>,
1213 select_next_is_case_sensitive: Option<bool>,
1214 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1215 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1216 accent_data: Option<AccentData>,
1217 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1218 use_base_text_line_numbers: bool,
1219}
1220
1221#[derive(Debug, PartialEq)]
1222struct AccentData {
1223 colors: AccentColors,
1224 overrides: Vec<SharedString>,
1225}
1226
1227fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1228 if debounce_ms > 0 {
1229 Some(Duration::from_millis(debounce_ms))
1230 } else {
1231 None
1232 }
1233}
1234
1235#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1236enum NextScrollCursorCenterTopBottom {
1237 #[default]
1238 Center,
1239 Top,
1240 Bottom,
1241}
1242
1243impl NextScrollCursorCenterTopBottom {
1244 fn next(&self) -> Self {
1245 match self {
1246 Self::Center => Self::Top,
1247 Self::Top => Self::Bottom,
1248 Self::Bottom => Self::Center,
1249 }
1250 }
1251}
1252
1253#[derive(Clone)]
1254pub struct EditorSnapshot {
1255 pub mode: EditorMode,
1256 show_gutter: bool,
1257 offset_content: bool,
1258 show_line_numbers: Option<bool>,
1259 show_git_diff_gutter: Option<bool>,
1260 show_code_actions: Option<bool>,
1261 show_runnables: Option<bool>,
1262 show_breakpoints: Option<bool>,
1263 git_blame_gutter_max_author_length: Option<usize>,
1264 pub display_snapshot: DisplaySnapshot,
1265 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1266 is_focused: bool,
1267 scroll_anchor: ScrollAnchor,
1268 ongoing_scroll: OngoingScroll,
1269 current_line_highlight: CurrentLineHighlight,
1270 gutter_hovered: bool,
1271}
1272
1273#[derive(Default, Debug, Clone, Copy)]
1274pub struct GutterDimensions {
1275 pub left_padding: Pixels,
1276 pub right_padding: Pixels,
1277 pub width: Pixels,
1278 pub margin: Pixels,
1279 pub git_blame_entries_width: Option<Pixels>,
1280}
1281
1282impl GutterDimensions {
1283 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1284 Self {
1285 margin: Self::default_gutter_margin(font_id, font_size, cx),
1286 ..Default::default()
1287 }
1288 }
1289
1290 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1291 -cx.text_system().descent(font_id, font_size)
1292 }
1293 /// The full width of the space taken up by the gutter.
1294 pub fn full_width(&self) -> Pixels {
1295 self.margin + self.width
1296 }
1297
1298 /// The width of the space reserved for the fold indicators,
1299 /// use alongside 'justify_end' and `gutter_width` to
1300 /// right align content with the line numbers
1301 pub fn fold_area_width(&self) -> Pixels {
1302 self.margin + self.right_padding
1303 }
1304}
1305
1306struct CharacterDimensions {
1307 em_width: Pixels,
1308 em_advance: Pixels,
1309 line_height: Pixels,
1310}
1311
1312#[derive(Debug)]
1313pub struct RemoteSelection {
1314 pub replica_id: ReplicaId,
1315 pub selection: Selection<Anchor>,
1316 pub cursor_shape: CursorShape,
1317 pub collaborator_id: CollaboratorId,
1318 pub line_mode: bool,
1319 pub user_name: Option<SharedString>,
1320 pub color: PlayerColor,
1321}
1322
1323#[derive(Clone, Debug)]
1324struct SelectionHistoryEntry {
1325 selections: Arc<[Selection<Anchor>]>,
1326 select_next_state: Option<SelectNextState>,
1327 select_prev_state: Option<SelectNextState>,
1328 add_selections_state: Option<AddSelectionsState>,
1329}
1330
1331#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1332enum SelectionHistoryMode {
1333 #[default]
1334 Normal,
1335 Undoing,
1336 Redoing,
1337 Skipping,
1338}
1339
1340#[derive(Clone, PartialEq, Eq, Hash)]
1341struct HoveredCursor {
1342 replica_id: ReplicaId,
1343 selection_id: usize,
1344}
1345
1346#[derive(Debug)]
1347/// SelectionEffects controls the side-effects of updating the selection.
1348///
1349/// The default behaviour does "what you mostly want":
1350/// - it pushes to the nav history if the cursor moved by >10 lines
1351/// - it re-triggers completion requests
1352/// - it scrolls to fit
1353///
1354/// You might want to modify these behaviours. For example when doing a "jump"
1355/// like go to definition, we always want to add to nav history; but when scrolling
1356/// in vim mode we never do.
1357///
1358/// Similarly, you might want to disable scrolling if you don't want the viewport to
1359/// move.
1360#[derive(Clone)]
1361pub struct SelectionEffects {
1362 nav_history: Option<bool>,
1363 completions: bool,
1364 scroll: Option<Autoscroll>,
1365}
1366
1367impl Default for SelectionEffects {
1368 fn default() -> Self {
1369 Self {
1370 nav_history: None,
1371 completions: true,
1372 scroll: Some(Autoscroll::fit()),
1373 }
1374 }
1375}
1376impl SelectionEffects {
1377 pub fn scroll(scroll: Autoscroll) -> Self {
1378 Self {
1379 scroll: Some(scroll),
1380 ..Default::default()
1381 }
1382 }
1383
1384 pub fn no_scroll() -> Self {
1385 Self {
1386 scroll: None,
1387 ..Default::default()
1388 }
1389 }
1390
1391 pub fn completions(self, completions: bool) -> Self {
1392 Self {
1393 completions,
1394 ..self
1395 }
1396 }
1397
1398 pub fn nav_history(self, nav_history: bool) -> Self {
1399 Self {
1400 nav_history: Some(nav_history),
1401 ..self
1402 }
1403 }
1404}
1405
1406struct DeferredSelectionEffectsState {
1407 changed: bool,
1408 effects: SelectionEffects,
1409 old_cursor_position: Anchor,
1410 history_entry: SelectionHistoryEntry,
1411}
1412
1413#[derive(Default)]
1414struct SelectionHistory {
1415 #[allow(clippy::type_complexity)]
1416 selections_by_transaction:
1417 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1418 mode: SelectionHistoryMode,
1419 undo_stack: VecDeque<SelectionHistoryEntry>,
1420 redo_stack: VecDeque<SelectionHistoryEntry>,
1421}
1422
1423impl SelectionHistory {
1424 #[track_caller]
1425 fn insert_transaction(
1426 &mut self,
1427 transaction_id: TransactionId,
1428 selections: Arc<[Selection<Anchor>]>,
1429 ) {
1430 if selections.is_empty() {
1431 log::error!(
1432 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1433 std::panic::Location::caller()
1434 );
1435 return;
1436 }
1437 self.selections_by_transaction
1438 .insert(transaction_id, (selections, None));
1439 }
1440
1441 #[allow(clippy::type_complexity)]
1442 fn transaction(
1443 &self,
1444 transaction_id: TransactionId,
1445 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1446 self.selections_by_transaction.get(&transaction_id)
1447 }
1448
1449 #[allow(clippy::type_complexity)]
1450 fn transaction_mut(
1451 &mut self,
1452 transaction_id: TransactionId,
1453 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1454 self.selections_by_transaction.get_mut(&transaction_id)
1455 }
1456
1457 fn push(&mut self, entry: SelectionHistoryEntry) {
1458 if !entry.selections.is_empty() {
1459 match self.mode {
1460 SelectionHistoryMode::Normal => {
1461 self.push_undo(entry);
1462 self.redo_stack.clear();
1463 }
1464 SelectionHistoryMode::Undoing => self.push_redo(entry),
1465 SelectionHistoryMode::Redoing => self.push_undo(entry),
1466 SelectionHistoryMode::Skipping => {}
1467 }
1468 }
1469 }
1470
1471 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1472 if self
1473 .undo_stack
1474 .back()
1475 .is_none_or(|e| e.selections != entry.selections)
1476 {
1477 self.undo_stack.push_back(entry);
1478 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1479 self.undo_stack.pop_front();
1480 }
1481 }
1482 }
1483
1484 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1485 if self
1486 .redo_stack
1487 .back()
1488 .is_none_or(|e| e.selections != entry.selections)
1489 {
1490 self.redo_stack.push_back(entry);
1491 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1492 self.redo_stack.pop_front();
1493 }
1494 }
1495 }
1496}
1497
1498#[derive(Clone, Copy)]
1499pub struct RowHighlightOptions {
1500 pub autoscroll: bool,
1501 pub include_gutter: bool,
1502}
1503
1504impl Default for RowHighlightOptions {
1505 fn default() -> Self {
1506 Self {
1507 autoscroll: Default::default(),
1508 include_gutter: true,
1509 }
1510 }
1511}
1512
1513struct RowHighlight {
1514 index: usize,
1515 range: Range<Anchor>,
1516 color: Hsla,
1517 options: RowHighlightOptions,
1518 type_id: TypeId,
1519}
1520
1521#[derive(Clone, Debug)]
1522struct AddSelectionsState {
1523 groups: Vec<AddSelectionsGroup>,
1524}
1525
1526#[derive(Clone, Debug)]
1527struct AddSelectionsGroup {
1528 above: bool,
1529 stack: Vec<usize>,
1530}
1531
1532#[derive(Clone)]
1533struct SelectNextState {
1534 query: AhoCorasick,
1535 wordwise: bool,
1536 done: bool,
1537}
1538
1539impl std::fmt::Debug for SelectNextState {
1540 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1541 f.debug_struct(std::any::type_name::<Self>())
1542 .field("wordwise", &self.wordwise)
1543 .field("done", &self.done)
1544 .finish()
1545 }
1546}
1547
1548#[derive(Debug)]
1549struct AutocloseRegion {
1550 selection_id: usize,
1551 range: Range<Anchor>,
1552 pair: BracketPair,
1553}
1554
1555#[derive(Debug)]
1556struct SnippetState {
1557 ranges: Vec<Vec<Range<Anchor>>>,
1558 active_index: usize,
1559 choices: Vec<Option<Vec<String>>>,
1560}
1561
1562#[doc(hidden)]
1563pub struct RenameState {
1564 pub range: Range<Anchor>,
1565 pub old_name: Arc<str>,
1566 pub editor: Entity<Editor>,
1567 block_id: CustomBlockId,
1568}
1569
1570struct InvalidationStack<T>(Vec<T>);
1571
1572struct RegisteredEditPredictionDelegate {
1573 provider: Arc<dyn EditPredictionDelegateHandle>,
1574 _subscription: Subscription,
1575}
1576
1577#[derive(Debug, PartialEq, Eq)]
1578pub struct ActiveDiagnosticGroup {
1579 pub active_range: Range<Anchor>,
1580 pub active_message: String,
1581 pub group_id: usize,
1582 pub blocks: HashSet<CustomBlockId>,
1583}
1584
1585#[derive(Debug, PartialEq, Eq)]
1586
1587pub(crate) enum ActiveDiagnostic {
1588 None,
1589 All,
1590 Group(ActiveDiagnosticGroup),
1591}
1592
1593#[derive(Serialize, Deserialize, Clone, Debug)]
1594pub struct ClipboardSelection {
1595 /// The number of bytes in this selection.
1596 pub len: usize,
1597 /// Whether this was a full-line selection.
1598 pub is_entire_line: bool,
1599 /// The indentation of the first line when this content was originally copied.
1600 pub first_line_indent: u32,
1601 #[serde(default)]
1602 pub file_path: Option<PathBuf>,
1603 #[serde(default)]
1604 pub line_range: Option<RangeInclusive<u32>>,
1605}
1606
1607impl ClipboardSelection {
1608 pub fn for_buffer(
1609 len: usize,
1610 is_entire_line: bool,
1611 range: Range<Point>,
1612 buffer: &MultiBufferSnapshot,
1613 project: Option<&Entity<Project>>,
1614 cx: &App,
1615 ) -> Self {
1616 let first_line_indent = buffer
1617 .indent_size_for_line(MultiBufferRow(range.start.row))
1618 .len;
1619
1620 let file_path = util::maybe!({
1621 let project = project?.read(cx);
1622 let file = buffer.file_at(range.start)?;
1623 let project_path = ProjectPath {
1624 worktree_id: file.worktree_id(cx),
1625 path: file.path().clone(),
1626 };
1627 project.absolute_path(&project_path, cx)
1628 });
1629
1630 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1631
1632 Self {
1633 len,
1634 is_entire_line,
1635 first_line_indent,
1636 file_path,
1637 line_range,
1638 }
1639 }
1640}
1641
1642// selections, scroll behavior, was newest selection reversed
1643type SelectSyntaxNodeHistoryState = (
1644 Box<[Selection<MultiBufferOffset>]>,
1645 SelectSyntaxNodeScrollBehavior,
1646 bool,
1647);
1648
1649#[derive(Default)]
1650struct SelectSyntaxNodeHistory {
1651 stack: Vec<SelectSyntaxNodeHistoryState>,
1652 // disable temporarily to allow changing selections without losing the stack
1653 pub disable_clearing: bool,
1654}
1655
1656impl SelectSyntaxNodeHistory {
1657 pub fn try_clear(&mut self) {
1658 if !self.disable_clearing {
1659 self.stack.clear();
1660 }
1661 }
1662
1663 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1664 self.stack.push(selection);
1665 }
1666
1667 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1668 self.stack.pop()
1669 }
1670}
1671
1672enum SelectSyntaxNodeScrollBehavior {
1673 CursorTop,
1674 FitSelection,
1675 CursorBottom,
1676}
1677
1678#[derive(Debug)]
1679pub(crate) struct NavigationData {
1680 cursor_anchor: Anchor,
1681 cursor_position: Point,
1682 scroll_anchor: ScrollAnchor,
1683 scroll_top_row: u32,
1684}
1685
1686#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1687pub enum GotoDefinitionKind {
1688 Symbol,
1689 Declaration,
1690 Type,
1691 Implementation,
1692}
1693
1694pub enum FormatTarget {
1695 Buffers(HashSet<Entity<Buffer>>),
1696 Ranges(Vec<Range<MultiBufferPoint>>),
1697}
1698
1699pub(crate) struct FocusedBlock {
1700 id: BlockId,
1701 focus_handle: WeakFocusHandle,
1702}
1703
1704#[derive(Clone, Debug)]
1705enum JumpData {
1706 MultiBufferRow {
1707 row: MultiBufferRow,
1708 line_offset_from_top: u32,
1709 },
1710 MultiBufferPoint {
1711 excerpt_id: ExcerptId,
1712 position: Point,
1713 anchor: text::Anchor,
1714 line_offset_from_top: u32,
1715 },
1716}
1717
1718pub enum MultibufferSelectionMode {
1719 First,
1720 All,
1721}
1722
1723#[derive(Clone, Copy, Debug, Default)]
1724pub struct RewrapOptions {
1725 pub override_language_settings: bool,
1726 pub preserve_existing_whitespace: bool,
1727}
1728
1729impl Editor {
1730 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1731 let buffer = cx.new(|cx| Buffer::local("", cx));
1732 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1733 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1734 }
1735
1736 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1737 let buffer = cx.new(|cx| Buffer::local("", cx));
1738 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1739 Self::new(EditorMode::full(), buffer, None, window, cx)
1740 }
1741
1742 pub fn auto_height(
1743 min_lines: usize,
1744 max_lines: usize,
1745 window: &mut Window,
1746 cx: &mut Context<Self>,
1747 ) -> Self {
1748 let buffer = cx.new(|cx| Buffer::local("", cx));
1749 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1750 Self::new(
1751 EditorMode::AutoHeight {
1752 min_lines,
1753 max_lines: Some(max_lines),
1754 },
1755 buffer,
1756 None,
1757 window,
1758 cx,
1759 )
1760 }
1761
1762 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1763 /// The editor grows as tall as needed to fit its content.
1764 pub fn auto_height_unbounded(
1765 min_lines: usize,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 let buffer = cx.new(|cx| Buffer::local("", cx));
1770 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1771 Self::new(
1772 EditorMode::AutoHeight {
1773 min_lines,
1774 max_lines: None,
1775 },
1776 buffer,
1777 None,
1778 window,
1779 cx,
1780 )
1781 }
1782
1783 pub fn for_buffer(
1784 buffer: Entity<Buffer>,
1785 project: Option<Entity<Project>>,
1786 window: &mut Window,
1787 cx: &mut Context<Self>,
1788 ) -> Self {
1789 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1790 Self::new(EditorMode::full(), buffer, project, window, cx)
1791 }
1792
1793 pub fn for_multibuffer(
1794 buffer: Entity<MultiBuffer>,
1795 project: Option<Entity<Project>>,
1796 window: &mut Window,
1797 cx: &mut Context<Self>,
1798 ) -> Self {
1799 Self::new(EditorMode::full(), buffer, project, window, cx)
1800 }
1801
1802 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1803 let mut clone = Self::new(
1804 self.mode.clone(),
1805 self.buffer.clone(),
1806 self.project.clone(),
1807 window,
1808 cx,
1809 );
1810 self.display_map.update(cx, |display_map, cx| {
1811 let snapshot = display_map.snapshot(cx);
1812 clone.display_map.update(cx, |display_map, cx| {
1813 display_map.set_state(&snapshot, cx);
1814 });
1815 });
1816 clone.folds_did_change(cx);
1817 clone.selections.clone_state(&self.selections);
1818 clone.scroll_manager.clone_state(&self.scroll_manager);
1819 clone.searchable = self.searchable;
1820 clone.read_only = self.read_only;
1821 clone
1822 }
1823
1824 pub fn new(
1825 mode: EditorMode,
1826 buffer: Entity<MultiBuffer>,
1827 project: Option<Entity<Project>>,
1828 window: &mut Window,
1829 cx: &mut Context<Self>,
1830 ) -> Self {
1831 Editor::new_internal(mode, buffer, project, None, window, cx)
1832 }
1833
1834 pub fn sticky_headers(
1835 &self,
1836 style: &EditorStyle,
1837 cx: &App,
1838 ) -> Option<Vec<OutlineItem<Anchor>>> {
1839 let multi_buffer = self.buffer().read(cx);
1840 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1841 let multi_buffer_visible_start = self
1842 .scroll_manager
1843 .anchor()
1844 .anchor
1845 .to_point(&multi_buffer_snapshot);
1846 let max_row = multi_buffer_snapshot.max_point().row;
1847
1848 let start_row = (multi_buffer_visible_start.row).min(max_row);
1849 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1850
1851 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1852 let outline_items = buffer
1853 .outline_items_containing(
1854 Point::new(start_row, 0)..Point::new(end_row, 0),
1855 true,
1856 Some(style.syntax.as_ref()),
1857 )
1858 .into_iter()
1859 .map(|outline_item| OutlineItem {
1860 depth: outline_item.depth,
1861 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1862 source_range_for_text: Anchor::range_in_buffer(
1863 *excerpt_id,
1864 outline_item.source_range_for_text,
1865 ),
1866 text: outline_item.text,
1867 highlight_ranges: outline_item.highlight_ranges,
1868 name_ranges: outline_item.name_ranges,
1869 body_range: outline_item
1870 .body_range
1871 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1872 annotation_range: outline_item
1873 .annotation_range
1874 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1875 });
1876 return Some(outline_items.collect());
1877 }
1878
1879 None
1880 }
1881
1882 fn new_internal(
1883 mode: EditorMode,
1884 multi_buffer: Entity<MultiBuffer>,
1885 project: Option<Entity<Project>>,
1886 display_map: Option<Entity<DisplayMap>>,
1887 window: &mut Window,
1888 cx: &mut Context<Self>,
1889 ) -> Self {
1890 debug_assert!(
1891 display_map.is_none() || mode.is_minimap(),
1892 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1893 );
1894
1895 let full_mode = mode.is_full();
1896 let is_minimap = mode.is_minimap();
1897 let diagnostics_max_severity = if full_mode {
1898 EditorSettings::get_global(cx)
1899 .diagnostics_max_severity
1900 .unwrap_or(DiagnosticSeverity::Hint)
1901 } else {
1902 DiagnosticSeverity::Off
1903 };
1904 let style = window.text_style();
1905 let font_size = style.font_size.to_pixels(window.rem_size());
1906 let editor = cx.entity().downgrade();
1907 let fold_placeholder = FoldPlaceholder {
1908 constrain_width: false,
1909 render: Arc::new(move |fold_id, fold_range, cx| {
1910 let editor = editor.clone();
1911 div()
1912 .id(fold_id)
1913 .bg(cx.theme().colors().ghost_element_background)
1914 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1915 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1916 .rounded_xs()
1917 .size_full()
1918 .cursor_pointer()
1919 .child("⋯")
1920 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1921 .on_click(move |_, _window, cx| {
1922 editor
1923 .update(cx, |editor, cx| {
1924 editor.unfold_ranges(
1925 &[fold_range.start..fold_range.end],
1926 true,
1927 false,
1928 cx,
1929 );
1930 cx.stop_propagation();
1931 })
1932 .ok();
1933 })
1934 .into_any()
1935 }),
1936 merge_adjacent: true,
1937 ..FoldPlaceholder::default()
1938 };
1939 let display_map = display_map.unwrap_or_else(|| {
1940 cx.new(|cx| {
1941 DisplayMap::new(
1942 multi_buffer.clone(),
1943 style.font(),
1944 font_size,
1945 None,
1946 FILE_HEADER_HEIGHT,
1947 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1948 fold_placeholder,
1949 diagnostics_max_severity,
1950 cx,
1951 )
1952 })
1953 });
1954
1955 let selections = SelectionsCollection::new();
1956
1957 let blink_manager = cx.new(|cx| {
1958 let mut blink_manager = BlinkManager::new(
1959 CURSOR_BLINK_INTERVAL,
1960 |cx| EditorSettings::get_global(cx).cursor_blink,
1961 cx,
1962 );
1963 if is_minimap {
1964 blink_manager.disable(cx);
1965 }
1966 blink_manager
1967 });
1968
1969 let soft_wrap_mode_override =
1970 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1971
1972 let mut project_subscriptions = Vec::new();
1973 if full_mode && let Some(project) = project.as_ref() {
1974 project_subscriptions.push(cx.subscribe_in(
1975 project,
1976 window,
1977 |editor, _, event, window, cx| match event {
1978 project::Event::RefreshCodeLens => {
1979 // we always query lens with actions, without storing them, always refreshing them
1980 }
1981 project::Event::RefreshInlayHints {
1982 server_id,
1983 request_id,
1984 } => {
1985 editor.refresh_inlay_hints(
1986 InlayHintRefreshReason::RefreshRequested {
1987 server_id: *server_id,
1988 request_id: *request_id,
1989 },
1990 cx,
1991 );
1992 }
1993 project::Event::LanguageServerRemoved(..) => {
1994 if editor.tasks_update_task.is_none() {
1995 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1996 }
1997 editor.registered_buffers.clear();
1998 editor.register_visible_buffers(cx);
1999 }
2000 project::Event::LanguageServerAdded(..) => {
2001 if editor.tasks_update_task.is_none() {
2002 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2003 }
2004 }
2005 project::Event::SnippetEdit(id, snippet_edits) => {
2006 // todo(lw): Non singletons
2007 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2008 let snapshot = buffer.read(cx).snapshot();
2009 let focus_handle = editor.focus_handle(cx);
2010 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2011 for (range, snippet) in snippet_edits {
2012 let buffer_range =
2013 language::range_from_lsp(*range).to_offset(&snapshot);
2014 editor
2015 .insert_snippet(
2016 &[MultiBufferOffset(buffer_range.start)
2017 ..MultiBufferOffset(buffer_range.end)],
2018 snippet.clone(),
2019 window,
2020 cx,
2021 )
2022 .ok();
2023 }
2024 }
2025 }
2026 }
2027 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2028 let buffer_id = *buffer_id;
2029 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2030 editor.register_buffer(buffer_id, cx);
2031 editor.update_lsp_data(Some(buffer_id), window, cx);
2032 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2033 refresh_linked_ranges(editor, window, cx);
2034 editor.refresh_code_actions(window, cx);
2035 editor.refresh_document_highlights(cx);
2036 }
2037 }
2038
2039 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2040 let Some(workspace) = editor.workspace() else {
2041 return;
2042 };
2043 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2044 else {
2045 return;
2046 };
2047
2048 if active_editor.entity_id() == cx.entity_id() {
2049 let entity_id = cx.entity_id();
2050 workspace.update(cx, |this, cx| {
2051 this.panes_mut()
2052 .iter_mut()
2053 .filter(|pane| pane.entity_id() != entity_id)
2054 .for_each(|p| {
2055 p.update(cx, |pane, _| {
2056 pane.nav_history_mut().rename_item(
2057 entity_id,
2058 project_path.clone(),
2059 abs_path.clone().into(),
2060 );
2061 })
2062 });
2063 });
2064
2065 Self::open_transaction_for_hidden_buffers(
2066 workspace,
2067 transaction.clone(),
2068 "Rename".to_string(),
2069 window,
2070 cx,
2071 );
2072 }
2073 }
2074
2075 project::Event::WorkspaceEditApplied(transaction) => {
2076 let Some(workspace) = editor.workspace() else {
2077 return;
2078 };
2079 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2080 else {
2081 return;
2082 };
2083
2084 if active_editor.entity_id() == cx.entity_id() {
2085 Self::open_transaction_for_hidden_buffers(
2086 workspace,
2087 transaction.clone(),
2088 "LSP Edit".to_string(),
2089 window,
2090 cx,
2091 );
2092 }
2093 }
2094
2095 _ => {}
2096 },
2097 ));
2098 if let Some(task_inventory) = project
2099 .read(cx)
2100 .task_store()
2101 .read(cx)
2102 .task_inventory()
2103 .cloned()
2104 {
2105 project_subscriptions.push(cx.observe_in(
2106 &task_inventory,
2107 window,
2108 |editor, _, window, cx| {
2109 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2110 },
2111 ));
2112 };
2113
2114 project_subscriptions.push(cx.subscribe_in(
2115 &project.read(cx).breakpoint_store(),
2116 window,
2117 |editor, _, event, window, cx| match event {
2118 BreakpointStoreEvent::ClearDebugLines => {
2119 editor.clear_row_highlights::<ActiveDebugLine>();
2120 editor.refresh_inline_values(cx);
2121 }
2122 BreakpointStoreEvent::SetDebugLine => {
2123 if editor.go_to_active_debug_line(window, cx) {
2124 cx.stop_propagation();
2125 }
2126
2127 editor.refresh_inline_values(cx);
2128 }
2129 _ => {}
2130 },
2131 ));
2132 let git_store = project.read(cx).git_store().clone();
2133 let project = project.clone();
2134 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2135 if let GitStoreEvent::RepositoryAdded = event {
2136 this.load_diff_task = Some(
2137 update_uncommitted_diff_for_buffer(
2138 cx.entity(),
2139 &project,
2140 this.buffer.read(cx).all_buffers(),
2141 this.buffer.clone(),
2142 cx,
2143 )
2144 .shared(),
2145 );
2146 }
2147 }));
2148 }
2149
2150 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2151
2152 let inlay_hint_settings =
2153 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2154 let focus_handle = cx.focus_handle();
2155 if !is_minimap {
2156 cx.on_focus(&focus_handle, window, Self::handle_focus)
2157 .detach();
2158 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2159 .detach();
2160 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2161 .detach();
2162 cx.on_blur(&focus_handle, window, Self::handle_blur)
2163 .detach();
2164 cx.observe_pending_input(window, Self::observe_pending_input)
2165 .detach();
2166 }
2167
2168 let show_indent_guides =
2169 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2170 Some(false)
2171 } else {
2172 None
2173 };
2174
2175 let breakpoint_store = match (&mode, project.as_ref()) {
2176 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2177 _ => None,
2178 };
2179
2180 let mut code_action_providers = Vec::new();
2181 let mut load_uncommitted_diff = None;
2182 if let Some(project) = project.clone() {
2183 load_uncommitted_diff = Some(
2184 update_uncommitted_diff_for_buffer(
2185 cx.entity(),
2186 &project,
2187 multi_buffer.read(cx).all_buffers(),
2188 multi_buffer.clone(),
2189 cx,
2190 )
2191 .shared(),
2192 );
2193 code_action_providers.push(Rc::new(project) as Rc<_>);
2194 }
2195
2196 let mut editor = Self {
2197 focus_handle,
2198 show_cursor_when_unfocused: false,
2199 last_focused_descendant: None,
2200 buffer: multi_buffer.clone(),
2201 display_map: display_map.clone(),
2202 placeholder_display_map: None,
2203 selections,
2204 scroll_manager: ScrollManager::new(cx),
2205 columnar_selection_state: None,
2206 add_selections_state: None,
2207 select_next_state: None,
2208 select_prev_state: None,
2209 selection_history: SelectionHistory::default(),
2210 defer_selection_effects: false,
2211 deferred_selection_effects_state: None,
2212 autoclose_regions: Vec::new(),
2213 snippet_stack: InvalidationStack::default(),
2214 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2215 ime_transaction: None,
2216 active_diagnostics: ActiveDiagnostic::None,
2217 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2218 inline_diagnostics_update: Task::ready(()),
2219 inline_diagnostics: Vec::new(),
2220 soft_wrap_mode_override,
2221 diagnostics_max_severity,
2222 hard_wrap: None,
2223 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2224 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2225 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2226 project,
2227 blink_manager: blink_manager.clone(),
2228 show_local_selections: true,
2229 show_scrollbars: ScrollbarAxes {
2230 horizontal: full_mode,
2231 vertical: full_mode,
2232 },
2233 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2234 offset_content: !matches!(mode, EditorMode::SingleLine),
2235 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2236 show_gutter: full_mode,
2237 show_line_numbers: (!full_mode).then_some(false),
2238 use_relative_line_numbers: None,
2239 disable_expand_excerpt_buttons: !full_mode,
2240 show_git_diff_gutter: None,
2241 show_code_actions: None,
2242 show_runnables: None,
2243 show_breakpoints: None,
2244 show_wrap_guides: None,
2245 show_indent_guides,
2246 buffers_with_disabled_indent_guides: HashSet::default(),
2247 highlight_order: 0,
2248 highlighted_rows: HashMap::default(),
2249 background_highlights: HashMap::default(),
2250 gutter_highlights: HashMap::default(),
2251 scrollbar_marker_state: ScrollbarMarkerState::default(),
2252 active_indent_guides_state: ActiveIndentGuidesState::default(),
2253 nav_history: None,
2254 context_menu: RefCell::new(None),
2255 context_menu_options: None,
2256 mouse_context_menu: None,
2257 completion_tasks: Vec::new(),
2258 inline_blame_popover: None,
2259 inline_blame_popover_show_task: None,
2260 signature_help_state: SignatureHelpState::default(),
2261 auto_signature_help: None,
2262 find_all_references_task_sources: Vec::new(),
2263 next_completion_id: 0,
2264 next_inlay_id: 0,
2265 code_action_providers,
2266 available_code_actions: None,
2267 code_actions_task: None,
2268 quick_selection_highlight_task: None,
2269 debounced_selection_highlight_task: None,
2270 document_highlights_task: None,
2271 linked_editing_range_task: None,
2272 pending_rename: None,
2273 searchable: !is_minimap,
2274 cursor_shape: EditorSettings::get_global(cx)
2275 .cursor_shape
2276 .unwrap_or_default(),
2277 cursor_offset_on_selection: false,
2278 current_line_highlight: None,
2279 autoindent_mode: Some(AutoindentMode::EachLine),
2280 collapse_matches: false,
2281 workspace: None,
2282 input_enabled: !is_minimap,
2283 use_modal_editing: full_mode,
2284 read_only: is_minimap,
2285 use_autoclose: true,
2286 use_auto_surround: true,
2287 auto_replace_emoji_shortcode: false,
2288 jsx_tag_auto_close_enabled_in_any_buffer: false,
2289 leader_id: None,
2290 remote_id: None,
2291 hover_state: HoverState::default(),
2292 pending_mouse_down: None,
2293 prev_pressure_stage: None,
2294 hovered_link_state: None,
2295 edit_prediction_provider: None,
2296 active_edit_prediction: None,
2297 stale_edit_prediction_in_menu: None,
2298 edit_prediction_preview: EditPredictionPreview::Inactive {
2299 released_too_fast: false,
2300 },
2301 inline_diagnostics_enabled: full_mode,
2302 diagnostics_enabled: full_mode,
2303 word_completions_enabled: full_mode,
2304 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2305 gutter_hovered: false,
2306 pixel_position_of_newest_cursor: None,
2307 last_bounds: None,
2308 last_position_map: None,
2309 expect_bounds_change: None,
2310 gutter_dimensions: GutterDimensions::default(),
2311 style: None,
2312 show_cursor_names: false,
2313 hovered_cursors: HashMap::default(),
2314 next_editor_action_id: EditorActionId::default(),
2315 editor_actions: Rc::default(),
2316 edit_predictions_hidden_for_vim_mode: false,
2317 show_edit_predictions_override: None,
2318 show_completions_on_input_override: None,
2319 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2320 edit_prediction_settings: EditPredictionSettings::Disabled,
2321 edit_prediction_indent_conflict: false,
2322 edit_prediction_requires_modifier_in_indent_conflict: true,
2323 custom_context_menu: None,
2324 show_git_blame_gutter: false,
2325 show_git_blame_inline: false,
2326 show_selection_menu: None,
2327 show_git_blame_inline_delay_task: None,
2328 git_blame_inline_enabled: full_mode
2329 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2330 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2331 buffer_serialization: is_minimap.not().then(|| {
2332 BufferSerialization::new(
2333 ProjectSettings::get_global(cx)
2334 .session
2335 .restore_unsaved_buffers,
2336 )
2337 }),
2338 blame: None,
2339 blame_subscription: None,
2340 tasks: BTreeMap::default(),
2341
2342 breakpoint_store,
2343 gutter_breakpoint_indicator: (None, None),
2344 hovered_diff_hunk_row: None,
2345 _subscriptions: (!is_minimap)
2346 .then(|| {
2347 vec![
2348 cx.observe(&multi_buffer, Self::on_buffer_changed),
2349 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2350 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2351 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2352 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2353 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2354 cx.observe_window_activation(window, |editor, window, cx| {
2355 let active = window.is_window_active();
2356 editor.blink_manager.update(cx, |blink_manager, cx| {
2357 if active {
2358 blink_manager.enable(cx);
2359 } else {
2360 blink_manager.disable(cx);
2361 }
2362 });
2363 if active {
2364 editor.show_mouse_cursor(cx);
2365 }
2366 }),
2367 ]
2368 })
2369 .unwrap_or_default(),
2370 tasks_update_task: None,
2371 pull_diagnostics_task: Task::ready(()),
2372 pull_diagnostics_background_task: Task::ready(()),
2373 colors: None,
2374 refresh_colors_task: Task::ready(()),
2375 inlay_hints: None,
2376 next_color_inlay_id: 0,
2377 post_scroll_update: Task::ready(()),
2378 linked_edit_ranges: Default::default(),
2379 in_project_search: false,
2380 previous_search_ranges: None,
2381 breadcrumb_header: None,
2382 focused_block: None,
2383 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2384 addons: HashMap::default(),
2385 registered_buffers: HashMap::default(),
2386 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2387 selection_mark_mode: false,
2388 toggle_fold_multiple_buffers: Task::ready(()),
2389 serialize_selections: Task::ready(()),
2390 serialize_folds: Task::ready(()),
2391 text_style_refinement: None,
2392 load_diff_task: load_uncommitted_diff,
2393 temporary_diff_override: false,
2394 mouse_cursor_hidden: false,
2395 minimap: None,
2396 hide_mouse_mode: EditorSettings::get_global(cx)
2397 .hide_mouse
2398 .unwrap_or_default(),
2399 change_list: ChangeList::new(),
2400 mode,
2401 selection_drag_state: SelectionDragState::None,
2402 folding_newlines: Task::ready(()),
2403 lookup_key: None,
2404 select_next_is_case_sensitive: None,
2405 applicable_language_settings: HashMap::default(),
2406 accent_data: None,
2407 fetched_tree_sitter_chunks: HashMap::default(),
2408 use_base_text_line_numbers: false,
2409 };
2410
2411 if is_minimap {
2412 return editor;
2413 }
2414
2415 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2416 editor.accent_data = editor.fetch_accent_data(cx);
2417
2418 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2419 editor
2420 ._subscriptions
2421 .push(cx.observe(breakpoints, |_, _, cx| {
2422 cx.notify();
2423 }));
2424 }
2425 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2426 editor._subscriptions.extend(project_subscriptions);
2427
2428 editor._subscriptions.push(cx.subscribe_in(
2429 &cx.entity(),
2430 window,
2431 |editor, _, e: &EditorEvent, window, cx| match e {
2432 EditorEvent::ScrollPositionChanged { local, .. } => {
2433 if *local {
2434 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2435 editor.inline_blame_popover.take();
2436 let new_anchor = editor.scroll_manager.anchor();
2437 let snapshot = editor.snapshot(window, cx);
2438 editor.update_restoration_data(cx, move |data| {
2439 data.scroll_position = (
2440 new_anchor.top_row(snapshot.buffer_snapshot()),
2441 new_anchor.offset,
2442 );
2443 });
2444
2445 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2446 cx.background_executor()
2447 .timer(Duration::from_millis(50))
2448 .await;
2449 editor
2450 .update_in(cx, |editor, window, cx| {
2451 editor.register_visible_buffers(cx);
2452 editor.refresh_colors_for_visible_range(None, window, cx);
2453 editor.refresh_inlay_hints(
2454 InlayHintRefreshReason::NewLinesShown,
2455 cx,
2456 );
2457 editor.colorize_brackets(false, cx);
2458 })
2459 .ok();
2460 });
2461 }
2462 }
2463 EditorEvent::Edited { .. } => {
2464 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2465 .map(|vim_mode| vim_mode.0)
2466 .unwrap_or(false);
2467 if !vim_mode {
2468 let display_map = editor.display_snapshot(cx);
2469 let selections = editor.selections.all_adjusted_display(&display_map);
2470 let pop_state = editor
2471 .change_list
2472 .last()
2473 .map(|previous| {
2474 previous.len() == selections.len()
2475 && previous.iter().enumerate().all(|(ix, p)| {
2476 p.to_display_point(&display_map).row()
2477 == selections[ix].head().row()
2478 })
2479 })
2480 .unwrap_or(false);
2481 let new_positions = selections
2482 .into_iter()
2483 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2484 .collect();
2485 editor
2486 .change_list
2487 .push_to_change_list(pop_state, new_positions);
2488 }
2489 }
2490 _ => (),
2491 },
2492 ));
2493
2494 if let Some(dap_store) = editor
2495 .project
2496 .as_ref()
2497 .map(|project| project.read(cx).dap_store())
2498 {
2499 let weak_editor = cx.weak_entity();
2500
2501 editor
2502 ._subscriptions
2503 .push(
2504 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2505 let session_entity = cx.entity();
2506 weak_editor
2507 .update(cx, |editor, cx| {
2508 editor._subscriptions.push(
2509 cx.subscribe(&session_entity, Self::on_debug_session_event),
2510 );
2511 })
2512 .ok();
2513 }),
2514 );
2515
2516 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2517 editor
2518 ._subscriptions
2519 .push(cx.subscribe(&session, Self::on_debug_session_event));
2520 }
2521 }
2522
2523 // skip adding the initial selection to selection history
2524 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2525 editor.end_selection(window, cx);
2526 editor.selection_history.mode = SelectionHistoryMode::Normal;
2527
2528 editor.scroll_manager.show_scrollbars(window, cx);
2529 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2530
2531 if full_mode {
2532 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2533 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2534
2535 if editor.git_blame_inline_enabled {
2536 editor.start_git_blame_inline(false, window, cx);
2537 }
2538
2539 editor.go_to_active_debug_line(window, cx);
2540
2541 editor.minimap =
2542 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2543 editor.colors = Some(LspColorData::new(cx));
2544 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2545
2546 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2547 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2548 }
2549 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2550 }
2551
2552 editor
2553 }
2554
2555 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2556 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2557 }
2558
2559 pub fn deploy_mouse_context_menu(
2560 &mut self,
2561 position: gpui::Point<Pixels>,
2562 context_menu: Entity<ContextMenu>,
2563 window: &mut Window,
2564 cx: &mut Context<Self>,
2565 ) {
2566 self.mouse_context_menu = Some(MouseContextMenu::new(
2567 self,
2568 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2569 context_menu,
2570 window,
2571 cx,
2572 ));
2573 }
2574
2575 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2576 self.mouse_context_menu
2577 .as_ref()
2578 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2579 }
2580
2581 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2582 if self
2583 .selections
2584 .pending_anchor()
2585 .is_some_and(|pending_selection| {
2586 let snapshot = self.buffer().read(cx).snapshot(cx);
2587 pending_selection.range().includes(range, &snapshot)
2588 })
2589 {
2590 return true;
2591 }
2592
2593 self.selections
2594 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2595 .into_iter()
2596 .any(|selection| {
2597 // This is needed to cover a corner case, if we just check for an existing
2598 // selection in the fold range, having a cursor at the start of the fold
2599 // marks it as selected. Non-empty selections don't cause this.
2600 let length = selection.end - selection.start;
2601 length > 0
2602 })
2603 }
2604
2605 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2606 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2607 }
2608
2609 fn key_context_internal(
2610 &self,
2611 has_active_edit_prediction: bool,
2612 window: &mut Window,
2613 cx: &mut App,
2614 ) -> KeyContext {
2615 let mut key_context = KeyContext::new_with_defaults();
2616 key_context.add("Editor");
2617 let mode = match self.mode {
2618 EditorMode::SingleLine => "single_line",
2619 EditorMode::AutoHeight { .. } => "auto_height",
2620 EditorMode::Minimap { .. } => "minimap",
2621 EditorMode::Full { .. } => "full",
2622 };
2623
2624 if EditorSettings::jupyter_enabled(cx) {
2625 key_context.add("jupyter");
2626 }
2627
2628 key_context.set("mode", mode);
2629 if self.pending_rename.is_some() {
2630 key_context.add("renaming");
2631 }
2632
2633 if let Some(snippet_stack) = self.snippet_stack.last() {
2634 key_context.add("in_snippet");
2635
2636 if snippet_stack.active_index > 0 {
2637 key_context.add("has_previous_tabstop");
2638 }
2639
2640 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2641 key_context.add("has_next_tabstop");
2642 }
2643 }
2644
2645 match self.context_menu.borrow().as_ref() {
2646 Some(CodeContextMenu::Completions(menu)) => {
2647 if menu.visible() {
2648 key_context.add("menu");
2649 key_context.add("showing_completions");
2650 }
2651 }
2652 Some(CodeContextMenu::CodeActions(menu)) => {
2653 if menu.visible() {
2654 key_context.add("menu");
2655 key_context.add("showing_code_actions")
2656 }
2657 }
2658 None => {}
2659 }
2660
2661 if self.signature_help_state.has_multiple_signatures() {
2662 key_context.add("showing_signature_help");
2663 }
2664
2665 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2666 if !self.focus_handle(cx).contains_focused(window, cx)
2667 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2668 {
2669 for addon in self.addons.values() {
2670 addon.extend_key_context(&mut key_context, cx)
2671 }
2672 }
2673
2674 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2675 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2676 Some(
2677 file.full_path(cx)
2678 .extension()?
2679 .to_string_lossy()
2680 .into_owned(),
2681 )
2682 }) {
2683 key_context.set("extension", extension);
2684 }
2685 } else {
2686 key_context.add("multibuffer");
2687 }
2688
2689 if has_active_edit_prediction {
2690 if self.edit_prediction_in_conflict() {
2691 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2692 } else {
2693 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2694 key_context.add("copilot_suggestion");
2695 }
2696 }
2697
2698 if self.selection_mark_mode {
2699 key_context.add("selection_mode");
2700 }
2701
2702 let disjoint = self.selections.disjoint_anchors();
2703 let snapshot = self.snapshot(window, cx);
2704 let snapshot = snapshot.buffer_snapshot();
2705 if self.mode == EditorMode::SingleLine
2706 && let [selection] = disjoint
2707 && selection.start == selection.end
2708 && selection.end.to_offset(snapshot) == snapshot.len()
2709 {
2710 key_context.add("end_of_input");
2711 }
2712
2713 if self.has_any_expanded_diff_hunks(cx) {
2714 key_context.add("diffs_expanded");
2715 }
2716
2717 key_context
2718 }
2719
2720 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2721 self.last_bounds.as_ref()
2722 }
2723
2724 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2725 if self.mouse_cursor_hidden {
2726 self.mouse_cursor_hidden = false;
2727 cx.notify();
2728 }
2729 }
2730
2731 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2732 let hide_mouse_cursor = match origin {
2733 HideMouseCursorOrigin::TypingAction => {
2734 matches!(
2735 self.hide_mouse_mode,
2736 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2737 )
2738 }
2739 HideMouseCursorOrigin::MovementAction => {
2740 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2741 }
2742 };
2743 if self.mouse_cursor_hidden != hide_mouse_cursor {
2744 self.mouse_cursor_hidden = hide_mouse_cursor;
2745 cx.notify();
2746 }
2747 }
2748
2749 pub fn edit_prediction_in_conflict(&self) -> bool {
2750 if !self.show_edit_predictions_in_menu() {
2751 return false;
2752 }
2753
2754 let showing_completions = self
2755 .context_menu
2756 .borrow()
2757 .as_ref()
2758 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2759
2760 showing_completions
2761 || self.edit_prediction_requires_modifier()
2762 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2763 // bindings to insert tab characters.
2764 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2765 }
2766
2767 pub fn accept_edit_prediction_keybind(
2768 &self,
2769 granularity: EditPredictionGranularity,
2770 window: &mut Window,
2771 cx: &mut App,
2772 ) -> AcceptEditPredictionBinding {
2773 let key_context = self.key_context_internal(true, window, cx);
2774 let in_conflict = self.edit_prediction_in_conflict();
2775
2776 let bindings =
2777 match granularity {
2778 EditPredictionGranularity::Word => window
2779 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2780 EditPredictionGranularity::Line => window
2781 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2782 EditPredictionGranularity::Full => {
2783 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2784 }
2785 };
2786
2787 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2788 !in_conflict
2789 || binding
2790 .keystrokes()
2791 .first()
2792 .is_some_and(|keystroke| keystroke.modifiers().modified())
2793 }))
2794 }
2795
2796 pub fn new_file(
2797 workspace: &mut Workspace,
2798 _: &workspace::NewFile,
2799 window: &mut Window,
2800 cx: &mut Context<Workspace>,
2801 ) {
2802 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2803 "Failed to create buffer",
2804 window,
2805 cx,
2806 |e, _, _| match e.error_code() {
2807 ErrorCode::RemoteUpgradeRequired => Some(format!(
2808 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2809 e.error_tag("required").unwrap_or("the latest version")
2810 )),
2811 _ => None,
2812 },
2813 );
2814 }
2815
2816 pub fn new_in_workspace(
2817 workspace: &mut Workspace,
2818 window: &mut Window,
2819 cx: &mut Context<Workspace>,
2820 ) -> Task<Result<Entity<Editor>>> {
2821 let project = workspace.project().clone();
2822 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2823
2824 cx.spawn_in(window, async move |workspace, cx| {
2825 let buffer = create.await?;
2826 workspace.update_in(cx, |workspace, window, cx| {
2827 let editor =
2828 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2829 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2830 editor
2831 })
2832 })
2833 }
2834
2835 fn new_file_vertical(
2836 workspace: &mut Workspace,
2837 _: &workspace::NewFileSplitVertical,
2838 window: &mut Window,
2839 cx: &mut Context<Workspace>,
2840 ) {
2841 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2842 }
2843
2844 fn new_file_horizontal(
2845 workspace: &mut Workspace,
2846 _: &workspace::NewFileSplitHorizontal,
2847 window: &mut Window,
2848 cx: &mut Context<Workspace>,
2849 ) {
2850 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2851 }
2852
2853 fn new_file_split(
2854 workspace: &mut Workspace,
2855 action: &workspace::NewFileSplit,
2856 window: &mut Window,
2857 cx: &mut Context<Workspace>,
2858 ) {
2859 Self::new_file_in_direction(workspace, action.0, window, cx)
2860 }
2861
2862 fn new_file_in_direction(
2863 workspace: &mut Workspace,
2864 direction: SplitDirection,
2865 window: &mut Window,
2866 cx: &mut Context<Workspace>,
2867 ) {
2868 let project = workspace.project().clone();
2869 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2870
2871 cx.spawn_in(window, async move |workspace, cx| {
2872 let buffer = create.await?;
2873 workspace.update_in(cx, move |workspace, window, cx| {
2874 workspace.split_item(
2875 direction,
2876 Box::new(
2877 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2878 ),
2879 window,
2880 cx,
2881 )
2882 })?;
2883 anyhow::Ok(())
2884 })
2885 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2886 match e.error_code() {
2887 ErrorCode::RemoteUpgradeRequired => Some(format!(
2888 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2889 e.error_tag("required").unwrap_or("the latest version")
2890 )),
2891 _ => None,
2892 }
2893 });
2894 }
2895
2896 pub fn leader_id(&self) -> Option<CollaboratorId> {
2897 self.leader_id
2898 }
2899
2900 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2901 &self.buffer
2902 }
2903
2904 pub fn project(&self) -> Option<&Entity<Project>> {
2905 self.project.as_ref()
2906 }
2907
2908 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2909 self.workspace.as_ref()?.0.upgrade()
2910 }
2911
2912 /// Returns the workspace serialization ID if this editor should be serialized.
2913 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2914 self.workspace
2915 .as_ref()
2916 .filter(|_| self.should_serialize_buffer())
2917 .and_then(|workspace| workspace.1)
2918 }
2919
2920 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2921 self.buffer().read(cx).title(cx)
2922 }
2923
2924 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2925 let git_blame_gutter_max_author_length = self
2926 .render_git_blame_gutter(cx)
2927 .then(|| {
2928 if let Some(blame) = self.blame.as_ref() {
2929 let max_author_length =
2930 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2931 Some(max_author_length)
2932 } else {
2933 None
2934 }
2935 })
2936 .flatten();
2937
2938 EditorSnapshot {
2939 mode: self.mode.clone(),
2940 show_gutter: self.show_gutter,
2941 offset_content: self.offset_content,
2942 show_line_numbers: self.show_line_numbers,
2943 show_git_diff_gutter: self.show_git_diff_gutter,
2944 show_code_actions: self.show_code_actions,
2945 show_runnables: self.show_runnables,
2946 show_breakpoints: self.show_breakpoints,
2947 git_blame_gutter_max_author_length,
2948 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2949 placeholder_display_snapshot: self
2950 .placeholder_display_map
2951 .as_ref()
2952 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2953 scroll_anchor: self.scroll_manager.anchor(),
2954 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2955 is_focused: self.focus_handle.is_focused(window),
2956 current_line_highlight: self
2957 .current_line_highlight
2958 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2959 gutter_hovered: self.gutter_hovered,
2960 }
2961 }
2962
2963 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2964 self.buffer.read(cx).language_at(point, cx)
2965 }
2966
2967 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2968 self.buffer.read(cx).read(cx).file_at(point).cloned()
2969 }
2970
2971 pub fn active_excerpt(
2972 &self,
2973 cx: &App,
2974 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2975 self.buffer
2976 .read(cx)
2977 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2978 }
2979
2980 pub fn mode(&self) -> &EditorMode {
2981 &self.mode
2982 }
2983
2984 pub fn set_mode(&mut self, mode: EditorMode) {
2985 self.mode = mode;
2986 }
2987
2988 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2989 self.collaboration_hub.as_deref()
2990 }
2991
2992 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2993 self.collaboration_hub = Some(hub);
2994 }
2995
2996 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2997 self.in_project_search = in_project_search;
2998 }
2999
3000 pub fn set_custom_context_menu(
3001 &mut self,
3002 f: impl 'static
3003 + Fn(
3004 &mut Self,
3005 DisplayPoint,
3006 &mut Window,
3007 &mut Context<Self>,
3008 ) -> Option<Entity<ui::ContextMenu>>,
3009 ) {
3010 self.custom_context_menu = Some(Box::new(f))
3011 }
3012
3013 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3014 self.completion_provider = provider;
3015 }
3016
3017 #[cfg(any(test, feature = "test-support"))]
3018 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3019 self.completion_provider.clone()
3020 }
3021
3022 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3023 self.semantics_provider.clone()
3024 }
3025
3026 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3027 self.semantics_provider = provider;
3028 }
3029
3030 pub fn set_edit_prediction_provider<T>(
3031 &mut self,
3032 provider: Option<Entity<T>>,
3033 window: &mut Window,
3034 cx: &mut Context<Self>,
3035 ) where
3036 T: EditPredictionDelegate,
3037 {
3038 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3039 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3040 if this.focus_handle.is_focused(window) {
3041 this.update_visible_edit_prediction(window, cx);
3042 }
3043 }),
3044 provider: Arc::new(provider),
3045 });
3046 self.update_edit_prediction_settings(cx);
3047 self.refresh_edit_prediction(false, false, window, cx);
3048 }
3049
3050 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3051 self.placeholder_display_map
3052 .as_ref()
3053 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3054 }
3055
3056 pub fn set_placeholder_text(
3057 &mut self,
3058 placeholder_text: &str,
3059 window: &mut Window,
3060 cx: &mut Context<Self>,
3061 ) {
3062 let multibuffer = cx
3063 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3064
3065 let style = window.text_style();
3066
3067 self.placeholder_display_map = Some(cx.new(|cx| {
3068 DisplayMap::new(
3069 multibuffer,
3070 style.font(),
3071 style.font_size.to_pixels(window.rem_size()),
3072 None,
3073 FILE_HEADER_HEIGHT,
3074 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3075 Default::default(),
3076 DiagnosticSeverity::Off,
3077 cx,
3078 )
3079 }));
3080 cx.notify();
3081 }
3082
3083 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3084 self.cursor_shape = cursor_shape;
3085
3086 // Disrupt blink for immediate user feedback that the cursor shape has changed
3087 self.blink_manager.update(cx, BlinkManager::show_cursor);
3088
3089 cx.notify();
3090 }
3091
3092 pub fn cursor_shape(&self) -> CursorShape {
3093 self.cursor_shape
3094 }
3095
3096 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3097 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3098 }
3099
3100 pub fn set_current_line_highlight(
3101 &mut self,
3102 current_line_highlight: Option<CurrentLineHighlight>,
3103 ) {
3104 self.current_line_highlight = current_line_highlight;
3105 }
3106
3107 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3108 self.collapse_matches = collapse_matches;
3109 }
3110
3111 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3112 if self.collapse_matches {
3113 return range.start..range.start;
3114 }
3115 range.clone()
3116 }
3117
3118 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3119 self.display_map.read(cx).clip_at_line_ends
3120 }
3121
3122 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3123 if self.display_map.read(cx).clip_at_line_ends != clip {
3124 self.display_map
3125 .update(cx, |map, _| map.clip_at_line_ends = clip);
3126 }
3127 }
3128
3129 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3130 self.input_enabled = input_enabled;
3131 }
3132
3133 pub fn set_edit_predictions_hidden_for_vim_mode(
3134 &mut self,
3135 hidden: bool,
3136 window: &mut Window,
3137 cx: &mut Context<Self>,
3138 ) {
3139 if hidden != self.edit_predictions_hidden_for_vim_mode {
3140 self.edit_predictions_hidden_for_vim_mode = hidden;
3141 if hidden {
3142 self.update_visible_edit_prediction(window, cx);
3143 } else {
3144 self.refresh_edit_prediction(true, false, window, cx);
3145 }
3146 }
3147 }
3148
3149 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3150 self.menu_edit_predictions_policy = value;
3151 }
3152
3153 pub fn set_autoindent(&mut self, autoindent: bool) {
3154 if autoindent {
3155 self.autoindent_mode = Some(AutoindentMode::EachLine);
3156 } else {
3157 self.autoindent_mode = None;
3158 }
3159 }
3160
3161 pub fn read_only(&self, cx: &App) -> bool {
3162 self.read_only || self.buffer.read(cx).read_only()
3163 }
3164
3165 pub fn set_read_only(&mut self, read_only: bool) {
3166 self.read_only = read_only;
3167 }
3168
3169 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3170 self.use_autoclose = autoclose;
3171 }
3172
3173 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3174 self.use_auto_surround = auto_surround;
3175 }
3176
3177 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3178 self.auto_replace_emoji_shortcode = auto_replace;
3179 }
3180
3181 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3182 self.buffer_serialization = should_serialize.then(|| {
3183 BufferSerialization::new(
3184 ProjectSettings::get_global(cx)
3185 .session
3186 .restore_unsaved_buffers,
3187 )
3188 })
3189 }
3190
3191 fn should_serialize_buffer(&self) -> bool {
3192 self.buffer_serialization.is_some()
3193 }
3194
3195 pub fn toggle_edit_predictions(
3196 &mut self,
3197 _: &ToggleEditPrediction,
3198 window: &mut Window,
3199 cx: &mut Context<Self>,
3200 ) {
3201 if self.show_edit_predictions_override.is_some() {
3202 self.set_show_edit_predictions(None, window, cx);
3203 } else {
3204 let show_edit_predictions = !self.edit_predictions_enabled();
3205 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3206 }
3207 }
3208
3209 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3210 self.show_completions_on_input_override = show_completions_on_input;
3211 }
3212
3213 pub fn set_show_edit_predictions(
3214 &mut self,
3215 show_edit_predictions: Option<bool>,
3216 window: &mut Window,
3217 cx: &mut Context<Self>,
3218 ) {
3219 self.show_edit_predictions_override = show_edit_predictions;
3220 self.update_edit_prediction_settings(cx);
3221
3222 if let Some(false) = show_edit_predictions {
3223 self.discard_edit_prediction(false, cx);
3224 } else {
3225 self.refresh_edit_prediction(false, true, window, cx);
3226 }
3227 }
3228
3229 fn edit_predictions_disabled_in_scope(
3230 &self,
3231 buffer: &Entity<Buffer>,
3232 buffer_position: language::Anchor,
3233 cx: &App,
3234 ) -> bool {
3235 let snapshot = buffer.read(cx).snapshot();
3236 let settings = snapshot.settings_at(buffer_position, cx);
3237
3238 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3239 return false;
3240 };
3241
3242 scope.override_name().is_some_and(|scope_name| {
3243 settings
3244 .edit_predictions_disabled_in
3245 .iter()
3246 .any(|s| s == scope_name)
3247 })
3248 }
3249
3250 pub fn set_use_modal_editing(&mut self, to: bool) {
3251 self.use_modal_editing = to;
3252 }
3253
3254 pub fn use_modal_editing(&self) -> bool {
3255 self.use_modal_editing
3256 }
3257
3258 fn selections_did_change(
3259 &mut self,
3260 local: bool,
3261 old_cursor_position: &Anchor,
3262 effects: SelectionEffects,
3263 window: &mut Window,
3264 cx: &mut Context<Self>,
3265 ) {
3266 window.invalidate_character_coordinates();
3267
3268 // Copy selections to primary selection buffer
3269 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3270 if local {
3271 let selections = self
3272 .selections
3273 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3274 let buffer_handle = self.buffer.read(cx).read(cx);
3275
3276 let mut text = String::new();
3277 for (index, selection) in selections.iter().enumerate() {
3278 let text_for_selection = buffer_handle
3279 .text_for_range(selection.start..selection.end)
3280 .collect::<String>();
3281
3282 text.push_str(&text_for_selection);
3283 if index != selections.len() - 1 {
3284 text.push('\n');
3285 }
3286 }
3287
3288 if !text.is_empty() {
3289 cx.write_to_primary(ClipboardItem::new_string(text));
3290 }
3291 }
3292
3293 let selection_anchors = self.selections.disjoint_anchors_arc();
3294
3295 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3296 self.buffer.update(cx, |buffer, cx| {
3297 buffer.set_active_selections(
3298 &selection_anchors,
3299 self.selections.line_mode(),
3300 self.cursor_shape,
3301 cx,
3302 )
3303 });
3304 }
3305 let display_map = self
3306 .display_map
3307 .update(cx, |display_map, cx| display_map.snapshot(cx));
3308 let buffer = display_map.buffer_snapshot();
3309 if self.selections.count() == 1 {
3310 self.add_selections_state = None;
3311 }
3312 self.select_next_state = None;
3313 self.select_prev_state = None;
3314 self.select_syntax_node_history.try_clear();
3315 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3316 self.snippet_stack.invalidate(&selection_anchors, buffer);
3317 self.take_rename(false, window, cx);
3318
3319 let newest_selection = self.selections.newest_anchor();
3320 let new_cursor_position = newest_selection.head();
3321 let selection_start = newest_selection.start;
3322
3323 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3324 self.push_to_nav_history(
3325 *old_cursor_position,
3326 Some(new_cursor_position.to_point(buffer)),
3327 false,
3328 effects.nav_history == Some(true),
3329 cx,
3330 );
3331 }
3332
3333 if local {
3334 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3335 self.register_buffer(buffer_id, cx);
3336 }
3337
3338 let mut context_menu = self.context_menu.borrow_mut();
3339 let completion_menu = match context_menu.as_ref() {
3340 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3341 Some(CodeContextMenu::CodeActions(_)) => {
3342 *context_menu = None;
3343 None
3344 }
3345 None => None,
3346 };
3347 let completion_position = completion_menu.map(|menu| menu.initial_position);
3348 drop(context_menu);
3349
3350 if effects.completions
3351 && let Some(completion_position) = completion_position
3352 {
3353 let start_offset = selection_start.to_offset(buffer);
3354 let position_matches = start_offset == completion_position.to_offset(buffer);
3355 let continue_showing = if position_matches {
3356 if self.snippet_stack.is_empty() {
3357 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3358 == Some(CharKind::Word)
3359 } else {
3360 // Snippet choices can be shown even when the cursor is in whitespace.
3361 // Dismissing the menu with actions like backspace is handled by
3362 // invalidation regions.
3363 true
3364 }
3365 } else {
3366 false
3367 };
3368
3369 if continue_showing {
3370 self.open_or_update_completions_menu(None, None, false, window, cx);
3371 } else {
3372 self.hide_context_menu(window, cx);
3373 }
3374 }
3375
3376 hide_hover(self, cx);
3377
3378 if old_cursor_position.to_display_point(&display_map).row()
3379 != new_cursor_position.to_display_point(&display_map).row()
3380 {
3381 self.available_code_actions.take();
3382 }
3383 self.refresh_code_actions(window, cx);
3384 self.refresh_document_highlights(cx);
3385 refresh_linked_ranges(self, window, cx);
3386
3387 self.refresh_selected_text_highlights(false, window, cx);
3388 self.refresh_matching_bracket_highlights(window, cx);
3389 self.update_visible_edit_prediction(window, cx);
3390 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3391 self.inline_blame_popover.take();
3392 if self.git_blame_inline_enabled {
3393 self.start_inline_blame_timer(window, cx);
3394 }
3395 }
3396
3397 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3398 cx.emit(EditorEvent::SelectionsChanged { local });
3399
3400 let selections = &self.selections.disjoint_anchors_arc();
3401 if selections.len() == 1 {
3402 cx.emit(SearchEvent::ActiveMatchChanged)
3403 }
3404 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3405 let inmemory_selections = selections
3406 .iter()
3407 .map(|s| {
3408 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3409 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3410 })
3411 .collect();
3412 self.update_restoration_data(cx, |data| {
3413 data.selections = inmemory_selections;
3414 });
3415
3416 if WorkspaceSettings::get(None, cx).restore_on_startup
3417 != RestoreOnStartupBehavior::EmptyTab
3418 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3419 {
3420 let snapshot = self.buffer().read(cx).snapshot(cx);
3421 let selections = selections.clone();
3422 let background_executor = cx.background_executor().clone();
3423 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3424 self.serialize_selections = cx.background_spawn(async move {
3425 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3426 let db_selections = selections
3427 .iter()
3428 .map(|selection| {
3429 (
3430 selection.start.to_offset(&snapshot).0,
3431 selection.end.to_offset(&snapshot).0,
3432 )
3433 })
3434 .collect();
3435
3436 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3437 .await
3438 .with_context(|| {
3439 format!(
3440 "persisting editor selections for editor {editor_id}, \
3441 workspace {workspace_id:?}"
3442 )
3443 })
3444 .log_err();
3445 });
3446 }
3447 }
3448
3449 cx.notify();
3450 }
3451
3452 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3453 use text::ToOffset as _;
3454 use text::ToPoint as _;
3455
3456 if self.mode.is_minimap()
3457 || WorkspaceSettings::get(None, cx).restore_on_startup
3458 == RestoreOnStartupBehavior::EmptyTab
3459 {
3460 return;
3461 }
3462
3463 if !self.buffer().read(cx).is_singleton() {
3464 return;
3465 }
3466
3467 let display_snapshot = self
3468 .display_map
3469 .update(cx, |display_map, cx| display_map.snapshot(cx));
3470 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3471 return;
3472 };
3473 let inmemory_folds = display_snapshot
3474 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3475 .map(|fold| {
3476 fold.range.start.text_anchor.to_point(&snapshot)
3477 ..fold.range.end.text_anchor.to_point(&snapshot)
3478 })
3479 .collect();
3480 self.update_restoration_data(cx, |data| {
3481 data.folds = inmemory_folds;
3482 });
3483
3484 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3485 return;
3486 };
3487 let background_executor = cx.background_executor().clone();
3488 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3489 let db_folds = display_snapshot
3490 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3491 .map(|fold| {
3492 (
3493 fold.range.start.text_anchor.to_offset(&snapshot),
3494 fold.range.end.text_anchor.to_offset(&snapshot),
3495 )
3496 })
3497 .collect();
3498 self.serialize_folds = cx.background_spawn(async move {
3499 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3500 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3501 .await
3502 .with_context(|| {
3503 format!(
3504 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3505 )
3506 })
3507 .log_err();
3508 });
3509 }
3510
3511 pub fn sync_selections(
3512 &mut self,
3513 other: Entity<Editor>,
3514 cx: &mut Context<Self>,
3515 ) -> gpui::Subscription {
3516 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3517 if !other_selections.is_empty() {
3518 self.selections
3519 .change_with(&self.display_snapshot(cx), |selections| {
3520 selections.select_anchors(other_selections);
3521 });
3522 }
3523
3524 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3525 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3526 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3527 if other_selections.is_empty() {
3528 return;
3529 }
3530 let snapshot = this.display_snapshot(cx);
3531 this.selections.change_with(&snapshot, |selections| {
3532 selections.select_anchors(other_selections);
3533 });
3534 }
3535 });
3536
3537 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3538 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3539 let these_selections = this.selections.disjoint_anchors().to_vec();
3540 if these_selections.is_empty() {
3541 return;
3542 }
3543 other.update(cx, |other_editor, cx| {
3544 let snapshot = other_editor.display_snapshot(cx);
3545 other_editor
3546 .selections
3547 .change_with(&snapshot, |selections| {
3548 selections.select_anchors(these_selections);
3549 })
3550 });
3551 }
3552 });
3553
3554 Subscription::join(other_subscription, this_subscription)
3555 }
3556
3557 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3558 if self.buffer().read(cx).is_singleton() {
3559 return;
3560 }
3561 let snapshot = self.buffer.read(cx).snapshot(cx);
3562 let buffer_ids: HashSet<BufferId> = self
3563 .selections
3564 .disjoint_anchor_ranges()
3565 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3566 .collect();
3567 for buffer_id in buffer_ids {
3568 self.unfold_buffer(buffer_id, cx);
3569 }
3570 }
3571
3572 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3573 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3574 /// effects of selection change occur at the end of the transaction.
3575 pub fn change_selections<R>(
3576 &mut self,
3577 effects: SelectionEffects,
3578 window: &mut Window,
3579 cx: &mut Context<Self>,
3580 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3581 ) -> R {
3582 let snapshot = self.display_snapshot(cx);
3583 if let Some(state) = &mut self.deferred_selection_effects_state {
3584 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3585 state.effects.completions = effects.completions;
3586 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3587 let (changed, result) = self.selections.change_with(&snapshot, change);
3588 state.changed |= changed;
3589 return result;
3590 }
3591 let mut state = DeferredSelectionEffectsState {
3592 changed: false,
3593 effects,
3594 old_cursor_position: self.selections.newest_anchor().head(),
3595 history_entry: SelectionHistoryEntry {
3596 selections: self.selections.disjoint_anchors_arc(),
3597 select_next_state: self.select_next_state.clone(),
3598 select_prev_state: self.select_prev_state.clone(),
3599 add_selections_state: self.add_selections_state.clone(),
3600 },
3601 };
3602 let (changed, result) = self.selections.change_with(&snapshot, change);
3603 state.changed = state.changed || changed;
3604 if self.defer_selection_effects {
3605 self.deferred_selection_effects_state = Some(state);
3606 } else {
3607 self.apply_selection_effects(state, window, cx);
3608 }
3609 result
3610 }
3611
3612 /// Defers the effects of selection change, so that the effects of multiple calls to
3613 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3614 /// to selection history and the state of popovers based on selection position aren't
3615 /// erroneously updated.
3616 pub fn with_selection_effects_deferred<R>(
3617 &mut self,
3618 window: &mut Window,
3619 cx: &mut Context<Self>,
3620 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3621 ) -> R {
3622 let already_deferred = self.defer_selection_effects;
3623 self.defer_selection_effects = true;
3624 let result = update(self, window, cx);
3625 if !already_deferred {
3626 self.defer_selection_effects = false;
3627 if let Some(state) = self.deferred_selection_effects_state.take() {
3628 self.apply_selection_effects(state, window, cx);
3629 }
3630 }
3631 result
3632 }
3633
3634 fn apply_selection_effects(
3635 &mut self,
3636 state: DeferredSelectionEffectsState,
3637 window: &mut Window,
3638 cx: &mut Context<Self>,
3639 ) {
3640 if state.changed {
3641 self.selection_history.push(state.history_entry);
3642
3643 if let Some(autoscroll) = state.effects.scroll {
3644 self.request_autoscroll(autoscroll, cx);
3645 }
3646
3647 let old_cursor_position = &state.old_cursor_position;
3648
3649 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3650
3651 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3652 self.show_signature_help(&ShowSignatureHelp, window, cx);
3653 }
3654 }
3655 }
3656
3657 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3658 where
3659 I: IntoIterator<Item = (Range<S>, T)>,
3660 S: ToOffset,
3661 T: Into<Arc<str>>,
3662 {
3663 if self.read_only(cx) {
3664 return;
3665 }
3666
3667 self.buffer
3668 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3669 }
3670
3671 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3672 where
3673 I: IntoIterator<Item = (Range<S>, T)>,
3674 S: ToOffset,
3675 T: Into<Arc<str>>,
3676 {
3677 if self.read_only(cx) {
3678 return;
3679 }
3680
3681 self.buffer.update(cx, |buffer, cx| {
3682 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3683 });
3684 }
3685
3686 pub fn edit_with_block_indent<I, S, T>(
3687 &mut self,
3688 edits: I,
3689 original_indent_columns: Vec<Option<u32>>,
3690 cx: &mut Context<Self>,
3691 ) where
3692 I: IntoIterator<Item = (Range<S>, T)>,
3693 S: ToOffset,
3694 T: Into<Arc<str>>,
3695 {
3696 if self.read_only(cx) {
3697 return;
3698 }
3699
3700 self.buffer.update(cx, |buffer, cx| {
3701 buffer.edit(
3702 edits,
3703 Some(AutoindentMode::Block {
3704 original_indent_columns,
3705 }),
3706 cx,
3707 )
3708 });
3709 }
3710
3711 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3712 self.hide_context_menu(window, cx);
3713
3714 match phase {
3715 SelectPhase::Begin {
3716 position,
3717 add,
3718 click_count,
3719 } => self.begin_selection(position, add, click_count, window, cx),
3720 SelectPhase::BeginColumnar {
3721 position,
3722 goal_column,
3723 reset,
3724 mode,
3725 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3726 SelectPhase::Extend {
3727 position,
3728 click_count,
3729 } => self.extend_selection(position, click_count, window, cx),
3730 SelectPhase::Update {
3731 position,
3732 goal_column,
3733 scroll_delta,
3734 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3735 SelectPhase::End => self.end_selection(window, cx),
3736 }
3737 }
3738
3739 fn extend_selection(
3740 &mut self,
3741 position: DisplayPoint,
3742 click_count: usize,
3743 window: &mut Window,
3744 cx: &mut Context<Self>,
3745 ) {
3746 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3747 let tail = self
3748 .selections
3749 .newest::<MultiBufferOffset>(&display_map)
3750 .tail();
3751 let click_count = click_count.max(match self.selections.select_mode() {
3752 SelectMode::Character => 1,
3753 SelectMode::Word(_) => 2,
3754 SelectMode::Line(_) => 3,
3755 SelectMode::All => 4,
3756 });
3757 self.begin_selection(position, false, click_count, window, cx);
3758
3759 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3760
3761 let current_selection = match self.selections.select_mode() {
3762 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3763 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3764 };
3765
3766 let mut pending_selection = self
3767 .selections
3768 .pending_anchor()
3769 .cloned()
3770 .expect("extend_selection not called with pending selection");
3771
3772 if pending_selection
3773 .start
3774 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3775 == Ordering::Greater
3776 {
3777 pending_selection.start = current_selection.start;
3778 }
3779 if pending_selection
3780 .end
3781 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3782 == Ordering::Less
3783 {
3784 pending_selection.end = current_selection.end;
3785 pending_selection.reversed = true;
3786 }
3787
3788 let mut pending_mode = self.selections.pending_mode().unwrap();
3789 match &mut pending_mode {
3790 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3791 _ => {}
3792 }
3793
3794 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3795 SelectionEffects::scroll(Autoscroll::fit())
3796 } else {
3797 SelectionEffects::no_scroll()
3798 };
3799
3800 self.change_selections(effects, window, cx, |s| {
3801 s.set_pending(pending_selection.clone(), pending_mode);
3802 s.set_is_extending(true);
3803 });
3804 }
3805
3806 fn begin_selection(
3807 &mut self,
3808 position: DisplayPoint,
3809 add: bool,
3810 click_count: usize,
3811 window: &mut Window,
3812 cx: &mut Context<Self>,
3813 ) {
3814 if !self.focus_handle.is_focused(window) {
3815 self.last_focused_descendant = None;
3816 window.focus(&self.focus_handle, cx);
3817 }
3818
3819 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3820 let buffer = display_map.buffer_snapshot();
3821 let position = display_map.clip_point(position, Bias::Left);
3822
3823 let start;
3824 let end;
3825 let mode;
3826 let mut auto_scroll;
3827 match click_count {
3828 1 => {
3829 start = buffer.anchor_before(position.to_point(&display_map));
3830 end = start;
3831 mode = SelectMode::Character;
3832 auto_scroll = true;
3833 }
3834 2 => {
3835 let position = display_map
3836 .clip_point(position, Bias::Left)
3837 .to_offset(&display_map, Bias::Left);
3838 let (range, _) = buffer.surrounding_word(position, None);
3839 start = buffer.anchor_before(range.start);
3840 end = buffer.anchor_before(range.end);
3841 mode = SelectMode::Word(start..end);
3842 auto_scroll = true;
3843 }
3844 3 => {
3845 let position = display_map
3846 .clip_point(position, Bias::Left)
3847 .to_point(&display_map);
3848 let line_start = display_map.prev_line_boundary(position).0;
3849 let next_line_start = buffer.clip_point(
3850 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3851 Bias::Left,
3852 );
3853 start = buffer.anchor_before(line_start);
3854 end = buffer.anchor_before(next_line_start);
3855 mode = SelectMode::Line(start..end);
3856 auto_scroll = true;
3857 }
3858 _ => {
3859 start = buffer.anchor_before(MultiBufferOffset(0));
3860 end = buffer.anchor_before(buffer.len());
3861 mode = SelectMode::All;
3862 auto_scroll = false;
3863 }
3864 }
3865 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3866
3867 let point_to_delete: Option<usize> = {
3868 let selected_points: Vec<Selection<Point>> =
3869 self.selections.disjoint_in_range(start..end, &display_map);
3870
3871 if !add || click_count > 1 {
3872 None
3873 } else if !selected_points.is_empty() {
3874 Some(selected_points[0].id)
3875 } else {
3876 let clicked_point_already_selected =
3877 self.selections.disjoint_anchors().iter().find(|selection| {
3878 selection.start.to_point(buffer) == start.to_point(buffer)
3879 || selection.end.to_point(buffer) == end.to_point(buffer)
3880 });
3881
3882 clicked_point_already_selected.map(|selection| selection.id)
3883 }
3884 };
3885
3886 let selections_count = self.selections.count();
3887 let effects = if auto_scroll {
3888 SelectionEffects::default()
3889 } else {
3890 SelectionEffects::no_scroll()
3891 };
3892
3893 self.change_selections(effects, window, cx, |s| {
3894 if let Some(point_to_delete) = point_to_delete {
3895 s.delete(point_to_delete);
3896
3897 if selections_count == 1 {
3898 s.set_pending_anchor_range(start..end, mode);
3899 }
3900 } else {
3901 if !add {
3902 s.clear_disjoint();
3903 }
3904
3905 s.set_pending_anchor_range(start..end, mode);
3906 }
3907 });
3908 }
3909
3910 fn begin_columnar_selection(
3911 &mut self,
3912 position: DisplayPoint,
3913 goal_column: u32,
3914 reset: bool,
3915 mode: ColumnarMode,
3916 window: &mut Window,
3917 cx: &mut Context<Self>,
3918 ) {
3919 if !self.focus_handle.is_focused(window) {
3920 self.last_focused_descendant = None;
3921 window.focus(&self.focus_handle, cx);
3922 }
3923
3924 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3925
3926 if reset {
3927 let pointer_position = display_map
3928 .buffer_snapshot()
3929 .anchor_before(position.to_point(&display_map));
3930
3931 self.change_selections(
3932 SelectionEffects::scroll(Autoscroll::newest()),
3933 window,
3934 cx,
3935 |s| {
3936 s.clear_disjoint();
3937 s.set_pending_anchor_range(
3938 pointer_position..pointer_position,
3939 SelectMode::Character,
3940 );
3941 },
3942 );
3943 };
3944
3945 let tail = self.selections.newest::<Point>(&display_map).tail();
3946 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3947 self.columnar_selection_state = match mode {
3948 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3949 selection_tail: selection_anchor,
3950 display_point: if reset {
3951 if position.column() != goal_column {
3952 Some(DisplayPoint::new(position.row(), goal_column))
3953 } else {
3954 None
3955 }
3956 } else {
3957 None
3958 },
3959 }),
3960 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3961 selection_tail: selection_anchor,
3962 }),
3963 };
3964
3965 if !reset {
3966 self.select_columns(position, goal_column, &display_map, window, cx);
3967 }
3968 }
3969
3970 fn update_selection(
3971 &mut self,
3972 position: DisplayPoint,
3973 goal_column: u32,
3974 scroll_delta: gpui::Point<f32>,
3975 window: &mut Window,
3976 cx: &mut Context<Self>,
3977 ) {
3978 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3979
3980 if self.columnar_selection_state.is_some() {
3981 self.select_columns(position, goal_column, &display_map, window, cx);
3982 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3983 let buffer = display_map.buffer_snapshot();
3984 let head;
3985 let tail;
3986 let mode = self.selections.pending_mode().unwrap();
3987 match &mode {
3988 SelectMode::Character => {
3989 head = position.to_point(&display_map);
3990 tail = pending.tail().to_point(buffer);
3991 }
3992 SelectMode::Word(original_range) => {
3993 let offset = display_map
3994 .clip_point(position, Bias::Left)
3995 .to_offset(&display_map, Bias::Left);
3996 let original_range = original_range.to_offset(buffer);
3997
3998 let head_offset = if buffer.is_inside_word(offset, None)
3999 || original_range.contains(&offset)
4000 {
4001 let (word_range, _) = buffer.surrounding_word(offset, None);
4002 if word_range.start < original_range.start {
4003 word_range.start
4004 } else {
4005 word_range.end
4006 }
4007 } else {
4008 offset
4009 };
4010
4011 head = head_offset.to_point(buffer);
4012 if head_offset <= original_range.start {
4013 tail = original_range.end.to_point(buffer);
4014 } else {
4015 tail = original_range.start.to_point(buffer);
4016 }
4017 }
4018 SelectMode::Line(original_range) => {
4019 let original_range = original_range.to_point(display_map.buffer_snapshot());
4020
4021 let position = display_map
4022 .clip_point(position, Bias::Left)
4023 .to_point(&display_map);
4024 let line_start = display_map.prev_line_boundary(position).0;
4025 let next_line_start = buffer.clip_point(
4026 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4027 Bias::Left,
4028 );
4029
4030 if line_start < original_range.start {
4031 head = line_start
4032 } else {
4033 head = next_line_start
4034 }
4035
4036 if head <= original_range.start {
4037 tail = original_range.end;
4038 } else {
4039 tail = original_range.start;
4040 }
4041 }
4042 SelectMode::All => {
4043 return;
4044 }
4045 };
4046
4047 if head < tail {
4048 pending.start = buffer.anchor_before(head);
4049 pending.end = buffer.anchor_before(tail);
4050 pending.reversed = true;
4051 } else {
4052 pending.start = buffer.anchor_before(tail);
4053 pending.end = buffer.anchor_before(head);
4054 pending.reversed = false;
4055 }
4056
4057 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4058 s.set_pending(pending.clone(), mode);
4059 });
4060 } else {
4061 log::error!("update_selection dispatched with no pending selection");
4062 return;
4063 }
4064
4065 self.apply_scroll_delta(scroll_delta, window, cx);
4066 cx.notify();
4067 }
4068
4069 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4070 self.columnar_selection_state.take();
4071 if let Some(pending_mode) = self.selections.pending_mode() {
4072 let selections = self
4073 .selections
4074 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4075 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4076 s.select(selections);
4077 s.clear_pending();
4078 if s.is_extending() {
4079 s.set_is_extending(false);
4080 } else {
4081 s.set_select_mode(pending_mode);
4082 }
4083 });
4084 }
4085 }
4086
4087 fn select_columns(
4088 &mut self,
4089 head: DisplayPoint,
4090 goal_column: u32,
4091 display_map: &DisplaySnapshot,
4092 window: &mut Window,
4093 cx: &mut Context<Self>,
4094 ) {
4095 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4096 return;
4097 };
4098
4099 let tail = match columnar_state {
4100 ColumnarSelectionState::FromMouse {
4101 selection_tail,
4102 display_point,
4103 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4104 ColumnarSelectionState::FromSelection { selection_tail } => {
4105 selection_tail.to_display_point(display_map)
4106 }
4107 };
4108
4109 let start_row = cmp::min(tail.row(), head.row());
4110 let end_row = cmp::max(tail.row(), head.row());
4111 let start_column = cmp::min(tail.column(), goal_column);
4112 let end_column = cmp::max(tail.column(), goal_column);
4113 let reversed = start_column < tail.column();
4114
4115 let selection_ranges = (start_row.0..=end_row.0)
4116 .map(DisplayRow)
4117 .filter_map(|row| {
4118 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4119 || start_column <= display_map.line_len(row))
4120 && !display_map.is_block_line(row)
4121 {
4122 let start = display_map
4123 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4124 .to_point(display_map);
4125 let end = display_map
4126 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4127 .to_point(display_map);
4128 if reversed {
4129 Some(end..start)
4130 } else {
4131 Some(start..end)
4132 }
4133 } else {
4134 None
4135 }
4136 })
4137 .collect::<Vec<_>>();
4138 if selection_ranges.is_empty() {
4139 return;
4140 }
4141
4142 let ranges = match columnar_state {
4143 ColumnarSelectionState::FromMouse { .. } => {
4144 let mut non_empty_ranges = selection_ranges
4145 .iter()
4146 .filter(|selection_range| selection_range.start != selection_range.end)
4147 .peekable();
4148 if non_empty_ranges.peek().is_some() {
4149 non_empty_ranges.cloned().collect()
4150 } else {
4151 selection_ranges
4152 }
4153 }
4154 _ => selection_ranges,
4155 };
4156
4157 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4158 s.select_ranges(ranges);
4159 });
4160 cx.notify();
4161 }
4162
4163 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4164 self.selections
4165 .all_adjusted(snapshot)
4166 .iter()
4167 .any(|selection| !selection.is_empty())
4168 }
4169
4170 pub fn has_pending_nonempty_selection(&self) -> bool {
4171 let pending_nonempty_selection = match self.selections.pending_anchor() {
4172 Some(Selection { start, end, .. }) => start != end,
4173 None => false,
4174 };
4175
4176 pending_nonempty_selection
4177 || (self.columnar_selection_state.is_some()
4178 && self.selections.disjoint_anchors().len() > 1)
4179 }
4180
4181 pub fn has_pending_selection(&self) -> bool {
4182 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4183 }
4184
4185 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4186 self.selection_mark_mode = false;
4187 self.selection_drag_state = SelectionDragState::None;
4188
4189 if self.dismiss_menus_and_popups(true, window, cx) {
4190 cx.notify();
4191 return;
4192 }
4193 if self.clear_expanded_diff_hunks(cx) {
4194 cx.notify();
4195 return;
4196 }
4197 if self.show_git_blame_gutter {
4198 self.show_git_blame_gutter = false;
4199 cx.notify();
4200 return;
4201 }
4202
4203 if self.mode.is_full()
4204 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4205 {
4206 cx.notify();
4207 return;
4208 }
4209
4210 cx.propagate();
4211 }
4212
4213 pub fn dismiss_menus_and_popups(
4214 &mut self,
4215 is_user_requested: bool,
4216 window: &mut Window,
4217 cx: &mut Context<Self>,
4218 ) -> bool {
4219 let mut dismissed = false;
4220
4221 dismissed |= self.take_rename(false, window, cx).is_some();
4222 dismissed |= self.hide_blame_popover(true, cx);
4223 dismissed |= hide_hover(self, cx);
4224 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4225 dismissed |= self.hide_context_menu(window, cx).is_some();
4226 dismissed |= self.mouse_context_menu.take().is_some();
4227 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4228 dismissed |= self.snippet_stack.pop().is_some();
4229
4230 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4231 self.dismiss_diagnostics(cx);
4232 dismissed = true;
4233 }
4234
4235 dismissed
4236 }
4237
4238 fn linked_editing_ranges_for(
4239 &self,
4240 selection: Range<text::Anchor>,
4241 cx: &App,
4242 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4243 if self.linked_edit_ranges.is_empty() {
4244 return None;
4245 }
4246 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4247 selection.end.buffer_id.and_then(|end_buffer_id| {
4248 if selection.start.buffer_id != Some(end_buffer_id) {
4249 return None;
4250 }
4251 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4252 let snapshot = buffer.read(cx).snapshot();
4253 self.linked_edit_ranges
4254 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4255 .map(|ranges| (ranges, snapshot, buffer))
4256 })?;
4257 use text::ToOffset as TO;
4258 // find offset from the start of current range to current cursor position
4259 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4260
4261 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4262 let start_difference = start_offset - start_byte_offset;
4263 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4264 let end_difference = end_offset - start_byte_offset;
4265 // Current range has associated linked ranges.
4266 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4267 for range in linked_ranges.iter() {
4268 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4269 let end_offset = start_offset + end_difference;
4270 let start_offset = start_offset + start_difference;
4271 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4272 continue;
4273 }
4274 if self.selections.disjoint_anchor_ranges().any(|s| {
4275 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4276 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4277 {
4278 return false;
4279 }
4280 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4281 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4282 }) {
4283 continue;
4284 }
4285 let start = buffer_snapshot.anchor_after(start_offset);
4286 let end = buffer_snapshot.anchor_after(end_offset);
4287 linked_edits
4288 .entry(buffer.clone())
4289 .or_default()
4290 .push(start..end);
4291 }
4292 Some(linked_edits)
4293 }
4294
4295 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4296 let text: Arc<str> = text.into();
4297
4298 if self.read_only(cx) {
4299 return;
4300 }
4301
4302 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4303
4304 self.unfold_buffers_with_selections(cx);
4305
4306 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4307 let mut bracket_inserted = false;
4308 let mut edits = Vec::new();
4309 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4310 let mut new_selections = Vec::with_capacity(selections.len());
4311 let mut new_autoclose_regions = Vec::new();
4312 let snapshot = self.buffer.read(cx).read(cx);
4313 let mut clear_linked_edit_ranges = false;
4314
4315 for (selection, autoclose_region) in
4316 self.selections_with_autoclose_regions(selections, &snapshot)
4317 {
4318 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4319 // Determine if the inserted text matches the opening or closing
4320 // bracket of any of this language's bracket pairs.
4321 let mut bracket_pair = None;
4322 let mut is_bracket_pair_start = false;
4323 let mut is_bracket_pair_end = false;
4324 if !text.is_empty() {
4325 let mut bracket_pair_matching_end = None;
4326 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4327 // and they are removing the character that triggered IME popup.
4328 for (pair, enabled) in scope.brackets() {
4329 if !pair.close && !pair.surround {
4330 continue;
4331 }
4332
4333 if enabled && pair.start.ends_with(text.as_ref()) {
4334 let prefix_len = pair.start.len() - text.len();
4335 let preceding_text_matches_prefix = prefix_len == 0
4336 || (selection.start.column >= (prefix_len as u32)
4337 && snapshot.contains_str_at(
4338 Point::new(
4339 selection.start.row,
4340 selection.start.column - (prefix_len as u32),
4341 ),
4342 &pair.start[..prefix_len],
4343 ));
4344 if preceding_text_matches_prefix {
4345 bracket_pair = Some(pair.clone());
4346 is_bracket_pair_start = true;
4347 break;
4348 }
4349 }
4350 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4351 {
4352 // take first bracket pair matching end, but don't break in case a later bracket
4353 // pair matches start
4354 bracket_pair_matching_end = Some(pair.clone());
4355 }
4356 }
4357 if let Some(end) = bracket_pair_matching_end
4358 && bracket_pair.is_none()
4359 {
4360 bracket_pair = Some(end);
4361 is_bracket_pair_end = true;
4362 }
4363 }
4364
4365 if let Some(bracket_pair) = bracket_pair {
4366 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4367 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4368 let auto_surround =
4369 self.use_auto_surround && snapshot_settings.use_auto_surround;
4370 if selection.is_empty() {
4371 if is_bracket_pair_start {
4372 // If the inserted text is a suffix of an opening bracket and the
4373 // selection is preceded by the rest of the opening bracket, then
4374 // insert the closing bracket.
4375 let following_text_allows_autoclose = snapshot
4376 .chars_at(selection.start)
4377 .next()
4378 .is_none_or(|c| scope.should_autoclose_before(c));
4379
4380 let preceding_text_allows_autoclose = selection.start.column == 0
4381 || snapshot
4382 .reversed_chars_at(selection.start)
4383 .next()
4384 .is_none_or(|c| {
4385 bracket_pair.start != bracket_pair.end
4386 || !snapshot
4387 .char_classifier_at(selection.start)
4388 .is_word(c)
4389 });
4390
4391 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4392 && bracket_pair.start.len() == 1
4393 {
4394 let target = bracket_pair.start.chars().next().unwrap();
4395 let mut byte_offset = 0u32;
4396 let current_line_count = snapshot
4397 .reversed_chars_at(selection.start)
4398 .take_while(|&c| c != '\n')
4399 .filter(|c| {
4400 byte_offset += c.len_utf8() as u32;
4401 if *c != target {
4402 return false;
4403 }
4404
4405 let point = Point::new(
4406 selection.start.row,
4407 selection.start.column.saturating_sub(byte_offset),
4408 );
4409
4410 let is_enabled = snapshot
4411 .language_scope_at(point)
4412 .and_then(|scope| {
4413 scope
4414 .brackets()
4415 .find(|(pair, _)| {
4416 pair.start == bracket_pair.start
4417 })
4418 .map(|(_, enabled)| enabled)
4419 })
4420 .unwrap_or(true);
4421
4422 let is_delimiter = snapshot
4423 .language_scope_at(Point::new(
4424 point.row,
4425 point.column + 1,
4426 ))
4427 .and_then(|scope| {
4428 scope
4429 .brackets()
4430 .find(|(pair, _)| {
4431 pair.start == bracket_pair.start
4432 })
4433 .map(|(_, enabled)| !enabled)
4434 })
4435 .unwrap_or(false);
4436
4437 is_enabled && !is_delimiter
4438 })
4439 .count();
4440 current_line_count % 2 == 1
4441 } else {
4442 false
4443 };
4444
4445 if autoclose
4446 && bracket_pair.close
4447 && following_text_allows_autoclose
4448 && preceding_text_allows_autoclose
4449 && !is_closing_quote
4450 {
4451 let anchor = snapshot.anchor_before(selection.end);
4452 new_selections.push((selection.map(|_| anchor), text.len()));
4453 new_autoclose_regions.push((
4454 anchor,
4455 text.len(),
4456 selection.id,
4457 bracket_pair.clone(),
4458 ));
4459 edits.push((
4460 selection.range(),
4461 format!("{}{}", text, bracket_pair.end).into(),
4462 ));
4463 bracket_inserted = true;
4464 continue;
4465 }
4466 }
4467
4468 if let Some(region) = autoclose_region {
4469 // If the selection is followed by an auto-inserted closing bracket,
4470 // then don't insert that closing bracket again; just move the selection
4471 // past the closing bracket.
4472 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4473 && text.as_ref() == region.pair.end.as_str()
4474 && snapshot.contains_str_at(region.range.end, text.as_ref());
4475 if should_skip {
4476 let anchor = snapshot.anchor_after(selection.end);
4477 new_selections
4478 .push((selection.map(|_| anchor), region.pair.end.len()));
4479 continue;
4480 }
4481 }
4482
4483 let always_treat_brackets_as_autoclosed = snapshot
4484 .language_settings_at(selection.start, cx)
4485 .always_treat_brackets_as_autoclosed;
4486 if always_treat_brackets_as_autoclosed
4487 && is_bracket_pair_end
4488 && snapshot.contains_str_at(selection.end, text.as_ref())
4489 {
4490 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4491 // and the inserted text is a closing bracket and the selection is followed
4492 // by the closing bracket then move the selection past the closing bracket.
4493 let anchor = snapshot.anchor_after(selection.end);
4494 new_selections.push((selection.map(|_| anchor), text.len()));
4495 continue;
4496 }
4497 }
4498 // If an opening bracket is 1 character long and is typed while
4499 // text is selected, then surround that text with the bracket pair.
4500 else if auto_surround
4501 && bracket_pair.surround
4502 && is_bracket_pair_start
4503 && bracket_pair.start.chars().count() == 1
4504 {
4505 edits.push((selection.start..selection.start, text.clone()));
4506 edits.push((
4507 selection.end..selection.end,
4508 bracket_pair.end.as_str().into(),
4509 ));
4510 bracket_inserted = true;
4511 new_selections.push((
4512 Selection {
4513 id: selection.id,
4514 start: snapshot.anchor_after(selection.start),
4515 end: snapshot.anchor_before(selection.end),
4516 reversed: selection.reversed,
4517 goal: selection.goal,
4518 },
4519 0,
4520 ));
4521 continue;
4522 }
4523 }
4524 }
4525
4526 if self.auto_replace_emoji_shortcode
4527 && selection.is_empty()
4528 && text.as_ref().ends_with(':')
4529 && let Some(possible_emoji_short_code) =
4530 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4531 && !possible_emoji_short_code.is_empty()
4532 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4533 {
4534 let emoji_shortcode_start = Point::new(
4535 selection.start.row,
4536 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4537 );
4538
4539 // Remove shortcode from buffer
4540 edits.push((
4541 emoji_shortcode_start..selection.start,
4542 "".to_string().into(),
4543 ));
4544 new_selections.push((
4545 Selection {
4546 id: selection.id,
4547 start: snapshot.anchor_after(emoji_shortcode_start),
4548 end: snapshot.anchor_before(selection.start),
4549 reversed: selection.reversed,
4550 goal: selection.goal,
4551 },
4552 0,
4553 ));
4554
4555 // Insert emoji
4556 let selection_start_anchor = snapshot.anchor_after(selection.start);
4557 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4558 edits.push((selection.start..selection.end, emoji.to_string().into()));
4559
4560 continue;
4561 }
4562
4563 // If not handling any auto-close operation, then just replace the selected
4564 // text with the given input and move the selection to the end of the
4565 // newly inserted text.
4566 let anchor = snapshot.anchor_after(selection.end);
4567 if !self.linked_edit_ranges.is_empty() {
4568 let start_anchor = snapshot.anchor_before(selection.start);
4569
4570 let is_word_char = text.chars().next().is_none_or(|char| {
4571 let classifier = snapshot
4572 .char_classifier_at(start_anchor.to_offset(&snapshot))
4573 .scope_context(Some(CharScopeContext::LinkedEdit));
4574 classifier.is_word(char)
4575 });
4576
4577 if is_word_char {
4578 if let Some(ranges) = self
4579 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4580 {
4581 for (buffer, edits) in ranges {
4582 linked_edits
4583 .entry(buffer.clone())
4584 .or_default()
4585 .extend(edits.into_iter().map(|range| (range, text.clone())));
4586 }
4587 }
4588 } else {
4589 clear_linked_edit_ranges = true;
4590 }
4591 }
4592
4593 new_selections.push((selection.map(|_| anchor), 0));
4594 edits.push((selection.start..selection.end, text.clone()));
4595 }
4596
4597 drop(snapshot);
4598
4599 self.transact(window, cx, |this, window, cx| {
4600 if clear_linked_edit_ranges {
4601 this.linked_edit_ranges.clear();
4602 }
4603 let initial_buffer_versions =
4604 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4605
4606 this.buffer.update(cx, |buffer, cx| {
4607 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4608 });
4609 for (buffer, edits) in linked_edits {
4610 buffer.update(cx, |buffer, cx| {
4611 let snapshot = buffer.snapshot();
4612 let edits = edits
4613 .into_iter()
4614 .map(|(range, text)| {
4615 use text::ToPoint as TP;
4616 let end_point = TP::to_point(&range.end, &snapshot);
4617 let start_point = TP::to_point(&range.start, &snapshot);
4618 (start_point..end_point, text)
4619 })
4620 .sorted_by_key(|(range, _)| range.start);
4621 buffer.edit(edits, None, cx);
4622 })
4623 }
4624 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4625 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4626 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4627 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4628 new_anchor_selections,
4629 &map,
4630 )
4631 .zip(new_selection_deltas)
4632 .map(|(selection, delta)| Selection {
4633 id: selection.id,
4634 start: selection.start + delta,
4635 end: selection.end + delta,
4636 reversed: selection.reversed,
4637 goal: SelectionGoal::None,
4638 })
4639 .collect::<Vec<_>>();
4640
4641 let mut i = 0;
4642 for (position, delta, selection_id, pair) in new_autoclose_regions {
4643 let position = position.to_offset(map.buffer_snapshot()) + delta;
4644 let start = map.buffer_snapshot().anchor_before(position);
4645 let end = map.buffer_snapshot().anchor_after(position);
4646 while let Some(existing_state) = this.autoclose_regions.get(i) {
4647 match existing_state
4648 .range
4649 .start
4650 .cmp(&start, map.buffer_snapshot())
4651 {
4652 Ordering::Less => i += 1,
4653 Ordering::Greater => break,
4654 Ordering::Equal => {
4655 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4656 Ordering::Less => i += 1,
4657 Ordering::Equal => break,
4658 Ordering::Greater => break,
4659 }
4660 }
4661 }
4662 }
4663 this.autoclose_regions.insert(
4664 i,
4665 AutocloseRegion {
4666 selection_id,
4667 range: start..end,
4668 pair,
4669 },
4670 );
4671 }
4672
4673 let had_active_edit_prediction = this.has_active_edit_prediction();
4674 this.change_selections(
4675 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4676 window,
4677 cx,
4678 |s| s.select(new_selections),
4679 );
4680
4681 if !bracket_inserted
4682 && let Some(on_type_format_task) =
4683 this.trigger_on_type_formatting(text.to_string(), window, cx)
4684 {
4685 on_type_format_task.detach_and_log_err(cx);
4686 }
4687
4688 let editor_settings = EditorSettings::get_global(cx);
4689 if bracket_inserted
4690 && (editor_settings.auto_signature_help
4691 || editor_settings.show_signature_help_after_edits)
4692 {
4693 this.show_signature_help(&ShowSignatureHelp, window, cx);
4694 }
4695
4696 let trigger_in_words =
4697 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4698 if this.hard_wrap.is_some() {
4699 let latest: Range<Point> = this.selections.newest(&map).range();
4700 if latest.is_empty()
4701 && this
4702 .buffer()
4703 .read(cx)
4704 .snapshot(cx)
4705 .line_len(MultiBufferRow(latest.start.row))
4706 == latest.start.column
4707 {
4708 this.rewrap_impl(
4709 RewrapOptions {
4710 override_language_settings: true,
4711 preserve_existing_whitespace: true,
4712 },
4713 cx,
4714 )
4715 }
4716 }
4717 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4718 refresh_linked_ranges(this, window, cx);
4719 this.refresh_edit_prediction(true, false, window, cx);
4720 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4721 });
4722 }
4723
4724 fn find_possible_emoji_shortcode_at_position(
4725 snapshot: &MultiBufferSnapshot,
4726 position: Point,
4727 ) -> Option<String> {
4728 let mut chars = Vec::new();
4729 let mut found_colon = false;
4730 for char in snapshot.reversed_chars_at(position).take(100) {
4731 // Found a possible emoji shortcode in the middle of the buffer
4732 if found_colon {
4733 if char.is_whitespace() {
4734 chars.reverse();
4735 return Some(chars.iter().collect());
4736 }
4737 // If the previous character is not a whitespace, we are in the middle of a word
4738 // and we only want to complete the shortcode if the word is made up of other emojis
4739 let mut containing_word = String::new();
4740 for ch in snapshot
4741 .reversed_chars_at(position)
4742 .skip(chars.len() + 1)
4743 .take(100)
4744 {
4745 if ch.is_whitespace() {
4746 break;
4747 }
4748 containing_word.push(ch);
4749 }
4750 let containing_word = containing_word.chars().rev().collect::<String>();
4751 if util::word_consists_of_emojis(containing_word.as_str()) {
4752 chars.reverse();
4753 return Some(chars.iter().collect());
4754 }
4755 }
4756
4757 if char.is_whitespace() || !char.is_ascii() {
4758 return None;
4759 }
4760 if char == ':' {
4761 found_colon = true;
4762 } else {
4763 chars.push(char);
4764 }
4765 }
4766 // Found a possible emoji shortcode at the beginning of the buffer
4767 chars.reverse();
4768 Some(chars.iter().collect())
4769 }
4770
4771 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4773 self.transact(window, cx, |this, window, cx| {
4774 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4775 let selections = this
4776 .selections
4777 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4778 let multi_buffer = this.buffer.read(cx);
4779 let buffer = multi_buffer.snapshot(cx);
4780 selections
4781 .iter()
4782 .map(|selection| {
4783 let start_point = selection.start.to_point(&buffer);
4784 let mut existing_indent =
4785 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4786 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4787 let start = selection.start;
4788 let end = selection.end;
4789 let selection_is_empty = start == end;
4790 let language_scope = buffer.language_scope_at(start);
4791 let (delimiter, newline_config) = if let Some(language) = &language_scope {
4792 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
4793 &buffer,
4794 start..end,
4795 language,
4796 )
4797 || NewlineConfig::insert_extra_newline_tree_sitter(
4798 &buffer,
4799 start..end,
4800 );
4801
4802 let mut newline_config = NewlineConfig::Newline {
4803 additional_indent: IndentSize::spaces(0),
4804 extra_line_additional_indent: if needs_extra_newline {
4805 Some(IndentSize::spaces(0))
4806 } else {
4807 None
4808 },
4809 prevent_auto_indent: false,
4810 };
4811
4812 let comment_delimiter = maybe!({
4813 if !selection_is_empty {
4814 return None;
4815 }
4816
4817 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4818 return None;
4819 }
4820
4821 return comment_delimiter_for_newline(
4822 &start_point,
4823 &buffer,
4824 language,
4825 );
4826 });
4827
4828 let doc_delimiter = maybe!({
4829 if !selection_is_empty {
4830 return None;
4831 }
4832
4833 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4834 return None;
4835 }
4836
4837 return documentation_delimiter_for_newline(
4838 &start_point,
4839 &buffer,
4840 language,
4841 &mut newline_config,
4842 );
4843 });
4844
4845 let list_delimiter = maybe!({
4846 if !selection_is_empty {
4847 return None;
4848 }
4849
4850 if !multi_buffer.language_settings(cx).extend_list_on_newline {
4851 return None;
4852 }
4853
4854 return list_delimiter_for_newline(
4855 &start_point,
4856 &buffer,
4857 language,
4858 &mut newline_config,
4859 );
4860 });
4861
4862 (
4863 comment_delimiter.or(doc_delimiter).or(list_delimiter),
4864 newline_config,
4865 )
4866 } else {
4867 (
4868 None,
4869 NewlineConfig::Newline {
4870 additional_indent: IndentSize::spaces(0),
4871 extra_line_additional_indent: None,
4872 prevent_auto_indent: false,
4873 },
4874 )
4875 };
4876
4877 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
4878 NewlineConfig::ClearCurrentLine => {
4879 let row_start =
4880 buffer.point_to_offset(Point::new(start_point.row, 0));
4881 (row_start, String::new(), false)
4882 }
4883 NewlineConfig::UnindentCurrentLine { continuation } => {
4884 let row_start =
4885 buffer.point_to_offset(Point::new(start_point.row, 0));
4886 let tab_size = buffer.language_settings_at(start, cx).tab_size;
4887 let tab_size_indent = IndentSize::spaces(tab_size.get());
4888 let reduced_indent =
4889 existing_indent.with_delta(Ordering::Less, tab_size_indent);
4890 let mut new_text = String::new();
4891 new_text.extend(reduced_indent.chars());
4892 new_text.push_str(continuation);
4893 (row_start, new_text, true)
4894 }
4895 NewlineConfig::Newline {
4896 additional_indent,
4897 extra_line_additional_indent,
4898 prevent_auto_indent,
4899 } => {
4900 let capacity_for_delimiter =
4901 delimiter.as_deref().map(str::len).unwrap_or_default();
4902 let extra_line_len = extra_line_additional_indent
4903 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
4904 .unwrap_or(0);
4905 let mut new_text = String::with_capacity(
4906 1 + capacity_for_delimiter
4907 + existing_indent.len as usize
4908 + additional_indent.len as usize
4909 + extra_line_len,
4910 );
4911 new_text.push('\n');
4912 new_text.extend(existing_indent.chars());
4913 new_text.extend(additional_indent.chars());
4914 if let Some(delimiter) = &delimiter {
4915 new_text.push_str(delimiter);
4916 }
4917 if let Some(extra_indent) = extra_line_additional_indent {
4918 new_text.push('\n');
4919 new_text.extend(existing_indent.chars());
4920 new_text.extend(extra_indent.chars());
4921 }
4922 (start, new_text, *prevent_auto_indent)
4923 }
4924 };
4925
4926 let anchor = buffer.anchor_after(end);
4927 let new_selection = selection.map(|_| anchor);
4928 (
4929 ((edit_start..end, new_text), prevent_auto_indent),
4930 (newline_config.has_extra_line(), new_selection),
4931 )
4932 })
4933 .unzip()
4934 };
4935
4936 let mut auto_indent_edits = Vec::new();
4937 let mut edits = Vec::new();
4938 for (edit, prevent_auto_indent) in edits_with_flags {
4939 if prevent_auto_indent {
4940 edits.push(edit);
4941 } else {
4942 auto_indent_edits.push(edit);
4943 }
4944 }
4945 if !edits.is_empty() {
4946 this.edit(edits, cx);
4947 }
4948 if !auto_indent_edits.is_empty() {
4949 this.edit_with_autoindent(auto_indent_edits, cx);
4950 }
4951
4952 let buffer = this.buffer.read(cx).snapshot(cx);
4953 let new_selections = selection_info
4954 .into_iter()
4955 .map(|(extra_newline_inserted, new_selection)| {
4956 let mut cursor = new_selection.end.to_point(&buffer);
4957 if extra_newline_inserted {
4958 cursor.row -= 1;
4959 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4960 }
4961 new_selection.map(|_| cursor)
4962 })
4963 .collect();
4964
4965 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4966 this.refresh_edit_prediction(true, false, window, cx);
4967 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
4968 task.detach_and_log_err(cx);
4969 }
4970 });
4971 }
4972
4973 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4974 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4975
4976 let buffer = self.buffer.read(cx);
4977 let snapshot = buffer.snapshot(cx);
4978
4979 let mut edits = Vec::new();
4980 let mut rows = Vec::new();
4981
4982 for (rows_inserted, selection) in self
4983 .selections
4984 .all_adjusted(&self.display_snapshot(cx))
4985 .into_iter()
4986 .enumerate()
4987 {
4988 let cursor = selection.head();
4989 let row = cursor.row;
4990
4991 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4992
4993 let newline = "\n".to_string();
4994 edits.push((start_of_line..start_of_line, newline));
4995
4996 rows.push(row + rows_inserted as u32);
4997 }
4998
4999 self.transact(window, cx, |editor, window, cx| {
5000 editor.edit(edits, cx);
5001
5002 editor.change_selections(Default::default(), window, cx, |s| {
5003 let mut index = 0;
5004 s.move_cursors_with(|map, _, _| {
5005 let row = rows[index];
5006 index += 1;
5007
5008 let point = Point::new(row, 0);
5009 let boundary = map.next_line_boundary(point).1;
5010 let clipped = map.clip_point(boundary, Bias::Left);
5011
5012 (clipped, SelectionGoal::None)
5013 });
5014 });
5015
5016 let mut indent_edits = Vec::new();
5017 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5018 for row in rows {
5019 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5020 for (row, indent) in indents {
5021 if indent.len == 0 {
5022 continue;
5023 }
5024
5025 let text = match indent.kind {
5026 IndentKind::Space => " ".repeat(indent.len as usize),
5027 IndentKind::Tab => "\t".repeat(indent.len as usize),
5028 };
5029 let point = Point::new(row.0, 0);
5030 indent_edits.push((point..point, text));
5031 }
5032 }
5033 editor.edit(indent_edits, cx);
5034 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5035 format.detach_and_log_err(cx);
5036 }
5037 });
5038 }
5039
5040 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5042
5043 let buffer = self.buffer.read(cx);
5044 let snapshot = buffer.snapshot(cx);
5045
5046 let mut edits = Vec::new();
5047 let mut rows = Vec::new();
5048 let mut rows_inserted = 0;
5049
5050 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5051 let cursor = selection.head();
5052 let row = cursor.row;
5053
5054 let point = Point::new(row + 1, 0);
5055 let start_of_line = snapshot.clip_point(point, Bias::Left);
5056
5057 let newline = "\n".to_string();
5058 edits.push((start_of_line..start_of_line, newline));
5059
5060 rows_inserted += 1;
5061 rows.push(row + rows_inserted);
5062 }
5063
5064 self.transact(window, cx, |editor, window, cx| {
5065 editor.edit(edits, cx);
5066
5067 editor.change_selections(Default::default(), window, cx, |s| {
5068 let mut index = 0;
5069 s.move_cursors_with(|map, _, _| {
5070 let row = rows[index];
5071 index += 1;
5072
5073 let point = Point::new(row, 0);
5074 let boundary = map.next_line_boundary(point).1;
5075 let clipped = map.clip_point(boundary, Bias::Left);
5076
5077 (clipped, SelectionGoal::None)
5078 });
5079 });
5080
5081 let mut indent_edits = Vec::new();
5082 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5083 for row in rows {
5084 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5085 for (row, indent) in indents {
5086 if indent.len == 0 {
5087 continue;
5088 }
5089
5090 let text = match indent.kind {
5091 IndentKind::Space => " ".repeat(indent.len as usize),
5092 IndentKind::Tab => "\t".repeat(indent.len as usize),
5093 };
5094 let point = Point::new(row.0, 0);
5095 indent_edits.push((point..point, text));
5096 }
5097 }
5098 editor.edit(indent_edits, cx);
5099 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5100 format.detach_and_log_err(cx);
5101 }
5102 });
5103 }
5104
5105 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5106 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5107 original_indent_columns: Vec::new(),
5108 });
5109 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5110 }
5111
5112 fn insert_with_autoindent_mode(
5113 &mut self,
5114 text: &str,
5115 autoindent_mode: Option<AutoindentMode>,
5116 window: &mut Window,
5117 cx: &mut Context<Self>,
5118 ) {
5119 if self.read_only(cx) {
5120 return;
5121 }
5122
5123 let text: Arc<str> = text.into();
5124 self.transact(window, cx, |this, window, cx| {
5125 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5126 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5127 let anchors = {
5128 let snapshot = buffer.read(cx);
5129 old_selections
5130 .iter()
5131 .map(|s| {
5132 let anchor = snapshot.anchor_after(s.head());
5133 s.map(|_| anchor)
5134 })
5135 .collect::<Vec<_>>()
5136 };
5137 buffer.edit(
5138 old_selections
5139 .iter()
5140 .map(|s| (s.start..s.end, text.clone())),
5141 autoindent_mode,
5142 cx,
5143 );
5144 anchors
5145 });
5146
5147 this.change_selections(Default::default(), window, cx, |s| {
5148 s.select_anchors(selection_anchors);
5149 });
5150
5151 cx.notify();
5152 });
5153 }
5154
5155 fn trigger_completion_on_input(
5156 &mut self,
5157 text: &str,
5158 trigger_in_words: bool,
5159 window: &mut Window,
5160 cx: &mut Context<Self>,
5161 ) {
5162 let completions_source = self
5163 .context_menu
5164 .borrow()
5165 .as_ref()
5166 .and_then(|menu| match menu {
5167 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5168 CodeContextMenu::CodeActions(_) => None,
5169 });
5170
5171 match completions_source {
5172 Some(CompletionsMenuSource::Words { .. }) => {
5173 self.open_or_update_completions_menu(
5174 Some(CompletionsMenuSource::Words {
5175 ignore_threshold: false,
5176 }),
5177 None,
5178 trigger_in_words,
5179 window,
5180 cx,
5181 );
5182 }
5183 _ => self.open_or_update_completions_menu(
5184 None,
5185 Some(text.to_owned()).filter(|x| !x.is_empty()),
5186 true,
5187 window,
5188 cx,
5189 ),
5190 }
5191 }
5192
5193 /// If any empty selections is touching the start of its innermost containing autoclose
5194 /// region, expand it to select the brackets.
5195 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5196 let selections = self
5197 .selections
5198 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5199 let buffer = self.buffer.read(cx).read(cx);
5200 let new_selections = self
5201 .selections_with_autoclose_regions(selections, &buffer)
5202 .map(|(mut selection, region)| {
5203 if !selection.is_empty() {
5204 return selection;
5205 }
5206
5207 if let Some(region) = region {
5208 let mut range = region.range.to_offset(&buffer);
5209 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5210 range.start -= region.pair.start.len();
5211 if buffer.contains_str_at(range.start, ®ion.pair.start)
5212 && buffer.contains_str_at(range.end, ®ion.pair.end)
5213 {
5214 range.end += region.pair.end.len();
5215 selection.start = range.start;
5216 selection.end = range.end;
5217
5218 return selection;
5219 }
5220 }
5221 }
5222
5223 let always_treat_brackets_as_autoclosed = buffer
5224 .language_settings_at(selection.start, cx)
5225 .always_treat_brackets_as_autoclosed;
5226
5227 if !always_treat_brackets_as_autoclosed {
5228 return selection;
5229 }
5230
5231 if let Some(scope) = buffer.language_scope_at(selection.start) {
5232 for (pair, enabled) in scope.brackets() {
5233 if !enabled || !pair.close {
5234 continue;
5235 }
5236
5237 if buffer.contains_str_at(selection.start, &pair.end) {
5238 let pair_start_len = pair.start.len();
5239 if buffer.contains_str_at(
5240 selection.start.saturating_sub_usize(pair_start_len),
5241 &pair.start,
5242 ) {
5243 selection.start -= pair_start_len;
5244 selection.end += pair.end.len();
5245
5246 return selection;
5247 }
5248 }
5249 }
5250 }
5251
5252 selection
5253 })
5254 .collect();
5255
5256 drop(buffer);
5257 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5258 selections.select(new_selections)
5259 });
5260 }
5261
5262 /// Iterate the given selections, and for each one, find the smallest surrounding
5263 /// autoclose region. This uses the ordering of the selections and the autoclose
5264 /// regions to avoid repeated comparisons.
5265 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5266 &'a self,
5267 selections: impl IntoIterator<Item = Selection<D>>,
5268 buffer: &'a MultiBufferSnapshot,
5269 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5270 let mut i = 0;
5271 let mut regions = self.autoclose_regions.as_slice();
5272 selections.into_iter().map(move |selection| {
5273 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5274
5275 let mut enclosing = None;
5276 while let Some(pair_state) = regions.get(i) {
5277 if pair_state.range.end.to_offset(buffer) < range.start {
5278 regions = ®ions[i + 1..];
5279 i = 0;
5280 } else if pair_state.range.start.to_offset(buffer) > range.end {
5281 break;
5282 } else {
5283 if pair_state.selection_id == selection.id {
5284 enclosing = Some(pair_state);
5285 }
5286 i += 1;
5287 }
5288 }
5289
5290 (selection, enclosing)
5291 })
5292 }
5293
5294 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5295 fn invalidate_autoclose_regions(
5296 &mut self,
5297 mut selections: &[Selection<Anchor>],
5298 buffer: &MultiBufferSnapshot,
5299 ) {
5300 self.autoclose_regions.retain(|state| {
5301 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5302 return false;
5303 }
5304
5305 let mut i = 0;
5306 while let Some(selection) = selections.get(i) {
5307 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5308 selections = &selections[1..];
5309 continue;
5310 }
5311 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5312 break;
5313 }
5314 if selection.id == state.selection_id {
5315 return true;
5316 } else {
5317 i += 1;
5318 }
5319 }
5320 false
5321 });
5322 }
5323
5324 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5325 let offset = position.to_offset(buffer);
5326 let (word_range, kind) =
5327 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5328 if offset > word_range.start && kind == Some(CharKind::Word) {
5329 Some(
5330 buffer
5331 .text_for_range(word_range.start..offset)
5332 .collect::<String>(),
5333 )
5334 } else {
5335 None
5336 }
5337 }
5338
5339 pub fn visible_excerpts(
5340 &self,
5341 lsp_related_only: bool,
5342 cx: &mut Context<Editor>,
5343 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5344 let project = self.project().cloned();
5345 let multi_buffer = self.buffer().read(cx);
5346 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5347 let multi_buffer_visible_start = self
5348 .scroll_manager
5349 .anchor()
5350 .anchor
5351 .to_point(&multi_buffer_snapshot);
5352 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5353 multi_buffer_visible_start
5354 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5355 Bias::Left,
5356 );
5357 multi_buffer_snapshot
5358 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5359 .into_iter()
5360 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5361 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5362 if !lsp_related_only {
5363 return Some((
5364 excerpt_id,
5365 (
5366 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5367 buffer.version().clone(),
5368 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5369 ),
5370 ));
5371 }
5372
5373 let project = project.as_ref()?.read(cx);
5374 let buffer_file = project::File::from_dyn(buffer.file())?;
5375 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5376 let worktree_entry = buffer_worktree
5377 .read(cx)
5378 .entry_for_id(buffer_file.project_entry_id()?)?;
5379 if worktree_entry.is_ignored {
5380 None
5381 } else {
5382 Some((
5383 excerpt_id,
5384 (
5385 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5386 buffer.version().clone(),
5387 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5388 ),
5389 ))
5390 }
5391 })
5392 .collect()
5393 }
5394
5395 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5396 TextLayoutDetails {
5397 text_system: window.text_system().clone(),
5398 editor_style: self.style.clone().unwrap(),
5399 rem_size: window.rem_size(),
5400 scroll_anchor: self.scroll_manager.anchor(),
5401 visible_rows: self.visible_line_count(),
5402 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5403 }
5404 }
5405
5406 fn trigger_on_type_formatting(
5407 &self,
5408 input: String,
5409 window: &mut Window,
5410 cx: &mut Context<Self>,
5411 ) -> Option<Task<Result<()>>> {
5412 if input.chars().count() != 1 {
5413 return None;
5414 }
5415
5416 let project = self.project()?;
5417 let position = self.selections.newest_anchor().head();
5418 let (buffer, buffer_position) = self
5419 .buffer
5420 .read(cx)
5421 .text_anchor_for_position(position, cx)?;
5422
5423 let settings = language_settings::language_settings(
5424 buffer
5425 .read(cx)
5426 .language_at(buffer_position)
5427 .map(|l| l.name()),
5428 buffer.read(cx).file(),
5429 cx,
5430 );
5431 if !settings.use_on_type_format {
5432 return None;
5433 }
5434
5435 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5436 // hence we do LSP request & edit on host side only — add formats to host's history.
5437 let push_to_lsp_host_history = true;
5438 // If this is not the host, append its history with new edits.
5439 let push_to_client_history = project.read(cx).is_via_collab();
5440
5441 let on_type_formatting = project.update(cx, |project, cx| {
5442 project.on_type_format(
5443 buffer.clone(),
5444 buffer_position,
5445 input,
5446 push_to_lsp_host_history,
5447 cx,
5448 )
5449 });
5450 Some(cx.spawn_in(window, async move |editor, cx| {
5451 if let Some(transaction) = on_type_formatting.await? {
5452 if push_to_client_history {
5453 buffer
5454 .update(cx, |buffer, _| {
5455 buffer.push_transaction(transaction, Instant::now());
5456 buffer.finalize_last_transaction();
5457 })
5458 .ok();
5459 }
5460 editor.update(cx, |editor, cx| {
5461 editor.refresh_document_highlights(cx);
5462 })?;
5463 }
5464 Ok(())
5465 }))
5466 }
5467
5468 pub fn show_word_completions(
5469 &mut self,
5470 _: &ShowWordCompletions,
5471 window: &mut Window,
5472 cx: &mut Context<Self>,
5473 ) {
5474 self.open_or_update_completions_menu(
5475 Some(CompletionsMenuSource::Words {
5476 ignore_threshold: true,
5477 }),
5478 None,
5479 false,
5480 window,
5481 cx,
5482 );
5483 }
5484
5485 pub fn show_completions(
5486 &mut self,
5487 _: &ShowCompletions,
5488 window: &mut Window,
5489 cx: &mut Context<Self>,
5490 ) {
5491 self.open_or_update_completions_menu(None, None, false, window, cx);
5492 }
5493
5494 fn open_or_update_completions_menu(
5495 &mut self,
5496 requested_source: Option<CompletionsMenuSource>,
5497 trigger: Option<String>,
5498 trigger_in_words: bool,
5499 window: &mut Window,
5500 cx: &mut Context<Self>,
5501 ) {
5502 if self.pending_rename.is_some() {
5503 return;
5504 }
5505
5506 let completions_source = self
5507 .context_menu
5508 .borrow()
5509 .as_ref()
5510 .and_then(|menu| match menu {
5511 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5512 CodeContextMenu::CodeActions(_) => None,
5513 });
5514
5515 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5516
5517 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5518 // inserted and selected. To handle that case, the start of the selection is used so that
5519 // the menu starts with all choices.
5520 let position = self
5521 .selections
5522 .newest_anchor()
5523 .start
5524 .bias_right(&multibuffer_snapshot);
5525 if position.diff_base_anchor.is_some() {
5526 return;
5527 }
5528 let buffer_position = multibuffer_snapshot.anchor_before(position);
5529 let Some(buffer) = buffer_position
5530 .text_anchor
5531 .buffer_id
5532 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5533 else {
5534 return;
5535 };
5536 let buffer_snapshot = buffer.read(cx).snapshot();
5537
5538 let menu_is_open = matches!(
5539 self.context_menu.borrow().as_ref(),
5540 Some(CodeContextMenu::Completions(_))
5541 );
5542
5543 let language = buffer_snapshot
5544 .language_at(buffer_position.text_anchor)
5545 .map(|language| language.name());
5546
5547 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5548 let completion_settings = language_settings.completions.clone();
5549
5550 let show_completions_on_input = self
5551 .show_completions_on_input_override
5552 .unwrap_or(language_settings.show_completions_on_input);
5553 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5554 return;
5555 }
5556
5557 let query: Option<Arc<String>> =
5558 Self::completion_query(&multibuffer_snapshot, buffer_position)
5559 .map(|query| query.into());
5560
5561 drop(multibuffer_snapshot);
5562
5563 // Hide the current completions menu when query is empty. Without this, cached
5564 // completions from before the trigger char may be reused (#32774).
5565 if query.is_none() && menu_is_open {
5566 self.hide_context_menu(window, cx);
5567 }
5568
5569 let mut ignore_word_threshold = false;
5570 let provider = match requested_source {
5571 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5572 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5573 ignore_word_threshold = ignore_threshold;
5574 None
5575 }
5576 Some(CompletionsMenuSource::SnippetChoices)
5577 | Some(CompletionsMenuSource::SnippetsOnly) => {
5578 log::error!("bug: SnippetChoices requested_source is not handled");
5579 None
5580 }
5581 };
5582
5583 let sort_completions = provider
5584 .as_ref()
5585 .is_some_and(|provider| provider.sort_completions());
5586
5587 let filter_completions = provider
5588 .as_ref()
5589 .is_none_or(|provider| provider.filter_completions());
5590
5591 let was_snippets_only = matches!(
5592 completions_source,
5593 Some(CompletionsMenuSource::SnippetsOnly)
5594 );
5595
5596 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5597 if filter_completions {
5598 menu.filter(
5599 query.clone().unwrap_or_default(),
5600 buffer_position.text_anchor,
5601 &buffer,
5602 provider.clone(),
5603 window,
5604 cx,
5605 );
5606 }
5607 // When `is_incomplete` is false, no need to re-query completions when the current query
5608 // is a suffix of the initial query.
5609 let was_complete = !menu.is_incomplete;
5610 if was_complete && !was_snippets_only {
5611 // If the new query is a suffix of the old query (typing more characters) and
5612 // the previous result was complete, the existing completions can be filtered.
5613 //
5614 // Note that snippet completions are always complete.
5615 let query_matches = match (&menu.initial_query, &query) {
5616 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5617 (None, _) => true,
5618 _ => false,
5619 };
5620 if query_matches {
5621 let position_matches = if menu.initial_position == position {
5622 true
5623 } else {
5624 let snapshot = self.buffer.read(cx).read(cx);
5625 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5626 };
5627 if position_matches {
5628 return;
5629 }
5630 }
5631 }
5632 };
5633
5634 let Anchor {
5635 excerpt_id: buffer_excerpt_id,
5636 text_anchor: buffer_position,
5637 ..
5638 } = buffer_position;
5639
5640 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5641 buffer_snapshot.surrounding_word(buffer_position, None)
5642 {
5643 let word_to_exclude = buffer_snapshot
5644 .text_for_range(word_range.clone())
5645 .collect::<String>();
5646 (
5647 buffer_snapshot.anchor_before(word_range.start)
5648 ..buffer_snapshot.anchor_after(buffer_position),
5649 Some(word_to_exclude),
5650 )
5651 } else {
5652 (buffer_position..buffer_position, None)
5653 };
5654
5655 let show_completion_documentation = buffer_snapshot
5656 .settings_at(buffer_position, cx)
5657 .show_completion_documentation;
5658
5659 // The document can be large, so stay in reasonable bounds when searching for words,
5660 // otherwise completion pop-up might be slow to appear.
5661 const WORD_LOOKUP_ROWS: u32 = 5_000;
5662 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5663 let min_word_search = buffer_snapshot.clip_point(
5664 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5665 Bias::Left,
5666 );
5667 let max_word_search = buffer_snapshot.clip_point(
5668 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5669 Bias::Right,
5670 );
5671 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5672 ..buffer_snapshot.point_to_offset(max_word_search);
5673
5674 let skip_digits = query
5675 .as_ref()
5676 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5677
5678 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5679 trigger.as_ref().is_none_or(|trigger| {
5680 provider.is_completion_trigger(
5681 &buffer,
5682 position.text_anchor,
5683 trigger,
5684 trigger_in_words,
5685 cx,
5686 )
5687 })
5688 });
5689
5690 let provider_responses = if let Some(provider) = &provider
5691 && load_provider_completions
5692 {
5693 let trigger_character =
5694 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5695 let completion_context = CompletionContext {
5696 trigger_kind: match &trigger_character {
5697 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5698 None => CompletionTriggerKind::INVOKED,
5699 },
5700 trigger_character,
5701 };
5702
5703 provider.completions(
5704 buffer_excerpt_id,
5705 &buffer,
5706 buffer_position,
5707 completion_context,
5708 window,
5709 cx,
5710 )
5711 } else {
5712 Task::ready(Ok(Vec::new()))
5713 };
5714
5715 let load_word_completions = if !self.word_completions_enabled {
5716 false
5717 } else if requested_source
5718 == Some(CompletionsMenuSource::Words {
5719 ignore_threshold: true,
5720 })
5721 {
5722 true
5723 } else {
5724 load_provider_completions
5725 && completion_settings.words != WordsCompletionMode::Disabled
5726 && (ignore_word_threshold || {
5727 let words_min_length = completion_settings.words_min_length;
5728 // check whether word has at least `words_min_length` characters
5729 let query_chars = query.iter().flat_map(|q| q.chars());
5730 query_chars.take(words_min_length).count() == words_min_length
5731 })
5732 };
5733
5734 let mut words = if load_word_completions {
5735 cx.background_spawn({
5736 let buffer_snapshot = buffer_snapshot.clone();
5737 async move {
5738 buffer_snapshot.words_in_range(WordsQuery {
5739 fuzzy_contents: None,
5740 range: word_search_range,
5741 skip_digits,
5742 })
5743 }
5744 })
5745 } else {
5746 Task::ready(BTreeMap::default())
5747 };
5748
5749 let snippets = if let Some(provider) = &provider
5750 && provider.show_snippets()
5751 && let Some(project) = self.project()
5752 {
5753 let char_classifier = buffer_snapshot
5754 .char_classifier_at(buffer_position)
5755 .scope_context(Some(CharScopeContext::Completion));
5756 project.update(cx, |project, cx| {
5757 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5758 })
5759 } else {
5760 Task::ready(Ok(CompletionResponse {
5761 completions: Vec::new(),
5762 display_options: Default::default(),
5763 is_incomplete: false,
5764 }))
5765 };
5766
5767 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5768
5769 let id = post_inc(&mut self.next_completion_id);
5770 let task = cx.spawn_in(window, async move |editor, cx| {
5771 let Ok(()) = editor.update(cx, |this, _| {
5772 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5773 }) else {
5774 return;
5775 };
5776
5777 // TODO: Ideally completions from different sources would be selectively re-queried, so
5778 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5779 let mut completions = Vec::new();
5780 let mut is_incomplete = false;
5781 let mut display_options: Option<CompletionDisplayOptions> = None;
5782 if let Some(provider_responses) = provider_responses.await.log_err()
5783 && !provider_responses.is_empty()
5784 {
5785 for response in provider_responses {
5786 completions.extend(response.completions);
5787 is_incomplete = is_incomplete || response.is_incomplete;
5788 match display_options.as_mut() {
5789 None => {
5790 display_options = Some(response.display_options);
5791 }
5792 Some(options) => options.merge(&response.display_options),
5793 }
5794 }
5795 if completion_settings.words == WordsCompletionMode::Fallback {
5796 words = Task::ready(BTreeMap::default());
5797 }
5798 }
5799 let display_options = display_options.unwrap_or_default();
5800
5801 let mut words = words.await;
5802 if let Some(word_to_exclude) = &word_to_exclude {
5803 words.remove(word_to_exclude);
5804 }
5805 for lsp_completion in &completions {
5806 words.remove(&lsp_completion.new_text);
5807 }
5808 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5809 replace_range: word_replace_range.clone(),
5810 new_text: word.clone(),
5811 label: CodeLabel::plain(word, None),
5812 match_start: None,
5813 snippet_deduplication_key: None,
5814 icon_path: None,
5815 documentation: None,
5816 source: CompletionSource::BufferWord {
5817 word_range,
5818 resolved: false,
5819 },
5820 insert_text_mode: Some(InsertTextMode::AS_IS),
5821 confirm: None,
5822 }));
5823
5824 completions.extend(
5825 snippets
5826 .await
5827 .into_iter()
5828 .flat_map(|response| response.completions),
5829 );
5830
5831 let menu = if completions.is_empty() {
5832 None
5833 } else {
5834 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5835 let languages = editor
5836 .workspace
5837 .as_ref()
5838 .and_then(|(workspace, _)| workspace.upgrade())
5839 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5840 let menu = CompletionsMenu::new(
5841 id,
5842 requested_source.unwrap_or(if load_provider_completions {
5843 CompletionsMenuSource::Normal
5844 } else {
5845 CompletionsMenuSource::SnippetsOnly
5846 }),
5847 sort_completions,
5848 show_completion_documentation,
5849 position,
5850 query.clone(),
5851 is_incomplete,
5852 buffer.clone(),
5853 completions.into(),
5854 editor
5855 .context_menu()
5856 .borrow_mut()
5857 .as_ref()
5858 .map(|menu| menu.primary_scroll_handle()),
5859 display_options,
5860 snippet_sort_order,
5861 languages,
5862 language,
5863 cx,
5864 );
5865
5866 let query = if filter_completions { query } else { None };
5867 let matches_task = menu.do_async_filtering(
5868 query.unwrap_or_default(),
5869 buffer_position,
5870 &buffer,
5871 cx,
5872 );
5873 (menu, matches_task)
5874 }) else {
5875 return;
5876 };
5877
5878 let matches = matches_task.await;
5879
5880 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5881 // Newer menu already set, so exit.
5882 if let Some(CodeContextMenu::Completions(prev_menu)) =
5883 editor.context_menu.borrow().as_ref()
5884 && prev_menu.id > id
5885 {
5886 return;
5887 };
5888
5889 // Only valid to take prev_menu because either the new menu is immediately set
5890 // below, or the menu is hidden.
5891 if let Some(CodeContextMenu::Completions(prev_menu)) =
5892 editor.context_menu.borrow_mut().take()
5893 {
5894 let position_matches =
5895 if prev_menu.initial_position == menu.initial_position {
5896 true
5897 } else {
5898 let snapshot = editor.buffer.read(cx).read(cx);
5899 prev_menu.initial_position.to_offset(&snapshot)
5900 == menu.initial_position.to_offset(&snapshot)
5901 };
5902 if position_matches {
5903 // Preserve markdown cache before `set_filter_results` because it will
5904 // try to populate the documentation cache.
5905 menu.preserve_markdown_cache(prev_menu);
5906 }
5907 };
5908
5909 menu.set_filter_results(matches, provider, window, cx);
5910 }) else {
5911 return;
5912 };
5913
5914 menu.visible().then_some(menu)
5915 };
5916
5917 editor
5918 .update_in(cx, |editor, window, cx| {
5919 if editor.focus_handle.is_focused(window)
5920 && let Some(menu) = menu
5921 {
5922 *editor.context_menu.borrow_mut() =
5923 Some(CodeContextMenu::Completions(menu));
5924
5925 crate::hover_popover::hide_hover(editor, cx);
5926 if editor.show_edit_predictions_in_menu() {
5927 editor.update_visible_edit_prediction(window, cx);
5928 } else {
5929 editor.discard_edit_prediction(false, cx);
5930 }
5931
5932 cx.notify();
5933 return;
5934 }
5935
5936 if editor.completion_tasks.len() <= 1 {
5937 // If there are no more completion tasks and the last menu was empty, we should hide it.
5938 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5939 // If it was already hidden and we don't show edit predictions in the menu,
5940 // we should also show the edit prediction when available.
5941 if was_hidden && editor.show_edit_predictions_in_menu() {
5942 editor.update_visible_edit_prediction(window, cx);
5943 }
5944 }
5945 })
5946 .ok();
5947 });
5948
5949 self.completion_tasks.push((id, task));
5950 }
5951
5952 #[cfg(feature = "test-support")]
5953 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5954 let menu = self.context_menu.borrow();
5955 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5956 let completions = menu.completions.borrow();
5957 Some(completions.to_vec())
5958 } else {
5959 None
5960 }
5961 }
5962
5963 pub fn with_completions_menu_matching_id<R>(
5964 &self,
5965 id: CompletionId,
5966 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5967 ) -> R {
5968 let mut context_menu = self.context_menu.borrow_mut();
5969 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5970 return f(None);
5971 };
5972 if completions_menu.id != id {
5973 return f(None);
5974 }
5975 f(Some(completions_menu))
5976 }
5977
5978 pub fn confirm_completion(
5979 &mut self,
5980 action: &ConfirmCompletion,
5981 window: &mut Window,
5982 cx: &mut Context<Self>,
5983 ) -> Option<Task<Result<()>>> {
5984 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5985 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5986 }
5987
5988 pub fn confirm_completion_insert(
5989 &mut self,
5990 _: &ConfirmCompletionInsert,
5991 window: &mut Window,
5992 cx: &mut Context<Self>,
5993 ) -> Option<Task<Result<()>>> {
5994 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5995 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5996 }
5997
5998 pub fn confirm_completion_replace(
5999 &mut self,
6000 _: &ConfirmCompletionReplace,
6001 window: &mut Window,
6002 cx: &mut Context<Self>,
6003 ) -> Option<Task<Result<()>>> {
6004 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6005 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6006 }
6007
6008 pub fn compose_completion(
6009 &mut self,
6010 action: &ComposeCompletion,
6011 window: &mut Window,
6012 cx: &mut Context<Self>,
6013 ) -> Option<Task<Result<()>>> {
6014 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6015 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6016 }
6017
6018 fn do_completion(
6019 &mut self,
6020 item_ix: Option<usize>,
6021 intent: CompletionIntent,
6022 window: &mut Window,
6023 cx: &mut Context<Editor>,
6024 ) -> Option<Task<Result<()>>> {
6025 use language::ToOffset as _;
6026
6027 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6028 else {
6029 return None;
6030 };
6031
6032 let candidate_id = {
6033 let entries = completions_menu.entries.borrow();
6034 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6035 if self.show_edit_predictions_in_menu() {
6036 self.discard_edit_prediction(true, cx);
6037 }
6038 mat.candidate_id
6039 };
6040
6041 let completion = completions_menu
6042 .completions
6043 .borrow()
6044 .get(candidate_id)?
6045 .clone();
6046 cx.stop_propagation();
6047
6048 let buffer_handle = completions_menu.buffer.clone();
6049
6050 let CompletionEdit {
6051 new_text,
6052 snippet,
6053 replace_range,
6054 } = process_completion_for_edit(
6055 &completion,
6056 intent,
6057 &buffer_handle,
6058 &completions_menu.initial_position.text_anchor,
6059 cx,
6060 );
6061
6062 let buffer = buffer_handle.read(cx);
6063 let snapshot = self.buffer.read(cx).snapshot(cx);
6064 let newest_anchor = self.selections.newest_anchor();
6065 let replace_range_multibuffer = {
6066 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6067 excerpt.map_range_from_buffer(replace_range.clone())
6068 };
6069 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6070 return None;
6071 }
6072
6073 let old_text = buffer
6074 .text_for_range(replace_range.clone())
6075 .collect::<String>();
6076 let lookbehind = newest_anchor
6077 .start
6078 .text_anchor
6079 .to_offset(buffer)
6080 .saturating_sub(replace_range.start.0);
6081 let lookahead = replace_range
6082 .end
6083 .0
6084 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6085 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6086 let suffix = &old_text[lookbehind.min(old_text.len())..];
6087
6088 let selections = self
6089 .selections
6090 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6091 let mut ranges = Vec::new();
6092 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6093
6094 for selection in &selections {
6095 let range = if selection.id == newest_anchor.id {
6096 replace_range_multibuffer.clone()
6097 } else {
6098 let mut range = selection.range();
6099
6100 // if prefix is present, don't duplicate it
6101 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6102 range.start = range.start.saturating_sub_usize(lookbehind);
6103
6104 // if suffix is also present, mimic the newest cursor and replace it
6105 if selection.id != newest_anchor.id
6106 && snapshot.contains_str_at(range.end, suffix)
6107 {
6108 range.end += lookahead;
6109 }
6110 }
6111 range
6112 };
6113
6114 ranges.push(range.clone());
6115
6116 if !self.linked_edit_ranges.is_empty() {
6117 let start_anchor = snapshot.anchor_before(range.start);
6118 let end_anchor = snapshot.anchor_after(range.end);
6119 if let Some(ranges) = self
6120 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6121 {
6122 for (buffer, edits) in ranges {
6123 linked_edits
6124 .entry(buffer.clone())
6125 .or_default()
6126 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6127 }
6128 }
6129 }
6130 }
6131
6132 let common_prefix_len = old_text
6133 .chars()
6134 .zip(new_text.chars())
6135 .take_while(|(a, b)| a == b)
6136 .map(|(a, _)| a.len_utf8())
6137 .sum::<usize>();
6138
6139 cx.emit(EditorEvent::InputHandled {
6140 utf16_range_to_replace: None,
6141 text: new_text[common_prefix_len..].into(),
6142 });
6143
6144 self.transact(window, cx, |editor, window, cx| {
6145 if let Some(mut snippet) = snippet {
6146 snippet.text = new_text.to_string();
6147 editor
6148 .insert_snippet(&ranges, snippet, window, cx)
6149 .log_err();
6150 } else {
6151 editor.buffer.update(cx, |multi_buffer, cx| {
6152 let auto_indent = match completion.insert_text_mode {
6153 Some(InsertTextMode::AS_IS) => None,
6154 _ => editor.autoindent_mode.clone(),
6155 };
6156 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6157 multi_buffer.edit(edits, auto_indent, cx);
6158 });
6159 }
6160 for (buffer, edits) in linked_edits {
6161 buffer.update(cx, |buffer, cx| {
6162 let snapshot = buffer.snapshot();
6163 let edits = edits
6164 .into_iter()
6165 .map(|(range, text)| {
6166 use text::ToPoint as TP;
6167 let end_point = TP::to_point(&range.end, &snapshot);
6168 let start_point = TP::to_point(&range.start, &snapshot);
6169 (start_point..end_point, text)
6170 })
6171 .sorted_by_key(|(range, _)| range.start);
6172 buffer.edit(edits, None, cx);
6173 })
6174 }
6175
6176 editor.refresh_edit_prediction(true, false, window, cx);
6177 });
6178 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6179
6180 let show_new_completions_on_confirm = completion
6181 .confirm
6182 .as_ref()
6183 .is_some_and(|confirm| confirm(intent, window, cx));
6184 if show_new_completions_on_confirm {
6185 self.open_or_update_completions_menu(None, None, false, window, cx);
6186 }
6187
6188 let provider = self.completion_provider.as_ref()?;
6189
6190 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6191 let command = lsp_store.as_ref().and_then(|lsp_store| {
6192 let CompletionSource::Lsp {
6193 lsp_completion,
6194 server_id,
6195 ..
6196 } = &completion.source
6197 else {
6198 return None;
6199 };
6200 let lsp_command = lsp_completion.command.as_ref()?;
6201 let available_commands = lsp_store
6202 .read(cx)
6203 .lsp_server_capabilities
6204 .get(server_id)
6205 .and_then(|server_capabilities| {
6206 server_capabilities
6207 .execute_command_provider
6208 .as_ref()
6209 .map(|options| options.commands.as_slice())
6210 })?;
6211 if available_commands.contains(&lsp_command.command) {
6212 Some(CodeAction {
6213 server_id: *server_id,
6214 range: language::Anchor::MIN..language::Anchor::MIN,
6215 lsp_action: LspAction::Command(lsp_command.clone()),
6216 resolved: false,
6217 })
6218 } else {
6219 None
6220 }
6221 });
6222
6223 drop(completion);
6224 let apply_edits = provider.apply_additional_edits_for_completion(
6225 buffer_handle.clone(),
6226 completions_menu.completions.clone(),
6227 candidate_id,
6228 true,
6229 cx,
6230 );
6231
6232 let editor_settings = EditorSettings::get_global(cx);
6233 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6234 // After the code completion is finished, users often want to know what signatures are needed.
6235 // so we should automatically call signature_help
6236 self.show_signature_help(&ShowSignatureHelp, window, cx);
6237 }
6238
6239 Some(cx.spawn_in(window, async move |editor, cx| {
6240 apply_edits.await?;
6241
6242 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6243 let title = command.lsp_action.title().to_owned();
6244 let project_transaction = lsp_store
6245 .update(cx, |lsp_store, cx| {
6246 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6247 })?
6248 .await
6249 .context("applying post-completion command")?;
6250 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6251 Self::open_project_transaction(
6252 &editor,
6253 workspace.downgrade(),
6254 project_transaction,
6255 title,
6256 cx,
6257 )
6258 .await?;
6259 }
6260 }
6261
6262 Ok(())
6263 }))
6264 }
6265
6266 pub fn toggle_code_actions(
6267 &mut self,
6268 action: &ToggleCodeActions,
6269 window: &mut Window,
6270 cx: &mut Context<Self>,
6271 ) {
6272 let quick_launch = action.quick_launch;
6273 let mut context_menu = self.context_menu.borrow_mut();
6274 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6275 if code_actions.deployed_from == action.deployed_from {
6276 // Toggle if we're selecting the same one
6277 *context_menu = None;
6278 cx.notify();
6279 return;
6280 } else {
6281 // Otherwise, clear it and start a new one
6282 *context_menu = None;
6283 cx.notify();
6284 }
6285 }
6286 drop(context_menu);
6287 let snapshot = self.snapshot(window, cx);
6288 let deployed_from = action.deployed_from.clone();
6289 let action = action.clone();
6290 self.completion_tasks.clear();
6291 self.discard_edit_prediction(false, cx);
6292
6293 let multibuffer_point = match &action.deployed_from {
6294 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6295 DisplayPoint::new(*row, 0).to_point(&snapshot)
6296 }
6297 _ => self
6298 .selections
6299 .newest::<Point>(&snapshot.display_snapshot)
6300 .head(),
6301 };
6302 let Some((buffer, buffer_row)) = snapshot
6303 .buffer_snapshot()
6304 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6305 .and_then(|(buffer_snapshot, range)| {
6306 self.buffer()
6307 .read(cx)
6308 .buffer(buffer_snapshot.remote_id())
6309 .map(|buffer| (buffer, range.start.row))
6310 })
6311 else {
6312 return;
6313 };
6314 let buffer_id = buffer.read(cx).remote_id();
6315 let tasks = self
6316 .tasks
6317 .get(&(buffer_id, buffer_row))
6318 .map(|t| Arc::new(t.to_owned()));
6319
6320 if !self.focus_handle.is_focused(window) {
6321 return;
6322 }
6323 let project = self.project.clone();
6324
6325 let code_actions_task = match deployed_from {
6326 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6327 _ => self.code_actions(buffer_row, window, cx),
6328 };
6329
6330 let runnable_task = match deployed_from {
6331 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6332 _ => {
6333 let mut task_context_task = Task::ready(None);
6334 if let Some(tasks) = &tasks
6335 && let Some(project) = project
6336 {
6337 task_context_task =
6338 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6339 }
6340
6341 cx.spawn_in(window, {
6342 let buffer = buffer.clone();
6343 async move |editor, cx| {
6344 let task_context = task_context_task.await;
6345
6346 let resolved_tasks =
6347 tasks
6348 .zip(task_context.clone())
6349 .map(|(tasks, task_context)| ResolvedTasks {
6350 templates: tasks.resolve(&task_context).collect(),
6351 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6352 multibuffer_point.row,
6353 tasks.column,
6354 )),
6355 });
6356 let debug_scenarios = editor
6357 .update(cx, |editor, cx| {
6358 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6359 })?
6360 .await;
6361 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6362 }
6363 })
6364 }
6365 };
6366
6367 cx.spawn_in(window, async move |editor, cx| {
6368 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6369 let code_actions = code_actions_task.await;
6370 let spawn_straight_away = quick_launch
6371 && resolved_tasks
6372 .as_ref()
6373 .is_some_and(|tasks| tasks.templates.len() == 1)
6374 && code_actions
6375 .as_ref()
6376 .is_none_or(|actions| actions.is_empty())
6377 && debug_scenarios.is_empty();
6378
6379 editor.update_in(cx, |editor, window, cx| {
6380 crate::hover_popover::hide_hover(editor, cx);
6381 let actions = CodeActionContents::new(
6382 resolved_tasks,
6383 code_actions,
6384 debug_scenarios,
6385 task_context.unwrap_or_default(),
6386 );
6387
6388 // Don't show the menu if there are no actions available
6389 if actions.is_empty() {
6390 cx.notify();
6391 return Task::ready(Ok(()));
6392 }
6393
6394 *editor.context_menu.borrow_mut() =
6395 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6396 buffer,
6397 actions,
6398 selected_item: Default::default(),
6399 scroll_handle: UniformListScrollHandle::default(),
6400 deployed_from,
6401 }));
6402 cx.notify();
6403 if spawn_straight_away
6404 && let Some(task) = editor.confirm_code_action(
6405 &ConfirmCodeAction { item_ix: Some(0) },
6406 window,
6407 cx,
6408 )
6409 {
6410 return task;
6411 }
6412
6413 Task::ready(Ok(()))
6414 })
6415 })
6416 .detach_and_log_err(cx);
6417 }
6418
6419 fn debug_scenarios(
6420 &mut self,
6421 resolved_tasks: &Option<ResolvedTasks>,
6422 buffer: &Entity<Buffer>,
6423 cx: &mut App,
6424 ) -> Task<Vec<task::DebugScenario>> {
6425 maybe!({
6426 let project = self.project()?;
6427 let dap_store = project.read(cx).dap_store();
6428 let mut scenarios = vec![];
6429 let resolved_tasks = resolved_tasks.as_ref()?;
6430 let buffer = buffer.read(cx);
6431 let language = buffer.language()?;
6432 let file = buffer.file();
6433 let debug_adapter = language_settings(language.name().into(), file, cx)
6434 .debuggers
6435 .first()
6436 .map(SharedString::from)
6437 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6438
6439 dap_store.update(cx, |dap_store, cx| {
6440 for (_, task) in &resolved_tasks.templates {
6441 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6442 task.original_task().clone(),
6443 debug_adapter.clone().into(),
6444 task.display_label().to_owned().into(),
6445 cx,
6446 );
6447 scenarios.push(maybe_scenario);
6448 }
6449 });
6450 Some(cx.background_spawn(async move {
6451 futures::future::join_all(scenarios)
6452 .await
6453 .into_iter()
6454 .flatten()
6455 .collect::<Vec<_>>()
6456 }))
6457 })
6458 .unwrap_or_else(|| Task::ready(vec![]))
6459 }
6460
6461 fn code_actions(
6462 &mut self,
6463 buffer_row: u32,
6464 window: &mut Window,
6465 cx: &mut Context<Self>,
6466 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6467 let mut task = self.code_actions_task.take();
6468 cx.spawn_in(window, async move |editor, cx| {
6469 while let Some(prev_task) = task {
6470 prev_task.await.log_err();
6471 task = editor
6472 .update(cx, |this, _| this.code_actions_task.take())
6473 .ok()?;
6474 }
6475
6476 editor
6477 .update(cx, |editor, cx| {
6478 editor
6479 .available_code_actions
6480 .clone()
6481 .and_then(|(location, code_actions)| {
6482 let snapshot = location.buffer.read(cx).snapshot();
6483 let point_range = location.range.to_point(&snapshot);
6484 let point_range = point_range.start.row..=point_range.end.row;
6485 if point_range.contains(&buffer_row) {
6486 Some(code_actions)
6487 } else {
6488 None
6489 }
6490 })
6491 })
6492 .ok()
6493 .flatten()
6494 })
6495 }
6496
6497 pub fn confirm_code_action(
6498 &mut self,
6499 action: &ConfirmCodeAction,
6500 window: &mut Window,
6501 cx: &mut Context<Self>,
6502 ) -> Option<Task<Result<()>>> {
6503 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6504
6505 let actions_menu =
6506 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6507 menu
6508 } else {
6509 return None;
6510 };
6511
6512 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6513 let action = actions_menu.actions.get(action_ix)?;
6514 let title = action.label();
6515 let buffer = actions_menu.buffer;
6516 let workspace = self.workspace()?;
6517
6518 match action {
6519 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6520 workspace.update(cx, |workspace, cx| {
6521 workspace.schedule_resolved_task(
6522 task_source_kind,
6523 resolved_task,
6524 false,
6525 window,
6526 cx,
6527 );
6528
6529 Some(Task::ready(Ok(())))
6530 })
6531 }
6532 CodeActionsItem::CodeAction {
6533 excerpt_id,
6534 action,
6535 provider,
6536 } => {
6537 let apply_code_action =
6538 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6539 let workspace = workspace.downgrade();
6540 Some(cx.spawn_in(window, async move |editor, cx| {
6541 let project_transaction = apply_code_action.await?;
6542 Self::open_project_transaction(
6543 &editor,
6544 workspace,
6545 project_transaction,
6546 title,
6547 cx,
6548 )
6549 .await
6550 }))
6551 }
6552 CodeActionsItem::DebugScenario(scenario) => {
6553 let context = actions_menu.actions.context;
6554
6555 workspace.update(cx, |workspace, cx| {
6556 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6557 workspace.start_debug_session(
6558 scenario,
6559 context,
6560 Some(buffer),
6561 None,
6562 window,
6563 cx,
6564 );
6565 });
6566 Some(Task::ready(Ok(())))
6567 }
6568 }
6569 }
6570
6571 fn open_transaction_for_hidden_buffers(
6572 workspace: Entity<Workspace>,
6573 transaction: ProjectTransaction,
6574 title: String,
6575 window: &mut Window,
6576 cx: &mut Context<Self>,
6577 ) {
6578 if transaction.0.is_empty() {
6579 return;
6580 }
6581
6582 let edited_buffers_already_open = {
6583 let other_editors: Vec<Entity<Editor>> = workspace
6584 .read(cx)
6585 .panes()
6586 .iter()
6587 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6588 .filter(|editor| editor.entity_id() != cx.entity_id())
6589 .collect();
6590
6591 transaction.0.keys().all(|buffer| {
6592 other_editors.iter().any(|editor| {
6593 let multi_buffer = editor.read(cx).buffer();
6594 multi_buffer.read(cx).is_singleton()
6595 && multi_buffer
6596 .read(cx)
6597 .as_singleton()
6598 .map_or(false, |singleton| {
6599 singleton.entity_id() == buffer.entity_id()
6600 })
6601 })
6602 })
6603 };
6604 if !edited_buffers_already_open {
6605 let workspace = workspace.downgrade();
6606 cx.defer_in(window, move |_, window, cx| {
6607 cx.spawn_in(window, async move |editor, cx| {
6608 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6609 .await
6610 .ok()
6611 })
6612 .detach();
6613 });
6614 }
6615 }
6616
6617 pub async fn open_project_transaction(
6618 editor: &WeakEntity<Editor>,
6619 workspace: WeakEntity<Workspace>,
6620 transaction: ProjectTransaction,
6621 title: String,
6622 cx: &mut AsyncWindowContext,
6623 ) -> Result<()> {
6624 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6625 cx.update(|_, cx| {
6626 entries.sort_unstable_by_key(|(buffer, _)| {
6627 buffer.read(cx).file().map(|f| f.path().clone())
6628 });
6629 })?;
6630 if entries.is_empty() {
6631 return Ok(());
6632 }
6633
6634 // If the project transaction's edits are all contained within this editor, then
6635 // avoid opening a new editor to display them.
6636
6637 if let [(buffer, transaction)] = &*entries {
6638 let excerpt = editor.update(cx, |editor, cx| {
6639 editor
6640 .buffer()
6641 .read(cx)
6642 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6643 })?;
6644 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6645 && excerpted_buffer == *buffer
6646 {
6647 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6648 let excerpt_range = excerpt_range.to_offset(buffer);
6649 buffer
6650 .edited_ranges_for_transaction::<usize>(transaction)
6651 .all(|range| {
6652 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6653 })
6654 })?;
6655
6656 if all_edits_within_excerpt {
6657 return Ok(());
6658 }
6659 }
6660 }
6661
6662 let mut ranges_to_highlight = Vec::new();
6663 let excerpt_buffer = cx.new(|cx| {
6664 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6665 for (buffer_handle, transaction) in &entries {
6666 let edited_ranges = buffer_handle
6667 .read(cx)
6668 .edited_ranges_for_transaction::<Point>(transaction)
6669 .collect::<Vec<_>>();
6670 let (ranges, _) = multibuffer.set_excerpts_for_path(
6671 PathKey::for_buffer(buffer_handle, cx),
6672 buffer_handle.clone(),
6673 edited_ranges,
6674 multibuffer_context_lines(cx),
6675 cx,
6676 );
6677
6678 ranges_to_highlight.extend(ranges);
6679 }
6680 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6681 multibuffer
6682 })?;
6683
6684 workspace.update_in(cx, |workspace, window, cx| {
6685 let project = workspace.project().clone();
6686 let editor =
6687 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6688 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6689 editor.update(cx, |editor, cx| {
6690 editor.highlight_background::<Self>(
6691 &ranges_to_highlight,
6692 |_, theme| theme.colors().editor_highlighted_line_background,
6693 cx,
6694 );
6695 });
6696 })?;
6697
6698 Ok(())
6699 }
6700
6701 pub fn clear_code_action_providers(&mut self) {
6702 self.code_action_providers.clear();
6703 self.available_code_actions.take();
6704 }
6705
6706 pub fn add_code_action_provider(
6707 &mut self,
6708 provider: Rc<dyn CodeActionProvider>,
6709 window: &mut Window,
6710 cx: &mut Context<Self>,
6711 ) {
6712 if self
6713 .code_action_providers
6714 .iter()
6715 .any(|existing_provider| existing_provider.id() == provider.id())
6716 {
6717 return;
6718 }
6719
6720 self.code_action_providers.push(provider);
6721 self.refresh_code_actions(window, cx);
6722 }
6723
6724 pub fn remove_code_action_provider(
6725 &mut self,
6726 id: Arc<str>,
6727 window: &mut Window,
6728 cx: &mut Context<Self>,
6729 ) {
6730 self.code_action_providers
6731 .retain(|provider| provider.id() != id);
6732 self.refresh_code_actions(window, cx);
6733 }
6734
6735 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6736 !self.code_action_providers.is_empty()
6737 && EditorSettings::get_global(cx).toolbar.code_actions
6738 }
6739
6740 pub fn has_available_code_actions(&self) -> bool {
6741 self.available_code_actions
6742 .as_ref()
6743 .is_some_and(|(_, actions)| !actions.is_empty())
6744 }
6745
6746 fn render_inline_code_actions(
6747 &self,
6748 icon_size: ui::IconSize,
6749 display_row: DisplayRow,
6750 is_active: bool,
6751 cx: &mut Context<Self>,
6752 ) -> AnyElement {
6753 let show_tooltip = !self.context_menu_visible();
6754 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6755 .icon_size(icon_size)
6756 .shape(ui::IconButtonShape::Square)
6757 .icon_color(ui::Color::Hidden)
6758 .toggle_state(is_active)
6759 .when(show_tooltip, |this| {
6760 this.tooltip({
6761 let focus_handle = self.focus_handle.clone();
6762 move |_window, cx| {
6763 Tooltip::for_action_in(
6764 "Toggle Code Actions",
6765 &ToggleCodeActions {
6766 deployed_from: None,
6767 quick_launch: false,
6768 },
6769 &focus_handle,
6770 cx,
6771 )
6772 }
6773 })
6774 })
6775 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6776 window.focus(&editor.focus_handle(cx), cx);
6777 editor.toggle_code_actions(
6778 &crate::actions::ToggleCodeActions {
6779 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6780 display_row,
6781 )),
6782 quick_launch: false,
6783 },
6784 window,
6785 cx,
6786 );
6787 }))
6788 .into_any_element()
6789 }
6790
6791 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6792 &self.context_menu
6793 }
6794
6795 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6796 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6797 cx.background_executor()
6798 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6799 .await;
6800
6801 let (start_buffer, start, _, end, newest_selection) = this
6802 .update(cx, |this, cx| {
6803 let newest_selection = this.selections.newest_anchor().clone();
6804 if newest_selection.head().diff_base_anchor.is_some() {
6805 return None;
6806 }
6807 let display_snapshot = this.display_snapshot(cx);
6808 let newest_selection_adjusted =
6809 this.selections.newest_adjusted(&display_snapshot);
6810 let buffer = this.buffer.read(cx);
6811
6812 let (start_buffer, start) =
6813 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6814 let (end_buffer, end) =
6815 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6816
6817 Some((start_buffer, start, end_buffer, end, newest_selection))
6818 })?
6819 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6820 .context(
6821 "Expected selection to lie in a single buffer when refreshing code actions",
6822 )?;
6823 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6824 let providers = this.code_action_providers.clone();
6825 let tasks = this
6826 .code_action_providers
6827 .iter()
6828 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6829 .collect::<Vec<_>>();
6830 (providers, tasks)
6831 })?;
6832
6833 let mut actions = Vec::new();
6834 for (provider, provider_actions) in
6835 providers.into_iter().zip(future::join_all(tasks).await)
6836 {
6837 if let Some(provider_actions) = provider_actions.log_err() {
6838 actions.extend(provider_actions.into_iter().map(|action| {
6839 AvailableCodeAction {
6840 excerpt_id: newest_selection.start.excerpt_id,
6841 action,
6842 provider: provider.clone(),
6843 }
6844 }));
6845 }
6846 }
6847
6848 this.update(cx, |this, cx| {
6849 this.available_code_actions = if actions.is_empty() {
6850 None
6851 } else {
6852 Some((
6853 Location {
6854 buffer: start_buffer,
6855 range: start..end,
6856 },
6857 actions.into(),
6858 ))
6859 };
6860 cx.notify();
6861 })
6862 }));
6863 }
6864
6865 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6866 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6867 self.show_git_blame_inline = false;
6868
6869 self.show_git_blame_inline_delay_task =
6870 Some(cx.spawn_in(window, async move |this, cx| {
6871 cx.background_executor().timer(delay).await;
6872
6873 this.update(cx, |this, cx| {
6874 this.show_git_blame_inline = true;
6875 cx.notify();
6876 })
6877 .log_err();
6878 }));
6879 }
6880 }
6881
6882 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6883 let snapshot = self.snapshot(window, cx);
6884 let cursor = self
6885 .selections
6886 .newest::<Point>(&snapshot.display_snapshot)
6887 .head();
6888 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6889 else {
6890 return;
6891 };
6892
6893 if self.blame.is_none() {
6894 self.start_git_blame(true, window, cx);
6895 }
6896 let Some(blame) = self.blame.as_ref() else {
6897 return;
6898 };
6899
6900 let row_info = RowInfo {
6901 buffer_id: Some(buffer.remote_id()),
6902 buffer_row: Some(point.row),
6903 ..Default::default()
6904 };
6905 let Some((buffer, blame_entry)) = blame
6906 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6907 .flatten()
6908 else {
6909 return;
6910 };
6911
6912 let anchor = self.selections.newest_anchor().head();
6913 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6914 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6915 self.show_blame_popover(
6916 buffer,
6917 &blame_entry,
6918 position + last_bounds.origin,
6919 true,
6920 cx,
6921 );
6922 };
6923 }
6924
6925 fn show_blame_popover(
6926 &mut self,
6927 buffer: BufferId,
6928 blame_entry: &BlameEntry,
6929 position: gpui::Point<Pixels>,
6930 ignore_timeout: bool,
6931 cx: &mut Context<Self>,
6932 ) {
6933 if let Some(state) = &mut self.inline_blame_popover {
6934 state.hide_task.take();
6935 } else {
6936 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6937 let blame_entry = blame_entry.clone();
6938 let show_task = cx.spawn(async move |editor, cx| {
6939 if !ignore_timeout {
6940 cx.background_executor()
6941 .timer(std::time::Duration::from_millis(blame_popover_delay))
6942 .await;
6943 }
6944 editor
6945 .update(cx, |editor, cx| {
6946 editor.inline_blame_popover_show_task.take();
6947 let Some(blame) = editor.blame.as_ref() else {
6948 return;
6949 };
6950 let blame = blame.read(cx);
6951 let details = blame.details_for_entry(buffer, &blame_entry);
6952 let markdown = cx.new(|cx| {
6953 Markdown::new(
6954 details
6955 .as_ref()
6956 .map(|message| message.message.clone())
6957 .unwrap_or_default(),
6958 None,
6959 None,
6960 cx,
6961 )
6962 });
6963 editor.inline_blame_popover = Some(InlineBlamePopover {
6964 position,
6965 hide_task: None,
6966 popover_bounds: None,
6967 popover_state: InlineBlamePopoverState {
6968 scroll_handle: ScrollHandle::new(),
6969 commit_message: details,
6970 markdown,
6971 },
6972 keyboard_grace: ignore_timeout,
6973 });
6974 cx.notify();
6975 })
6976 .ok();
6977 });
6978 self.inline_blame_popover_show_task = Some(show_task);
6979 }
6980 }
6981
6982 pub fn has_mouse_context_menu(&self) -> bool {
6983 self.mouse_context_menu.is_some()
6984 }
6985
6986 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6987 self.inline_blame_popover_show_task.take();
6988 if let Some(state) = &mut self.inline_blame_popover {
6989 let hide_task = cx.spawn(async move |editor, cx| {
6990 if !ignore_timeout {
6991 cx.background_executor()
6992 .timer(std::time::Duration::from_millis(100))
6993 .await;
6994 }
6995 editor
6996 .update(cx, |editor, cx| {
6997 editor.inline_blame_popover.take();
6998 cx.notify();
6999 })
7000 .ok();
7001 });
7002 state.hide_task = Some(hide_task);
7003 true
7004 } else {
7005 false
7006 }
7007 }
7008
7009 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7010 if self.pending_rename.is_some() {
7011 return None;
7012 }
7013
7014 let provider = self.semantics_provider.clone()?;
7015 let buffer = self.buffer.read(cx);
7016 let newest_selection = self.selections.newest_anchor().clone();
7017 let cursor_position = newest_selection.head();
7018 let (cursor_buffer, cursor_buffer_position) =
7019 buffer.text_anchor_for_position(cursor_position, cx)?;
7020 let (tail_buffer, tail_buffer_position) =
7021 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7022 if cursor_buffer != tail_buffer {
7023 return None;
7024 }
7025
7026 let snapshot = cursor_buffer.read(cx).snapshot();
7027 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7028 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7029 if start_word_range != end_word_range {
7030 self.document_highlights_task.take();
7031 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7032 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7033 return None;
7034 }
7035
7036 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7037 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7038 cx.background_executor()
7039 .timer(Duration::from_millis(debounce))
7040 .await;
7041
7042 let highlights = if let Some(highlights) = cx
7043 .update(|cx| {
7044 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7045 })
7046 .ok()
7047 .flatten()
7048 {
7049 highlights.await.log_err()
7050 } else {
7051 None
7052 };
7053
7054 if let Some(highlights) = highlights {
7055 this.update(cx, |this, cx| {
7056 if this.pending_rename.is_some() {
7057 return;
7058 }
7059
7060 let buffer = this.buffer.read(cx);
7061 if buffer
7062 .text_anchor_for_position(cursor_position, cx)
7063 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7064 {
7065 return;
7066 }
7067
7068 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7069 let mut write_ranges = Vec::new();
7070 let mut read_ranges = Vec::new();
7071 for highlight in highlights {
7072 let buffer_id = cursor_buffer.read(cx).remote_id();
7073 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7074 {
7075 let start = highlight
7076 .range
7077 .start
7078 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7079 let end = highlight
7080 .range
7081 .end
7082 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7083 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7084 continue;
7085 }
7086
7087 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7088 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7089 write_ranges.push(range);
7090 } else {
7091 read_ranges.push(range);
7092 }
7093 }
7094 }
7095
7096 this.highlight_background::<DocumentHighlightRead>(
7097 &read_ranges,
7098 |_, theme| theme.colors().editor_document_highlight_read_background,
7099 cx,
7100 );
7101 this.highlight_background::<DocumentHighlightWrite>(
7102 &write_ranges,
7103 |_, theme| theme.colors().editor_document_highlight_write_background,
7104 cx,
7105 );
7106 cx.notify();
7107 })
7108 .log_err();
7109 }
7110 }));
7111 None
7112 }
7113
7114 fn prepare_highlight_query_from_selection(
7115 &mut self,
7116 window: &Window,
7117 cx: &mut Context<Editor>,
7118 ) -> Option<(String, Range<Anchor>)> {
7119 if matches!(self.mode, EditorMode::SingleLine) {
7120 return None;
7121 }
7122 if !EditorSettings::get_global(cx).selection_highlight {
7123 return None;
7124 }
7125 if self.selections.count() != 1 || self.selections.line_mode() {
7126 return None;
7127 }
7128 let snapshot = self.snapshot(window, cx);
7129 let selection = self.selections.newest::<Point>(&snapshot);
7130 // If the selection spans multiple rows OR it is empty
7131 if selection.start.row != selection.end.row
7132 || selection.start.column == selection.end.column
7133 {
7134 return None;
7135 }
7136 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7137 let query = snapshot
7138 .buffer_snapshot()
7139 .text_for_range(selection_anchor_range.clone())
7140 .collect::<String>();
7141 if query.trim().is_empty() {
7142 return None;
7143 }
7144 Some((query, selection_anchor_range))
7145 }
7146
7147 #[ztracing::instrument(skip_all)]
7148 fn update_selection_occurrence_highlights(
7149 &mut self,
7150 query_text: String,
7151 query_range: Range<Anchor>,
7152 multi_buffer_range_to_query: Range<Point>,
7153 use_debounce: bool,
7154 window: &mut Window,
7155 cx: &mut Context<Editor>,
7156 ) -> Task<()> {
7157 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7158 cx.spawn_in(window, async move |editor, cx| {
7159 if use_debounce {
7160 cx.background_executor()
7161 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7162 .await;
7163 }
7164 let match_task = cx.background_spawn(async move {
7165 let buffer_ranges = multi_buffer_snapshot
7166 .range_to_buffer_ranges(multi_buffer_range_to_query)
7167 .into_iter()
7168 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7169 let mut match_ranges = Vec::new();
7170 let Ok(regex) = project::search::SearchQuery::text(
7171 query_text.clone(),
7172 false,
7173 false,
7174 false,
7175 Default::default(),
7176 Default::default(),
7177 false,
7178 None,
7179 ) else {
7180 return Vec::default();
7181 };
7182 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7183 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7184 match_ranges.extend(
7185 regex
7186 .search(
7187 buffer_snapshot,
7188 Some(search_range.start.0..search_range.end.0),
7189 )
7190 .await
7191 .into_iter()
7192 .filter_map(|match_range| {
7193 let match_start = buffer_snapshot
7194 .anchor_after(search_range.start + match_range.start);
7195 let match_end = buffer_snapshot
7196 .anchor_before(search_range.start + match_range.end);
7197 let match_anchor_range =
7198 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7199 (match_anchor_range != query_range).then_some(match_anchor_range)
7200 }),
7201 );
7202 }
7203 match_ranges
7204 });
7205 let match_ranges = match_task.await;
7206 editor
7207 .update_in(cx, |editor, _, cx| {
7208 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7209 if !match_ranges.is_empty() {
7210 editor.highlight_background::<SelectedTextHighlight>(
7211 &match_ranges,
7212 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7213 cx,
7214 )
7215 }
7216 })
7217 .log_err();
7218 })
7219 }
7220
7221 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7222 struct NewlineFold;
7223 let type_id = std::any::TypeId::of::<NewlineFold>();
7224 if !self.mode.is_single_line() {
7225 return;
7226 }
7227 let snapshot = self.snapshot(window, cx);
7228 if snapshot.buffer_snapshot().max_point().row == 0 {
7229 return;
7230 }
7231 let task = cx.background_spawn(async move {
7232 let new_newlines = snapshot
7233 .buffer_chars_at(MultiBufferOffset(0))
7234 .filter_map(|(c, i)| {
7235 if c == '\n' {
7236 Some(
7237 snapshot.buffer_snapshot().anchor_after(i)
7238 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7239 )
7240 } else {
7241 None
7242 }
7243 })
7244 .collect::<Vec<_>>();
7245 let existing_newlines = snapshot
7246 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7247 .filter_map(|fold| {
7248 if fold.placeholder.type_tag == Some(type_id) {
7249 Some(fold.range.start..fold.range.end)
7250 } else {
7251 None
7252 }
7253 })
7254 .collect::<Vec<_>>();
7255
7256 (new_newlines, existing_newlines)
7257 });
7258 self.folding_newlines = cx.spawn(async move |this, cx| {
7259 let (new_newlines, existing_newlines) = task.await;
7260 if new_newlines == existing_newlines {
7261 return;
7262 }
7263 let placeholder = FoldPlaceholder {
7264 render: Arc::new(move |_, _, cx| {
7265 div()
7266 .bg(cx.theme().status().hint_background)
7267 .border_b_1()
7268 .size_full()
7269 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7270 .border_color(cx.theme().status().hint)
7271 .child("\\n")
7272 .into_any()
7273 }),
7274 constrain_width: false,
7275 merge_adjacent: false,
7276 type_tag: Some(type_id),
7277 };
7278 let creases = new_newlines
7279 .into_iter()
7280 .map(|range| Crease::simple(range, placeholder.clone()))
7281 .collect();
7282 this.update(cx, |this, cx| {
7283 this.display_map.update(cx, |display_map, cx| {
7284 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7285 display_map.fold(creases, cx);
7286 });
7287 })
7288 .ok();
7289 });
7290 }
7291
7292 #[ztracing::instrument(skip_all)]
7293 fn refresh_selected_text_highlights(
7294 &mut self,
7295 on_buffer_edit: bool,
7296 window: &mut Window,
7297 cx: &mut Context<Editor>,
7298 ) {
7299 let Some((query_text, query_range)) =
7300 self.prepare_highlight_query_from_selection(window, cx)
7301 else {
7302 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7303 self.quick_selection_highlight_task.take();
7304 self.debounced_selection_highlight_task.take();
7305 return;
7306 };
7307 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7308 if on_buffer_edit
7309 || self
7310 .quick_selection_highlight_task
7311 .as_ref()
7312 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7313 {
7314 let multi_buffer_visible_start = self
7315 .scroll_manager
7316 .anchor()
7317 .anchor
7318 .to_point(&multi_buffer_snapshot);
7319 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7320 multi_buffer_visible_start
7321 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7322 Bias::Left,
7323 );
7324 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7325 self.quick_selection_highlight_task = Some((
7326 query_range.clone(),
7327 self.update_selection_occurrence_highlights(
7328 query_text.clone(),
7329 query_range.clone(),
7330 multi_buffer_visible_range,
7331 false,
7332 window,
7333 cx,
7334 ),
7335 ));
7336 }
7337 if on_buffer_edit
7338 || self
7339 .debounced_selection_highlight_task
7340 .as_ref()
7341 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7342 {
7343 let multi_buffer_start = multi_buffer_snapshot
7344 .anchor_before(MultiBufferOffset(0))
7345 .to_point(&multi_buffer_snapshot);
7346 let multi_buffer_end = multi_buffer_snapshot
7347 .anchor_after(multi_buffer_snapshot.len())
7348 .to_point(&multi_buffer_snapshot);
7349 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7350 self.debounced_selection_highlight_task = Some((
7351 query_range.clone(),
7352 self.update_selection_occurrence_highlights(
7353 query_text,
7354 query_range,
7355 multi_buffer_full_range,
7356 true,
7357 window,
7358 cx,
7359 ),
7360 ));
7361 }
7362 }
7363
7364 pub fn refresh_edit_prediction(
7365 &mut self,
7366 debounce: bool,
7367 user_requested: bool,
7368 window: &mut Window,
7369 cx: &mut Context<Self>,
7370 ) -> Option<()> {
7371 if DisableAiSettings::get_global(cx).disable_ai {
7372 return None;
7373 }
7374
7375 let provider = self.edit_prediction_provider()?;
7376 let cursor = self.selections.newest_anchor().head();
7377 let (buffer, cursor_buffer_position) =
7378 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7379
7380 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7381 self.discard_edit_prediction(false, cx);
7382 return None;
7383 }
7384
7385 self.update_visible_edit_prediction(window, cx);
7386
7387 if !user_requested
7388 && (!self.should_show_edit_predictions()
7389 || !self.is_focused(window)
7390 || buffer.read(cx).is_empty())
7391 {
7392 self.discard_edit_prediction(false, cx);
7393 return None;
7394 }
7395
7396 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7397 Some(())
7398 }
7399
7400 fn show_edit_predictions_in_menu(&self) -> bool {
7401 match self.edit_prediction_settings {
7402 EditPredictionSettings::Disabled => false,
7403 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7404 }
7405 }
7406
7407 pub fn edit_predictions_enabled(&self) -> bool {
7408 match self.edit_prediction_settings {
7409 EditPredictionSettings::Disabled => false,
7410 EditPredictionSettings::Enabled { .. } => true,
7411 }
7412 }
7413
7414 fn edit_prediction_requires_modifier(&self) -> bool {
7415 match self.edit_prediction_settings {
7416 EditPredictionSettings::Disabled => false,
7417 EditPredictionSettings::Enabled {
7418 preview_requires_modifier,
7419 ..
7420 } => preview_requires_modifier,
7421 }
7422 }
7423
7424 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7425 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7426 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7427 self.discard_edit_prediction(false, cx);
7428 } else {
7429 let selection = self.selections.newest_anchor();
7430 let cursor = selection.head();
7431
7432 if let Some((buffer, cursor_buffer_position)) =
7433 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7434 {
7435 self.edit_prediction_settings =
7436 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7437 }
7438 }
7439 }
7440
7441 fn edit_prediction_settings_at_position(
7442 &self,
7443 buffer: &Entity<Buffer>,
7444 buffer_position: language::Anchor,
7445 cx: &App,
7446 ) -> EditPredictionSettings {
7447 if !self.mode.is_full()
7448 || !self.show_edit_predictions_override.unwrap_or(true)
7449 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7450 {
7451 return EditPredictionSettings::Disabled;
7452 }
7453
7454 let buffer = buffer.read(cx);
7455
7456 let file = buffer.file();
7457
7458 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7459 return EditPredictionSettings::Disabled;
7460 };
7461
7462 let by_provider = matches!(
7463 self.menu_edit_predictions_policy,
7464 MenuEditPredictionsPolicy::ByProvider
7465 );
7466
7467 let show_in_menu = by_provider
7468 && self
7469 .edit_prediction_provider
7470 .as_ref()
7471 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7472
7473 let preview_requires_modifier =
7474 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7475
7476 EditPredictionSettings::Enabled {
7477 show_in_menu,
7478 preview_requires_modifier,
7479 }
7480 }
7481
7482 fn should_show_edit_predictions(&self) -> bool {
7483 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7484 }
7485
7486 pub fn edit_prediction_preview_is_active(&self) -> bool {
7487 matches!(
7488 self.edit_prediction_preview,
7489 EditPredictionPreview::Active { .. }
7490 )
7491 }
7492
7493 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7494 let cursor = self.selections.newest_anchor().head();
7495 if let Some((buffer, cursor_position)) =
7496 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7497 {
7498 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7499 } else {
7500 false
7501 }
7502 }
7503
7504 pub fn supports_minimap(&self, cx: &App) -> bool {
7505 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7506 }
7507
7508 fn edit_predictions_enabled_in_buffer(
7509 &self,
7510 buffer: &Entity<Buffer>,
7511 buffer_position: language::Anchor,
7512 cx: &App,
7513 ) -> bool {
7514 maybe!({
7515 if self.read_only(cx) {
7516 return Some(false);
7517 }
7518 let provider = self.edit_prediction_provider()?;
7519 if !provider.is_enabled(buffer, buffer_position, cx) {
7520 return Some(false);
7521 }
7522 let buffer = buffer.read(cx);
7523 let Some(file) = buffer.file() else {
7524 return Some(true);
7525 };
7526 let settings = all_language_settings(Some(file), cx);
7527 Some(settings.edit_predictions_enabled_for_file(file, cx))
7528 })
7529 .unwrap_or(false)
7530 }
7531
7532 pub fn show_edit_prediction(
7533 &mut self,
7534 _: &ShowEditPrediction,
7535 window: &mut Window,
7536 cx: &mut Context<Self>,
7537 ) {
7538 if !self.has_active_edit_prediction() {
7539 self.refresh_edit_prediction(false, true, window, cx);
7540 return;
7541 }
7542
7543 self.update_visible_edit_prediction(window, cx);
7544 }
7545
7546 pub fn display_cursor_names(
7547 &mut self,
7548 _: &DisplayCursorNames,
7549 window: &mut Window,
7550 cx: &mut Context<Self>,
7551 ) {
7552 self.show_cursor_names(window, cx);
7553 }
7554
7555 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7556 self.show_cursor_names = true;
7557 cx.notify();
7558 cx.spawn_in(window, async move |this, cx| {
7559 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7560 this.update(cx, |this, cx| {
7561 this.show_cursor_names = false;
7562 cx.notify()
7563 })
7564 .ok()
7565 })
7566 .detach();
7567 }
7568
7569 pub fn accept_partial_edit_prediction(
7570 &mut self,
7571 granularity: EditPredictionGranularity,
7572 window: &mut Window,
7573 cx: &mut Context<Self>,
7574 ) {
7575 if self.show_edit_predictions_in_menu() {
7576 self.hide_context_menu(window, cx);
7577 }
7578
7579 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7580 return;
7581 };
7582
7583 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7584 return;
7585 }
7586
7587 match &active_edit_prediction.completion {
7588 EditPrediction::MoveWithin { target, .. } => {
7589 let target = *target;
7590
7591 if matches!(granularity, EditPredictionGranularity::Full) {
7592 if let Some(position_map) = &self.last_position_map {
7593 let target_row = target.to_display_point(&position_map.snapshot).row();
7594 let is_visible = position_map.visible_row_range.contains(&target_row);
7595
7596 if is_visible || !self.edit_prediction_requires_modifier() {
7597 self.unfold_ranges(&[target..target], true, false, cx);
7598 self.change_selections(
7599 SelectionEffects::scroll(Autoscroll::newest()),
7600 window,
7601 cx,
7602 |selections| {
7603 selections.select_anchor_ranges([target..target]);
7604 },
7605 );
7606 self.clear_row_highlights::<EditPredictionPreview>();
7607 self.edit_prediction_preview
7608 .set_previous_scroll_position(None);
7609 } else {
7610 // Highlight and request scroll
7611 self.edit_prediction_preview
7612 .set_previous_scroll_position(Some(
7613 position_map.snapshot.scroll_anchor,
7614 ));
7615 self.highlight_rows::<EditPredictionPreview>(
7616 target..target,
7617 cx.theme().colors().editor_highlighted_line_background,
7618 RowHighlightOptions {
7619 autoscroll: true,
7620 ..Default::default()
7621 },
7622 cx,
7623 );
7624 self.request_autoscroll(Autoscroll::fit(), cx);
7625 }
7626 }
7627 } else {
7628 self.change_selections(
7629 SelectionEffects::scroll(Autoscroll::newest()),
7630 window,
7631 cx,
7632 |selections| {
7633 selections.select_anchor_ranges([target..target]);
7634 },
7635 );
7636 }
7637 }
7638 EditPrediction::MoveOutside { snapshot, target } => {
7639 if let Some(workspace) = self.workspace() {
7640 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7641 .detach_and_log_err(cx);
7642 }
7643 }
7644 EditPrediction::Edit { edits, .. } => {
7645 self.report_edit_prediction_event(
7646 active_edit_prediction.completion_id.clone(),
7647 true,
7648 cx,
7649 );
7650
7651 match granularity {
7652 EditPredictionGranularity::Full => {
7653 if let Some(provider) = self.edit_prediction_provider() {
7654 provider.accept(cx);
7655 }
7656
7657 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7658 let snapshot = self.buffer.read(cx).snapshot(cx);
7659 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7660
7661 self.buffer.update(cx, |buffer, cx| {
7662 buffer.edit(edits.iter().cloned(), None, cx)
7663 });
7664
7665 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7666 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7667 });
7668
7669 let selections = self.selections.disjoint_anchors_arc();
7670 if let Some(transaction_id_now) =
7671 self.buffer.read(cx).last_transaction_id(cx)
7672 {
7673 if transaction_id_prev != Some(transaction_id_now) {
7674 self.selection_history
7675 .insert_transaction(transaction_id_now, selections);
7676 }
7677 }
7678
7679 self.update_visible_edit_prediction(window, cx);
7680 if self.active_edit_prediction.is_none() {
7681 self.refresh_edit_prediction(true, true, window, cx);
7682 }
7683 cx.notify();
7684 }
7685 _ => {
7686 let snapshot = self.buffer.read(cx).snapshot(cx);
7687 let cursor_offset = self
7688 .selections
7689 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7690 .head();
7691
7692 let insertion = edits.iter().find_map(|(range, text)| {
7693 let range = range.to_offset(&snapshot);
7694 if range.is_empty() && range.start == cursor_offset {
7695 Some(text)
7696 } else {
7697 None
7698 }
7699 });
7700
7701 if let Some(text) = insertion {
7702 let text_to_insert = match granularity {
7703 EditPredictionGranularity::Word => {
7704 let mut partial = text
7705 .chars()
7706 .by_ref()
7707 .take_while(|c| c.is_alphabetic())
7708 .collect::<String>();
7709 if partial.is_empty() {
7710 partial = text
7711 .chars()
7712 .by_ref()
7713 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7714 .collect::<String>();
7715 }
7716 partial
7717 }
7718 EditPredictionGranularity::Line => {
7719 if let Some(line) = text.split_inclusive('\n').next() {
7720 line.to_string()
7721 } else {
7722 text.to_string()
7723 }
7724 }
7725 EditPredictionGranularity::Full => unreachable!(),
7726 };
7727
7728 cx.emit(EditorEvent::InputHandled {
7729 utf16_range_to_replace: None,
7730 text: text_to_insert.clone().into(),
7731 });
7732
7733 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7734 self.refresh_edit_prediction(true, true, window, cx);
7735 cx.notify();
7736 } else {
7737 self.accept_partial_edit_prediction(
7738 EditPredictionGranularity::Full,
7739 window,
7740 cx,
7741 );
7742 }
7743 }
7744 }
7745 }
7746 }
7747
7748 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7749 }
7750
7751 pub fn accept_next_word_edit_prediction(
7752 &mut self,
7753 _: &AcceptNextWordEditPrediction,
7754 window: &mut Window,
7755 cx: &mut Context<Self>,
7756 ) {
7757 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7758 }
7759
7760 pub fn accept_next_line_edit_prediction(
7761 &mut self,
7762 _: &AcceptNextLineEditPrediction,
7763 window: &mut Window,
7764 cx: &mut Context<Self>,
7765 ) {
7766 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7767 }
7768
7769 pub fn accept_edit_prediction(
7770 &mut self,
7771 _: &AcceptEditPrediction,
7772 window: &mut Window,
7773 cx: &mut Context<Self>,
7774 ) {
7775 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7776 }
7777
7778 fn discard_edit_prediction(
7779 &mut self,
7780 should_report_edit_prediction_event: bool,
7781 cx: &mut Context<Self>,
7782 ) -> bool {
7783 if should_report_edit_prediction_event {
7784 let completion_id = self
7785 .active_edit_prediction
7786 .as_ref()
7787 .and_then(|active_completion| active_completion.completion_id.clone());
7788
7789 self.report_edit_prediction_event(completion_id, false, cx);
7790 }
7791
7792 if let Some(provider) = self.edit_prediction_provider() {
7793 provider.discard(cx);
7794 }
7795
7796 self.take_active_edit_prediction(cx)
7797 }
7798
7799 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7800 let Some(provider) = self.edit_prediction_provider() else {
7801 return;
7802 };
7803
7804 let Some((_, buffer, _)) = self
7805 .buffer
7806 .read(cx)
7807 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7808 else {
7809 return;
7810 };
7811
7812 let extension = buffer
7813 .read(cx)
7814 .file()
7815 .and_then(|file| Some(file.path().extension()?.to_string()));
7816
7817 let event_type = match accepted {
7818 true => "Edit Prediction Accepted",
7819 false => "Edit Prediction Discarded",
7820 };
7821 telemetry::event!(
7822 event_type,
7823 provider = provider.name(),
7824 prediction_id = id,
7825 suggestion_accepted = accepted,
7826 file_extension = extension,
7827 );
7828 }
7829
7830 fn open_editor_at_anchor(
7831 snapshot: &language::BufferSnapshot,
7832 target: language::Anchor,
7833 workspace: &Entity<Workspace>,
7834 window: &mut Window,
7835 cx: &mut App,
7836 ) -> Task<Result<()>> {
7837 workspace.update(cx, |workspace, cx| {
7838 let path = snapshot.file().map(|file| file.full_path(cx));
7839 let Some(path) =
7840 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7841 else {
7842 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7843 };
7844 let target = text::ToPoint::to_point(&target, snapshot);
7845 let item = workspace.open_path(path, None, true, window, cx);
7846 window.spawn(cx, async move |cx| {
7847 let Some(editor) = item.await?.downcast::<Editor>() else {
7848 return Ok(());
7849 };
7850 editor
7851 .update_in(cx, |editor, window, cx| {
7852 editor.go_to_singleton_buffer_point(target, window, cx);
7853 })
7854 .ok();
7855 anyhow::Ok(())
7856 })
7857 })
7858 }
7859
7860 pub fn has_active_edit_prediction(&self) -> bool {
7861 self.active_edit_prediction.is_some()
7862 }
7863
7864 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7865 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7866 return false;
7867 };
7868
7869 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7870 self.clear_highlights::<EditPredictionHighlight>(cx);
7871 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7872 true
7873 }
7874
7875 /// Returns true when we're displaying the edit prediction popover below the cursor
7876 /// like we are not previewing and the LSP autocomplete menu is visible
7877 /// or we are in `when_holding_modifier` mode.
7878 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7879 if self.edit_prediction_preview_is_active()
7880 || !self.show_edit_predictions_in_menu()
7881 || !self.edit_predictions_enabled()
7882 {
7883 return false;
7884 }
7885
7886 if self.has_visible_completions_menu() {
7887 return true;
7888 }
7889
7890 has_completion && self.edit_prediction_requires_modifier()
7891 }
7892
7893 fn handle_modifiers_changed(
7894 &mut self,
7895 modifiers: Modifiers,
7896 position_map: &PositionMap,
7897 window: &mut Window,
7898 cx: &mut Context<Self>,
7899 ) {
7900 // Ensure that the edit prediction preview is updated, even when not
7901 // enabled, if there's an active edit prediction preview.
7902 if self.show_edit_predictions_in_menu()
7903 || matches!(
7904 self.edit_prediction_preview,
7905 EditPredictionPreview::Active { .. }
7906 )
7907 {
7908 self.update_edit_prediction_preview(&modifiers, window, cx);
7909 }
7910
7911 self.update_selection_mode(&modifiers, position_map, window, cx);
7912
7913 let mouse_position = window.mouse_position();
7914 if !position_map.text_hitbox.is_hovered(window) {
7915 return;
7916 }
7917
7918 self.update_hovered_link(
7919 position_map.point_for_position(mouse_position),
7920 &position_map.snapshot,
7921 modifiers,
7922 window,
7923 cx,
7924 )
7925 }
7926
7927 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7928 match EditorSettings::get_global(cx).multi_cursor_modifier {
7929 MultiCursorModifier::Alt => modifiers.secondary(),
7930 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7931 }
7932 }
7933
7934 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7935 match EditorSettings::get_global(cx).multi_cursor_modifier {
7936 MultiCursorModifier::Alt => modifiers.alt,
7937 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7938 }
7939 }
7940
7941 fn columnar_selection_mode(
7942 modifiers: &Modifiers,
7943 cx: &mut Context<Self>,
7944 ) -> Option<ColumnarMode> {
7945 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7946 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7947 Some(ColumnarMode::FromMouse)
7948 } else if Self::is_alt_pressed(modifiers, cx) {
7949 Some(ColumnarMode::FromSelection)
7950 } else {
7951 None
7952 }
7953 } else {
7954 None
7955 }
7956 }
7957
7958 fn update_selection_mode(
7959 &mut self,
7960 modifiers: &Modifiers,
7961 position_map: &PositionMap,
7962 window: &mut Window,
7963 cx: &mut Context<Self>,
7964 ) {
7965 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7966 return;
7967 };
7968 if self.selections.pending_anchor().is_none() {
7969 return;
7970 }
7971
7972 let mouse_position = window.mouse_position();
7973 let point_for_position = position_map.point_for_position(mouse_position);
7974 let position = point_for_position.previous_valid;
7975
7976 self.select(
7977 SelectPhase::BeginColumnar {
7978 position,
7979 reset: false,
7980 mode,
7981 goal_column: point_for_position.exact_unclipped.column(),
7982 },
7983 window,
7984 cx,
7985 );
7986 }
7987
7988 fn update_edit_prediction_preview(
7989 &mut self,
7990 modifiers: &Modifiers,
7991 window: &mut Window,
7992 cx: &mut Context<Self>,
7993 ) {
7994 let mut modifiers_held = false;
7995
7996 // Check bindings for all granularities.
7997 // If the user holds the key for Word, Line, or Full, we want to show the preview.
7998 let granularities = [
7999 EditPredictionGranularity::Full,
8000 EditPredictionGranularity::Line,
8001 EditPredictionGranularity::Word,
8002 ];
8003
8004 for granularity in granularities {
8005 if let Some(keystroke) = self
8006 .accept_edit_prediction_keybind(granularity, window, cx)
8007 .keystroke()
8008 {
8009 modifiers_held = modifiers_held
8010 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8011 }
8012 }
8013
8014 if modifiers_held {
8015 if matches!(
8016 self.edit_prediction_preview,
8017 EditPredictionPreview::Inactive { .. }
8018 ) {
8019 if let Some(provider) = self.edit_prediction_provider.as_ref() {
8020 provider.provider.did_show(cx)
8021 }
8022
8023 self.edit_prediction_preview = EditPredictionPreview::Active {
8024 previous_scroll_position: None,
8025 since: Instant::now(),
8026 };
8027
8028 self.update_visible_edit_prediction(window, cx);
8029 cx.notify();
8030 }
8031 } else if let EditPredictionPreview::Active {
8032 previous_scroll_position,
8033 since,
8034 } = self.edit_prediction_preview
8035 {
8036 if let (Some(previous_scroll_position), Some(position_map)) =
8037 (previous_scroll_position, self.last_position_map.as_ref())
8038 {
8039 self.set_scroll_position(
8040 previous_scroll_position
8041 .scroll_position(&position_map.snapshot.display_snapshot),
8042 window,
8043 cx,
8044 );
8045 }
8046
8047 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8048 released_too_fast: since.elapsed() < Duration::from_millis(200),
8049 };
8050 self.clear_row_highlights::<EditPredictionPreview>();
8051 self.update_visible_edit_prediction(window, cx);
8052 cx.notify();
8053 }
8054 }
8055
8056 fn update_visible_edit_prediction(
8057 &mut self,
8058 _window: &mut Window,
8059 cx: &mut Context<Self>,
8060 ) -> Option<()> {
8061 if DisableAiSettings::get_global(cx).disable_ai {
8062 return None;
8063 }
8064
8065 if self.ime_transaction.is_some() {
8066 self.discard_edit_prediction(false, cx);
8067 return None;
8068 }
8069
8070 let selection = self.selections.newest_anchor();
8071 let cursor = selection.head();
8072 let multibuffer = self.buffer.read(cx).snapshot(cx);
8073 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8074 let excerpt_id = cursor.excerpt_id;
8075
8076 let show_in_menu = self.show_edit_predictions_in_menu();
8077 let completions_menu_has_precedence = !show_in_menu
8078 && (self.context_menu.borrow().is_some()
8079 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8080
8081 if completions_menu_has_precedence
8082 || !offset_selection.is_empty()
8083 || self
8084 .active_edit_prediction
8085 .as_ref()
8086 .is_some_and(|completion| {
8087 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8088 return false;
8089 };
8090 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8091 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8092 !invalidation_range.contains(&offset_selection.head())
8093 })
8094 {
8095 self.discard_edit_prediction(false, cx);
8096 return None;
8097 }
8098
8099 self.take_active_edit_prediction(cx);
8100 let Some(provider) = self.edit_prediction_provider() else {
8101 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8102 return None;
8103 };
8104
8105 let (buffer, cursor_buffer_position) =
8106 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8107
8108 self.edit_prediction_settings =
8109 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8110
8111 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8112
8113 if self.edit_prediction_indent_conflict {
8114 let cursor_point = cursor.to_point(&multibuffer);
8115 let mut suggested_indent = None;
8116 multibuffer.suggested_indents_callback(
8117 cursor_point.row..cursor_point.row + 1,
8118 |_, indent| {
8119 suggested_indent = Some(indent);
8120 ControlFlow::Break(())
8121 },
8122 cx,
8123 );
8124
8125 if let Some(indent) = suggested_indent
8126 && indent.len == cursor_point.column
8127 {
8128 self.edit_prediction_indent_conflict = false;
8129 }
8130 }
8131
8132 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8133
8134 let (completion_id, edits, edit_preview) = match edit_prediction {
8135 edit_prediction_types::EditPrediction::Local {
8136 id,
8137 edits,
8138 edit_preview,
8139 } => (id, edits, edit_preview),
8140 edit_prediction_types::EditPrediction::Jump {
8141 id,
8142 snapshot,
8143 target,
8144 } => {
8145 self.stale_edit_prediction_in_menu = None;
8146 self.active_edit_prediction = Some(EditPredictionState {
8147 inlay_ids: vec![],
8148 completion: EditPrediction::MoveOutside { snapshot, target },
8149 completion_id: id,
8150 invalidation_range: None,
8151 });
8152 cx.notify();
8153 return Some(());
8154 }
8155 };
8156
8157 let edits = edits
8158 .into_iter()
8159 .flat_map(|(range, new_text)| {
8160 Some((
8161 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8162 new_text,
8163 ))
8164 })
8165 .collect::<Vec<_>>();
8166 if edits.is_empty() {
8167 return None;
8168 }
8169
8170 let first_edit_start = edits.first().unwrap().0.start;
8171 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8172 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8173
8174 let last_edit_end = edits.last().unwrap().0.end;
8175 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8176 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8177
8178 let cursor_row = cursor.to_point(&multibuffer).row;
8179
8180 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8181
8182 let mut inlay_ids = Vec::new();
8183 let invalidation_row_range;
8184 let move_invalidation_row_range = if cursor_row < edit_start_row {
8185 Some(cursor_row..edit_end_row)
8186 } else if cursor_row > edit_end_row {
8187 Some(edit_start_row..cursor_row)
8188 } else {
8189 None
8190 };
8191 let supports_jump = self
8192 .edit_prediction_provider
8193 .as_ref()
8194 .map(|provider| provider.provider.supports_jump_to_edit())
8195 .unwrap_or(true);
8196
8197 let is_move = supports_jump
8198 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8199 let completion = if is_move {
8200 invalidation_row_range =
8201 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8202 let target = first_edit_start;
8203 EditPrediction::MoveWithin { target, snapshot }
8204 } else {
8205 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8206 && !self.edit_predictions_hidden_for_vim_mode;
8207
8208 if show_completions_in_buffer {
8209 if let Some(provider) = &self.edit_prediction_provider {
8210 provider.provider.did_show(cx);
8211 }
8212 if edits
8213 .iter()
8214 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8215 {
8216 let mut inlays = Vec::new();
8217 for (range, new_text) in &edits {
8218 let inlay = Inlay::edit_prediction(
8219 post_inc(&mut self.next_inlay_id),
8220 range.start,
8221 new_text.as_ref(),
8222 );
8223 inlay_ids.push(inlay.id);
8224 inlays.push(inlay);
8225 }
8226
8227 self.splice_inlays(&[], inlays, cx);
8228 } else {
8229 let background_color = cx.theme().status().deleted_background;
8230 self.highlight_text::<EditPredictionHighlight>(
8231 edits.iter().map(|(range, _)| range.clone()).collect(),
8232 HighlightStyle {
8233 background_color: Some(background_color),
8234 ..Default::default()
8235 },
8236 cx,
8237 );
8238 }
8239 }
8240
8241 invalidation_row_range = edit_start_row..edit_end_row;
8242
8243 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8244 if provider.show_tab_accept_marker() {
8245 EditDisplayMode::TabAccept
8246 } else {
8247 EditDisplayMode::Inline
8248 }
8249 } else {
8250 EditDisplayMode::DiffPopover
8251 };
8252
8253 EditPrediction::Edit {
8254 edits,
8255 edit_preview,
8256 display_mode,
8257 snapshot,
8258 }
8259 };
8260
8261 let invalidation_range = multibuffer
8262 .anchor_before(Point::new(invalidation_row_range.start, 0))
8263 ..multibuffer.anchor_after(Point::new(
8264 invalidation_row_range.end,
8265 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8266 ));
8267
8268 self.stale_edit_prediction_in_menu = None;
8269 self.active_edit_prediction = Some(EditPredictionState {
8270 inlay_ids,
8271 completion,
8272 completion_id,
8273 invalidation_range: Some(invalidation_range),
8274 });
8275
8276 cx.notify();
8277
8278 Some(())
8279 }
8280
8281 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8282 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8283 }
8284
8285 fn clear_tasks(&mut self) {
8286 self.tasks.clear()
8287 }
8288
8289 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8290 if self.tasks.insert(key, value).is_some() {
8291 // This case should hopefully be rare, but just in case...
8292 log::error!(
8293 "multiple different run targets found on a single line, only the last target will be rendered"
8294 )
8295 }
8296 }
8297
8298 /// Get all display points of breakpoints that will be rendered within editor
8299 ///
8300 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8301 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8302 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8303 fn active_breakpoints(
8304 &self,
8305 range: Range<DisplayRow>,
8306 window: &mut Window,
8307 cx: &mut Context<Self>,
8308 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8309 let mut breakpoint_display_points = HashMap::default();
8310
8311 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8312 return breakpoint_display_points;
8313 };
8314
8315 let snapshot = self.snapshot(window, cx);
8316
8317 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8318 let Some(project) = self.project() else {
8319 return breakpoint_display_points;
8320 };
8321
8322 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8323 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8324
8325 for (buffer_snapshot, range, excerpt_id) in
8326 multi_buffer_snapshot.range_to_buffer_ranges(range)
8327 {
8328 let Some(buffer) = project
8329 .read(cx)
8330 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8331 else {
8332 continue;
8333 };
8334 let breakpoints = breakpoint_store.read(cx).breakpoints(
8335 &buffer,
8336 Some(
8337 buffer_snapshot.anchor_before(range.start)
8338 ..buffer_snapshot.anchor_after(range.end),
8339 ),
8340 buffer_snapshot,
8341 cx,
8342 );
8343 for (breakpoint, state) in breakpoints {
8344 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8345 let position = multi_buffer_anchor
8346 .to_point(&multi_buffer_snapshot)
8347 .to_display_point(&snapshot);
8348
8349 breakpoint_display_points.insert(
8350 position.row(),
8351 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8352 );
8353 }
8354 }
8355
8356 breakpoint_display_points
8357 }
8358
8359 fn breakpoint_context_menu(
8360 &self,
8361 anchor: Anchor,
8362 window: &mut Window,
8363 cx: &mut Context<Self>,
8364 ) -> Entity<ui::ContextMenu> {
8365 let weak_editor = cx.weak_entity();
8366 let focus_handle = self.focus_handle(cx);
8367
8368 let row = self
8369 .buffer
8370 .read(cx)
8371 .snapshot(cx)
8372 .summary_for_anchor::<Point>(&anchor)
8373 .row;
8374
8375 let breakpoint = self
8376 .breakpoint_at_row(row, window, cx)
8377 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8378
8379 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8380 "Edit Log Breakpoint"
8381 } else {
8382 "Set Log Breakpoint"
8383 };
8384
8385 let condition_breakpoint_msg = if breakpoint
8386 .as_ref()
8387 .is_some_and(|bp| bp.1.condition.is_some())
8388 {
8389 "Edit Condition Breakpoint"
8390 } else {
8391 "Set Condition Breakpoint"
8392 };
8393
8394 let hit_condition_breakpoint_msg = if breakpoint
8395 .as_ref()
8396 .is_some_and(|bp| bp.1.hit_condition.is_some())
8397 {
8398 "Edit Hit Condition Breakpoint"
8399 } else {
8400 "Set Hit Condition Breakpoint"
8401 };
8402
8403 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8404 "Unset Breakpoint"
8405 } else {
8406 "Set Breakpoint"
8407 };
8408
8409 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8410
8411 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8412 BreakpointState::Enabled => Some("Disable"),
8413 BreakpointState::Disabled => Some("Enable"),
8414 });
8415
8416 let (anchor, breakpoint) =
8417 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8418
8419 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8420 menu.on_blur_subscription(Subscription::new(|| {}))
8421 .context(focus_handle)
8422 .when(run_to_cursor, |this| {
8423 let weak_editor = weak_editor.clone();
8424 this.entry("Run to cursor", None, move |window, cx| {
8425 weak_editor
8426 .update(cx, |editor, cx| {
8427 editor.change_selections(
8428 SelectionEffects::no_scroll(),
8429 window,
8430 cx,
8431 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8432 );
8433 })
8434 .ok();
8435
8436 window.dispatch_action(Box::new(RunToCursor), cx);
8437 })
8438 .separator()
8439 })
8440 .when_some(toggle_state_msg, |this, msg| {
8441 this.entry(msg, None, {
8442 let weak_editor = weak_editor.clone();
8443 let breakpoint = breakpoint.clone();
8444 move |_window, cx| {
8445 weak_editor
8446 .update(cx, |this, cx| {
8447 this.edit_breakpoint_at_anchor(
8448 anchor,
8449 breakpoint.as_ref().clone(),
8450 BreakpointEditAction::InvertState,
8451 cx,
8452 );
8453 })
8454 .log_err();
8455 }
8456 })
8457 })
8458 .entry(set_breakpoint_msg, None, {
8459 let weak_editor = weak_editor.clone();
8460 let breakpoint = breakpoint.clone();
8461 move |_window, cx| {
8462 weak_editor
8463 .update(cx, |this, cx| {
8464 this.edit_breakpoint_at_anchor(
8465 anchor,
8466 breakpoint.as_ref().clone(),
8467 BreakpointEditAction::Toggle,
8468 cx,
8469 );
8470 })
8471 .log_err();
8472 }
8473 })
8474 .entry(log_breakpoint_msg, None, {
8475 let breakpoint = breakpoint.clone();
8476 let weak_editor = weak_editor.clone();
8477 move |window, cx| {
8478 weak_editor
8479 .update(cx, |this, cx| {
8480 this.add_edit_breakpoint_block(
8481 anchor,
8482 breakpoint.as_ref(),
8483 BreakpointPromptEditAction::Log,
8484 window,
8485 cx,
8486 );
8487 })
8488 .log_err();
8489 }
8490 })
8491 .entry(condition_breakpoint_msg, None, {
8492 let breakpoint = breakpoint.clone();
8493 let weak_editor = weak_editor.clone();
8494 move |window, cx| {
8495 weak_editor
8496 .update(cx, |this, cx| {
8497 this.add_edit_breakpoint_block(
8498 anchor,
8499 breakpoint.as_ref(),
8500 BreakpointPromptEditAction::Condition,
8501 window,
8502 cx,
8503 );
8504 })
8505 .log_err();
8506 }
8507 })
8508 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8509 weak_editor
8510 .update(cx, |this, cx| {
8511 this.add_edit_breakpoint_block(
8512 anchor,
8513 breakpoint.as_ref(),
8514 BreakpointPromptEditAction::HitCondition,
8515 window,
8516 cx,
8517 );
8518 })
8519 .log_err();
8520 })
8521 })
8522 }
8523
8524 fn render_breakpoint(
8525 &self,
8526 position: Anchor,
8527 row: DisplayRow,
8528 breakpoint: &Breakpoint,
8529 state: Option<BreakpointSessionState>,
8530 cx: &mut Context<Self>,
8531 ) -> IconButton {
8532 let is_rejected = state.is_some_and(|s| !s.verified);
8533 // Is it a breakpoint that shows up when hovering over gutter?
8534 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8535 (false, false),
8536 |PhantomBreakpointIndicator {
8537 is_active,
8538 display_row,
8539 collides_with_existing_breakpoint,
8540 }| {
8541 (
8542 is_active && display_row == row,
8543 collides_with_existing_breakpoint,
8544 )
8545 },
8546 );
8547
8548 let (color, icon) = {
8549 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8550 (false, false) => ui::IconName::DebugBreakpoint,
8551 (true, false) => ui::IconName::DebugLogBreakpoint,
8552 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8553 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8554 };
8555
8556 let color = cx.theme().colors();
8557
8558 let color = if is_phantom {
8559 if collides_with_existing {
8560 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8561 } else {
8562 Color::Hint
8563 }
8564 } else if is_rejected {
8565 Color::Disabled
8566 } else {
8567 Color::Debugger
8568 };
8569
8570 (color, icon)
8571 };
8572
8573 let breakpoint = Arc::from(breakpoint.clone());
8574
8575 let alt_as_text = gpui::Keystroke {
8576 modifiers: Modifiers::secondary_key(),
8577 ..Default::default()
8578 };
8579 let primary_action_text = if breakpoint.is_disabled() {
8580 "Enable breakpoint"
8581 } else if is_phantom && !collides_with_existing {
8582 "Set breakpoint"
8583 } else {
8584 "Unset breakpoint"
8585 };
8586 let focus_handle = self.focus_handle.clone();
8587
8588 let meta = if is_rejected {
8589 SharedString::from("No executable code is associated with this line.")
8590 } else if collides_with_existing && !breakpoint.is_disabled() {
8591 SharedString::from(format!(
8592 "{alt_as_text}-click to disable,\nright-click for more options."
8593 ))
8594 } else {
8595 SharedString::from("Right-click for more options.")
8596 };
8597 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8598 .icon_size(IconSize::XSmall)
8599 .size(ui::ButtonSize::None)
8600 .when(is_rejected, |this| {
8601 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8602 })
8603 .icon_color(color)
8604 .style(ButtonStyle::Transparent)
8605 .on_click(cx.listener({
8606 move |editor, event: &ClickEvent, window, cx| {
8607 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8608 BreakpointEditAction::InvertState
8609 } else {
8610 BreakpointEditAction::Toggle
8611 };
8612
8613 window.focus(&editor.focus_handle(cx), cx);
8614 editor.edit_breakpoint_at_anchor(
8615 position,
8616 breakpoint.as_ref().clone(),
8617 edit_action,
8618 cx,
8619 );
8620 }
8621 }))
8622 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8623 editor.set_breakpoint_context_menu(
8624 row,
8625 Some(position),
8626 event.position(),
8627 window,
8628 cx,
8629 );
8630 }))
8631 .tooltip(move |_window, cx| {
8632 Tooltip::with_meta_in(
8633 primary_action_text,
8634 Some(&ToggleBreakpoint),
8635 meta.clone(),
8636 &focus_handle,
8637 cx,
8638 )
8639 })
8640 }
8641
8642 fn build_tasks_context(
8643 project: &Entity<Project>,
8644 buffer: &Entity<Buffer>,
8645 buffer_row: u32,
8646 tasks: &Arc<RunnableTasks>,
8647 cx: &mut Context<Self>,
8648 ) -> Task<Option<task::TaskContext>> {
8649 let position = Point::new(buffer_row, tasks.column);
8650 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8651 let location = Location {
8652 buffer: buffer.clone(),
8653 range: range_start..range_start,
8654 };
8655 // Fill in the environmental variables from the tree-sitter captures
8656 let mut captured_task_variables = TaskVariables::default();
8657 for (capture_name, value) in tasks.extra_variables.clone() {
8658 captured_task_variables.insert(
8659 task::VariableName::Custom(capture_name.into()),
8660 value.clone(),
8661 );
8662 }
8663 project.update(cx, |project, cx| {
8664 project.task_store().update(cx, |task_store, cx| {
8665 task_store.task_context_for_location(captured_task_variables, location, cx)
8666 })
8667 })
8668 }
8669
8670 pub fn spawn_nearest_task(
8671 &mut self,
8672 action: &SpawnNearestTask,
8673 window: &mut Window,
8674 cx: &mut Context<Self>,
8675 ) {
8676 let Some((workspace, _)) = self.workspace.clone() else {
8677 return;
8678 };
8679 let Some(project) = self.project.clone() else {
8680 return;
8681 };
8682
8683 // Try to find a closest, enclosing node using tree-sitter that has a task
8684 let Some((buffer, buffer_row, tasks)) = self
8685 .find_enclosing_node_task(cx)
8686 // Or find the task that's closest in row-distance.
8687 .or_else(|| self.find_closest_task(cx))
8688 else {
8689 return;
8690 };
8691
8692 let reveal_strategy = action.reveal;
8693 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8694 cx.spawn_in(window, async move |_, cx| {
8695 let context = task_context.await?;
8696 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8697
8698 let resolved = &mut resolved_task.resolved;
8699 resolved.reveal = reveal_strategy;
8700
8701 workspace
8702 .update_in(cx, |workspace, window, cx| {
8703 workspace.schedule_resolved_task(
8704 task_source_kind,
8705 resolved_task,
8706 false,
8707 window,
8708 cx,
8709 );
8710 })
8711 .ok()
8712 })
8713 .detach();
8714 }
8715
8716 fn find_closest_task(
8717 &mut self,
8718 cx: &mut Context<Self>,
8719 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8720 let cursor_row = self
8721 .selections
8722 .newest_adjusted(&self.display_snapshot(cx))
8723 .head()
8724 .row;
8725
8726 let ((buffer_id, row), tasks) = self
8727 .tasks
8728 .iter()
8729 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8730
8731 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8732 let tasks = Arc::new(tasks.to_owned());
8733 Some((buffer, *row, tasks))
8734 }
8735
8736 fn find_enclosing_node_task(
8737 &mut self,
8738 cx: &mut Context<Self>,
8739 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8740 let snapshot = self.buffer.read(cx).snapshot(cx);
8741 let offset = self
8742 .selections
8743 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8744 .head();
8745 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8746 let offset = excerpt.map_offset_to_buffer(offset);
8747 let buffer_id = excerpt.buffer().remote_id();
8748
8749 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8750 let mut cursor = layer.node().walk();
8751
8752 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8753 if cursor.node().end_byte() == offset.0 {
8754 cursor.goto_next_sibling();
8755 }
8756 }
8757
8758 // Ascend to the smallest ancestor that contains the range and has a task.
8759 loop {
8760 let node = cursor.node();
8761 let node_range = node.byte_range();
8762 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8763
8764 // Check if this node contains our offset
8765 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8766 // If it contains offset, check for task
8767 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8768 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8769 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8770 }
8771 }
8772
8773 if !cursor.goto_parent() {
8774 break;
8775 }
8776 }
8777 None
8778 }
8779
8780 fn render_run_indicator(
8781 &self,
8782 _style: &EditorStyle,
8783 is_active: bool,
8784 row: DisplayRow,
8785 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8786 cx: &mut Context<Self>,
8787 ) -> IconButton {
8788 let color = Color::Muted;
8789 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8790
8791 IconButton::new(
8792 ("run_indicator", row.0 as usize),
8793 ui::IconName::PlayOutlined,
8794 )
8795 .shape(ui::IconButtonShape::Square)
8796 .icon_size(IconSize::XSmall)
8797 .icon_color(color)
8798 .toggle_state(is_active)
8799 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8800 let quick_launch = match e {
8801 ClickEvent::Keyboard(_) => true,
8802 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8803 };
8804
8805 window.focus(&editor.focus_handle(cx), cx);
8806 editor.toggle_code_actions(
8807 &ToggleCodeActions {
8808 deployed_from: Some(CodeActionSource::RunMenu(row)),
8809 quick_launch,
8810 },
8811 window,
8812 cx,
8813 );
8814 }))
8815 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8816 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8817 }))
8818 }
8819
8820 pub fn context_menu_visible(&self) -> bool {
8821 !self.edit_prediction_preview_is_active()
8822 && self
8823 .context_menu
8824 .borrow()
8825 .as_ref()
8826 .is_some_and(|menu| menu.visible())
8827 }
8828
8829 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8830 self.context_menu
8831 .borrow()
8832 .as_ref()
8833 .map(|menu| menu.origin())
8834 }
8835
8836 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8837 self.context_menu_options = Some(options);
8838 }
8839
8840 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8841 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8842
8843 fn render_edit_prediction_popover(
8844 &mut self,
8845 text_bounds: &Bounds<Pixels>,
8846 content_origin: gpui::Point<Pixels>,
8847 right_margin: Pixels,
8848 editor_snapshot: &EditorSnapshot,
8849 visible_row_range: Range<DisplayRow>,
8850 scroll_top: ScrollOffset,
8851 scroll_bottom: ScrollOffset,
8852 line_layouts: &[LineWithInvisibles],
8853 line_height: Pixels,
8854 scroll_position: gpui::Point<ScrollOffset>,
8855 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8856 newest_selection_head: Option<DisplayPoint>,
8857 editor_width: Pixels,
8858 style: &EditorStyle,
8859 window: &mut Window,
8860 cx: &mut App,
8861 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8862 if self.mode().is_minimap() {
8863 return None;
8864 }
8865 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8866
8867 if self.edit_prediction_visible_in_cursor_popover(true) {
8868 return None;
8869 }
8870
8871 match &active_edit_prediction.completion {
8872 EditPrediction::MoveWithin { target, .. } => {
8873 let target_display_point = target.to_display_point(editor_snapshot);
8874
8875 if self.edit_prediction_requires_modifier() {
8876 if !self.edit_prediction_preview_is_active() {
8877 return None;
8878 }
8879
8880 self.render_edit_prediction_modifier_jump_popover(
8881 text_bounds,
8882 content_origin,
8883 visible_row_range,
8884 line_layouts,
8885 line_height,
8886 scroll_pixel_position,
8887 newest_selection_head,
8888 target_display_point,
8889 window,
8890 cx,
8891 )
8892 } else {
8893 self.render_edit_prediction_eager_jump_popover(
8894 text_bounds,
8895 content_origin,
8896 editor_snapshot,
8897 visible_row_range,
8898 scroll_top,
8899 scroll_bottom,
8900 line_height,
8901 scroll_pixel_position,
8902 target_display_point,
8903 editor_width,
8904 window,
8905 cx,
8906 )
8907 }
8908 }
8909 EditPrediction::Edit {
8910 display_mode: EditDisplayMode::Inline,
8911 ..
8912 } => None,
8913 EditPrediction::Edit {
8914 display_mode: EditDisplayMode::TabAccept,
8915 edits,
8916 ..
8917 } => {
8918 let range = &edits.first()?.0;
8919 let target_display_point = range.end.to_display_point(editor_snapshot);
8920
8921 self.render_edit_prediction_end_of_line_popover(
8922 "Accept",
8923 editor_snapshot,
8924 visible_row_range,
8925 target_display_point,
8926 line_height,
8927 scroll_pixel_position,
8928 content_origin,
8929 editor_width,
8930 window,
8931 cx,
8932 )
8933 }
8934 EditPrediction::Edit {
8935 edits,
8936 edit_preview,
8937 display_mode: EditDisplayMode::DiffPopover,
8938 snapshot,
8939 } => self.render_edit_prediction_diff_popover(
8940 text_bounds,
8941 content_origin,
8942 right_margin,
8943 editor_snapshot,
8944 visible_row_range,
8945 line_layouts,
8946 line_height,
8947 scroll_position,
8948 scroll_pixel_position,
8949 newest_selection_head,
8950 editor_width,
8951 style,
8952 edits,
8953 edit_preview,
8954 snapshot,
8955 window,
8956 cx,
8957 ),
8958 EditPrediction::MoveOutside { snapshot, .. } => {
8959 let mut element = self
8960 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8961 .into_any();
8962
8963 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8964 let origin_x = text_bounds.size.width - size.width - px(30.);
8965 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8966 element.prepaint_at(origin, window, cx);
8967
8968 Some((element, origin))
8969 }
8970 }
8971 }
8972
8973 fn render_edit_prediction_modifier_jump_popover(
8974 &mut self,
8975 text_bounds: &Bounds<Pixels>,
8976 content_origin: gpui::Point<Pixels>,
8977 visible_row_range: Range<DisplayRow>,
8978 line_layouts: &[LineWithInvisibles],
8979 line_height: Pixels,
8980 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8981 newest_selection_head: Option<DisplayPoint>,
8982 target_display_point: DisplayPoint,
8983 window: &mut Window,
8984 cx: &mut App,
8985 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8986 let scrolled_content_origin =
8987 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8988
8989 const SCROLL_PADDING_Y: Pixels = px(12.);
8990
8991 if target_display_point.row() < visible_row_range.start {
8992 return self.render_edit_prediction_scroll_popover(
8993 |_| SCROLL_PADDING_Y,
8994 IconName::ArrowUp,
8995 visible_row_range,
8996 line_layouts,
8997 newest_selection_head,
8998 scrolled_content_origin,
8999 window,
9000 cx,
9001 );
9002 } else if target_display_point.row() >= visible_row_range.end {
9003 return self.render_edit_prediction_scroll_popover(
9004 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9005 IconName::ArrowDown,
9006 visible_row_range,
9007 line_layouts,
9008 newest_selection_head,
9009 scrolled_content_origin,
9010 window,
9011 cx,
9012 );
9013 }
9014
9015 const POLE_WIDTH: Pixels = px(2.);
9016
9017 let line_layout =
9018 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9019 let target_column = target_display_point.column() as usize;
9020
9021 let target_x = line_layout.x_for_index(target_column);
9022 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9023 - scroll_pixel_position.y;
9024
9025 let flag_on_right = target_x < text_bounds.size.width / 2.;
9026
9027 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9028 border_color.l += 0.001;
9029
9030 let mut element = v_flex()
9031 .items_end()
9032 .when(flag_on_right, |el| el.items_start())
9033 .child(if flag_on_right {
9034 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9035 .rounded_bl(px(0.))
9036 .rounded_tl(px(0.))
9037 .border_l_2()
9038 .border_color(border_color)
9039 } else {
9040 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9041 .rounded_br(px(0.))
9042 .rounded_tr(px(0.))
9043 .border_r_2()
9044 .border_color(border_color)
9045 })
9046 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9047 .into_any();
9048
9049 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9050
9051 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9052 - point(
9053 if flag_on_right {
9054 POLE_WIDTH
9055 } else {
9056 size.width - POLE_WIDTH
9057 },
9058 size.height - line_height,
9059 );
9060
9061 origin.x = origin.x.max(content_origin.x);
9062
9063 element.prepaint_at(origin, window, cx);
9064
9065 Some((element, origin))
9066 }
9067
9068 fn render_edit_prediction_scroll_popover(
9069 &mut self,
9070 to_y: impl Fn(Size<Pixels>) -> Pixels,
9071 scroll_icon: IconName,
9072 visible_row_range: Range<DisplayRow>,
9073 line_layouts: &[LineWithInvisibles],
9074 newest_selection_head: Option<DisplayPoint>,
9075 scrolled_content_origin: gpui::Point<Pixels>,
9076 window: &mut Window,
9077 cx: &mut App,
9078 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9079 let mut element = self
9080 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9081 .into_any();
9082
9083 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9084
9085 let cursor = newest_selection_head?;
9086 let cursor_row_layout =
9087 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9088 let cursor_column = cursor.column() as usize;
9089
9090 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9091
9092 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9093
9094 element.prepaint_at(origin, window, cx);
9095 Some((element, origin))
9096 }
9097
9098 fn render_edit_prediction_eager_jump_popover(
9099 &mut self,
9100 text_bounds: &Bounds<Pixels>,
9101 content_origin: gpui::Point<Pixels>,
9102 editor_snapshot: &EditorSnapshot,
9103 visible_row_range: Range<DisplayRow>,
9104 scroll_top: ScrollOffset,
9105 scroll_bottom: ScrollOffset,
9106 line_height: Pixels,
9107 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9108 target_display_point: DisplayPoint,
9109 editor_width: Pixels,
9110 window: &mut Window,
9111 cx: &mut App,
9112 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9113 if target_display_point.row().as_f64() < scroll_top {
9114 let mut element = self
9115 .render_edit_prediction_line_popover(
9116 "Jump to Edit",
9117 Some(IconName::ArrowUp),
9118 window,
9119 cx,
9120 )
9121 .into_any();
9122
9123 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9124 let offset = point(
9125 (text_bounds.size.width - size.width) / 2.,
9126 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9127 );
9128
9129 let origin = text_bounds.origin + offset;
9130 element.prepaint_at(origin, window, cx);
9131 Some((element, origin))
9132 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9133 let mut element = self
9134 .render_edit_prediction_line_popover(
9135 "Jump to Edit",
9136 Some(IconName::ArrowDown),
9137 window,
9138 cx,
9139 )
9140 .into_any();
9141
9142 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9143 let offset = point(
9144 (text_bounds.size.width - size.width) / 2.,
9145 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9146 );
9147
9148 let origin = text_bounds.origin + offset;
9149 element.prepaint_at(origin, window, cx);
9150 Some((element, origin))
9151 } else {
9152 self.render_edit_prediction_end_of_line_popover(
9153 "Jump to Edit",
9154 editor_snapshot,
9155 visible_row_range,
9156 target_display_point,
9157 line_height,
9158 scroll_pixel_position,
9159 content_origin,
9160 editor_width,
9161 window,
9162 cx,
9163 )
9164 }
9165 }
9166
9167 fn render_edit_prediction_end_of_line_popover(
9168 self: &mut Editor,
9169 label: &'static str,
9170 editor_snapshot: &EditorSnapshot,
9171 visible_row_range: Range<DisplayRow>,
9172 target_display_point: DisplayPoint,
9173 line_height: Pixels,
9174 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9175 content_origin: gpui::Point<Pixels>,
9176 editor_width: Pixels,
9177 window: &mut Window,
9178 cx: &mut App,
9179 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9180 let target_line_end = DisplayPoint::new(
9181 target_display_point.row(),
9182 editor_snapshot.line_len(target_display_point.row()),
9183 );
9184
9185 let mut element = self
9186 .render_edit_prediction_line_popover(label, None, window, cx)
9187 .into_any();
9188
9189 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9190
9191 let line_origin =
9192 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9193
9194 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9195 let mut origin = start_point
9196 + line_origin
9197 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9198 origin.x = origin.x.max(content_origin.x);
9199
9200 let max_x = content_origin.x + editor_width - size.width;
9201
9202 if origin.x > max_x {
9203 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9204
9205 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9206 origin.y += offset;
9207 IconName::ArrowUp
9208 } else {
9209 origin.y -= offset;
9210 IconName::ArrowDown
9211 };
9212
9213 element = self
9214 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9215 .into_any();
9216
9217 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9218
9219 origin.x = content_origin.x + editor_width - size.width - px(2.);
9220 }
9221
9222 element.prepaint_at(origin, window, cx);
9223 Some((element, origin))
9224 }
9225
9226 fn render_edit_prediction_diff_popover(
9227 self: &Editor,
9228 text_bounds: &Bounds<Pixels>,
9229 content_origin: gpui::Point<Pixels>,
9230 right_margin: Pixels,
9231 editor_snapshot: &EditorSnapshot,
9232 visible_row_range: Range<DisplayRow>,
9233 line_layouts: &[LineWithInvisibles],
9234 line_height: Pixels,
9235 scroll_position: gpui::Point<ScrollOffset>,
9236 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9237 newest_selection_head: Option<DisplayPoint>,
9238 editor_width: Pixels,
9239 style: &EditorStyle,
9240 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9241 edit_preview: &Option<language::EditPreview>,
9242 snapshot: &language::BufferSnapshot,
9243 window: &mut Window,
9244 cx: &mut App,
9245 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9246 let edit_start = edits
9247 .first()
9248 .unwrap()
9249 .0
9250 .start
9251 .to_display_point(editor_snapshot);
9252 let edit_end = edits
9253 .last()
9254 .unwrap()
9255 .0
9256 .end
9257 .to_display_point(editor_snapshot);
9258
9259 let is_visible = visible_row_range.contains(&edit_start.row())
9260 || visible_row_range.contains(&edit_end.row());
9261 if !is_visible {
9262 return None;
9263 }
9264
9265 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9266 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9267 } else {
9268 // Fallback for providers without edit_preview
9269 crate::edit_prediction_fallback_text(edits, cx)
9270 };
9271
9272 let styled_text = highlighted_edits.to_styled_text(&style.text);
9273 let line_count = highlighted_edits.text.lines().count();
9274
9275 const BORDER_WIDTH: Pixels = px(1.);
9276
9277 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9278 let has_keybind = keybind.is_some();
9279
9280 let mut element = h_flex()
9281 .items_start()
9282 .child(
9283 h_flex()
9284 .bg(cx.theme().colors().editor_background)
9285 .border(BORDER_WIDTH)
9286 .shadow_xs()
9287 .border_color(cx.theme().colors().border)
9288 .rounded_l_lg()
9289 .when(line_count > 1, |el| el.rounded_br_lg())
9290 .pr_1()
9291 .child(styled_text),
9292 )
9293 .child(
9294 h_flex()
9295 .h(line_height + BORDER_WIDTH * 2.)
9296 .px_1p5()
9297 .gap_1()
9298 // Workaround: For some reason, there's a gap if we don't do this
9299 .ml(-BORDER_WIDTH)
9300 .shadow(vec![gpui::BoxShadow {
9301 color: gpui::black().opacity(0.05),
9302 offset: point(px(1.), px(1.)),
9303 blur_radius: px(2.),
9304 spread_radius: px(0.),
9305 }])
9306 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9307 .border(BORDER_WIDTH)
9308 .border_color(cx.theme().colors().border)
9309 .rounded_r_lg()
9310 .id("edit_prediction_diff_popover_keybind")
9311 .when(!has_keybind, |el| {
9312 let status_colors = cx.theme().status();
9313
9314 el.bg(status_colors.error_background)
9315 .border_color(status_colors.error.opacity(0.6))
9316 .child(Icon::new(IconName::Info).color(Color::Error))
9317 .cursor_default()
9318 .hoverable_tooltip(move |_window, cx| {
9319 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9320 })
9321 })
9322 .children(keybind),
9323 )
9324 .into_any();
9325
9326 let longest_row =
9327 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9328 let longest_line_width = if visible_row_range.contains(&longest_row) {
9329 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9330 } else {
9331 layout_line(
9332 longest_row,
9333 editor_snapshot,
9334 style,
9335 editor_width,
9336 |_| false,
9337 window,
9338 cx,
9339 )
9340 .width
9341 };
9342
9343 let viewport_bounds =
9344 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9345 right: -right_margin,
9346 ..Default::default()
9347 });
9348
9349 let x_after_longest = Pixels::from(
9350 ScrollPixelOffset::from(
9351 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9352 ) - scroll_pixel_position.x,
9353 );
9354
9355 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9356
9357 // Fully visible if it can be displayed within the window (allow overlapping other
9358 // panes). However, this is only allowed if the popover starts within text_bounds.
9359 let can_position_to_the_right = x_after_longest < text_bounds.right()
9360 && x_after_longest + element_bounds.width < viewport_bounds.right();
9361
9362 let mut origin = if can_position_to_the_right {
9363 point(
9364 x_after_longest,
9365 text_bounds.origin.y
9366 + Pixels::from(
9367 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9368 - scroll_pixel_position.y,
9369 ),
9370 )
9371 } else {
9372 let cursor_row = newest_selection_head.map(|head| head.row());
9373 let above_edit = edit_start
9374 .row()
9375 .0
9376 .checked_sub(line_count as u32)
9377 .map(DisplayRow);
9378 let below_edit = Some(edit_end.row() + 1);
9379 let above_cursor =
9380 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9381 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9382
9383 // Place the edit popover adjacent to the edit if there is a location
9384 // available that is onscreen and does not obscure the cursor. Otherwise,
9385 // place it adjacent to the cursor.
9386 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9387 .into_iter()
9388 .flatten()
9389 .find(|&start_row| {
9390 let end_row = start_row + line_count as u32;
9391 visible_row_range.contains(&start_row)
9392 && visible_row_range.contains(&end_row)
9393 && cursor_row
9394 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9395 })?;
9396
9397 content_origin
9398 + point(
9399 Pixels::from(-scroll_pixel_position.x),
9400 Pixels::from(
9401 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9402 ),
9403 )
9404 };
9405
9406 origin.x -= BORDER_WIDTH;
9407
9408 window.defer_draw(element, origin, 1);
9409
9410 // Do not return an element, since it will already be drawn due to defer_draw.
9411 None
9412 }
9413
9414 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9415 px(30.)
9416 }
9417
9418 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9419 if self.read_only(cx) {
9420 cx.theme().players().read_only()
9421 } else {
9422 self.style.as_ref().unwrap().local_player
9423 }
9424 }
9425
9426 fn render_edit_prediction_accept_keybind(
9427 &self,
9428 window: &mut Window,
9429 cx: &mut App,
9430 ) -> Option<AnyElement> {
9431 let accept_binding =
9432 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9433 let accept_keystroke = accept_binding.keystroke()?;
9434
9435 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9436
9437 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9438 Color::Accent
9439 } else {
9440 Color::Muted
9441 };
9442
9443 h_flex()
9444 .px_0p5()
9445 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9446 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9447 .text_size(TextSize::XSmall.rems(cx))
9448 .child(h_flex().children(ui::render_modifiers(
9449 accept_keystroke.modifiers(),
9450 PlatformStyle::platform(),
9451 Some(modifiers_color),
9452 Some(IconSize::XSmall.rems().into()),
9453 true,
9454 )))
9455 .when(is_platform_style_mac, |parent| {
9456 parent.child(accept_keystroke.key().to_string())
9457 })
9458 .when(!is_platform_style_mac, |parent| {
9459 parent.child(
9460 Key::new(
9461 util::capitalize(accept_keystroke.key()),
9462 Some(Color::Default),
9463 )
9464 .size(Some(IconSize::XSmall.rems().into())),
9465 )
9466 })
9467 .into_any()
9468 .into()
9469 }
9470
9471 fn render_edit_prediction_line_popover(
9472 &self,
9473 label: impl Into<SharedString>,
9474 icon: Option<IconName>,
9475 window: &mut Window,
9476 cx: &mut App,
9477 ) -> Stateful<Div> {
9478 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9479
9480 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9481 let has_keybind = keybind.is_some();
9482
9483 h_flex()
9484 .id("ep-line-popover")
9485 .py_0p5()
9486 .pl_1()
9487 .pr(padding_right)
9488 .gap_1()
9489 .rounded_md()
9490 .border_1()
9491 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9492 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9493 .shadow_xs()
9494 .when(!has_keybind, |el| {
9495 let status_colors = cx.theme().status();
9496
9497 el.bg(status_colors.error_background)
9498 .border_color(status_colors.error.opacity(0.6))
9499 .pl_2()
9500 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9501 .cursor_default()
9502 .hoverable_tooltip(move |_window, cx| {
9503 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9504 })
9505 })
9506 .children(keybind)
9507 .child(
9508 Label::new(label)
9509 .size(LabelSize::Small)
9510 .when(!has_keybind, |el| {
9511 el.color(cx.theme().status().error.into()).strikethrough()
9512 }),
9513 )
9514 .when(!has_keybind, |el| {
9515 el.child(
9516 h_flex().ml_1().child(
9517 Icon::new(IconName::Info)
9518 .size(IconSize::Small)
9519 .color(cx.theme().status().error.into()),
9520 ),
9521 )
9522 })
9523 .when_some(icon, |element, icon| {
9524 element.child(
9525 div()
9526 .mt(px(1.5))
9527 .child(Icon::new(icon).size(IconSize::Small)),
9528 )
9529 })
9530 }
9531
9532 fn render_edit_prediction_jump_outside_popover(
9533 &self,
9534 snapshot: &BufferSnapshot,
9535 window: &mut Window,
9536 cx: &mut App,
9537 ) -> Stateful<Div> {
9538 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9539 let has_keybind = keybind.is_some();
9540
9541 let file_name = snapshot
9542 .file()
9543 .map(|file| SharedString::new(file.file_name(cx)))
9544 .unwrap_or(SharedString::new_static("untitled"));
9545
9546 h_flex()
9547 .id("ep-jump-outside-popover")
9548 .py_1()
9549 .px_2()
9550 .gap_1()
9551 .rounded_md()
9552 .border_1()
9553 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9554 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9555 .shadow_xs()
9556 .when(!has_keybind, |el| {
9557 let status_colors = cx.theme().status();
9558
9559 el.bg(status_colors.error_background)
9560 .border_color(status_colors.error.opacity(0.6))
9561 .pl_2()
9562 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9563 .cursor_default()
9564 .hoverable_tooltip(move |_window, cx| {
9565 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9566 })
9567 })
9568 .children(keybind)
9569 .child(
9570 Label::new(file_name)
9571 .size(LabelSize::Small)
9572 .buffer_font(cx)
9573 .when(!has_keybind, |el| {
9574 el.color(cx.theme().status().error.into()).strikethrough()
9575 }),
9576 )
9577 .when(!has_keybind, |el| {
9578 el.child(
9579 h_flex().ml_1().child(
9580 Icon::new(IconName::Info)
9581 .size(IconSize::Small)
9582 .color(cx.theme().status().error.into()),
9583 ),
9584 )
9585 })
9586 .child(
9587 div()
9588 .mt(px(1.5))
9589 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9590 )
9591 }
9592
9593 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9594 let accent_color = cx.theme().colors().text_accent;
9595 let editor_bg_color = cx.theme().colors().editor_background;
9596 editor_bg_color.blend(accent_color.opacity(0.1))
9597 }
9598
9599 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9600 let accent_color = cx.theme().colors().text_accent;
9601 let editor_bg_color = cx.theme().colors().editor_background;
9602 editor_bg_color.blend(accent_color.opacity(0.6))
9603 }
9604 fn get_prediction_provider_icon_name(
9605 provider: &Option<RegisteredEditPredictionDelegate>,
9606 ) -> IconName {
9607 match provider {
9608 Some(provider) => match provider.provider.name() {
9609 "copilot" => IconName::Copilot,
9610 "supermaven" => IconName::Supermaven,
9611 _ => IconName::ZedPredict,
9612 },
9613 None => IconName::ZedPredict,
9614 }
9615 }
9616
9617 fn render_edit_prediction_cursor_popover(
9618 &self,
9619 min_width: Pixels,
9620 max_width: Pixels,
9621 cursor_point: Point,
9622 style: &EditorStyle,
9623 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9624 _window: &Window,
9625 cx: &mut Context<Editor>,
9626 ) -> Option<AnyElement> {
9627 let provider = self.edit_prediction_provider.as_ref()?;
9628 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9629
9630 let is_refreshing = provider.provider.is_refreshing(cx);
9631
9632 fn pending_completion_container(icon: IconName) -> Div {
9633 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9634 }
9635
9636 let completion = match &self.active_edit_prediction {
9637 Some(prediction) => {
9638 if !self.has_visible_completions_menu() {
9639 const RADIUS: Pixels = px(6.);
9640 const BORDER_WIDTH: Pixels = px(1.);
9641
9642 return Some(
9643 h_flex()
9644 .elevation_2(cx)
9645 .border(BORDER_WIDTH)
9646 .border_color(cx.theme().colors().border)
9647 .when(accept_keystroke.is_none(), |el| {
9648 el.border_color(cx.theme().status().error)
9649 })
9650 .rounded(RADIUS)
9651 .rounded_tl(px(0.))
9652 .overflow_hidden()
9653 .child(div().px_1p5().child(match &prediction.completion {
9654 EditPrediction::MoveWithin { target, snapshot } => {
9655 use text::ToPoint as _;
9656 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9657 {
9658 Icon::new(IconName::ZedPredictDown)
9659 } else {
9660 Icon::new(IconName::ZedPredictUp)
9661 }
9662 }
9663 EditPrediction::MoveOutside { .. } => {
9664 // TODO [zeta2] custom icon for external jump?
9665 Icon::new(provider_icon)
9666 }
9667 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9668 }))
9669 .child(
9670 h_flex()
9671 .gap_1()
9672 .py_1()
9673 .px_2()
9674 .rounded_r(RADIUS - BORDER_WIDTH)
9675 .border_l_1()
9676 .border_color(cx.theme().colors().border)
9677 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9678 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9679 el.child(
9680 Label::new("Hold")
9681 .size(LabelSize::Small)
9682 .when(accept_keystroke.is_none(), |el| {
9683 el.strikethrough()
9684 })
9685 .line_height_style(LineHeightStyle::UiLabel),
9686 )
9687 })
9688 .id("edit_prediction_cursor_popover_keybind")
9689 .when(accept_keystroke.is_none(), |el| {
9690 let status_colors = cx.theme().status();
9691
9692 el.bg(status_colors.error_background)
9693 .border_color(status_colors.error.opacity(0.6))
9694 .child(Icon::new(IconName::Info).color(Color::Error))
9695 .cursor_default()
9696 .hoverable_tooltip(move |_window, cx| {
9697 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9698 .into()
9699 })
9700 })
9701 .when_some(
9702 accept_keystroke.as_ref(),
9703 |el, accept_keystroke| {
9704 el.child(h_flex().children(ui::render_modifiers(
9705 accept_keystroke.modifiers(),
9706 PlatformStyle::platform(),
9707 Some(Color::Default),
9708 Some(IconSize::XSmall.rems().into()),
9709 false,
9710 )))
9711 },
9712 ),
9713 )
9714 .into_any(),
9715 );
9716 }
9717
9718 self.render_edit_prediction_cursor_popover_preview(
9719 prediction,
9720 cursor_point,
9721 style,
9722 cx,
9723 )?
9724 }
9725
9726 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9727 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9728 stale_completion,
9729 cursor_point,
9730 style,
9731 cx,
9732 )?,
9733
9734 None => pending_completion_container(provider_icon)
9735 .child(Label::new("...").size(LabelSize::Small)),
9736 },
9737
9738 None => pending_completion_container(provider_icon)
9739 .child(Label::new("...").size(LabelSize::Small)),
9740 };
9741
9742 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9743 completion
9744 .with_animation(
9745 "loading-completion",
9746 Animation::new(Duration::from_secs(2))
9747 .repeat()
9748 .with_easing(pulsating_between(0.4, 0.8)),
9749 |label, delta| label.opacity(delta),
9750 )
9751 .into_any_element()
9752 } else {
9753 completion.into_any_element()
9754 };
9755
9756 let has_completion = self.active_edit_prediction.is_some();
9757
9758 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9759 Some(
9760 h_flex()
9761 .min_w(min_width)
9762 .max_w(max_width)
9763 .flex_1()
9764 .elevation_2(cx)
9765 .border_color(cx.theme().colors().border)
9766 .child(
9767 div()
9768 .flex_1()
9769 .py_1()
9770 .px_2()
9771 .overflow_hidden()
9772 .child(completion),
9773 )
9774 .when_some(accept_keystroke, |el, accept_keystroke| {
9775 if !accept_keystroke.modifiers().modified() {
9776 return el;
9777 }
9778
9779 el.child(
9780 h_flex()
9781 .h_full()
9782 .border_l_1()
9783 .rounded_r_lg()
9784 .border_color(cx.theme().colors().border)
9785 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9786 .gap_1()
9787 .py_1()
9788 .px_2()
9789 .child(
9790 h_flex()
9791 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9792 .when(is_platform_style_mac, |parent| parent.gap_1())
9793 .child(h_flex().children(ui::render_modifiers(
9794 accept_keystroke.modifiers(),
9795 PlatformStyle::platform(),
9796 Some(if !has_completion {
9797 Color::Muted
9798 } else {
9799 Color::Default
9800 }),
9801 None,
9802 false,
9803 ))),
9804 )
9805 .child(Label::new("Preview").into_any_element())
9806 .opacity(if has_completion { 1.0 } else { 0.4 }),
9807 )
9808 })
9809 .into_any(),
9810 )
9811 }
9812
9813 fn render_edit_prediction_cursor_popover_preview(
9814 &self,
9815 completion: &EditPredictionState,
9816 cursor_point: Point,
9817 style: &EditorStyle,
9818 cx: &mut Context<Editor>,
9819 ) -> Option<Div> {
9820 use text::ToPoint as _;
9821
9822 fn render_relative_row_jump(
9823 prefix: impl Into<String>,
9824 current_row: u32,
9825 target_row: u32,
9826 ) -> Div {
9827 let (row_diff, arrow) = if target_row < current_row {
9828 (current_row - target_row, IconName::ArrowUp)
9829 } else {
9830 (target_row - current_row, IconName::ArrowDown)
9831 };
9832
9833 h_flex()
9834 .child(
9835 Label::new(format!("{}{}", prefix.into(), row_diff))
9836 .color(Color::Muted)
9837 .size(LabelSize::Small),
9838 )
9839 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9840 }
9841
9842 let supports_jump = self
9843 .edit_prediction_provider
9844 .as_ref()
9845 .map(|provider| provider.provider.supports_jump_to_edit())
9846 .unwrap_or(true);
9847
9848 match &completion.completion {
9849 EditPrediction::MoveWithin {
9850 target, snapshot, ..
9851 } => {
9852 if !supports_jump {
9853 return None;
9854 }
9855
9856 Some(
9857 h_flex()
9858 .px_2()
9859 .gap_2()
9860 .flex_1()
9861 .child(
9862 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9863 Icon::new(IconName::ZedPredictDown)
9864 } else {
9865 Icon::new(IconName::ZedPredictUp)
9866 },
9867 )
9868 .child(Label::new("Jump to Edit")),
9869 )
9870 }
9871 EditPrediction::MoveOutside { snapshot, .. } => {
9872 let file_name = snapshot
9873 .file()
9874 .map(|file| file.file_name(cx))
9875 .unwrap_or("untitled");
9876 Some(
9877 h_flex()
9878 .px_2()
9879 .gap_2()
9880 .flex_1()
9881 .child(Icon::new(IconName::ZedPredict))
9882 .child(Label::new(format!("Jump to {file_name}"))),
9883 )
9884 }
9885 EditPrediction::Edit {
9886 edits,
9887 edit_preview,
9888 snapshot,
9889 display_mode: _,
9890 } => {
9891 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9892
9893 let (highlighted_edits, has_more_lines) =
9894 if let Some(edit_preview) = edit_preview.as_ref() {
9895 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9896 .first_line_preview()
9897 } else {
9898 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9899 };
9900
9901 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9902 .with_default_highlights(&style.text, highlighted_edits.highlights);
9903
9904 let preview = h_flex()
9905 .gap_1()
9906 .min_w_16()
9907 .child(styled_text)
9908 .when(has_more_lines, |parent| parent.child("…"));
9909
9910 let left = if supports_jump && first_edit_row != cursor_point.row {
9911 render_relative_row_jump("", cursor_point.row, first_edit_row)
9912 .into_any_element()
9913 } else {
9914 let icon_name =
9915 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9916 Icon::new(icon_name).into_any_element()
9917 };
9918
9919 Some(
9920 h_flex()
9921 .h_full()
9922 .flex_1()
9923 .gap_2()
9924 .pr_1()
9925 .overflow_x_hidden()
9926 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9927 .child(left)
9928 .child(preview),
9929 )
9930 }
9931 }
9932 }
9933
9934 pub fn render_context_menu(
9935 &mut self,
9936 max_height_in_lines: u32,
9937 window: &mut Window,
9938 cx: &mut Context<Editor>,
9939 ) -> Option<AnyElement> {
9940 let menu = self.context_menu.borrow();
9941 let menu = menu.as_ref()?;
9942 if !menu.visible() {
9943 return None;
9944 };
9945 self.style
9946 .as_ref()
9947 .map(|style| menu.render(style, max_height_in_lines, window, cx))
9948 }
9949
9950 fn render_context_menu_aside(
9951 &mut self,
9952 max_size: Size<Pixels>,
9953 window: &mut Window,
9954 cx: &mut Context<Editor>,
9955 ) -> Option<AnyElement> {
9956 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9957 if menu.visible() {
9958 menu.render_aside(max_size, window, cx)
9959 } else {
9960 None
9961 }
9962 })
9963 }
9964
9965 fn hide_context_menu(
9966 &mut self,
9967 window: &mut Window,
9968 cx: &mut Context<Self>,
9969 ) -> Option<CodeContextMenu> {
9970 cx.notify();
9971 self.completion_tasks.clear();
9972 let context_menu = self.context_menu.borrow_mut().take();
9973 self.stale_edit_prediction_in_menu.take();
9974 self.update_visible_edit_prediction(window, cx);
9975 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9976 && let Some(completion_provider) = &self.completion_provider
9977 {
9978 completion_provider.selection_changed(None, window, cx);
9979 }
9980 context_menu
9981 }
9982
9983 fn show_snippet_choices(
9984 &mut self,
9985 choices: &Vec<String>,
9986 selection: Range<Anchor>,
9987 cx: &mut Context<Self>,
9988 ) {
9989 let Some((_, buffer, _)) = self
9990 .buffer()
9991 .read(cx)
9992 .excerpt_containing(selection.start, cx)
9993 else {
9994 return;
9995 };
9996 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9997 else {
9998 return;
9999 };
10000 if buffer != end_buffer {
10001 log::error!("expected anchor range to have matching buffer IDs");
10002 return;
10003 }
10004
10005 let id = post_inc(&mut self.next_completion_id);
10006 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10007 let mut context_menu = self.context_menu.borrow_mut();
10008 let old_menu = context_menu.take();
10009 *context_menu = Some(CodeContextMenu::Completions(
10010 CompletionsMenu::new_snippet_choices(
10011 id,
10012 true,
10013 choices,
10014 selection,
10015 buffer,
10016 old_menu.map(|menu| menu.primary_scroll_handle()),
10017 snippet_sort_order,
10018 ),
10019 ));
10020 }
10021
10022 pub fn insert_snippet(
10023 &mut self,
10024 insertion_ranges: &[Range<MultiBufferOffset>],
10025 snippet: Snippet,
10026 window: &mut Window,
10027 cx: &mut Context<Self>,
10028 ) -> Result<()> {
10029 struct Tabstop<T> {
10030 is_end_tabstop: bool,
10031 ranges: Vec<Range<T>>,
10032 choices: Option<Vec<String>>,
10033 }
10034
10035 let tabstops = self.buffer.update(cx, |buffer, cx| {
10036 let snippet_text: Arc<str> = snippet.text.clone().into();
10037 let edits = insertion_ranges
10038 .iter()
10039 .cloned()
10040 .map(|range| (range, snippet_text.clone()));
10041 let autoindent_mode = AutoindentMode::Block {
10042 original_indent_columns: Vec::new(),
10043 };
10044 buffer.edit(edits, Some(autoindent_mode), cx);
10045
10046 let snapshot = &*buffer.read(cx);
10047 let snippet = &snippet;
10048 snippet
10049 .tabstops
10050 .iter()
10051 .map(|tabstop| {
10052 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10053 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10054 });
10055 let mut tabstop_ranges = tabstop
10056 .ranges
10057 .iter()
10058 .flat_map(|tabstop_range| {
10059 let mut delta = 0_isize;
10060 insertion_ranges.iter().map(move |insertion_range| {
10061 let insertion_start = insertion_range.start + delta;
10062 delta += snippet.text.len() as isize
10063 - (insertion_range.end - insertion_range.start) as isize;
10064
10065 let start =
10066 (insertion_start + tabstop_range.start).min(snapshot.len());
10067 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10068 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10069 })
10070 })
10071 .collect::<Vec<_>>();
10072 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10073
10074 Tabstop {
10075 is_end_tabstop,
10076 ranges: tabstop_ranges,
10077 choices: tabstop.choices.clone(),
10078 }
10079 })
10080 .collect::<Vec<_>>()
10081 });
10082 if let Some(tabstop) = tabstops.first() {
10083 self.change_selections(Default::default(), window, cx, |s| {
10084 // Reverse order so that the first range is the newest created selection.
10085 // Completions will use it and autoscroll will prioritize it.
10086 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10087 });
10088
10089 if let Some(choices) = &tabstop.choices
10090 && let Some(selection) = tabstop.ranges.first()
10091 {
10092 self.show_snippet_choices(choices, selection.clone(), cx)
10093 }
10094
10095 // If we're already at the last tabstop and it's at the end of the snippet,
10096 // we're done, we don't need to keep the state around.
10097 if !tabstop.is_end_tabstop {
10098 let choices = tabstops
10099 .iter()
10100 .map(|tabstop| tabstop.choices.clone())
10101 .collect();
10102
10103 let ranges = tabstops
10104 .into_iter()
10105 .map(|tabstop| tabstop.ranges)
10106 .collect::<Vec<_>>();
10107
10108 self.snippet_stack.push(SnippetState {
10109 active_index: 0,
10110 ranges,
10111 choices,
10112 });
10113 }
10114
10115 // Check whether the just-entered snippet ends with an auto-closable bracket.
10116 if self.autoclose_regions.is_empty() {
10117 let snapshot = self.buffer.read(cx).snapshot(cx);
10118 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10119 let selection_head = selection.head();
10120 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10121 continue;
10122 };
10123
10124 let mut bracket_pair = None;
10125 let max_lookup_length = scope
10126 .brackets()
10127 .map(|(pair, _)| {
10128 pair.start
10129 .as_str()
10130 .chars()
10131 .count()
10132 .max(pair.end.as_str().chars().count())
10133 })
10134 .max();
10135 if let Some(max_lookup_length) = max_lookup_length {
10136 let next_text = snapshot
10137 .chars_at(selection_head)
10138 .take(max_lookup_length)
10139 .collect::<String>();
10140 let prev_text = snapshot
10141 .reversed_chars_at(selection_head)
10142 .take(max_lookup_length)
10143 .collect::<String>();
10144
10145 for (pair, enabled) in scope.brackets() {
10146 if enabled
10147 && pair.close
10148 && prev_text.starts_with(pair.start.as_str())
10149 && next_text.starts_with(pair.end.as_str())
10150 {
10151 bracket_pair = Some(pair.clone());
10152 break;
10153 }
10154 }
10155 }
10156
10157 if let Some(pair) = bracket_pair {
10158 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10159 let autoclose_enabled =
10160 self.use_autoclose && snapshot_settings.use_autoclose;
10161 if autoclose_enabled {
10162 let start = snapshot.anchor_after(selection_head);
10163 let end = snapshot.anchor_after(selection_head);
10164 self.autoclose_regions.push(AutocloseRegion {
10165 selection_id: selection.id,
10166 range: start..end,
10167 pair,
10168 });
10169 }
10170 }
10171 }
10172 }
10173 }
10174 Ok(())
10175 }
10176
10177 pub fn move_to_next_snippet_tabstop(
10178 &mut self,
10179 window: &mut Window,
10180 cx: &mut Context<Self>,
10181 ) -> bool {
10182 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10183 }
10184
10185 pub fn move_to_prev_snippet_tabstop(
10186 &mut self,
10187 window: &mut Window,
10188 cx: &mut Context<Self>,
10189 ) -> bool {
10190 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10191 }
10192
10193 pub fn move_to_snippet_tabstop(
10194 &mut self,
10195 bias: Bias,
10196 window: &mut Window,
10197 cx: &mut Context<Self>,
10198 ) -> bool {
10199 if let Some(mut snippet) = self.snippet_stack.pop() {
10200 match bias {
10201 Bias::Left => {
10202 if snippet.active_index > 0 {
10203 snippet.active_index -= 1;
10204 } else {
10205 self.snippet_stack.push(snippet);
10206 return false;
10207 }
10208 }
10209 Bias::Right => {
10210 if snippet.active_index + 1 < snippet.ranges.len() {
10211 snippet.active_index += 1;
10212 } else {
10213 self.snippet_stack.push(snippet);
10214 return false;
10215 }
10216 }
10217 }
10218 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10219 self.change_selections(Default::default(), window, cx, |s| {
10220 // Reverse order so that the first range is the newest created selection.
10221 // Completions will use it and autoscroll will prioritize it.
10222 s.select_ranges(current_ranges.iter().rev().cloned())
10223 });
10224
10225 if let Some(choices) = &snippet.choices[snippet.active_index]
10226 && let Some(selection) = current_ranges.first()
10227 {
10228 self.show_snippet_choices(choices, selection.clone(), cx);
10229 }
10230
10231 // If snippet state is not at the last tabstop, push it back on the stack
10232 if snippet.active_index + 1 < snippet.ranges.len() {
10233 self.snippet_stack.push(snippet);
10234 }
10235 return true;
10236 }
10237 }
10238
10239 false
10240 }
10241
10242 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10243 self.transact(window, cx, |this, window, cx| {
10244 this.select_all(&SelectAll, window, cx);
10245 this.insert("", window, cx);
10246 });
10247 }
10248
10249 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10250 if self.read_only(cx) {
10251 return;
10252 }
10253 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10254 self.transact(window, cx, |this, window, cx| {
10255 this.select_autoclose_pair(window, cx);
10256
10257 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10258
10259 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10260 if !this.linked_edit_ranges.is_empty() {
10261 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10262 let snapshot = this.buffer.read(cx).snapshot(cx);
10263
10264 for selection in selections.iter() {
10265 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10266 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10267 if selection_start.buffer_id != selection_end.buffer_id {
10268 continue;
10269 }
10270 if let Some(ranges) =
10271 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10272 {
10273 for (buffer, entries) in ranges {
10274 linked_ranges.entry(buffer).or_default().extend(entries);
10275 }
10276 }
10277 }
10278 }
10279
10280 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10281 for selection in &mut selections {
10282 if selection.is_empty() {
10283 let old_head = selection.head();
10284 let mut new_head =
10285 movement::left(&display_map, old_head.to_display_point(&display_map))
10286 .to_point(&display_map);
10287 if let Some((buffer, line_buffer_range)) = display_map
10288 .buffer_snapshot()
10289 .buffer_line_for_row(MultiBufferRow(old_head.row))
10290 {
10291 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10292 let indent_len = match indent_size.kind {
10293 IndentKind::Space => {
10294 buffer.settings_at(line_buffer_range.start, cx).tab_size
10295 }
10296 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10297 };
10298 if old_head.column <= indent_size.len && old_head.column > 0 {
10299 let indent_len = indent_len.get();
10300 new_head = cmp::min(
10301 new_head,
10302 MultiBufferPoint::new(
10303 old_head.row,
10304 ((old_head.column - 1) / indent_len) * indent_len,
10305 ),
10306 );
10307 }
10308 }
10309
10310 selection.set_head(new_head, SelectionGoal::None);
10311 }
10312 }
10313
10314 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10315 this.insert("", window, cx);
10316 let empty_str: Arc<str> = Arc::from("");
10317 for (buffer, edits) in linked_ranges {
10318 let snapshot = buffer.read(cx).snapshot();
10319 use text::ToPoint as TP;
10320
10321 let edits = edits
10322 .into_iter()
10323 .map(|range| {
10324 let end_point = TP::to_point(&range.end, &snapshot);
10325 let mut start_point = TP::to_point(&range.start, &snapshot);
10326
10327 if end_point == start_point {
10328 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10329 .saturating_sub(1);
10330 start_point =
10331 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10332 };
10333
10334 (start_point..end_point, empty_str.clone())
10335 })
10336 .sorted_by_key(|(range, _)| range.start)
10337 .collect::<Vec<_>>();
10338 buffer.update(cx, |this, cx| {
10339 this.edit(edits, None, cx);
10340 })
10341 }
10342 this.refresh_edit_prediction(true, false, window, cx);
10343 refresh_linked_ranges(this, window, cx);
10344 });
10345 }
10346
10347 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10348 if self.read_only(cx) {
10349 return;
10350 }
10351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10352 self.transact(window, cx, |this, window, cx| {
10353 this.change_selections(Default::default(), window, cx, |s| {
10354 s.move_with(|map, selection| {
10355 if selection.is_empty() {
10356 let cursor = movement::right(map, selection.head());
10357 selection.end = cursor;
10358 selection.reversed = true;
10359 selection.goal = SelectionGoal::None;
10360 }
10361 })
10362 });
10363 this.insert("", window, cx);
10364 this.refresh_edit_prediction(true, false, window, cx);
10365 });
10366 }
10367
10368 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10369 if self.mode.is_single_line() {
10370 cx.propagate();
10371 return;
10372 }
10373
10374 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10375 if self.move_to_prev_snippet_tabstop(window, cx) {
10376 return;
10377 }
10378 self.outdent(&Outdent, window, cx);
10379 }
10380
10381 pub fn next_snippet_tabstop(
10382 &mut self,
10383 _: &NextSnippetTabstop,
10384 window: &mut Window,
10385 cx: &mut Context<Self>,
10386 ) {
10387 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10388 cx.propagate();
10389 return;
10390 }
10391
10392 if self.move_to_next_snippet_tabstop(window, cx) {
10393 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10394 return;
10395 }
10396 cx.propagate();
10397 }
10398
10399 pub fn previous_snippet_tabstop(
10400 &mut self,
10401 _: &PreviousSnippetTabstop,
10402 window: &mut Window,
10403 cx: &mut Context<Self>,
10404 ) {
10405 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10406 cx.propagate();
10407 return;
10408 }
10409
10410 if self.move_to_prev_snippet_tabstop(window, cx) {
10411 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10412 return;
10413 }
10414 cx.propagate();
10415 }
10416
10417 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10418 if self.mode.is_single_line() {
10419 cx.propagate();
10420 return;
10421 }
10422
10423 if self.move_to_next_snippet_tabstop(window, cx) {
10424 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10425 return;
10426 }
10427 if self.read_only(cx) {
10428 return;
10429 }
10430 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10431 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10432 let buffer = self.buffer.read(cx);
10433 let snapshot = buffer.snapshot(cx);
10434 let rows_iter = selections.iter().map(|s| s.head().row);
10435 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10436
10437 let has_some_cursor_in_whitespace = selections
10438 .iter()
10439 .filter(|selection| selection.is_empty())
10440 .any(|selection| {
10441 let cursor = selection.head();
10442 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10443 cursor.column < current_indent.len
10444 });
10445
10446 let mut edits = Vec::new();
10447 let mut prev_edited_row = 0;
10448 let mut row_delta = 0;
10449 for selection in &mut selections {
10450 if selection.start.row != prev_edited_row {
10451 row_delta = 0;
10452 }
10453 prev_edited_row = selection.end.row;
10454
10455 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10456 if selection.is_empty() {
10457 let cursor = selection.head();
10458 let settings = buffer.language_settings_at(cursor, cx);
10459 if settings.indent_list_on_tab {
10460 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10461 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10462 row_delta = Self::indent_selection(
10463 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10464 );
10465 continue;
10466 }
10467 }
10468 }
10469 }
10470
10471 // If the selection is non-empty, then increase the indentation of the selected lines.
10472 if !selection.is_empty() {
10473 row_delta =
10474 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10475 continue;
10476 }
10477
10478 let cursor = selection.head();
10479 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10480 if let Some(suggested_indent) =
10481 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10482 {
10483 // Don't do anything if already at suggested indent
10484 // and there is any other cursor which is not
10485 if has_some_cursor_in_whitespace
10486 && cursor.column == current_indent.len
10487 && current_indent.len == suggested_indent.len
10488 {
10489 continue;
10490 }
10491
10492 // Adjust line and move cursor to suggested indent
10493 // if cursor is not at suggested indent
10494 if cursor.column < suggested_indent.len
10495 && cursor.column <= current_indent.len
10496 && current_indent.len <= suggested_indent.len
10497 {
10498 selection.start = Point::new(cursor.row, suggested_indent.len);
10499 selection.end = selection.start;
10500 if row_delta == 0 {
10501 edits.extend(Buffer::edit_for_indent_size_adjustment(
10502 cursor.row,
10503 current_indent,
10504 suggested_indent,
10505 ));
10506 row_delta = suggested_indent.len - current_indent.len;
10507 }
10508 continue;
10509 }
10510
10511 // If current indent is more than suggested indent
10512 // only move cursor to current indent and skip indent
10513 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10514 selection.start = Point::new(cursor.row, current_indent.len);
10515 selection.end = selection.start;
10516 continue;
10517 }
10518 }
10519
10520 // Otherwise, insert a hard or soft tab.
10521 let settings = buffer.language_settings_at(cursor, cx);
10522 let tab_size = if settings.hard_tabs {
10523 IndentSize::tab()
10524 } else {
10525 let tab_size = settings.tab_size.get();
10526 let indent_remainder = snapshot
10527 .text_for_range(Point::new(cursor.row, 0)..cursor)
10528 .flat_map(str::chars)
10529 .fold(row_delta % tab_size, |counter: u32, c| {
10530 if c == '\t' {
10531 0
10532 } else {
10533 (counter + 1) % tab_size
10534 }
10535 });
10536
10537 let chars_to_next_tab_stop = tab_size - indent_remainder;
10538 IndentSize::spaces(chars_to_next_tab_stop)
10539 };
10540 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10541 selection.end = selection.start;
10542 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10543 row_delta += tab_size.len;
10544 }
10545
10546 self.transact(window, cx, |this, window, cx| {
10547 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10548 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10549 this.refresh_edit_prediction(true, false, window, cx);
10550 });
10551 }
10552
10553 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10554 if self.read_only(cx) {
10555 return;
10556 }
10557 if self.mode.is_single_line() {
10558 cx.propagate();
10559 return;
10560 }
10561
10562 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10563 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10564 let mut prev_edited_row = 0;
10565 let mut row_delta = 0;
10566 let mut edits = Vec::new();
10567 let buffer = self.buffer.read(cx);
10568 let snapshot = buffer.snapshot(cx);
10569 for selection in &mut selections {
10570 if selection.start.row != prev_edited_row {
10571 row_delta = 0;
10572 }
10573 prev_edited_row = selection.end.row;
10574
10575 row_delta =
10576 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10577 }
10578
10579 self.transact(window, cx, |this, window, cx| {
10580 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10581 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10582 });
10583 }
10584
10585 fn indent_selection(
10586 buffer: &MultiBuffer,
10587 snapshot: &MultiBufferSnapshot,
10588 selection: &mut Selection<Point>,
10589 edits: &mut Vec<(Range<Point>, String)>,
10590 delta_for_start_row: u32,
10591 cx: &App,
10592 ) -> u32 {
10593 let settings = buffer.language_settings_at(selection.start, cx);
10594 let tab_size = settings.tab_size.get();
10595 let indent_kind = if settings.hard_tabs {
10596 IndentKind::Tab
10597 } else {
10598 IndentKind::Space
10599 };
10600 let mut start_row = selection.start.row;
10601 let mut end_row = selection.end.row + 1;
10602
10603 // If a selection ends at the beginning of a line, don't indent
10604 // that last line.
10605 if selection.end.column == 0 && selection.end.row > selection.start.row {
10606 end_row -= 1;
10607 }
10608
10609 // Avoid re-indenting a row that has already been indented by a
10610 // previous selection, but still update this selection's column
10611 // to reflect that indentation.
10612 if delta_for_start_row > 0 {
10613 start_row += 1;
10614 selection.start.column += delta_for_start_row;
10615 if selection.end.row == selection.start.row {
10616 selection.end.column += delta_for_start_row;
10617 }
10618 }
10619
10620 let mut delta_for_end_row = 0;
10621 let has_multiple_rows = start_row + 1 != end_row;
10622 for row in start_row..end_row {
10623 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10624 let indent_delta = match (current_indent.kind, indent_kind) {
10625 (IndentKind::Space, IndentKind::Space) => {
10626 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10627 IndentSize::spaces(columns_to_next_tab_stop)
10628 }
10629 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10630 (_, IndentKind::Tab) => IndentSize::tab(),
10631 };
10632
10633 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10634 0
10635 } else {
10636 selection.start.column
10637 };
10638 let row_start = Point::new(row, start);
10639 edits.push((
10640 row_start..row_start,
10641 indent_delta.chars().collect::<String>(),
10642 ));
10643
10644 // Update this selection's endpoints to reflect the indentation.
10645 if row == selection.start.row {
10646 selection.start.column += indent_delta.len;
10647 }
10648 if row == selection.end.row {
10649 selection.end.column += indent_delta.len;
10650 delta_for_end_row = indent_delta.len;
10651 }
10652 }
10653
10654 if selection.start.row == selection.end.row {
10655 delta_for_start_row + delta_for_end_row
10656 } else {
10657 delta_for_end_row
10658 }
10659 }
10660
10661 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10662 if self.read_only(cx) {
10663 return;
10664 }
10665 if self.mode.is_single_line() {
10666 cx.propagate();
10667 return;
10668 }
10669
10670 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10671 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10672 let selections = self.selections.all::<Point>(&display_map);
10673 let mut deletion_ranges = Vec::new();
10674 let mut last_outdent = None;
10675 {
10676 let buffer = self.buffer.read(cx);
10677 let snapshot = buffer.snapshot(cx);
10678 for selection in &selections {
10679 let settings = buffer.language_settings_at(selection.start, cx);
10680 let tab_size = settings.tab_size.get();
10681 let mut rows = selection.spanned_rows(false, &display_map);
10682
10683 // Avoid re-outdenting a row that has already been outdented by a
10684 // previous selection.
10685 if let Some(last_row) = last_outdent
10686 && last_row == rows.start
10687 {
10688 rows.start = rows.start.next_row();
10689 }
10690 let has_multiple_rows = rows.len() > 1;
10691 for row in rows.iter_rows() {
10692 let indent_size = snapshot.indent_size_for_line(row);
10693 if indent_size.len > 0 {
10694 let deletion_len = match indent_size.kind {
10695 IndentKind::Space => {
10696 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10697 if columns_to_prev_tab_stop == 0 {
10698 tab_size
10699 } else {
10700 columns_to_prev_tab_stop
10701 }
10702 }
10703 IndentKind::Tab => 1,
10704 };
10705 let start = if has_multiple_rows
10706 || deletion_len > selection.start.column
10707 || indent_size.len < selection.start.column
10708 {
10709 0
10710 } else {
10711 selection.start.column - deletion_len
10712 };
10713 deletion_ranges.push(
10714 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10715 );
10716 last_outdent = Some(row);
10717 }
10718 }
10719 }
10720 }
10721
10722 self.transact(window, cx, |this, window, cx| {
10723 this.buffer.update(cx, |buffer, cx| {
10724 let empty_str: Arc<str> = Arc::default();
10725 buffer.edit(
10726 deletion_ranges
10727 .into_iter()
10728 .map(|range| (range, empty_str.clone())),
10729 None,
10730 cx,
10731 );
10732 });
10733 let selections = this
10734 .selections
10735 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10736 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10737 });
10738 }
10739
10740 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10741 if self.read_only(cx) {
10742 return;
10743 }
10744 if self.mode.is_single_line() {
10745 cx.propagate();
10746 return;
10747 }
10748
10749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10750 let selections = self
10751 .selections
10752 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10753 .into_iter()
10754 .map(|s| s.range());
10755
10756 self.transact(window, cx, |this, window, cx| {
10757 this.buffer.update(cx, |buffer, cx| {
10758 buffer.autoindent_ranges(selections, cx);
10759 });
10760 let selections = this
10761 .selections
10762 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10763 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10764 });
10765 }
10766
10767 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10768 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10769 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10770 let selections = self.selections.all::<Point>(&display_map);
10771
10772 let mut new_cursors = Vec::new();
10773 let mut edit_ranges = Vec::new();
10774 let mut selections = selections.iter().peekable();
10775 while let Some(selection) = selections.next() {
10776 let mut rows = selection.spanned_rows(false, &display_map);
10777
10778 // Accumulate contiguous regions of rows that we want to delete.
10779 while let Some(next_selection) = selections.peek() {
10780 let next_rows = next_selection.spanned_rows(false, &display_map);
10781 if next_rows.start <= rows.end {
10782 rows.end = next_rows.end;
10783 selections.next().unwrap();
10784 } else {
10785 break;
10786 }
10787 }
10788
10789 let buffer = display_map.buffer_snapshot();
10790 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10791 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10792 // If there's a line after the range, delete the \n from the end of the row range
10793 (
10794 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10795 rows.end,
10796 )
10797 } else {
10798 // If there isn't a line after the range, delete the \n from the line before the
10799 // start of the row range
10800 edit_start = edit_start.saturating_sub_usize(1);
10801 (buffer.len(), rows.start.previous_row())
10802 };
10803
10804 let text_layout_details = self.text_layout_details(window);
10805 let x = display_map.x_for_display_point(
10806 selection.head().to_display_point(&display_map),
10807 &text_layout_details,
10808 );
10809 let row = Point::new(target_row.0, 0)
10810 .to_display_point(&display_map)
10811 .row();
10812 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10813
10814 new_cursors.push((
10815 selection.id,
10816 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10817 SelectionGoal::None,
10818 ));
10819 edit_ranges.push(edit_start..edit_end);
10820 }
10821
10822 self.transact(window, cx, |this, window, cx| {
10823 let buffer = this.buffer.update(cx, |buffer, cx| {
10824 let empty_str: Arc<str> = Arc::default();
10825 buffer.edit(
10826 edit_ranges
10827 .into_iter()
10828 .map(|range| (range, empty_str.clone())),
10829 None,
10830 cx,
10831 );
10832 buffer.snapshot(cx)
10833 });
10834 let new_selections = new_cursors
10835 .into_iter()
10836 .map(|(id, cursor, goal)| {
10837 let cursor = cursor.to_point(&buffer);
10838 Selection {
10839 id,
10840 start: cursor,
10841 end: cursor,
10842 reversed: false,
10843 goal,
10844 }
10845 })
10846 .collect();
10847
10848 this.change_selections(Default::default(), window, cx, |s| {
10849 s.select(new_selections);
10850 });
10851 });
10852 }
10853
10854 pub fn join_lines_impl(
10855 &mut self,
10856 insert_whitespace: bool,
10857 window: &mut Window,
10858 cx: &mut Context<Self>,
10859 ) {
10860 if self.read_only(cx) {
10861 return;
10862 }
10863 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10864 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10865 let start = MultiBufferRow(selection.start.row);
10866 // Treat single line selections as if they include the next line. Otherwise this action
10867 // would do nothing for single line selections individual cursors.
10868 let end = if selection.start.row == selection.end.row {
10869 MultiBufferRow(selection.start.row + 1)
10870 } else {
10871 MultiBufferRow(selection.end.row)
10872 };
10873
10874 if let Some(last_row_range) = row_ranges.last_mut()
10875 && start <= last_row_range.end
10876 {
10877 last_row_range.end = end;
10878 continue;
10879 }
10880 row_ranges.push(start..end);
10881 }
10882
10883 let snapshot = self.buffer.read(cx).snapshot(cx);
10884 let mut cursor_positions = Vec::new();
10885 for row_range in &row_ranges {
10886 let anchor = snapshot.anchor_before(Point::new(
10887 row_range.end.previous_row().0,
10888 snapshot.line_len(row_range.end.previous_row()),
10889 ));
10890 cursor_positions.push(anchor..anchor);
10891 }
10892
10893 self.transact(window, cx, |this, window, cx| {
10894 for row_range in row_ranges.into_iter().rev() {
10895 for row in row_range.iter_rows().rev() {
10896 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10897 let next_line_row = row.next_row();
10898 let indent = snapshot.indent_size_for_line(next_line_row);
10899 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10900
10901 let replace =
10902 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10903 " "
10904 } else {
10905 ""
10906 };
10907
10908 this.buffer.update(cx, |buffer, cx| {
10909 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10910 });
10911 }
10912 }
10913
10914 this.change_selections(Default::default(), window, cx, |s| {
10915 s.select_anchor_ranges(cursor_positions)
10916 });
10917 });
10918 }
10919
10920 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10921 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10922 self.join_lines_impl(true, window, cx);
10923 }
10924
10925 pub fn sort_lines_case_sensitive(
10926 &mut self,
10927 _: &SortLinesCaseSensitive,
10928 window: &mut Window,
10929 cx: &mut Context<Self>,
10930 ) {
10931 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10932 }
10933
10934 pub fn sort_lines_by_length(
10935 &mut self,
10936 _: &SortLinesByLength,
10937 window: &mut Window,
10938 cx: &mut Context<Self>,
10939 ) {
10940 self.manipulate_immutable_lines(window, cx, |lines| {
10941 lines.sort_by_key(|&line| line.chars().count())
10942 })
10943 }
10944
10945 pub fn sort_lines_case_insensitive(
10946 &mut self,
10947 _: &SortLinesCaseInsensitive,
10948 window: &mut Window,
10949 cx: &mut Context<Self>,
10950 ) {
10951 self.manipulate_immutable_lines(window, cx, |lines| {
10952 lines.sort_by_key(|line| line.to_lowercase())
10953 })
10954 }
10955
10956 pub fn unique_lines_case_insensitive(
10957 &mut self,
10958 _: &UniqueLinesCaseInsensitive,
10959 window: &mut Window,
10960 cx: &mut Context<Self>,
10961 ) {
10962 self.manipulate_immutable_lines(window, cx, |lines| {
10963 let mut seen = HashSet::default();
10964 lines.retain(|line| seen.insert(line.to_lowercase()));
10965 })
10966 }
10967
10968 pub fn unique_lines_case_sensitive(
10969 &mut self,
10970 _: &UniqueLinesCaseSensitive,
10971 window: &mut Window,
10972 cx: &mut Context<Self>,
10973 ) {
10974 self.manipulate_immutable_lines(window, cx, |lines| {
10975 let mut seen = HashSet::default();
10976 lines.retain(|line| seen.insert(*line));
10977 })
10978 }
10979
10980 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10981 let snapshot = self.buffer.read(cx).snapshot(cx);
10982 for selection in self.selections.disjoint_anchors_arc().iter() {
10983 if snapshot
10984 .language_at(selection.start)
10985 .and_then(|lang| lang.config().wrap_characters.as_ref())
10986 .is_some()
10987 {
10988 return true;
10989 }
10990 }
10991 false
10992 }
10993
10994 fn wrap_selections_in_tag(
10995 &mut self,
10996 _: &WrapSelectionsInTag,
10997 window: &mut Window,
10998 cx: &mut Context<Self>,
10999 ) {
11000 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11001
11002 let snapshot = self.buffer.read(cx).snapshot(cx);
11003
11004 let mut edits = Vec::new();
11005 let mut boundaries = Vec::new();
11006
11007 for selection in self
11008 .selections
11009 .all_adjusted(&self.display_snapshot(cx))
11010 .iter()
11011 {
11012 let Some(wrap_config) = snapshot
11013 .language_at(selection.start)
11014 .and_then(|lang| lang.config().wrap_characters.clone())
11015 else {
11016 continue;
11017 };
11018
11019 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11020 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11021
11022 let start_before = snapshot.anchor_before(selection.start);
11023 let end_after = snapshot.anchor_after(selection.end);
11024
11025 edits.push((start_before..start_before, open_tag));
11026 edits.push((end_after..end_after, close_tag));
11027
11028 boundaries.push((
11029 start_before,
11030 end_after,
11031 wrap_config.start_prefix.len(),
11032 wrap_config.end_suffix.len(),
11033 ));
11034 }
11035
11036 if edits.is_empty() {
11037 return;
11038 }
11039
11040 self.transact(window, cx, |this, window, cx| {
11041 let buffer = this.buffer.update(cx, |buffer, cx| {
11042 buffer.edit(edits, None, cx);
11043 buffer.snapshot(cx)
11044 });
11045
11046 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11047 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11048 boundaries.into_iter()
11049 {
11050 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11051 let close_offset = end_after
11052 .to_offset(&buffer)
11053 .saturating_sub_usize(end_suffix_len);
11054 new_selections.push(open_offset..open_offset);
11055 new_selections.push(close_offset..close_offset);
11056 }
11057
11058 this.change_selections(Default::default(), window, cx, |s| {
11059 s.select_ranges(new_selections);
11060 });
11061
11062 this.request_autoscroll(Autoscroll::fit(), cx);
11063 });
11064 }
11065
11066 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11067 let Some(project) = self.project.clone() else {
11068 return;
11069 };
11070 self.reload(project, window, cx)
11071 .detach_and_notify_err(window, cx);
11072 }
11073
11074 pub fn restore_file(
11075 &mut self,
11076 _: &::git::RestoreFile,
11077 window: &mut Window,
11078 cx: &mut Context<Self>,
11079 ) {
11080 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11081 let mut buffer_ids = HashSet::default();
11082 let snapshot = self.buffer().read(cx).snapshot(cx);
11083 for selection in self
11084 .selections
11085 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11086 {
11087 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11088 }
11089
11090 let buffer = self.buffer().read(cx);
11091 let ranges = buffer_ids
11092 .into_iter()
11093 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11094 .collect::<Vec<_>>();
11095
11096 self.restore_hunks_in_ranges(ranges, window, cx);
11097 }
11098
11099 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11100 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11101 let selections = self
11102 .selections
11103 .all(&self.display_snapshot(cx))
11104 .into_iter()
11105 .map(|s| s.range())
11106 .collect();
11107 self.restore_hunks_in_ranges(selections, window, cx);
11108 }
11109
11110 pub fn restore_hunks_in_ranges(
11111 &mut self,
11112 ranges: Vec<Range<Point>>,
11113 window: &mut Window,
11114 cx: &mut Context<Editor>,
11115 ) {
11116 let mut revert_changes = HashMap::default();
11117 let chunk_by = self
11118 .snapshot(window, cx)
11119 .hunks_for_ranges(ranges)
11120 .into_iter()
11121 .chunk_by(|hunk| hunk.buffer_id);
11122 for (buffer_id, hunks) in &chunk_by {
11123 let hunks = hunks.collect::<Vec<_>>();
11124 for hunk in &hunks {
11125 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11126 }
11127 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11128 }
11129 drop(chunk_by);
11130 if !revert_changes.is_empty() {
11131 self.transact(window, cx, |editor, window, cx| {
11132 editor.restore(revert_changes, window, cx);
11133 });
11134 }
11135 }
11136
11137 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11138 if let Some(status) = self
11139 .addons
11140 .iter()
11141 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11142 {
11143 return Some(status);
11144 }
11145 self.project
11146 .as_ref()?
11147 .read(cx)
11148 .status_for_buffer_id(buffer_id, cx)
11149 }
11150
11151 pub fn open_active_item_in_terminal(
11152 &mut self,
11153 _: &OpenInTerminal,
11154 window: &mut Window,
11155 cx: &mut Context<Self>,
11156 ) {
11157 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11158 let project_path = buffer.read(cx).project_path(cx)?;
11159 let project = self.project()?.read(cx);
11160 let entry = project.entry_for_path(&project_path, cx)?;
11161 let parent = match &entry.canonical_path {
11162 Some(canonical_path) => canonical_path.to_path_buf(),
11163 None => project.absolute_path(&project_path, cx)?,
11164 }
11165 .parent()?
11166 .to_path_buf();
11167 Some(parent)
11168 }) {
11169 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11170 }
11171 }
11172
11173 fn set_breakpoint_context_menu(
11174 &mut self,
11175 display_row: DisplayRow,
11176 position: Option<Anchor>,
11177 clicked_point: gpui::Point<Pixels>,
11178 window: &mut Window,
11179 cx: &mut Context<Self>,
11180 ) {
11181 let source = self
11182 .buffer
11183 .read(cx)
11184 .snapshot(cx)
11185 .anchor_before(Point::new(display_row.0, 0u32));
11186
11187 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11188
11189 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11190 self,
11191 source,
11192 clicked_point,
11193 context_menu,
11194 window,
11195 cx,
11196 );
11197 }
11198
11199 fn add_edit_breakpoint_block(
11200 &mut self,
11201 anchor: Anchor,
11202 breakpoint: &Breakpoint,
11203 edit_action: BreakpointPromptEditAction,
11204 window: &mut Window,
11205 cx: &mut Context<Self>,
11206 ) {
11207 let weak_editor = cx.weak_entity();
11208 let bp_prompt = cx.new(|cx| {
11209 BreakpointPromptEditor::new(
11210 weak_editor,
11211 anchor,
11212 breakpoint.clone(),
11213 edit_action,
11214 window,
11215 cx,
11216 )
11217 });
11218
11219 let height = bp_prompt.update(cx, |this, cx| {
11220 this.prompt
11221 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11222 });
11223 let cloned_prompt = bp_prompt.clone();
11224 let blocks = vec![BlockProperties {
11225 style: BlockStyle::Sticky,
11226 placement: BlockPlacement::Above(anchor),
11227 height: Some(height),
11228 render: Arc::new(move |cx| {
11229 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11230 cloned_prompt.clone().into_any_element()
11231 }),
11232 priority: 0,
11233 }];
11234
11235 let focus_handle = bp_prompt.focus_handle(cx);
11236 window.focus(&focus_handle, cx);
11237
11238 let block_ids = self.insert_blocks(blocks, None, cx);
11239 bp_prompt.update(cx, |prompt, _| {
11240 prompt.add_block_ids(block_ids);
11241 });
11242 }
11243
11244 pub(crate) fn breakpoint_at_row(
11245 &self,
11246 row: u32,
11247 window: &mut Window,
11248 cx: &mut Context<Self>,
11249 ) -> Option<(Anchor, Breakpoint)> {
11250 let snapshot = self.snapshot(window, cx);
11251 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11252
11253 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11254 }
11255
11256 pub(crate) fn breakpoint_at_anchor(
11257 &self,
11258 breakpoint_position: Anchor,
11259 snapshot: &EditorSnapshot,
11260 cx: &mut Context<Self>,
11261 ) -> Option<(Anchor, Breakpoint)> {
11262 let buffer = self
11263 .buffer
11264 .read(cx)
11265 .buffer_for_anchor(breakpoint_position, cx)?;
11266
11267 let enclosing_excerpt = breakpoint_position.excerpt_id;
11268 let buffer_snapshot = buffer.read(cx).snapshot();
11269
11270 let row = buffer_snapshot
11271 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11272 .row;
11273
11274 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11275 let anchor_end = snapshot
11276 .buffer_snapshot()
11277 .anchor_after(Point::new(row, line_len));
11278
11279 self.breakpoint_store
11280 .as_ref()?
11281 .read_with(cx, |breakpoint_store, cx| {
11282 breakpoint_store
11283 .breakpoints(
11284 &buffer,
11285 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11286 &buffer_snapshot,
11287 cx,
11288 )
11289 .next()
11290 .and_then(|(bp, _)| {
11291 let breakpoint_row = buffer_snapshot
11292 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11293 .row;
11294
11295 if breakpoint_row == row {
11296 snapshot
11297 .buffer_snapshot()
11298 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11299 .map(|position| (position, bp.bp.clone()))
11300 } else {
11301 None
11302 }
11303 })
11304 })
11305 }
11306
11307 pub fn edit_log_breakpoint(
11308 &mut self,
11309 _: &EditLogBreakpoint,
11310 window: &mut Window,
11311 cx: &mut Context<Self>,
11312 ) {
11313 if self.breakpoint_store.is_none() {
11314 return;
11315 }
11316
11317 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11318 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11319 message: None,
11320 state: BreakpointState::Enabled,
11321 condition: None,
11322 hit_condition: None,
11323 });
11324
11325 self.add_edit_breakpoint_block(
11326 anchor,
11327 &breakpoint,
11328 BreakpointPromptEditAction::Log,
11329 window,
11330 cx,
11331 );
11332 }
11333 }
11334
11335 fn breakpoints_at_cursors(
11336 &self,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11340 let snapshot = self.snapshot(window, cx);
11341 let cursors = self
11342 .selections
11343 .disjoint_anchors_arc()
11344 .iter()
11345 .map(|selection| {
11346 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11347
11348 let breakpoint_position = self
11349 .breakpoint_at_row(cursor_position.row, window, cx)
11350 .map(|bp| bp.0)
11351 .unwrap_or_else(|| {
11352 snapshot
11353 .display_snapshot
11354 .buffer_snapshot()
11355 .anchor_after(Point::new(cursor_position.row, 0))
11356 });
11357
11358 let breakpoint = self
11359 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11360 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11361
11362 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11363 })
11364 // 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.
11365 .collect::<HashMap<Anchor, _>>();
11366
11367 cursors.into_iter().collect()
11368 }
11369
11370 pub fn enable_breakpoint(
11371 &mut self,
11372 _: &crate::actions::EnableBreakpoint,
11373 window: &mut Window,
11374 cx: &mut Context<Self>,
11375 ) {
11376 if self.breakpoint_store.is_none() {
11377 return;
11378 }
11379
11380 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11381 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11382 continue;
11383 };
11384 self.edit_breakpoint_at_anchor(
11385 anchor,
11386 breakpoint,
11387 BreakpointEditAction::InvertState,
11388 cx,
11389 );
11390 }
11391 }
11392
11393 pub fn disable_breakpoint(
11394 &mut self,
11395 _: &crate::actions::DisableBreakpoint,
11396 window: &mut Window,
11397 cx: &mut Context<Self>,
11398 ) {
11399 if self.breakpoint_store.is_none() {
11400 return;
11401 }
11402
11403 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11404 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11405 continue;
11406 };
11407 self.edit_breakpoint_at_anchor(
11408 anchor,
11409 breakpoint,
11410 BreakpointEditAction::InvertState,
11411 cx,
11412 );
11413 }
11414 }
11415
11416 pub fn toggle_breakpoint(
11417 &mut self,
11418 _: &crate::actions::ToggleBreakpoint,
11419 window: &mut Window,
11420 cx: &mut Context<Self>,
11421 ) {
11422 if self.breakpoint_store.is_none() {
11423 return;
11424 }
11425
11426 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11427 if let Some(breakpoint) = breakpoint {
11428 self.edit_breakpoint_at_anchor(
11429 anchor,
11430 breakpoint,
11431 BreakpointEditAction::Toggle,
11432 cx,
11433 );
11434 } else {
11435 self.edit_breakpoint_at_anchor(
11436 anchor,
11437 Breakpoint::new_standard(),
11438 BreakpointEditAction::Toggle,
11439 cx,
11440 );
11441 }
11442 }
11443 }
11444
11445 pub fn edit_breakpoint_at_anchor(
11446 &mut self,
11447 breakpoint_position: Anchor,
11448 breakpoint: Breakpoint,
11449 edit_action: BreakpointEditAction,
11450 cx: &mut Context<Self>,
11451 ) {
11452 let Some(breakpoint_store) = &self.breakpoint_store else {
11453 return;
11454 };
11455
11456 let Some(buffer) = self
11457 .buffer
11458 .read(cx)
11459 .buffer_for_anchor(breakpoint_position, cx)
11460 else {
11461 return;
11462 };
11463
11464 breakpoint_store.update(cx, |breakpoint_store, cx| {
11465 breakpoint_store.toggle_breakpoint(
11466 buffer,
11467 BreakpointWithPosition {
11468 position: breakpoint_position.text_anchor,
11469 bp: breakpoint,
11470 },
11471 edit_action,
11472 cx,
11473 );
11474 });
11475
11476 cx.notify();
11477 }
11478
11479 #[cfg(any(test, feature = "test-support"))]
11480 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11481 self.breakpoint_store.clone()
11482 }
11483
11484 pub fn prepare_restore_change(
11485 &self,
11486 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11487 hunk: &MultiBufferDiffHunk,
11488 cx: &mut App,
11489 ) -> Option<()> {
11490 if hunk.is_created_file() {
11491 return None;
11492 }
11493 let buffer = self.buffer.read(cx);
11494 let diff = buffer.diff_for(hunk.buffer_id)?;
11495 let buffer = buffer.buffer(hunk.buffer_id)?;
11496 let buffer = buffer.read(cx);
11497 let original_text = diff
11498 .read(cx)
11499 .base_text()
11500 .as_rope()
11501 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11502 let buffer_snapshot = buffer.snapshot();
11503 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11504 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11505 probe
11506 .0
11507 .start
11508 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11509 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11510 }) {
11511 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11512 Some(())
11513 } else {
11514 None
11515 }
11516 }
11517
11518 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11519 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11520 }
11521
11522 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11523 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11524 }
11525
11526 pub fn rotate_selections_forward(
11527 &mut self,
11528 _: &RotateSelectionsForward,
11529 window: &mut Window,
11530 cx: &mut Context<Self>,
11531 ) {
11532 self.rotate_selections(window, cx, false)
11533 }
11534
11535 pub fn rotate_selections_backward(
11536 &mut self,
11537 _: &RotateSelectionsBackward,
11538 window: &mut Window,
11539 cx: &mut Context<Self>,
11540 ) {
11541 self.rotate_selections(window, cx, true)
11542 }
11543
11544 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11545 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11546 let display_snapshot = self.display_snapshot(cx);
11547 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11548
11549 if selections.len() < 2 {
11550 return;
11551 }
11552
11553 let (edits, new_selections) = {
11554 let buffer = self.buffer.read(cx).read(cx);
11555 let has_selections = selections.iter().any(|s| !s.is_empty());
11556 if has_selections {
11557 let mut selected_texts: Vec<String> = selections
11558 .iter()
11559 .map(|selection| {
11560 buffer
11561 .text_for_range(selection.start..selection.end)
11562 .collect()
11563 })
11564 .collect();
11565
11566 if reverse {
11567 selected_texts.rotate_left(1);
11568 } else {
11569 selected_texts.rotate_right(1);
11570 }
11571
11572 let mut offset_delta: i64 = 0;
11573 let mut new_selections = Vec::new();
11574 let edits: Vec<_> = selections
11575 .iter()
11576 .zip(selected_texts.iter())
11577 .map(|(selection, new_text)| {
11578 let old_len = (selection.end.0 - selection.start.0) as i64;
11579 let new_len = new_text.len() as i64;
11580 let adjusted_start =
11581 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11582 let adjusted_end =
11583 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11584
11585 new_selections.push(Selection {
11586 id: selection.id,
11587 start: adjusted_start,
11588 end: adjusted_end,
11589 reversed: selection.reversed,
11590 goal: selection.goal,
11591 });
11592
11593 offset_delta += new_len - old_len;
11594 (selection.start..selection.end, new_text.clone())
11595 })
11596 .collect();
11597 (edits, new_selections)
11598 } else {
11599 let mut all_rows: Vec<u32> = selections
11600 .iter()
11601 .map(|selection| buffer.offset_to_point(selection.start).row)
11602 .collect();
11603 all_rows.sort_unstable();
11604 all_rows.dedup();
11605
11606 if all_rows.len() < 2 {
11607 return;
11608 }
11609
11610 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11611 .iter()
11612 .map(|&row| {
11613 let start = Point::new(row, 0);
11614 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11615 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11616 })
11617 .collect();
11618
11619 let mut line_texts: Vec<String> = line_ranges
11620 .iter()
11621 .map(|range| buffer.text_for_range(range.clone()).collect())
11622 .collect();
11623
11624 if reverse {
11625 line_texts.rotate_left(1);
11626 } else {
11627 line_texts.rotate_right(1);
11628 }
11629
11630 let edits = line_ranges
11631 .iter()
11632 .zip(line_texts.iter())
11633 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11634 .collect();
11635
11636 let num_rows = all_rows.len();
11637 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11638 .iter()
11639 .enumerate()
11640 .map(|(i, &row)| (row, i))
11641 .collect();
11642
11643 // Compute new line start offsets after rotation (handles CRLF)
11644 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11645 let first_line_start = line_ranges[0].start.0;
11646 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11647 for text in line_texts.iter().take(num_rows - 1) {
11648 let prev_start = *new_line_starts.last().unwrap();
11649 new_line_starts.push(prev_start + text.len() + newline_len);
11650 }
11651
11652 let new_selections = selections
11653 .iter()
11654 .map(|selection| {
11655 let point = buffer.offset_to_point(selection.start);
11656 let old_index = row_to_index[&point.row];
11657 let new_index = if reverse {
11658 (old_index + num_rows - 1) % num_rows
11659 } else {
11660 (old_index + 1) % num_rows
11661 };
11662 let new_offset =
11663 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11664 Selection {
11665 id: selection.id,
11666 start: new_offset,
11667 end: new_offset,
11668 reversed: selection.reversed,
11669 goal: selection.goal,
11670 }
11671 })
11672 .collect();
11673
11674 (edits, new_selections)
11675 }
11676 };
11677
11678 self.transact(window, cx, |this, window, cx| {
11679 this.buffer.update(cx, |buffer, cx| {
11680 buffer.edit(edits, None, cx);
11681 });
11682 this.change_selections(Default::default(), window, cx, |s| {
11683 s.select(new_selections);
11684 });
11685 });
11686 }
11687
11688 fn manipulate_lines<M>(
11689 &mut self,
11690 window: &mut Window,
11691 cx: &mut Context<Self>,
11692 mut manipulate: M,
11693 ) where
11694 M: FnMut(&str) -> LineManipulationResult,
11695 {
11696 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11697
11698 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11699 let buffer = self.buffer.read(cx).snapshot(cx);
11700
11701 let mut edits = Vec::new();
11702
11703 let selections = self.selections.all::<Point>(&display_map);
11704 let mut selections = selections.iter().peekable();
11705 let mut contiguous_row_selections = Vec::new();
11706 let mut new_selections = Vec::new();
11707 let mut added_lines = 0;
11708 let mut removed_lines = 0;
11709
11710 while let Some(selection) = selections.next() {
11711 let (start_row, end_row) = consume_contiguous_rows(
11712 &mut contiguous_row_selections,
11713 selection,
11714 &display_map,
11715 &mut selections,
11716 );
11717
11718 let start_point = Point::new(start_row.0, 0);
11719 let end_point = Point::new(
11720 end_row.previous_row().0,
11721 buffer.line_len(end_row.previous_row()),
11722 );
11723 let text = buffer
11724 .text_for_range(start_point..end_point)
11725 .collect::<String>();
11726
11727 let LineManipulationResult {
11728 new_text,
11729 line_count_before,
11730 line_count_after,
11731 } = manipulate(&text);
11732
11733 edits.push((start_point..end_point, new_text));
11734
11735 // Selections must change based on added and removed line count
11736 let start_row =
11737 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11738 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11739 new_selections.push(Selection {
11740 id: selection.id,
11741 start: start_row,
11742 end: end_row,
11743 goal: SelectionGoal::None,
11744 reversed: selection.reversed,
11745 });
11746
11747 if line_count_after > line_count_before {
11748 added_lines += line_count_after - line_count_before;
11749 } else if line_count_before > line_count_after {
11750 removed_lines += line_count_before - line_count_after;
11751 }
11752 }
11753
11754 self.transact(window, cx, |this, window, cx| {
11755 let buffer = this.buffer.update(cx, |buffer, cx| {
11756 buffer.edit(edits, None, cx);
11757 buffer.snapshot(cx)
11758 });
11759
11760 // Recalculate offsets on newly edited buffer
11761 let new_selections = new_selections
11762 .iter()
11763 .map(|s| {
11764 let start_point = Point::new(s.start.0, 0);
11765 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11766 Selection {
11767 id: s.id,
11768 start: buffer.point_to_offset(start_point),
11769 end: buffer.point_to_offset(end_point),
11770 goal: s.goal,
11771 reversed: s.reversed,
11772 }
11773 })
11774 .collect();
11775
11776 this.change_selections(Default::default(), window, cx, |s| {
11777 s.select(new_selections);
11778 });
11779
11780 this.request_autoscroll(Autoscroll::fit(), cx);
11781 });
11782 }
11783
11784 fn manipulate_immutable_lines<Fn>(
11785 &mut self,
11786 window: &mut Window,
11787 cx: &mut Context<Self>,
11788 mut callback: Fn,
11789 ) where
11790 Fn: FnMut(&mut Vec<&str>),
11791 {
11792 self.manipulate_lines(window, cx, |text| {
11793 let mut lines: Vec<&str> = text.split('\n').collect();
11794 let line_count_before = lines.len();
11795
11796 callback(&mut lines);
11797
11798 LineManipulationResult {
11799 new_text: lines.join("\n"),
11800 line_count_before,
11801 line_count_after: lines.len(),
11802 }
11803 });
11804 }
11805
11806 fn manipulate_mutable_lines<Fn>(
11807 &mut self,
11808 window: &mut Window,
11809 cx: &mut Context<Self>,
11810 mut callback: Fn,
11811 ) where
11812 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11813 {
11814 self.manipulate_lines(window, cx, |text| {
11815 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11816 let line_count_before = lines.len();
11817
11818 callback(&mut lines);
11819
11820 LineManipulationResult {
11821 new_text: lines.join("\n"),
11822 line_count_before,
11823 line_count_after: lines.len(),
11824 }
11825 });
11826 }
11827
11828 pub fn convert_indentation_to_spaces(
11829 &mut self,
11830 _: &ConvertIndentationToSpaces,
11831 window: &mut Window,
11832 cx: &mut Context<Self>,
11833 ) {
11834 let settings = self.buffer.read(cx).language_settings(cx);
11835 let tab_size = settings.tab_size.get() as usize;
11836
11837 self.manipulate_mutable_lines(window, cx, |lines| {
11838 // Allocates a reasonably sized scratch buffer once for the whole loop
11839 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11840 // Avoids recomputing spaces that could be inserted many times
11841 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11842 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11843 .collect();
11844
11845 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11846 let mut chars = line.as_ref().chars();
11847 let mut col = 0;
11848 let mut changed = false;
11849
11850 for ch in chars.by_ref() {
11851 match ch {
11852 ' ' => {
11853 reindented_line.push(' ');
11854 col += 1;
11855 }
11856 '\t' => {
11857 // \t are converted to spaces depending on the current column
11858 let spaces_len = tab_size - (col % tab_size);
11859 reindented_line.extend(&space_cache[spaces_len - 1]);
11860 col += spaces_len;
11861 changed = true;
11862 }
11863 _ => {
11864 // If we dont append before break, the character is consumed
11865 reindented_line.push(ch);
11866 break;
11867 }
11868 }
11869 }
11870
11871 if !changed {
11872 reindented_line.clear();
11873 continue;
11874 }
11875 // Append the rest of the line and replace old reference with new one
11876 reindented_line.extend(chars);
11877 *line = Cow::Owned(reindented_line.clone());
11878 reindented_line.clear();
11879 }
11880 });
11881 }
11882
11883 pub fn convert_indentation_to_tabs(
11884 &mut self,
11885 _: &ConvertIndentationToTabs,
11886 window: &mut Window,
11887 cx: &mut Context<Self>,
11888 ) {
11889 let settings = self.buffer.read(cx).language_settings(cx);
11890 let tab_size = settings.tab_size.get() as usize;
11891
11892 self.manipulate_mutable_lines(window, cx, |lines| {
11893 // Allocates a reasonably sized buffer once for the whole loop
11894 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11895 // Avoids recomputing spaces that could be inserted many times
11896 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11897 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11898 .collect();
11899
11900 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11901 let mut chars = line.chars();
11902 let mut spaces_count = 0;
11903 let mut first_non_indent_char = None;
11904 let mut changed = false;
11905
11906 for ch in chars.by_ref() {
11907 match ch {
11908 ' ' => {
11909 // Keep track of spaces. Append \t when we reach tab_size
11910 spaces_count += 1;
11911 changed = true;
11912 if spaces_count == tab_size {
11913 reindented_line.push('\t');
11914 spaces_count = 0;
11915 }
11916 }
11917 '\t' => {
11918 reindented_line.push('\t');
11919 spaces_count = 0;
11920 }
11921 _ => {
11922 // Dont append it yet, we might have remaining spaces
11923 first_non_indent_char = Some(ch);
11924 break;
11925 }
11926 }
11927 }
11928
11929 if !changed {
11930 reindented_line.clear();
11931 continue;
11932 }
11933 // Remaining spaces that didn't make a full tab stop
11934 if spaces_count > 0 {
11935 reindented_line.extend(&space_cache[spaces_count - 1]);
11936 }
11937 // If we consume an extra character that was not indentation, add it back
11938 if let Some(extra_char) = first_non_indent_char {
11939 reindented_line.push(extra_char);
11940 }
11941 // Append the rest of the line and replace old reference with new one
11942 reindented_line.extend(chars);
11943 *line = Cow::Owned(reindented_line.clone());
11944 reindented_line.clear();
11945 }
11946 });
11947 }
11948
11949 pub fn convert_to_upper_case(
11950 &mut self,
11951 _: &ConvertToUpperCase,
11952 window: &mut Window,
11953 cx: &mut Context<Self>,
11954 ) {
11955 self.manipulate_text(window, cx, |text| text.to_uppercase())
11956 }
11957
11958 pub fn convert_to_lower_case(
11959 &mut self,
11960 _: &ConvertToLowerCase,
11961 window: &mut Window,
11962 cx: &mut Context<Self>,
11963 ) {
11964 self.manipulate_text(window, cx, |text| text.to_lowercase())
11965 }
11966
11967 pub fn convert_to_title_case(
11968 &mut self,
11969 _: &ConvertToTitleCase,
11970 window: &mut Window,
11971 cx: &mut Context<Self>,
11972 ) {
11973 self.manipulate_text(window, cx, |text| {
11974 text.split('\n')
11975 .map(|line| line.to_case(Case::Title))
11976 .join("\n")
11977 })
11978 }
11979
11980 pub fn convert_to_snake_case(
11981 &mut self,
11982 _: &ConvertToSnakeCase,
11983 window: &mut Window,
11984 cx: &mut Context<Self>,
11985 ) {
11986 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11987 }
11988
11989 pub fn convert_to_kebab_case(
11990 &mut self,
11991 _: &ConvertToKebabCase,
11992 window: &mut Window,
11993 cx: &mut Context<Self>,
11994 ) {
11995 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11996 }
11997
11998 pub fn convert_to_upper_camel_case(
11999 &mut self,
12000 _: &ConvertToUpperCamelCase,
12001 window: &mut Window,
12002 cx: &mut Context<Self>,
12003 ) {
12004 self.manipulate_text(window, cx, |text| {
12005 text.split('\n')
12006 .map(|line| line.to_case(Case::UpperCamel))
12007 .join("\n")
12008 })
12009 }
12010
12011 pub fn convert_to_lower_camel_case(
12012 &mut self,
12013 _: &ConvertToLowerCamelCase,
12014 window: &mut Window,
12015 cx: &mut Context<Self>,
12016 ) {
12017 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12018 }
12019
12020 pub fn convert_to_opposite_case(
12021 &mut self,
12022 _: &ConvertToOppositeCase,
12023 window: &mut Window,
12024 cx: &mut Context<Self>,
12025 ) {
12026 self.manipulate_text(window, cx, |text| {
12027 text.chars()
12028 .fold(String::with_capacity(text.len()), |mut t, c| {
12029 if c.is_uppercase() {
12030 t.extend(c.to_lowercase());
12031 } else {
12032 t.extend(c.to_uppercase());
12033 }
12034 t
12035 })
12036 })
12037 }
12038
12039 pub fn convert_to_sentence_case(
12040 &mut self,
12041 _: &ConvertToSentenceCase,
12042 window: &mut Window,
12043 cx: &mut Context<Self>,
12044 ) {
12045 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12046 }
12047
12048 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12049 self.manipulate_text(window, cx, |text| {
12050 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12051 if has_upper_case_characters {
12052 text.to_lowercase()
12053 } else {
12054 text.to_uppercase()
12055 }
12056 })
12057 }
12058
12059 pub fn convert_to_rot13(
12060 &mut self,
12061 _: &ConvertToRot13,
12062 window: &mut Window,
12063 cx: &mut Context<Self>,
12064 ) {
12065 self.manipulate_text(window, cx, |text| {
12066 text.chars()
12067 .map(|c| match c {
12068 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12069 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12070 _ => c,
12071 })
12072 .collect()
12073 })
12074 }
12075
12076 pub fn convert_to_rot47(
12077 &mut self,
12078 _: &ConvertToRot47,
12079 window: &mut Window,
12080 cx: &mut Context<Self>,
12081 ) {
12082 self.manipulate_text(window, cx, |text| {
12083 text.chars()
12084 .map(|c| {
12085 let code_point = c as u32;
12086 if code_point >= 33 && code_point <= 126 {
12087 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12088 }
12089 c
12090 })
12091 .collect()
12092 })
12093 }
12094
12095 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12096 where
12097 Fn: FnMut(&str) -> String,
12098 {
12099 let buffer = self.buffer.read(cx).snapshot(cx);
12100
12101 let mut new_selections = Vec::new();
12102 let mut edits = Vec::new();
12103 let mut selection_adjustment = 0isize;
12104
12105 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12106 let selection_is_empty = selection.is_empty();
12107
12108 let (start, end) = if selection_is_empty {
12109 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12110 (word_range.start, word_range.end)
12111 } else {
12112 (
12113 buffer.point_to_offset(selection.start),
12114 buffer.point_to_offset(selection.end),
12115 )
12116 };
12117
12118 let text = buffer.text_for_range(start..end).collect::<String>();
12119 let old_length = text.len() as isize;
12120 let text = callback(&text);
12121
12122 new_selections.push(Selection {
12123 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12124 end: MultiBufferOffset(
12125 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12126 ),
12127 goal: SelectionGoal::None,
12128 id: selection.id,
12129 reversed: selection.reversed,
12130 });
12131
12132 selection_adjustment += old_length - text.len() as isize;
12133
12134 edits.push((start..end, text));
12135 }
12136
12137 self.transact(window, cx, |this, window, cx| {
12138 this.buffer.update(cx, |buffer, cx| {
12139 buffer.edit(edits, None, cx);
12140 });
12141
12142 this.change_selections(Default::default(), window, cx, |s| {
12143 s.select(new_selections);
12144 });
12145
12146 this.request_autoscroll(Autoscroll::fit(), cx);
12147 });
12148 }
12149
12150 pub fn move_selection_on_drop(
12151 &mut self,
12152 selection: &Selection<Anchor>,
12153 target: DisplayPoint,
12154 is_cut: bool,
12155 window: &mut Window,
12156 cx: &mut Context<Self>,
12157 ) {
12158 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12159 let buffer = display_map.buffer_snapshot();
12160 let mut edits = Vec::new();
12161 let insert_point = display_map
12162 .clip_point(target, Bias::Left)
12163 .to_point(&display_map);
12164 let text = buffer
12165 .text_for_range(selection.start..selection.end)
12166 .collect::<String>();
12167 if is_cut {
12168 edits.push(((selection.start..selection.end), String::new()));
12169 }
12170 let insert_anchor = buffer.anchor_before(insert_point);
12171 edits.push(((insert_anchor..insert_anchor), text));
12172 let last_edit_start = insert_anchor.bias_left(buffer);
12173 let last_edit_end = insert_anchor.bias_right(buffer);
12174 self.transact(window, cx, |this, window, cx| {
12175 this.buffer.update(cx, |buffer, cx| {
12176 buffer.edit(edits, None, cx);
12177 });
12178 this.change_selections(Default::default(), window, cx, |s| {
12179 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12180 });
12181 });
12182 }
12183
12184 pub fn clear_selection_drag_state(&mut self) {
12185 self.selection_drag_state = SelectionDragState::None;
12186 }
12187
12188 pub fn duplicate(
12189 &mut self,
12190 upwards: bool,
12191 whole_lines: bool,
12192 window: &mut Window,
12193 cx: &mut Context<Self>,
12194 ) {
12195 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12196
12197 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12198 let buffer = display_map.buffer_snapshot();
12199 let selections = self.selections.all::<Point>(&display_map);
12200
12201 let mut edits = Vec::new();
12202 let mut selections_iter = selections.iter().peekable();
12203 while let Some(selection) = selections_iter.next() {
12204 let mut rows = selection.spanned_rows(false, &display_map);
12205 // duplicate line-wise
12206 if whole_lines || selection.start == selection.end {
12207 // Avoid duplicating the same lines twice.
12208 while let Some(next_selection) = selections_iter.peek() {
12209 let next_rows = next_selection.spanned_rows(false, &display_map);
12210 if next_rows.start < rows.end {
12211 rows.end = next_rows.end;
12212 selections_iter.next().unwrap();
12213 } else {
12214 break;
12215 }
12216 }
12217
12218 // Copy the text from the selected row region and splice it either at the start
12219 // or end of the region.
12220 let start = Point::new(rows.start.0, 0);
12221 let end = Point::new(
12222 rows.end.previous_row().0,
12223 buffer.line_len(rows.end.previous_row()),
12224 );
12225
12226 let mut text = buffer.text_for_range(start..end).collect::<String>();
12227
12228 let insert_location = if upwards {
12229 // When duplicating upward, we need to insert before the current line.
12230 // If we're on the last line and it doesn't end with a newline,
12231 // we need to add a newline before the duplicated content.
12232 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12233 && buffer.max_point().column > 0
12234 && !text.ends_with('\n');
12235
12236 if needs_leading_newline {
12237 text.insert(0, '\n');
12238 end
12239 } else {
12240 text.push('\n');
12241 Point::new(rows.start.0, 0)
12242 }
12243 } else {
12244 text.push('\n');
12245 start
12246 };
12247 edits.push((insert_location..insert_location, text));
12248 } else {
12249 // duplicate character-wise
12250 let start = selection.start;
12251 let end = selection.end;
12252 let text = buffer.text_for_range(start..end).collect::<String>();
12253 edits.push((selection.end..selection.end, text));
12254 }
12255 }
12256
12257 self.transact(window, cx, |this, window, cx| {
12258 this.buffer.update(cx, |buffer, cx| {
12259 buffer.edit(edits, None, cx);
12260 });
12261
12262 // When duplicating upward with whole lines, move the cursor to the duplicated line
12263 if upwards && whole_lines {
12264 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12265
12266 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12267 let mut new_ranges = Vec::new();
12268 let selections = s.all::<Point>(&display_map);
12269 let mut selections_iter = selections.iter().peekable();
12270
12271 while let Some(first_selection) = selections_iter.next() {
12272 // Group contiguous selections together to find the total row span
12273 let mut group_selections = vec![first_selection];
12274 let mut rows = first_selection.spanned_rows(false, &display_map);
12275
12276 while let Some(next_selection) = selections_iter.peek() {
12277 let next_rows = next_selection.spanned_rows(false, &display_map);
12278 if next_rows.start < rows.end {
12279 rows.end = next_rows.end;
12280 group_selections.push(selections_iter.next().unwrap());
12281 } else {
12282 break;
12283 }
12284 }
12285
12286 let row_count = rows.end.0 - rows.start.0;
12287
12288 // Move all selections in this group up by the total number of duplicated rows
12289 for selection in group_selections {
12290 let new_start = Point::new(
12291 selection.start.row.saturating_sub(row_count),
12292 selection.start.column,
12293 );
12294
12295 let new_end = Point::new(
12296 selection.end.row.saturating_sub(row_count),
12297 selection.end.column,
12298 );
12299
12300 new_ranges.push(new_start..new_end);
12301 }
12302 }
12303
12304 s.select_ranges(new_ranges);
12305 });
12306 }
12307
12308 this.request_autoscroll(Autoscroll::fit(), cx);
12309 });
12310 }
12311
12312 pub fn duplicate_line_up(
12313 &mut self,
12314 _: &DuplicateLineUp,
12315 window: &mut Window,
12316 cx: &mut Context<Self>,
12317 ) {
12318 self.duplicate(true, true, window, cx);
12319 }
12320
12321 pub fn duplicate_line_down(
12322 &mut self,
12323 _: &DuplicateLineDown,
12324 window: &mut Window,
12325 cx: &mut Context<Self>,
12326 ) {
12327 self.duplicate(false, true, window, cx);
12328 }
12329
12330 pub fn duplicate_selection(
12331 &mut self,
12332 _: &DuplicateSelection,
12333 window: &mut Window,
12334 cx: &mut Context<Self>,
12335 ) {
12336 self.duplicate(false, false, window, cx);
12337 }
12338
12339 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12340 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12341 if self.mode.is_single_line() {
12342 cx.propagate();
12343 return;
12344 }
12345
12346 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12347 let buffer = self.buffer.read(cx).snapshot(cx);
12348
12349 let mut edits = Vec::new();
12350 let mut unfold_ranges = Vec::new();
12351 let mut refold_creases = Vec::new();
12352
12353 let selections = self.selections.all::<Point>(&display_map);
12354 let mut selections = selections.iter().peekable();
12355 let mut contiguous_row_selections = Vec::new();
12356 let mut new_selections = Vec::new();
12357
12358 while let Some(selection) = selections.next() {
12359 // Find all the selections that span a contiguous row range
12360 let (start_row, end_row) = consume_contiguous_rows(
12361 &mut contiguous_row_selections,
12362 selection,
12363 &display_map,
12364 &mut selections,
12365 );
12366
12367 // Move the text spanned by the row range to be before the line preceding the row range
12368 if start_row.0 > 0 {
12369 let range_to_move = Point::new(
12370 start_row.previous_row().0,
12371 buffer.line_len(start_row.previous_row()),
12372 )
12373 ..Point::new(
12374 end_row.previous_row().0,
12375 buffer.line_len(end_row.previous_row()),
12376 );
12377 let insertion_point = display_map
12378 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12379 .0;
12380
12381 // Don't move lines across excerpts
12382 if buffer
12383 .excerpt_containing(insertion_point..range_to_move.end)
12384 .is_some()
12385 {
12386 let text = buffer
12387 .text_for_range(range_to_move.clone())
12388 .flat_map(|s| s.chars())
12389 .skip(1)
12390 .chain(['\n'])
12391 .collect::<String>();
12392
12393 edits.push((
12394 buffer.anchor_after(range_to_move.start)
12395 ..buffer.anchor_before(range_to_move.end),
12396 String::new(),
12397 ));
12398 let insertion_anchor = buffer.anchor_after(insertion_point);
12399 edits.push((insertion_anchor..insertion_anchor, text));
12400
12401 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12402
12403 // Move selections up
12404 new_selections.extend(contiguous_row_selections.drain(..).map(
12405 |mut selection| {
12406 selection.start.row -= row_delta;
12407 selection.end.row -= row_delta;
12408 selection
12409 },
12410 ));
12411
12412 // Move folds up
12413 unfold_ranges.push(range_to_move.clone());
12414 for fold in display_map.folds_in_range(
12415 buffer.anchor_before(range_to_move.start)
12416 ..buffer.anchor_after(range_to_move.end),
12417 ) {
12418 let mut start = fold.range.start.to_point(&buffer);
12419 let mut end = fold.range.end.to_point(&buffer);
12420 start.row -= row_delta;
12421 end.row -= row_delta;
12422 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12423 }
12424 }
12425 }
12426
12427 // If we didn't move line(s), preserve the existing selections
12428 new_selections.append(&mut contiguous_row_selections);
12429 }
12430
12431 self.transact(window, cx, |this, window, cx| {
12432 this.unfold_ranges(&unfold_ranges, true, true, cx);
12433 this.buffer.update(cx, |buffer, cx| {
12434 for (range, text) in edits {
12435 buffer.edit([(range, text)], None, cx);
12436 }
12437 });
12438 this.fold_creases(refold_creases, true, window, cx);
12439 this.change_selections(Default::default(), window, cx, |s| {
12440 s.select(new_selections);
12441 })
12442 });
12443 }
12444
12445 pub fn move_line_down(
12446 &mut self,
12447 _: &MoveLineDown,
12448 window: &mut Window,
12449 cx: &mut Context<Self>,
12450 ) {
12451 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12452 if self.mode.is_single_line() {
12453 cx.propagate();
12454 return;
12455 }
12456
12457 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12458 let buffer = self.buffer.read(cx).snapshot(cx);
12459
12460 let mut edits = Vec::new();
12461 let mut unfold_ranges = Vec::new();
12462 let mut refold_creases = Vec::new();
12463
12464 let selections = self.selections.all::<Point>(&display_map);
12465 let mut selections = selections.iter().peekable();
12466 let mut contiguous_row_selections = Vec::new();
12467 let mut new_selections = Vec::new();
12468
12469 while let Some(selection) = selections.next() {
12470 // Find all the selections that span a contiguous row range
12471 let (start_row, end_row) = consume_contiguous_rows(
12472 &mut contiguous_row_selections,
12473 selection,
12474 &display_map,
12475 &mut selections,
12476 );
12477
12478 // Move the text spanned by the row range to be after the last line of the row range
12479 if end_row.0 <= buffer.max_point().row {
12480 let range_to_move =
12481 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12482 let insertion_point = display_map
12483 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12484 .0;
12485
12486 // Don't move lines across excerpt boundaries
12487 if buffer
12488 .excerpt_containing(range_to_move.start..insertion_point)
12489 .is_some()
12490 {
12491 let mut text = String::from("\n");
12492 text.extend(buffer.text_for_range(range_to_move.clone()));
12493 text.pop(); // Drop trailing newline
12494 edits.push((
12495 buffer.anchor_after(range_to_move.start)
12496 ..buffer.anchor_before(range_to_move.end),
12497 String::new(),
12498 ));
12499 let insertion_anchor = buffer.anchor_after(insertion_point);
12500 edits.push((insertion_anchor..insertion_anchor, text));
12501
12502 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12503
12504 // Move selections down
12505 new_selections.extend(contiguous_row_selections.drain(..).map(
12506 |mut selection| {
12507 selection.start.row += row_delta;
12508 selection.end.row += row_delta;
12509 selection
12510 },
12511 ));
12512
12513 // Move folds down
12514 unfold_ranges.push(range_to_move.clone());
12515 for fold in display_map.folds_in_range(
12516 buffer.anchor_before(range_to_move.start)
12517 ..buffer.anchor_after(range_to_move.end),
12518 ) {
12519 let mut start = fold.range.start.to_point(&buffer);
12520 let mut end = fold.range.end.to_point(&buffer);
12521 start.row += row_delta;
12522 end.row += row_delta;
12523 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12524 }
12525 }
12526 }
12527
12528 // If we didn't move line(s), preserve the existing selections
12529 new_selections.append(&mut contiguous_row_selections);
12530 }
12531
12532 self.transact(window, cx, |this, window, cx| {
12533 this.unfold_ranges(&unfold_ranges, true, true, cx);
12534 this.buffer.update(cx, |buffer, cx| {
12535 for (range, text) in edits {
12536 buffer.edit([(range, text)], None, cx);
12537 }
12538 });
12539 this.fold_creases(refold_creases, true, window, cx);
12540 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12541 });
12542 }
12543
12544 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12545 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12546 let text_layout_details = &self.text_layout_details(window);
12547 self.transact(window, cx, |this, window, cx| {
12548 let edits = this.change_selections(Default::default(), window, cx, |s| {
12549 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12550 s.move_with(|display_map, selection| {
12551 if !selection.is_empty() {
12552 return;
12553 }
12554
12555 let mut head = selection.head();
12556 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12557 if head.column() == display_map.line_len(head.row()) {
12558 transpose_offset = display_map
12559 .buffer_snapshot()
12560 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12561 }
12562
12563 if transpose_offset == MultiBufferOffset(0) {
12564 return;
12565 }
12566
12567 *head.column_mut() += 1;
12568 head = display_map.clip_point(head, Bias::Right);
12569 let goal = SelectionGoal::HorizontalPosition(
12570 display_map
12571 .x_for_display_point(head, text_layout_details)
12572 .into(),
12573 );
12574 selection.collapse_to(head, goal);
12575
12576 let transpose_start = display_map
12577 .buffer_snapshot()
12578 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12579 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12580 let transpose_end = display_map
12581 .buffer_snapshot()
12582 .clip_offset(transpose_offset + 1usize, Bias::Right);
12583 if let Some(ch) = display_map
12584 .buffer_snapshot()
12585 .chars_at(transpose_start)
12586 .next()
12587 {
12588 edits.push((transpose_start..transpose_offset, String::new()));
12589 edits.push((transpose_end..transpose_end, ch.to_string()));
12590 }
12591 }
12592 });
12593 edits
12594 });
12595 this.buffer
12596 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12597 let selections = this
12598 .selections
12599 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12600 this.change_selections(Default::default(), window, cx, |s| {
12601 s.select(selections);
12602 });
12603 });
12604 }
12605
12606 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12607 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12608 if self.mode.is_single_line() {
12609 cx.propagate();
12610 return;
12611 }
12612
12613 self.rewrap_impl(RewrapOptions::default(), cx)
12614 }
12615
12616 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12617 let buffer = self.buffer.read(cx).snapshot(cx);
12618 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12619
12620 #[derive(Clone, Debug, PartialEq)]
12621 enum CommentFormat {
12622 /// single line comment, with prefix for line
12623 Line(String),
12624 /// single line within a block comment, with prefix for line
12625 BlockLine(String),
12626 /// a single line of a block comment that includes the initial delimiter
12627 BlockCommentWithStart(BlockCommentConfig),
12628 /// a single line of a block comment that includes the ending delimiter
12629 BlockCommentWithEnd(BlockCommentConfig),
12630 }
12631
12632 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12633 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12634 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12635 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12636 .peekable();
12637
12638 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12639 row
12640 } else {
12641 return Vec::new();
12642 };
12643
12644 let language_settings = buffer.language_settings_at(selection.head(), cx);
12645 let language_scope = buffer.language_scope_at(selection.head());
12646
12647 let indent_and_prefix_for_row =
12648 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12649 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12650 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12651 &language_scope
12652 {
12653 let indent_end = Point::new(row, indent.len);
12654 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12655 let line_text_after_indent = buffer
12656 .text_for_range(indent_end..line_end)
12657 .collect::<String>();
12658
12659 let is_within_comment_override = buffer
12660 .language_scope_at(indent_end)
12661 .is_some_and(|scope| scope.override_name() == Some("comment"));
12662 let comment_delimiters = if is_within_comment_override {
12663 // we are within a comment syntax node, but we don't
12664 // yet know what kind of comment: block, doc or line
12665 match (
12666 language_scope.documentation_comment(),
12667 language_scope.block_comment(),
12668 ) {
12669 (Some(config), _) | (_, Some(config))
12670 if buffer.contains_str_at(indent_end, &config.start) =>
12671 {
12672 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12673 }
12674 (Some(config), _) | (_, Some(config))
12675 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12676 {
12677 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12678 }
12679 (Some(config), _) | (_, Some(config))
12680 if buffer.contains_str_at(indent_end, &config.prefix) =>
12681 {
12682 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12683 }
12684 (_, _) => language_scope
12685 .line_comment_prefixes()
12686 .iter()
12687 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12688 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12689 }
12690 } else {
12691 // we not in an overridden comment node, but we may
12692 // be within a non-overridden line comment node
12693 language_scope
12694 .line_comment_prefixes()
12695 .iter()
12696 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12697 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12698 };
12699
12700 let rewrap_prefix = language_scope
12701 .rewrap_prefixes()
12702 .iter()
12703 .find_map(|prefix_regex| {
12704 prefix_regex.find(&line_text_after_indent).map(|mat| {
12705 if mat.start() == 0 {
12706 Some(mat.as_str().to_string())
12707 } else {
12708 None
12709 }
12710 })
12711 })
12712 .flatten();
12713 (comment_delimiters, rewrap_prefix)
12714 } else {
12715 (None, None)
12716 };
12717 (indent, comment_prefix, rewrap_prefix)
12718 };
12719
12720 let mut ranges = Vec::new();
12721 let from_empty_selection = selection.is_empty();
12722
12723 let mut current_range_start = first_row;
12724 let mut prev_row = first_row;
12725 let (
12726 mut current_range_indent,
12727 mut current_range_comment_delimiters,
12728 mut current_range_rewrap_prefix,
12729 ) = indent_and_prefix_for_row(first_row);
12730
12731 for row in non_blank_rows_iter.skip(1) {
12732 let has_paragraph_break = row > prev_row + 1;
12733
12734 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12735 indent_and_prefix_for_row(row);
12736
12737 let has_indent_change = row_indent != current_range_indent;
12738 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12739
12740 let has_boundary_change = has_comment_change
12741 || row_rewrap_prefix.is_some()
12742 || (has_indent_change && current_range_comment_delimiters.is_some());
12743
12744 if has_paragraph_break || has_boundary_change {
12745 ranges.push((
12746 language_settings.clone(),
12747 Point::new(current_range_start, 0)
12748 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12749 current_range_indent,
12750 current_range_comment_delimiters.clone(),
12751 current_range_rewrap_prefix.clone(),
12752 from_empty_selection,
12753 ));
12754 current_range_start = row;
12755 current_range_indent = row_indent;
12756 current_range_comment_delimiters = row_comment_delimiters;
12757 current_range_rewrap_prefix = row_rewrap_prefix;
12758 }
12759 prev_row = row;
12760 }
12761
12762 ranges.push((
12763 language_settings.clone(),
12764 Point::new(current_range_start, 0)
12765 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12766 current_range_indent,
12767 current_range_comment_delimiters,
12768 current_range_rewrap_prefix,
12769 from_empty_selection,
12770 ));
12771
12772 ranges
12773 });
12774
12775 let mut edits = Vec::new();
12776 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12777
12778 for (
12779 language_settings,
12780 wrap_range,
12781 mut indent_size,
12782 comment_prefix,
12783 rewrap_prefix,
12784 from_empty_selection,
12785 ) in wrap_ranges
12786 {
12787 let mut start_row = wrap_range.start.row;
12788 let mut end_row = wrap_range.end.row;
12789
12790 // Skip selections that overlap with a range that has already been rewrapped.
12791 let selection_range = start_row..end_row;
12792 if rewrapped_row_ranges
12793 .iter()
12794 .any(|range| range.overlaps(&selection_range))
12795 {
12796 continue;
12797 }
12798
12799 let tab_size = language_settings.tab_size;
12800
12801 let (line_prefix, inside_comment) = match &comment_prefix {
12802 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12803 (Some(prefix.as_str()), true)
12804 }
12805 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12806 (Some(prefix.as_ref()), true)
12807 }
12808 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12809 start: _,
12810 end: _,
12811 prefix,
12812 tab_size,
12813 })) => {
12814 indent_size.len += tab_size;
12815 (Some(prefix.as_ref()), true)
12816 }
12817 None => (None, false),
12818 };
12819 let indent_prefix = indent_size.chars().collect::<String>();
12820 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12821
12822 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12823 RewrapBehavior::InComments => inside_comment,
12824 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12825 RewrapBehavior::Anywhere => true,
12826 };
12827
12828 let should_rewrap = options.override_language_settings
12829 || allow_rewrap_based_on_language
12830 || self.hard_wrap.is_some();
12831 if !should_rewrap {
12832 continue;
12833 }
12834
12835 if from_empty_selection {
12836 'expand_upwards: while start_row > 0 {
12837 let prev_row = start_row - 1;
12838 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12839 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12840 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12841 {
12842 start_row = prev_row;
12843 } else {
12844 break 'expand_upwards;
12845 }
12846 }
12847
12848 'expand_downwards: while end_row < buffer.max_point().row {
12849 let next_row = end_row + 1;
12850 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12851 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12852 && !buffer.is_line_blank(MultiBufferRow(next_row))
12853 {
12854 end_row = next_row;
12855 } else {
12856 break 'expand_downwards;
12857 }
12858 }
12859 }
12860
12861 let start = Point::new(start_row, 0);
12862 let start_offset = ToOffset::to_offset(&start, &buffer);
12863 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12864 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12865 let mut first_line_delimiter = None;
12866 let mut last_line_delimiter = None;
12867 let Some(lines_without_prefixes) = selection_text
12868 .lines()
12869 .enumerate()
12870 .map(|(ix, line)| {
12871 let line_trimmed = line.trim_start();
12872 if rewrap_prefix.is_some() && ix > 0 {
12873 Ok(line_trimmed)
12874 } else if let Some(
12875 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12876 start,
12877 prefix,
12878 end,
12879 tab_size,
12880 })
12881 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12882 start,
12883 prefix,
12884 end,
12885 tab_size,
12886 }),
12887 ) = &comment_prefix
12888 {
12889 let line_trimmed = line_trimmed
12890 .strip_prefix(start.as_ref())
12891 .map(|s| {
12892 let mut indent_size = indent_size;
12893 indent_size.len -= tab_size;
12894 let indent_prefix: String = indent_size.chars().collect();
12895 first_line_delimiter = Some((indent_prefix, start));
12896 s.trim_start()
12897 })
12898 .unwrap_or(line_trimmed);
12899 let line_trimmed = line_trimmed
12900 .strip_suffix(end.as_ref())
12901 .map(|s| {
12902 last_line_delimiter = Some(end);
12903 s.trim_end()
12904 })
12905 .unwrap_or(line_trimmed);
12906 let line_trimmed = line_trimmed
12907 .strip_prefix(prefix.as_ref())
12908 .unwrap_or(line_trimmed);
12909 Ok(line_trimmed)
12910 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12911 line_trimmed.strip_prefix(prefix).with_context(|| {
12912 format!("line did not start with prefix {prefix:?}: {line:?}")
12913 })
12914 } else {
12915 line_trimmed
12916 .strip_prefix(&line_prefix.trim_start())
12917 .with_context(|| {
12918 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12919 })
12920 }
12921 })
12922 .collect::<Result<Vec<_>, _>>()
12923 .log_err()
12924 else {
12925 continue;
12926 };
12927
12928 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12929 buffer
12930 .language_settings_at(Point::new(start_row, 0), cx)
12931 .preferred_line_length as usize
12932 });
12933
12934 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12935 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12936 } else {
12937 line_prefix.clone()
12938 };
12939
12940 let wrapped_text = {
12941 let mut wrapped_text = wrap_with_prefix(
12942 line_prefix,
12943 subsequent_lines_prefix,
12944 lines_without_prefixes.join("\n"),
12945 wrap_column,
12946 tab_size,
12947 options.preserve_existing_whitespace,
12948 );
12949
12950 if let Some((indent, delimiter)) = first_line_delimiter {
12951 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12952 }
12953 if let Some(last_line) = last_line_delimiter {
12954 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12955 }
12956
12957 wrapped_text
12958 };
12959
12960 // TODO: should always use char-based diff while still supporting cursor behavior that
12961 // matches vim.
12962 let mut diff_options = DiffOptions::default();
12963 if options.override_language_settings {
12964 diff_options.max_word_diff_len = 0;
12965 diff_options.max_word_diff_line_count = 0;
12966 } else {
12967 diff_options.max_word_diff_len = usize::MAX;
12968 diff_options.max_word_diff_line_count = usize::MAX;
12969 }
12970
12971 for (old_range, new_text) in
12972 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12973 {
12974 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12975 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12976 edits.push((edit_start..edit_end, new_text));
12977 }
12978
12979 rewrapped_row_ranges.push(start_row..=end_row);
12980 }
12981
12982 self.buffer
12983 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12984 }
12985
12986 pub fn cut_common(
12987 &mut self,
12988 cut_no_selection_line: bool,
12989 window: &mut Window,
12990 cx: &mut Context<Self>,
12991 ) -> ClipboardItem {
12992 let mut text = String::new();
12993 let buffer = self.buffer.read(cx).snapshot(cx);
12994 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12995 let mut clipboard_selections = Vec::with_capacity(selections.len());
12996 {
12997 let max_point = buffer.max_point();
12998 let mut is_first = true;
12999 let mut prev_selection_was_entire_line = false;
13000 for selection in &mut selections {
13001 let is_entire_line =
13002 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13003 if is_entire_line {
13004 selection.start = Point::new(selection.start.row, 0);
13005 if !selection.is_empty() && selection.end.column == 0 {
13006 selection.end = cmp::min(max_point, selection.end);
13007 } else {
13008 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13009 }
13010 selection.goal = SelectionGoal::None;
13011 }
13012 if is_first {
13013 is_first = false;
13014 } else if !prev_selection_was_entire_line {
13015 text += "\n";
13016 }
13017 prev_selection_was_entire_line = is_entire_line;
13018 let mut len = 0;
13019 for chunk in buffer.text_for_range(selection.start..selection.end) {
13020 text.push_str(chunk);
13021 len += chunk.len();
13022 }
13023
13024 clipboard_selections.push(ClipboardSelection::for_buffer(
13025 len,
13026 is_entire_line,
13027 selection.range(),
13028 &buffer,
13029 self.project.as_ref(),
13030 cx,
13031 ));
13032 }
13033 }
13034
13035 self.transact(window, cx, |this, window, cx| {
13036 this.change_selections(Default::default(), window, cx, |s| {
13037 s.select(selections);
13038 });
13039 this.insert("", window, cx);
13040 });
13041 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13042 }
13043
13044 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13045 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13046 let item = self.cut_common(true, window, cx);
13047 cx.write_to_clipboard(item);
13048 }
13049
13050 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13051 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13052 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13053 s.move_with(|snapshot, sel| {
13054 if sel.is_empty() {
13055 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13056 }
13057 if sel.is_empty() {
13058 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13059 }
13060 });
13061 });
13062 let item = self.cut_common(false, window, cx);
13063 cx.set_global(KillRing(item))
13064 }
13065
13066 pub fn kill_ring_yank(
13067 &mut self,
13068 _: &KillRingYank,
13069 window: &mut Window,
13070 cx: &mut Context<Self>,
13071 ) {
13072 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13073 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13074 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13075 (kill_ring.text().to_string(), kill_ring.metadata_json())
13076 } else {
13077 return;
13078 }
13079 } else {
13080 return;
13081 };
13082 self.do_paste(&text, metadata, false, window, cx);
13083 }
13084
13085 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13086 self.do_copy(true, cx);
13087 }
13088
13089 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13090 self.do_copy(false, cx);
13091 }
13092
13093 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13094 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13095 let buffer = self.buffer.read(cx).read(cx);
13096 let mut text = String::new();
13097
13098 let mut clipboard_selections = Vec::with_capacity(selections.len());
13099 {
13100 let max_point = buffer.max_point();
13101 let mut is_first = true;
13102 let mut prev_selection_was_entire_line = false;
13103 for selection in &selections {
13104 let mut start = selection.start;
13105 let mut end = selection.end;
13106 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13107 let mut add_trailing_newline = false;
13108 if is_entire_line {
13109 start = Point::new(start.row, 0);
13110 let next_line_start = Point::new(end.row + 1, 0);
13111 if next_line_start <= max_point {
13112 end = next_line_start;
13113 } else {
13114 // We're on the last line without a trailing newline.
13115 // Copy to the end of the line and add a newline afterwards.
13116 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13117 add_trailing_newline = true;
13118 }
13119 }
13120
13121 let mut trimmed_selections = Vec::new();
13122 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13123 let row = MultiBufferRow(start.row);
13124 let first_indent = buffer.indent_size_for_line(row);
13125 if first_indent.len == 0 || start.column > first_indent.len {
13126 trimmed_selections.push(start..end);
13127 } else {
13128 trimmed_selections.push(
13129 Point::new(row.0, first_indent.len)
13130 ..Point::new(row.0, buffer.line_len(row)),
13131 );
13132 for row in start.row + 1..=end.row {
13133 let mut line_len = buffer.line_len(MultiBufferRow(row));
13134 if row == end.row {
13135 line_len = end.column;
13136 }
13137 if line_len == 0 {
13138 trimmed_selections
13139 .push(Point::new(row, 0)..Point::new(row, line_len));
13140 continue;
13141 }
13142 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13143 if row_indent_size.len >= first_indent.len {
13144 trimmed_selections.push(
13145 Point::new(row, first_indent.len)..Point::new(row, line_len),
13146 );
13147 } else {
13148 trimmed_selections.clear();
13149 trimmed_selections.push(start..end);
13150 break;
13151 }
13152 }
13153 }
13154 } else {
13155 trimmed_selections.push(start..end);
13156 }
13157
13158 for trimmed_range in trimmed_selections {
13159 if is_first {
13160 is_first = false;
13161 } else if !prev_selection_was_entire_line {
13162 text += "\n";
13163 }
13164 prev_selection_was_entire_line = is_entire_line;
13165 let mut len = 0;
13166 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13167 text.push_str(chunk);
13168 len += chunk.len();
13169 }
13170 if add_trailing_newline {
13171 text.push('\n');
13172 len += 1;
13173 }
13174 clipboard_selections.push(ClipboardSelection::for_buffer(
13175 len,
13176 is_entire_line,
13177 trimmed_range,
13178 &buffer,
13179 self.project.as_ref(),
13180 cx,
13181 ));
13182 }
13183 }
13184 }
13185
13186 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13187 text,
13188 clipboard_selections,
13189 ));
13190 }
13191
13192 pub fn do_paste(
13193 &mut self,
13194 text: &String,
13195 clipboard_selections: Option<Vec<ClipboardSelection>>,
13196 handle_entire_lines: bool,
13197 window: &mut Window,
13198 cx: &mut Context<Self>,
13199 ) {
13200 if self.read_only(cx) {
13201 return;
13202 }
13203
13204 let clipboard_text = Cow::Borrowed(text.as_str());
13205
13206 self.transact(window, cx, |this, window, cx| {
13207 let had_active_edit_prediction = this.has_active_edit_prediction();
13208 let display_map = this.display_snapshot(cx);
13209 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13210 let cursor_offset = this
13211 .selections
13212 .last::<MultiBufferOffset>(&display_map)
13213 .head();
13214
13215 if let Some(mut clipboard_selections) = clipboard_selections {
13216 let all_selections_were_entire_line =
13217 clipboard_selections.iter().all(|s| s.is_entire_line);
13218 let first_selection_indent_column =
13219 clipboard_selections.first().map(|s| s.first_line_indent);
13220 if clipboard_selections.len() != old_selections.len() {
13221 clipboard_selections.drain(..);
13222 }
13223 let mut auto_indent_on_paste = true;
13224
13225 this.buffer.update(cx, |buffer, cx| {
13226 let snapshot = buffer.read(cx);
13227 auto_indent_on_paste = snapshot
13228 .language_settings_at(cursor_offset, cx)
13229 .auto_indent_on_paste;
13230
13231 let mut start_offset = 0;
13232 let mut edits = Vec::new();
13233 let mut original_indent_columns = Vec::new();
13234 for (ix, selection) in old_selections.iter().enumerate() {
13235 let to_insert;
13236 let entire_line;
13237 let original_indent_column;
13238 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13239 let end_offset = start_offset + clipboard_selection.len;
13240 to_insert = &clipboard_text[start_offset..end_offset];
13241 entire_line = clipboard_selection.is_entire_line;
13242 start_offset = if entire_line {
13243 end_offset
13244 } else {
13245 end_offset + 1
13246 };
13247 original_indent_column = Some(clipboard_selection.first_line_indent);
13248 } else {
13249 to_insert = &*clipboard_text;
13250 entire_line = all_selections_were_entire_line;
13251 original_indent_column = first_selection_indent_column
13252 }
13253
13254 let (range, to_insert) =
13255 if selection.is_empty() && handle_entire_lines && entire_line {
13256 // If the corresponding selection was empty when this slice of the
13257 // clipboard text was written, then the entire line containing the
13258 // selection was copied. If this selection is also currently empty,
13259 // then paste the line before the current line of the buffer.
13260 let column = selection.start.to_point(&snapshot).column as usize;
13261 let line_start = selection.start - column;
13262 (line_start..line_start, Cow::Borrowed(to_insert))
13263 } else {
13264 let language = snapshot.language_at(selection.head());
13265 let range = selection.range();
13266 if let Some(language) = language
13267 && language.name() == "Markdown".into()
13268 {
13269 edit_for_markdown_paste(
13270 &snapshot,
13271 range,
13272 to_insert,
13273 url::Url::parse(to_insert).ok(),
13274 )
13275 } else {
13276 (range, Cow::Borrowed(to_insert))
13277 }
13278 };
13279
13280 edits.push((range, to_insert));
13281 original_indent_columns.push(original_indent_column);
13282 }
13283 drop(snapshot);
13284
13285 buffer.edit(
13286 edits,
13287 if auto_indent_on_paste {
13288 Some(AutoindentMode::Block {
13289 original_indent_columns,
13290 })
13291 } else {
13292 None
13293 },
13294 cx,
13295 );
13296 });
13297
13298 let selections = this
13299 .selections
13300 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13301 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13302 } else {
13303 let url = url::Url::parse(&clipboard_text).ok();
13304
13305 let auto_indent_mode = if !clipboard_text.is_empty() {
13306 Some(AutoindentMode::Block {
13307 original_indent_columns: Vec::new(),
13308 })
13309 } else {
13310 None
13311 };
13312
13313 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13314 let snapshot = buffer.snapshot(cx);
13315
13316 let anchors = old_selections
13317 .iter()
13318 .map(|s| {
13319 let anchor = snapshot.anchor_after(s.head());
13320 s.map(|_| anchor)
13321 })
13322 .collect::<Vec<_>>();
13323
13324 let mut edits = Vec::new();
13325
13326 for selection in old_selections.iter() {
13327 let language = snapshot.language_at(selection.head());
13328 let range = selection.range();
13329
13330 let (edit_range, edit_text) = if let Some(language) = language
13331 && language.name() == "Markdown".into()
13332 {
13333 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13334 } else {
13335 (range, clipboard_text.clone())
13336 };
13337
13338 edits.push((edit_range, edit_text));
13339 }
13340
13341 drop(snapshot);
13342 buffer.edit(edits, auto_indent_mode, cx);
13343
13344 anchors
13345 });
13346
13347 this.change_selections(Default::default(), window, cx, |s| {
13348 s.select_anchors(selection_anchors);
13349 });
13350 }
13351
13352 // 🤔 | .. | show_in_menu |
13353 // | .. | true true
13354 // | had_edit_prediction | false true
13355
13356 let trigger_in_words =
13357 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13358
13359 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13360 });
13361 }
13362
13363 pub fn diff_clipboard_with_selection(
13364 &mut self,
13365 _: &DiffClipboardWithSelection,
13366 window: &mut Window,
13367 cx: &mut Context<Self>,
13368 ) {
13369 let selections = self
13370 .selections
13371 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13372
13373 if selections.is_empty() {
13374 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13375 return;
13376 };
13377
13378 let clipboard_text = match cx.read_from_clipboard() {
13379 Some(item) => match item.entries().first() {
13380 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13381 _ => None,
13382 },
13383 None => None,
13384 };
13385
13386 let Some(clipboard_text) = clipboard_text else {
13387 log::warn!("Clipboard doesn't contain text.");
13388 return;
13389 };
13390
13391 window.dispatch_action(
13392 Box::new(DiffClipboardWithSelectionData {
13393 clipboard_text,
13394 editor: cx.entity(),
13395 }),
13396 cx,
13397 );
13398 }
13399
13400 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13401 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13402 if let Some(item) = cx.read_from_clipboard() {
13403 let entries = item.entries();
13404
13405 match entries.first() {
13406 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13407 // of all the pasted entries.
13408 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13409 .do_paste(
13410 clipboard_string.text(),
13411 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13412 true,
13413 window,
13414 cx,
13415 ),
13416 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13417 }
13418 }
13419 }
13420
13421 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13422 if self.read_only(cx) {
13423 return;
13424 }
13425
13426 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13427
13428 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13429 if let Some((selections, _)) =
13430 self.selection_history.transaction(transaction_id).cloned()
13431 {
13432 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13433 s.select_anchors(selections.to_vec());
13434 });
13435 } else {
13436 log::error!(
13437 "No entry in selection_history found for undo. \
13438 This may correspond to a bug where undo does not update the selection. \
13439 If this is occurring, please add details to \
13440 https://github.com/zed-industries/zed/issues/22692"
13441 );
13442 }
13443 self.request_autoscroll(Autoscroll::fit(), cx);
13444 self.unmark_text(window, cx);
13445 self.refresh_edit_prediction(true, false, window, cx);
13446 cx.emit(EditorEvent::Edited { transaction_id });
13447 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13448 }
13449 }
13450
13451 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13452 if self.read_only(cx) {
13453 return;
13454 }
13455
13456 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13457
13458 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13459 if let Some((_, Some(selections))) =
13460 self.selection_history.transaction(transaction_id).cloned()
13461 {
13462 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13463 s.select_anchors(selections.to_vec());
13464 });
13465 } else {
13466 log::error!(
13467 "No entry in selection_history found for redo. \
13468 This may correspond to a bug where undo does not update the selection. \
13469 If this is occurring, please add details to \
13470 https://github.com/zed-industries/zed/issues/22692"
13471 );
13472 }
13473 self.request_autoscroll(Autoscroll::fit(), cx);
13474 self.unmark_text(window, cx);
13475 self.refresh_edit_prediction(true, false, window, cx);
13476 cx.emit(EditorEvent::Edited { transaction_id });
13477 }
13478 }
13479
13480 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13481 self.buffer
13482 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13483 }
13484
13485 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13486 self.buffer
13487 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13488 }
13489
13490 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13492 self.change_selections(Default::default(), window, cx, |s| {
13493 s.move_with(|map, selection| {
13494 let cursor = if selection.is_empty() {
13495 movement::left(map, selection.start)
13496 } else {
13497 selection.start
13498 };
13499 selection.collapse_to(cursor, SelectionGoal::None);
13500 });
13501 })
13502 }
13503
13504 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13506 self.change_selections(Default::default(), window, cx, |s| {
13507 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13508 })
13509 }
13510
13511 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13512 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13513 self.change_selections(Default::default(), window, cx, |s| {
13514 s.move_with(|map, selection| {
13515 let cursor = if selection.is_empty() {
13516 movement::right(map, selection.end)
13517 } else {
13518 selection.end
13519 };
13520 selection.collapse_to(cursor, SelectionGoal::None)
13521 });
13522 })
13523 }
13524
13525 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13527 self.change_selections(Default::default(), window, cx, |s| {
13528 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13529 });
13530 }
13531
13532 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13533 if self.take_rename(true, window, cx).is_some() {
13534 return;
13535 }
13536
13537 if self.mode.is_single_line() {
13538 cx.propagate();
13539 return;
13540 }
13541
13542 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13543
13544 let text_layout_details = &self.text_layout_details(window);
13545 let selection_count = self.selections.count();
13546 let first_selection = self.selections.first_anchor();
13547
13548 self.change_selections(Default::default(), window, cx, |s| {
13549 s.move_with(|map, selection| {
13550 if !selection.is_empty() {
13551 selection.goal = SelectionGoal::None;
13552 }
13553 let (cursor, goal) = movement::up(
13554 map,
13555 selection.start,
13556 selection.goal,
13557 false,
13558 text_layout_details,
13559 );
13560 selection.collapse_to(cursor, goal);
13561 });
13562 });
13563
13564 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13565 {
13566 cx.propagate();
13567 }
13568 }
13569
13570 pub fn move_up_by_lines(
13571 &mut self,
13572 action: &MoveUpByLines,
13573 window: &mut Window,
13574 cx: &mut Context<Self>,
13575 ) {
13576 if self.take_rename(true, window, cx).is_some() {
13577 return;
13578 }
13579
13580 if self.mode.is_single_line() {
13581 cx.propagate();
13582 return;
13583 }
13584
13585 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13586
13587 let text_layout_details = &self.text_layout_details(window);
13588
13589 self.change_selections(Default::default(), window, cx, |s| {
13590 s.move_with(|map, selection| {
13591 if !selection.is_empty() {
13592 selection.goal = SelectionGoal::None;
13593 }
13594 let (cursor, goal) = movement::up_by_rows(
13595 map,
13596 selection.start,
13597 action.lines,
13598 selection.goal,
13599 false,
13600 text_layout_details,
13601 );
13602 selection.collapse_to(cursor, goal);
13603 });
13604 })
13605 }
13606
13607 pub fn move_down_by_lines(
13608 &mut self,
13609 action: &MoveDownByLines,
13610 window: &mut Window,
13611 cx: &mut Context<Self>,
13612 ) {
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
13626 self.change_selections(Default::default(), window, cx, |s| {
13627 s.move_with(|map, selection| {
13628 if !selection.is_empty() {
13629 selection.goal = SelectionGoal::None;
13630 }
13631 let (cursor, goal) = movement::down_by_rows(
13632 map,
13633 selection.start,
13634 action.lines,
13635 selection.goal,
13636 false,
13637 text_layout_details,
13638 );
13639 selection.collapse_to(cursor, goal);
13640 });
13641 })
13642 }
13643
13644 pub fn select_down_by_lines(
13645 &mut self,
13646 action: &SelectDownByLines,
13647 window: &mut Window,
13648 cx: &mut Context<Self>,
13649 ) {
13650 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13651 let text_layout_details = &self.text_layout_details(window);
13652 self.change_selections(Default::default(), window, cx, |s| {
13653 s.move_heads_with(|map, head, goal| {
13654 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13655 })
13656 })
13657 }
13658
13659 pub fn select_up_by_lines(
13660 &mut self,
13661 action: &SelectUpByLines,
13662 window: &mut Window,
13663 cx: &mut Context<Self>,
13664 ) {
13665 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13666 let text_layout_details = &self.text_layout_details(window);
13667 self.change_selections(Default::default(), window, cx, |s| {
13668 s.move_heads_with(|map, head, goal| {
13669 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13670 })
13671 })
13672 }
13673
13674 pub fn select_page_up(
13675 &mut self,
13676 _: &SelectPageUp,
13677 window: &mut Window,
13678 cx: &mut Context<Self>,
13679 ) {
13680 let Some(row_count) = self.visible_row_count() else {
13681 return;
13682 };
13683
13684 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13685
13686 let text_layout_details = &self.text_layout_details(window);
13687
13688 self.change_selections(Default::default(), window, cx, |s| {
13689 s.move_heads_with(|map, head, goal| {
13690 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13691 })
13692 })
13693 }
13694
13695 pub fn move_page_up(
13696 &mut self,
13697 action: &MovePageUp,
13698 window: &mut Window,
13699 cx: &mut Context<Self>,
13700 ) {
13701 if self.take_rename(true, window, cx).is_some() {
13702 return;
13703 }
13704
13705 if self
13706 .context_menu
13707 .borrow_mut()
13708 .as_mut()
13709 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13710 .unwrap_or(false)
13711 {
13712 return;
13713 }
13714
13715 if matches!(self.mode, EditorMode::SingleLine) {
13716 cx.propagate();
13717 return;
13718 }
13719
13720 let Some(row_count) = self.visible_row_count() else {
13721 return;
13722 };
13723
13724 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13725
13726 let effects = if action.center_cursor {
13727 SelectionEffects::scroll(Autoscroll::center())
13728 } else {
13729 SelectionEffects::default()
13730 };
13731
13732 let text_layout_details = &self.text_layout_details(window);
13733
13734 self.change_selections(effects, window, cx, |s| {
13735 s.move_with(|map, selection| {
13736 if !selection.is_empty() {
13737 selection.goal = SelectionGoal::None;
13738 }
13739 let (cursor, goal) = movement::up_by_rows(
13740 map,
13741 selection.end,
13742 row_count,
13743 selection.goal,
13744 false,
13745 text_layout_details,
13746 );
13747 selection.collapse_to(cursor, goal);
13748 });
13749 });
13750 }
13751
13752 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13753 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13754 let text_layout_details = &self.text_layout_details(window);
13755 self.change_selections(Default::default(), window, cx, |s| {
13756 s.move_heads_with(|map, head, goal| {
13757 movement::up(map, head, goal, false, text_layout_details)
13758 })
13759 })
13760 }
13761
13762 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13763 self.take_rename(true, window, cx);
13764
13765 if self.mode.is_single_line() {
13766 cx.propagate();
13767 return;
13768 }
13769
13770 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13771
13772 let text_layout_details = &self.text_layout_details(window);
13773 let selection_count = self.selections.count();
13774 let first_selection = self.selections.first_anchor();
13775
13776 self.change_selections(Default::default(), window, cx, |s| {
13777 s.move_with(|map, selection| {
13778 if !selection.is_empty() {
13779 selection.goal = SelectionGoal::None;
13780 }
13781 let (cursor, goal) = movement::down(
13782 map,
13783 selection.end,
13784 selection.goal,
13785 false,
13786 text_layout_details,
13787 );
13788 selection.collapse_to(cursor, goal);
13789 });
13790 });
13791
13792 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13793 {
13794 cx.propagate();
13795 }
13796 }
13797
13798 pub fn select_page_down(
13799 &mut self,
13800 _: &SelectPageDown,
13801 window: &mut Window,
13802 cx: &mut Context<Self>,
13803 ) {
13804 let Some(row_count) = self.visible_row_count() else {
13805 return;
13806 };
13807
13808 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13809
13810 let text_layout_details = &self.text_layout_details(window);
13811
13812 self.change_selections(Default::default(), window, cx, |s| {
13813 s.move_heads_with(|map, head, goal| {
13814 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13815 })
13816 })
13817 }
13818
13819 pub fn move_page_down(
13820 &mut self,
13821 action: &MovePageDown,
13822 window: &mut Window,
13823 cx: &mut Context<Self>,
13824 ) {
13825 if self.take_rename(true, window, cx).is_some() {
13826 return;
13827 }
13828
13829 if self
13830 .context_menu
13831 .borrow_mut()
13832 .as_mut()
13833 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13834 .unwrap_or(false)
13835 {
13836 return;
13837 }
13838
13839 if matches!(self.mode, EditorMode::SingleLine) {
13840 cx.propagate();
13841 return;
13842 }
13843
13844 let Some(row_count) = self.visible_row_count() else {
13845 return;
13846 };
13847
13848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13849
13850 let effects = if action.center_cursor {
13851 SelectionEffects::scroll(Autoscroll::center())
13852 } else {
13853 SelectionEffects::default()
13854 };
13855
13856 let text_layout_details = &self.text_layout_details(window);
13857 self.change_selections(effects, window, cx, |s| {
13858 s.move_with(|map, selection| {
13859 if !selection.is_empty() {
13860 selection.goal = SelectionGoal::None;
13861 }
13862 let (cursor, goal) = movement::down_by_rows(
13863 map,
13864 selection.end,
13865 row_count,
13866 selection.goal,
13867 false,
13868 text_layout_details,
13869 );
13870 selection.collapse_to(cursor, goal);
13871 });
13872 });
13873 }
13874
13875 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13876 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13877 let text_layout_details = &self.text_layout_details(window);
13878 self.change_selections(Default::default(), window, cx, |s| {
13879 s.move_heads_with(|map, head, goal| {
13880 movement::down(map, head, goal, false, text_layout_details)
13881 })
13882 });
13883 }
13884
13885 pub fn context_menu_first(
13886 &mut self,
13887 _: &ContextMenuFirst,
13888 window: &mut Window,
13889 cx: &mut Context<Self>,
13890 ) {
13891 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13892 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13893 }
13894 }
13895
13896 pub fn context_menu_prev(
13897 &mut self,
13898 _: &ContextMenuPrevious,
13899 window: &mut Window,
13900 cx: &mut Context<Self>,
13901 ) {
13902 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13903 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13904 }
13905 }
13906
13907 pub fn context_menu_next(
13908 &mut self,
13909 _: &ContextMenuNext,
13910 window: &mut Window,
13911 cx: &mut Context<Self>,
13912 ) {
13913 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13914 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13915 }
13916 }
13917
13918 pub fn context_menu_last(
13919 &mut self,
13920 _: &ContextMenuLast,
13921 window: &mut Window,
13922 cx: &mut Context<Self>,
13923 ) {
13924 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13925 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13926 }
13927 }
13928
13929 pub fn signature_help_prev(
13930 &mut self,
13931 _: &SignatureHelpPrevious,
13932 _: &mut Window,
13933 cx: &mut Context<Self>,
13934 ) {
13935 if let Some(popover) = self.signature_help_state.popover_mut() {
13936 if popover.current_signature == 0 {
13937 popover.current_signature = popover.signatures.len() - 1;
13938 } else {
13939 popover.current_signature -= 1;
13940 }
13941 cx.notify();
13942 }
13943 }
13944
13945 pub fn signature_help_next(
13946 &mut self,
13947 _: &SignatureHelpNext,
13948 _: &mut Window,
13949 cx: &mut Context<Self>,
13950 ) {
13951 if let Some(popover) = self.signature_help_state.popover_mut() {
13952 if popover.current_signature + 1 == popover.signatures.len() {
13953 popover.current_signature = 0;
13954 } else {
13955 popover.current_signature += 1;
13956 }
13957 cx.notify();
13958 }
13959 }
13960
13961 pub fn move_to_previous_word_start(
13962 &mut self,
13963 _: &MoveToPreviousWordStart,
13964 window: &mut Window,
13965 cx: &mut Context<Self>,
13966 ) {
13967 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13968 self.change_selections(Default::default(), window, cx, |s| {
13969 s.move_cursors_with(|map, head, _| {
13970 (
13971 movement::previous_word_start(map, head),
13972 SelectionGoal::None,
13973 )
13974 });
13975 })
13976 }
13977
13978 pub fn move_to_previous_subword_start(
13979 &mut self,
13980 _: &MoveToPreviousSubwordStart,
13981 window: &mut Window,
13982 cx: &mut Context<Self>,
13983 ) {
13984 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13985 self.change_selections(Default::default(), window, cx, |s| {
13986 s.move_cursors_with(|map, head, _| {
13987 (
13988 movement::previous_subword_start(map, head),
13989 SelectionGoal::None,
13990 )
13991 });
13992 })
13993 }
13994
13995 pub fn select_to_previous_word_start(
13996 &mut self,
13997 _: &SelectToPreviousWordStart,
13998 window: &mut Window,
13999 cx: &mut Context<Self>,
14000 ) {
14001 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14002 self.change_selections(Default::default(), window, cx, |s| {
14003 s.move_heads_with(|map, head, _| {
14004 (
14005 movement::previous_word_start(map, head),
14006 SelectionGoal::None,
14007 )
14008 });
14009 })
14010 }
14011
14012 pub fn select_to_previous_subword_start(
14013 &mut self,
14014 _: &SelectToPreviousSubwordStart,
14015 window: &mut Window,
14016 cx: &mut Context<Self>,
14017 ) {
14018 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14019 self.change_selections(Default::default(), window, cx, |s| {
14020 s.move_heads_with(|map, head, _| {
14021 (
14022 movement::previous_subword_start(map, head),
14023 SelectionGoal::None,
14024 )
14025 });
14026 })
14027 }
14028
14029 pub fn delete_to_previous_word_start(
14030 &mut self,
14031 action: &DeleteToPreviousWordStart,
14032 window: &mut Window,
14033 cx: &mut Context<Self>,
14034 ) {
14035 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14036 self.transact(window, cx, |this, window, cx| {
14037 this.select_autoclose_pair(window, cx);
14038 this.change_selections(Default::default(), window, cx, |s| {
14039 s.move_with(|map, selection| {
14040 if selection.is_empty() {
14041 let mut cursor = if action.ignore_newlines {
14042 movement::previous_word_start(map, selection.head())
14043 } else {
14044 movement::previous_word_start_or_newline(map, selection.head())
14045 };
14046 cursor = movement::adjust_greedy_deletion(
14047 map,
14048 selection.head(),
14049 cursor,
14050 action.ignore_brackets,
14051 );
14052 selection.set_head(cursor, SelectionGoal::None);
14053 }
14054 });
14055 });
14056 this.insert("", window, cx);
14057 });
14058 }
14059
14060 pub fn delete_to_previous_subword_start(
14061 &mut self,
14062 _: &DeleteToPreviousSubwordStart,
14063 window: &mut Window,
14064 cx: &mut Context<Self>,
14065 ) {
14066 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14067 self.transact(window, cx, |this, window, cx| {
14068 this.select_autoclose_pair(window, cx);
14069 this.change_selections(Default::default(), window, cx, |s| {
14070 s.move_with(|map, selection| {
14071 if selection.is_empty() {
14072 let mut cursor = movement::previous_subword_start(map, selection.head());
14073 cursor =
14074 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14075 selection.set_head(cursor, SelectionGoal::None);
14076 }
14077 });
14078 });
14079 this.insert("", window, cx);
14080 });
14081 }
14082
14083 pub fn move_to_next_word_end(
14084 &mut self,
14085 _: &MoveToNextWordEnd,
14086 window: &mut Window,
14087 cx: &mut Context<Self>,
14088 ) {
14089 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14090 self.change_selections(Default::default(), window, cx, |s| {
14091 s.move_cursors_with(|map, head, _| {
14092 (movement::next_word_end(map, head), SelectionGoal::None)
14093 });
14094 })
14095 }
14096
14097 pub fn move_to_next_subword_end(
14098 &mut self,
14099 _: &MoveToNextSubwordEnd,
14100 window: &mut Window,
14101 cx: &mut Context<Self>,
14102 ) {
14103 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14104 self.change_selections(Default::default(), window, cx, |s| {
14105 s.move_cursors_with(|map, head, _| {
14106 (movement::next_subword_end(map, head), SelectionGoal::None)
14107 });
14108 })
14109 }
14110
14111 pub fn select_to_next_word_end(
14112 &mut self,
14113 _: &SelectToNextWordEnd,
14114 window: &mut Window,
14115 cx: &mut Context<Self>,
14116 ) {
14117 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14118 self.change_selections(Default::default(), window, cx, |s| {
14119 s.move_heads_with(|map, head, _| {
14120 (movement::next_word_end(map, head), SelectionGoal::None)
14121 });
14122 })
14123 }
14124
14125 pub fn select_to_next_subword_end(
14126 &mut self,
14127 _: &SelectToNextSubwordEnd,
14128 window: &mut Window,
14129 cx: &mut Context<Self>,
14130 ) {
14131 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14132 self.change_selections(Default::default(), window, cx, |s| {
14133 s.move_heads_with(|map, head, _| {
14134 (movement::next_subword_end(map, head), SelectionGoal::None)
14135 });
14136 })
14137 }
14138
14139 pub fn delete_to_next_word_end(
14140 &mut self,
14141 action: &DeleteToNextWordEnd,
14142 window: &mut Window,
14143 cx: &mut Context<Self>,
14144 ) {
14145 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14146 self.transact(window, cx, |this, window, cx| {
14147 this.change_selections(Default::default(), window, cx, |s| {
14148 s.move_with(|map, selection| {
14149 if selection.is_empty() {
14150 let mut cursor = if action.ignore_newlines {
14151 movement::next_word_end(map, selection.head())
14152 } else {
14153 movement::next_word_end_or_newline(map, selection.head())
14154 };
14155 cursor = movement::adjust_greedy_deletion(
14156 map,
14157 selection.head(),
14158 cursor,
14159 action.ignore_brackets,
14160 );
14161 selection.set_head(cursor, SelectionGoal::None);
14162 }
14163 });
14164 });
14165 this.insert("", window, cx);
14166 });
14167 }
14168
14169 pub fn delete_to_next_subword_end(
14170 &mut self,
14171 _: &DeleteToNextSubwordEnd,
14172 window: &mut Window,
14173 cx: &mut Context<Self>,
14174 ) {
14175 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14176 self.transact(window, cx, |this, window, cx| {
14177 this.change_selections(Default::default(), window, cx, |s| {
14178 s.move_with(|map, selection| {
14179 if selection.is_empty() {
14180 let mut cursor = movement::next_subword_end(map, selection.head());
14181 cursor =
14182 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14183 selection.set_head(cursor, SelectionGoal::None);
14184 }
14185 });
14186 });
14187 this.insert("", window, cx);
14188 });
14189 }
14190
14191 pub fn move_to_beginning_of_line(
14192 &mut self,
14193 action: &MoveToBeginningOfLine,
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_cursors_with(|map, head, _| {
14200 (
14201 movement::indented_line_beginning(
14202 map,
14203 head,
14204 action.stop_at_soft_wraps,
14205 action.stop_at_indent,
14206 ),
14207 SelectionGoal::None,
14208 )
14209 });
14210 })
14211 }
14212
14213 pub fn select_to_beginning_of_line(
14214 &mut self,
14215 action: &SelectToBeginningOfLine,
14216 window: &mut Window,
14217 cx: &mut Context<Self>,
14218 ) {
14219 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14220 self.change_selections(Default::default(), window, cx, |s| {
14221 s.move_heads_with(|map, head, _| {
14222 (
14223 movement::indented_line_beginning(
14224 map,
14225 head,
14226 action.stop_at_soft_wraps,
14227 action.stop_at_indent,
14228 ),
14229 SelectionGoal::None,
14230 )
14231 });
14232 });
14233 }
14234
14235 pub fn delete_to_beginning_of_line(
14236 &mut self,
14237 action: &DeleteToBeginningOfLine,
14238 window: &mut Window,
14239 cx: &mut Context<Self>,
14240 ) {
14241 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14242 self.transact(window, cx, |this, window, cx| {
14243 this.change_selections(Default::default(), window, cx, |s| {
14244 s.move_with(|_, selection| {
14245 selection.reversed = true;
14246 });
14247 });
14248
14249 this.select_to_beginning_of_line(
14250 &SelectToBeginningOfLine {
14251 stop_at_soft_wraps: false,
14252 stop_at_indent: action.stop_at_indent,
14253 },
14254 window,
14255 cx,
14256 );
14257 this.backspace(&Backspace, window, cx);
14258 });
14259 }
14260
14261 pub fn move_to_end_of_line(
14262 &mut self,
14263 action: &MoveToEndOfLine,
14264 window: &mut Window,
14265 cx: &mut Context<Self>,
14266 ) {
14267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14268 self.change_selections(Default::default(), window, cx, |s| {
14269 s.move_cursors_with(|map, head, _| {
14270 (
14271 movement::line_end(map, head, action.stop_at_soft_wraps),
14272 SelectionGoal::None,
14273 )
14274 });
14275 })
14276 }
14277
14278 pub fn select_to_end_of_line(
14279 &mut self,
14280 action: &SelectToEndOfLine,
14281 window: &mut Window,
14282 cx: &mut Context<Self>,
14283 ) {
14284 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14285 self.change_selections(Default::default(), window, cx, |s| {
14286 s.move_heads_with(|map, head, _| {
14287 (
14288 movement::line_end(map, head, action.stop_at_soft_wraps),
14289 SelectionGoal::None,
14290 )
14291 });
14292 })
14293 }
14294
14295 pub fn delete_to_end_of_line(
14296 &mut self,
14297 _: &DeleteToEndOfLine,
14298 window: &mut Window,
14299 cx: &mut Context<Self>,
14300 ) {
14301 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14302 self.transact(window, cx, |this, window, cx| {
14303 this.select_to_end_of_line(
14304 &SelectToEndOfLine {
14305 stop_at_soft_wraps: false,
14306 },
14307 window,
14308 cx,
14309 );
14310 this.delete(&Delete, window, cx);
14311 });
14312 }
14313
14314 pub fn cut_to_end_of_line(
14315 &mut self,
14316 action: &CutToEndOfLine,
14317 window: &mut Window,
14318 cx: &mut Context<Self>,
14319 ) {
14320 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14321 self.transact(window, cx, |this, window, cx| {
14322 this.select_to_end_of_line(
14323 &SelectToEndOfLine {
14324 stop_at_soft_wraps: false,
14325 },
14326 window,
14327 cx,
14328 );
14329 if !action.stop_at_newlines {
14330 this.change_selections(Default::default(), window, cx, |s| {
14331 s.move_with(|_, sel| {
14332 if sel.is_empty() {
14333 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14334 }
14335 });
14336 });
14337 }
14338 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14339 let item = this.cut_common(false, window, cx);
14340 cx.write_to_clipboard(item);
14341 });
14342 }
14343
14344 pub fn move_to_start_of_paragraph(
14345 &mut self,
14346 _: &MoveToStartOfParagraph,
14347 window: &mut Window,
14348 cx: &mut Context<Self>,
14349 ) {
14350 if matches!(self.mode, EditorMode::SingleLine) {
14351 cx.propagate();
14352 return;
14353 }
14354 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14355 self.change_selections(Default::default(), window, cx, |s| {
14356 s.move_with(|map, selection| {
14357 selection.collapse_to(
14358 movement::start_of_paragraph(map, selection.head(), 1),
14359 SelectionGoal::None,
14360 )
14361 });
14362 })
14363 }
14364
14365 pub fn move_to_end_of_paragraph(
14366 &mut self,
14367 _: &MoveToEndOfParagraph,
14368 window: &mut Window,
14369 cx: &mut Context<Self>,
14370 ) {
14371 if matches!(self.mode, EditorMode::SingleLine) {
14372 cx.propagate();
14373 return;
14374 }
14375 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14376 self.change_selections(Default::default(), window, cx, |s| {
14377 s.move_with(|map, selection| {
14378 selection.collapse_to(
14379 movement::end_of_paragraph(map, selection.head(), 1),
14380 SelectionGoal::None,
14381 )
14382 });
14383 })
14384 }
14385
14386 pub fn select_to_start_of_paragraph(
14387 &mut self,
14388 _: &SelectToStartOfParagraph,
14389 window: &mut Window,
14390 cx: &mut Context<Self>,
14391 ) {
14392 if matches!(self.mode, EditorMode::SingleLine) {
14393 cx.propagate();
14394 return;
14395 }
14396 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14397 self.change_selections(Default::default(), window, cx, |s| {
14398 s.move_heads_with(|map, head, _| {
14399 (
14400 movement::start_of_paragraph(map, head, 1),
14401 SelectionGoal::None,
14402 )
14403 });
14404 })
14405 }
14406
14407 pub fn select_to_end_of_paragraph(
14408 &mut self,
14409 _: &SelectToEndOfParagraph,
14410 window: &mut Window,
14411 cx: &mut Context<Self>,
14412 ) {
14413 if matches!(self.mode, EditorMode::SingleLine) {
14414 cx.propagate();
14415 return;
14416 }
14417 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14418 self.change_selections(Default::default(), window, cx, |s| {
14419 s.move_heads_with(|map, head, _| {
14420 (
14421 movement::end_of_paragraph(map, head, 1),
14422 SelectionGoal::None,
14423 )
14424 });
14425 })
14426 }
14427
14428 pub fn move_to_start_of_excerpt(
14429 &mut self,
14430 _: &MoveToStartOfExcerpt,
14431 window: &mut Window,
14432 cx: &mut Context<Self>,
14433 ) {
14434 if matches!(self.mode, EditorMode::SingleLine) {
14435 cx.propagate();
14436 return;
14437 }
14438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14439 self.change_selections(Default::default(), window, cx, |s| {
14440 s.move_with(|map, selection| {
14441 selection.collapse_to(
14442 movement::start_of_excerpt(
14443 map,
14444 selection.head(),
14445 workspace::searchable::Direction::Prev,
14446 ),
14447 SelectionGoal::None,
14448 )
14449 });
14450 })
14451 }
14452
14453 pub fn move_to_start_of_next_excerpt(
14454 &mut self,
14455 _: &MoveToStartOfNextExcerpt,
14456 window: &mut Window,
14457 cx: &mut Context<Self>,
14458 ) {
14459 if matches!(self.mode, EditorMode::SingleLine) {
14460 cx.propagate();
14461 return;
14462 }
14463
14464 self.change_selections(Default::default(), window, cx, |s| {
14465 s.move_with(|map, selection| {
14466 selection.collapse_to(
14467 movement::start_of_excerpt(
14468 map,
14469 selection.head(),
14470 workspace::searchable::Direction::Next,
14471 ),
14472 SelectionGoal::None,
14473 )
14474 });
14475 })
14476 }
14477
14478 pub fn move_to_end_of_excerpt(
14479 &mut self,
14480 _: &MoveToEndOfExcerpt,
14481 window: &mut Window,
14482 cx: &mut Context<Self>,
14483 ) {
14484 if matches!(self.mode, EditorMode::SingleLine) {
14485 cx.propagate();
14486 return;
14487 }
14488 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14489 self.change_selections(Default::default(), window, cx, |s| {
14490 s.move_with(|map, selection| {
14491 selection.collapse_to(
14492 movement::end_of_excerpt(
14493 map,
14494 selection.head(),
14495 workspace::searchable::Direction::Next,
14496 ),
14497 SelectionGoal::None,
14498 )
14499 });
14500 })
14501 }
14502
14503 pub fn move_to_end_of_previous_excerpt(
14504 &mut self,
14505 _: &MoveToEndOfPreviousExcerpt,
14506 window: &mut Window,
14507 cx: &mut Context<Self>,
14508 ) {
14509 if matches!(self.mode, EditorMode::SingleLine) {
14510 cx.propagate();
14511 return;
14512 }
14513 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14514 self.change_selections(Default::default(), window, cx, |s| {
14515 s.move_with(|map, selection| {
14516 selection.collapse_to(
14517 movement::end_of_excerpt(
14518 map,
14519 selection.head(),
14520 workspace::searchable::Direction::Prev,
14521 ),
14522 SelectionGoal::None,
14523 )
14524 });
14525 })
14526 }
14527
14528 pub fn select_to_start_of_excerpt(
14529 &mut self,
14530 _: &SelectToStartOfExcerpt,
14531 window: &mut Window,
14532 cx: &mut Context<Self>,
14533 ) {
14534 if matches!(self.mode, EditorMode::SingleLine) {
14535 cx.propagate();
14536 return;
14537 }
14538 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14539 self.change_selections(Default::default(), window, cx, |s| {
14540 s.move_heads_with(|map, head, _| {
14541 (
14542 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14543 SelectionGoal::None,
14544 )
14545 });
14546 })
14547 }
14548
14549 pub fn select_to_start_of_next_excerpt(
14550 &mut self,
14551 _: &SelectToStartOfNextExcerpt,
14552 window: &mut Window,
14553 cx: &mut Context<Self>,
14554 ) {
14555 if matches!(self.mode, EditorMode::SingleLine) {
14556 cx.propagate();
14557 return;
14558 }
14559 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14560 self.change_selections(Default::default(), window, cx, |s| {
14561 s.move_heads_with(|map, head, _| {
14562 (
14563 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14564 SelectionGoal::None,
14565 )
14566 });
14567 })
14568 }
14569
14570 pub fn select_to_end_of_excerpt(
14571 &mut self,
14572 _: &SelectToEndOfExcerpt,
14573 window: &mut Window,
14574 cx: &mut Context<Self>,
14575 ) {
14576 if matches!(self.mode, EditorMode::SingleLine) {
14577 cx.propagate();
14578 return;
14579 }
14580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14581 self.change_selections(Default::default(), window, cx, |s| {
14582 s.move_heads_with(|map, head, _| {
14583 (
14584 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14585 SelectionGoal::None,
14586 )
14587 });
14588 })
14589 }
14590
14591 pub fn select_to_end_of_previous_excerpt(
14592 &mut self,
14593 _: &SelectToEndOfPreviousExcerpt,
14594 window: &mut Window,
14595 cx: &mut Context<Self>,
14596 ) {
14597 if matches!(self.mode, EditorMode::SingleLine) {
14598 cx.propagate();
14599 return;
14600 }
14601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14602 self.change_selections(Default::default(), window, cx, |s| {
14603 s.move_heads_with(|map, head, _| {
14604 (
14605 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14606 SelectionGoal::None,
14607 )
14608 });
14609 })
14610 }
14611
14612 pub fn move_to_beginning(
14613 &mut self,
14614 _: &MoveToBeginning,
14615 window: &mut Window,
14616 cx: &mut Context<Self>,
14617 ) {
14618 if matches!(self.mode, EditorMode::SingleLine) {
14619 cx.propagate();
14620 return;
14621 }
14622 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14623 self.change_selections(Default::default(), window, cx, |s| {
14624 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14625 });
14626 }
14627
14628 pub fn select_to_beginning(
14629 &mut self,
14630 _: &SelectToBeginning,
14631 window: &mut Window,
14632 cx: &mut Context<Self>,
14633 ) {
14634 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14635 selection.set_head(Point::zero(), SelectionGoal::None);
14636 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14637 self.change_selections(Default::default(), window, cx, |s| {
14638 s.select(vec![selection]);
14639 });
14640 }
14641
14642 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14643 if matches!(self.mode, EditorMode::SingleLine) {
14644 cx.propagate();
14645 return;
14646 }
14647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14648 let cursor = self.buffer.read(cx).read(cx).len();
14649 self.change_selections(Default::default(), window, cx, |s| {
14650 s.select_ranges(vec![cursor..cursor])
14651 });
14652 }
14653
14654 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14655 self.nav_history = nav_history;
14656 }
14657
14658 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14659 self.nav_history.as_ref()
14660 }
14661
14662 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14663 self.push_to_nav_history(
14664 self.selections.newest_anchor().head(),
14665 None,
14666 false,
14667 true,
14668 cx,
14669 );
14670 }
14671
14672 fn push_to_nav_history(
14673 &mut self,
14674 cursor_anchor: Anchor,
14675 new_position: Option<Point>,
14676 is_deactivate: bool,
14677 always: bool,
14678 cx: &mut Context<Self>,
14679 ) {
14680 if let Some(nav_history) = self.nav_history.as_mut() {
14681 let buffer = self.buffer.read(cx).read(cx);
14682 let cursor_position = cursor_anchor.to_point(&buffer);
14683 let scroll_state = self.scroll_manager.anchor();
14684 let scroll_top_row = scroll_state.top_row(&buffer);
14685 drop(buffer);
14686
14687 if let Some(new_position) = new_position {
14688 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14689 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14690 return;
14691 }
14692 }
14693
14694 nav_history.push(
14695 Some(NavigationData {
14696 cursor_anchor,
14697 cursor_position,
14698 scroll_anchor: scroll_state,
14699 scroll_top_row,
14700 }),
14701 cx,
14702 );
14703 cx.emit(EditorEvent::PushedToNavHistory {
14704 anchor: cursor_anchor,
14705 is_deactivate,
14706 })
14707 }
14708 }
14709
14710 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14711 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14712 let buffer = self.buffer.read(cx).snapshot(cx);
14713 let mut selection = self
14714 .selections
14715 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14716 selection.set_head(buffer.len(), SelectionGoal::None);
14717 self.change_selections(Default::default(), window, cx, |s| {
14718 s.select(vec![selection]);
14719 });
14720 }
14721
14722 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14723 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14724 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14725 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14726 });
14727 }
14728
14729 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14730 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14731 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14732 let mut selections = self.selections.all::<Point>(&display_map);
14733 let max_point = display_map.buffer_snapshot().max_point();
14734 for selection in &mut selections {
14735 let rows = selection.spanned_rows(true, &display_map);
14736 selection.start = Point::new(rows.start.0, 0);
14737 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14738 selection.reversed = false;
14739 }
14740 self.change_selections(Default::default(), window, cx, |s| {
14741 s.select(selections);
14742 });
14743 }
14744
14745 pub fn split_selection_into_lines(
14746 &mut self,
14747 action: &SplitSelectionIntoLines,
14748 window: &mut Window,
14749 cx: &mut Context<Self>,
14750 ) {
14751 let selections = self
14752 .selections
14753 .all::<Point>(&self.display_snapshot(cx))
14754 .into_iter()
14755 .map(|selection| selection.start..selection.end)
14756 .collect::<Vec<_>>();
14757 self.unfold_ranges(&selections, true, true, cx);
14758
14759 let mut new_selection_ranges = Vec::new();
14760 {
14761 let buffer = self.buffer.read(cx).read(cx);
14762 for selection in selections {
14763 for row in selection.start.row..selection.end.row {
14764 let line_start = Point::new(row, 0);
14765 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14766
14767 if action.keep_selections {
14768 // Keep the selection range for each line
14769 let selection_start = if row == selection.start.row {
14770 selection.start
14771 } else {
14772 line_start
14773 };
14774 new_selection_ranges.push(selection_start..line_end);
14775 } else {
14776 // Collapse to cursor at end of line
14777 new_selection_ranges.push(line_end..line_end);
14778 }
14779 }
14780
14781 let is_multiline_selection = selection.start.row != selection.end.row;
14782 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14783 // so this action feels more ergonomic when paired with other selection operations
14784 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14785 if !should_skip_last {
14786 if action.keep_selections {
14787 if is_multiline_selection {
14788 let line_start = Point::new(selection.end.row, 0);
14789 new_selection_ranges.push(line_start..selection.end);
14790 } else {
14791 new_selection_ranges.push(selection.start..selection.end);
14792 }
14793 } else {
14794 new_selection_ranges.push(selection.end..selection.end);
14795 }
14796 }
14797 }
14798 }
14799 self.change_selections(Default::default(), window, cx, |s| {
14800 s.select_ranges(new_selection_ranges);
14801 });
14802 }
14803
14804 pub fn add_selection_above(
14805 &mut self,
14806 action: &AddSelectionAbove,
14807 window: &mut Window,
14808 cx: &mut Context<Self>,
14809 ) {
14810 self.add_selection(true, action.skip_soft_wrap, window, cx);
14811 }
14812
14813 pub fn add_selection_below(
14814 &mut self,
14815 action: &AddSelectionBelow,
14816 window: &mut Window,
14817 cx: &mut Context<Self>,
14818 ) {
14819 self.add_selection(false, action.skip_soft_wrap, window, cx);
14820 }
14821
14822 fn add_selection(
14823 &mut self,
14824 above: bool,
14825 skip_soft_wrap: bool,
14826 window: &mut Window,
14827 cx: &mut Context<Self>,
14828 ) {
14829 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14830
14831 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14832 let all_selections = self.selections.all::<Point>(&display_map);
14833 let text_layout_details = self.text_layout_details(window);
14834
14835 let (mut columnar_selections, new_selections_to_columnarize) = {
14836 if let Some(state) = self.add_selections_state.as_ref() {
14837 let columnar_selection_ids: HashSet<_> = state
14838 .groups
14839 .iter()
14840 .flat_map(|group| group.stack.iter())
14841 .copied()
14842 .collect();
14843
14844 all_selections
14845 .into_iter()
14846 .partition(|s| columnar_selection_ids.contains(&s.id))
14847 } else {
14848 (Vec::new(), all_selections)
14849 }
14850 };
14851
14852 let mut state = self
14853 .add_selections_state
14854 .take()
14855 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14856
14857 for selection in new_selections_to_columnarize {
14858 let range = selection.display_range(&display_map).sorted();
14859 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14860 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14861 let positions = start_x.min(end_x)..start_x.max(end_x);
14862 let mut stack = Vec::new();
14863 for row in range.start.row().0..=range.end.row().0 {
14864 if let Some(selection) = self.selections.build_columnar_selection(
14865 &display_map,
14866 DisplayRow(row),
14867 &positions,
14868 selection.reversed,
14869 &text_layout_details,
14870 ) {
14871 stack.push(selection.id);
14872 columnar_selections.push(selection);
14873 }
14874 }
14875 if !stack.is_empty() {
14876 if above {
14877 stack.reverse();
14878 }
14879 state.groups.push(AddSelectionsGroup { above, stack });
14880 }
14881 }
14882
14883 let mut final_selections = Vec::new();
14884 let end_row = if above {
14885 DisplayRow(0)
14886 } else {
14887 display_map.max_point().row()
14888 };
14889
14890 let mut last_added_item_per_group = HashMap::default();
14891 for group in state.groups.iter_mut() {
14892 if let Some(last_id) = group.stack.last() {
14893 last_added_item_per_group.insert(*last_id, group);
14894 }
14895 }
14896
14897 for selection in columnar_selections {
14898 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14899 if above == group.above {
14900 let range = selection.display_range(&display_map).sorted();
14901 debug_assert_eq!(range.start.row(), range.end.row());
14902 let mut row = range.start.row();
14903 let positions =
14904 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14905 Pixels::from(start)..Pixels::from(end)
14906 } else {
14907 let start_x =
14908 display_map.x_for_display_point(range.start, &text_layout_details);
14909 let end_x =
14910 display_map.x_for_display_point(range.end, &text_layout_details);
14911 start_x.min(end_x)..start_x.max(end_x)
14912 };
14913
14914 let mut maybe_new_selection = None;
14915 let direction = if above { -1 } else { 1 };
14916
14917 while row != end_row {
14918 if skip_soft_wrap {
14919 row = display_map
14920 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14921 .row();
14922 } else if above {
14923 row.0 -= 1;
14924 } else {
14925 row.0 += 1;
14926 }
14927
14928 if let Some(new_selection) = self.selections.build_columnar_selection(
14929 &display_map,
14930 row,
14931 &positions,
14932 selection.reversed,
14933 &text_layout_details,
14934 ) {
14935 maybe_new_selection = Some(new_selection);
14936 break;
14937 }
14938 }
14939
14940 if let Some(new_selection) = maybe_new_selection {
14941 group.stack.push(new_selection.id);
14942 if above {
14943 final_selections.push(new_selection);
14944 final_selections.push(selection);
14945 } else {
14946 final_selections.push(selection);
14947 final_selections.push(new_selection);
14948 }
14949 } else {
14950 final_selections.push(selection);
14951 }
14952 } else {
14953 group.stack.pop();
14954 }
14955 } else {
14956 final_selections.push(selection);
14957 }
14958 }
14959
14960 self.change_selections(Default::default(), window, cx, |s| {
14961 s.select(final_selections);
14962 });
14963
14964 let final_selection_ids: HashSet<_> = self
14965 .selections
14966 .all::<Point>(&display_map)
14967 .iter()
14968 .map(|s| s.id)
14969 .collect();
14970 state.groups.retain_mut(|group| {
14971 // selections might get merged above so we remove invalid items from stacks
14972 group.stack.retain(|id| final_selection_ids.contains(id));
14973
14974 // single selection in stack can be treated as initial state
14975 group.stack.len() > 1
14976 });
14977
14978 if !state.groups.is_empty() {
14979 self.add_selections_state = Some(state);
14980 }
14981 }
14982
14983 pub fn insert_snippet_at_selections(
14984 &mut self,
14985 action: &InsertSnippet,
14986 window: &mut Window,
14987 cx: &mut Context<Self>,
14988 ) {
14989 self.try_insert_snippet_at_selections(action, window, cx)
14990 .log_err();
14991 }
14992
14993 fn try_insert_snippet_at_selections(
14994 &mut self,
14995 action: &InsertSnippet,
14996 window: &mut Window,
14997 cx: &mut Context<Self>,
14998 ) -> Result<()> {
14999 let insertion_ranges = self
15000 .selections
15001 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15002 .into_iter()
15003 .map(|selection| selection.range())
15004 .collect_vec();
15005
15006 let snippet = if let Some(snippet_body) = &action.snippet {
15007 if action.language.is_none() && action.name.is_none() {
15008 Snippet::parse(snippet_body)?
15009 } else {
15010 bail!("`snippet` is mutually exclusive with `language` and `name`")
15011 }
15012 } else if let Some(name) = &action.name {
15013 let project = self.project().context("no project")?;
15014 let snippet_store = project.read(cx).snippets().read(cx);
15015 let snippet = snippet_store
15016 .snippets_for(action.language.clone(), cx)
15017 .into_iter()
15018 .find(|snippet| snippet.name == *name)
15019 .context("snippet not found")?;
15020 Snippet::parse(&snippet.body)?
15021 } else {
15022 // todo(andrew): open modal to select snippet
15023 bail!("`name` or `snippet` is required")
15024 };
15025
15026 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15027 }
15028
15029 fn select_match_ranges(
15030 &mut self,
15031 range: Range<MultiBufferOffset>,
15032 reversed: bool,
15033 replace_newest: bool,
15034 auto_scroll: Option<Autoscroll>,
15035 window: &mut Window,
15036 cx: &mut Context<Editor>,
15037 ) {
15038 self.unfold_ranges(
15039 std::slice::from_ref(&range),
15040 false,
15041 auto_scroll.is_some(),
15042 cx,
15043 );
15044 let effects = if let Some(scroll) = auto_scroll {
15045 SelectionEffects::scroll(scroll)
15046 } else {
15047 SelectionEffects::no_scroll()
15048 };
15049 self.change_selections(effects, window, cx, |s| {
15050 if replace_newest {
15051 s.delete(s.newest_anchor().id);
15052 }
15053 if reversed {
15054 s.insert_range(range.end..range.start);
15055 } else {
15056 s.insert_range(range);
15057 }
15058 });
15059 }
15060
15061 pub fn select_next_match_internal(
15062 &mut self,
15063 display_map: &DisplaySnapshot,
15064 replace_newest: bool,
15065 autoscroll: Option<Autoscroll>,
15066 window: &mut Window,
15067 cx: &mut Context<Self>,
15068 ) -> Result<()> {
15069 let buffer = display_map.buffer_snapshot();
15070 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15071 if let Some(mut select_next_state) = self.select_next_state.take() {
15072 let query = &select_next_state.query;
15073 if !select_next_state.done {
15074 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15075 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15076 let mut next_selected_range = None;
15077
15078 let bytes_after_last_selection =
15079 buffer.bytes_in_range(last_selection.end..buffer.len());
15080 let bytes_before_first_selection =
15081 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15082 let query_matches = query
15083 .stream_find_iter(bytes_after_last_selection)
15084 .map(|result| (last_selection.end, result))
15085 .chain(
15086 query
15087 .stream_find_iter(bytes_before_first_selection)
15088 .map(|result| (MultiBufferOffset(0), result)),
15089 );
15090
15091 for (start_offset, query_match) in query_matches {
15092 let query_match = query_match.unwrap(); // can only fail due to I/O
15093 let offset_range =
15094 start_offset + query_match.start()..start_offset + query_match.end();
15095
15096 if !select_next_state.wordwise
15097 || (!buffer.is_inside_word(offset_range.start, None)
15098 && !buffer.is_inside_word(offset_range.end, None))
15099 {
15100 let idx = selections
15101 .partition_point(|selection| selection.end <= offset_range.start);
15102 let overlaps = selections
15103 .get(idx)
15104 .map_or(false, |selection| selection.start < offset_range.end);
15105
15106 if !overlaps {
15107 next_selected_range = Some(offset_range);
15108 break;
15109 }
15110 }
15111 }
15112
15113 if let Some(next_selected_range) = next_selected_range {
15114 self.select_match_ranges(
15115 next_selected_range,
15116 last_selection.reversed,
15117 replace_newest,
15118 autoscroll,
15119 window,
15120 cx,
15121 );
15122 } else {
15123 select_next_state.done = true;
15124 }
15125 }
15126
15127 self.select_next_state = Some(select_next_state);
15128 } else {
15129 let mut only_carets = true;
15130 let mut same_text_selected = true;
15131 let mut selected_text = None;
15132
15133 let mut selections_iter = selections.iter().peekable();
15134 while let Some(selection) = selections_iter.next() {
15135 if selection.start != selection.end {
15136 only_carets = false;
15137 }
15138
15139 if same_text_selected {
15140 if selected_text.is_none() {
15141 selected_text =
15142 Some(buffer.text_for_range(selection.range()).collect::<String>());
15143 }
15144
15145 if let Some(next_selection) = selections_iter.peek() {
15146 if next_selection.len() == selection.len() {
15147 let next_selected_text = buffer
15148 .text_for_range(next_selection.range())
15149 .collect::<String>();
15150 if Some(next_selected_text) != selected_text {
15151 same_text_selected = false;
15152 selected_text = None;
15153 }
15154 } else {
15155 same_text_selected = false;
15156 selected_text = None;
15157 }
15158 }
15159 }
15160 }
15161
15162 if only_carets {
15163 for selection in &mut selections {
15164 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15165 selection.start = word_range.start;
15166 selection.end = word_range.end;
15167 selection.goal = SelectionGoal::None;
15168 selection.reversed = false;
15169 self.select_match_ranges(
15170 selection.start..selection.end,
15171 selection.reversed,
15172 replace_newest,
15173 autoscroll,
15174 window,
15175 cx,
15176 );
15177 }
15178
15179 if selections.len() == 1 {
15180 let selection = selections
15181 .last()
15182 .expect("ensured that there's only one selection");
15183 let query = buffer
15184 .text_for_range(selection.start..selection.end)
15185 .collect::<String>();
15186 let is_empty = query.is_empty();
15187 let select_state = SelectNextState {
15188 query: self.build_query(&[query], cx)?,
15189 wordwise: true,
15190 done: is_empty,
15191 };
15192 self.select_next_state = Some(select_state);
15193 } else {
15194 self.select_next_state = None;
15195 }
15196 } else if let Some(selected_text) = selected_text {
15197 self.select_next_state = Some(SelectNextState {
15198 query: self.build_query(&[selected_text], cx)?,
15199 wordwise: false,
15200 done: false,
15201 });
15202 self.select_next_match_internal(
15203 display_map,
15204 replace_newest,
15205 autoscroll,
15206 window,
15207 cx,
15208 )?;
15209 }
15210 }
15211 Ok(())
15212 }
15213
15214 pub fn select_all_matches(
15215 &mut self,
15216 _action: &SelectAllMatches,
15217 window: &mut Window,
15218 cx: &mut Context<Self>,
15219 ) -> Result<()> {
15220 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15221
15222 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15223
15224 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15225 let Some(select_next_state) = self.select_next_state.as_mut() else {
15226 return Ok(());
15227 };
15228 if select_next_state.done {
15229 return Ok(());
15230 }
15231
15232 let mut new_selections = Vec::new();
15233
15234 let reversed = self
15235 .selections
15236 .oldest::<MultiBufferOffset>(&display_map)
15237 .reversed;
15238 let buffer = display_map.buffer_snapshot();
15239 let query_matches = select_next_state
15240 .query
15241 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15242
15243 for query_match in query_matches.into_iter() {
15244 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15245 let offset_range = if reversed {
15246 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15247 } else {
15248 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15249 };
15250
15251 if !select_next_state.wordwise
15252 || (!buffer.is_inside_word(offset_range.start, None)
15253 && !buffer.is_inside_word(offset_range.end, None))
15254 {
15255 new_selections.push(offset_range.start..offset_range.end);
15256 }
15257 }
15258
15259 select_next_state.done = true;
15260
15261 if new_selections.is_empty() {
15262 log::error!("bug: new_selections is empty in select_all_matches");
15263 return Ok(());
15264 }
15265
15266 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15267 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15268 selections.select_ranges(new_selections)
15269 });
15270
15271 Ok(())
15272 }
15273
15274 pub fn select_next(
15275 &mut self,
15276 action: &SelectNext,
15277 window: &mut Window,
15278 cx: &mut Context<Self>,
15279 ) -> Result<()> {
15280 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15281 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15282 self.select_next_match_internal(
15283 &display_map,
15284 action.replace_newest,
15285 Some(Autoscroll::newest()),
15286 window,
15287 cx,
15288 )?;
15289 Ok(())
15290 }
15291
15292 pub fn select_previous(
15293 &mut self,
15294 action: &SelectPrevious,
15295 window: &mut Window,
15296 cx: &mut Context<Self>,
15297 ) -> Result<()> {
15298 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15299 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15300 let buffer = display_map.buffer_snapshot();
15301 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15302 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15303 let query = &select_prev_state.query;
15304 if !select_prev_state.done {
15305 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15306 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15307 let mut next_selected_range = None;
15308 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15309 let bytes_before_last_selection =
15310 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15311 let bytes_after_first_selection =
15312 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15313 let query_matches = query
15314 .stream_find_iter(bytes_before_last_selection)
15315 .map(|result| (last_selection.start, result))
15316 .chain(
15317 query
15318 .stream_find_iter(bytes_after_first_selection)
15319 .map(|result| (buffer.len(), result)),
15320 );
15321 for (end_offset, query_match) in query_matches {
15322 let query_match = query_match.unwrap(); // can only fail due to I/O
15323 let offset_range =
15324 end_offset - query_match.end()..end_offset - query_match.start();
15325
15326 if !select_prev_state.wordwise
15327 || (!buffer.is_inside_word(offset_range.start, None)
15328 && !buffer.is_inside_word(offset_range.end, None))
15329 {
15330 next_selected_range = Some(offset_range);
15331 break;
15332 }
15333 }
15334
15335 if let Some(next_selected_range) = next_selected_range {
15336 self.select_match_ranges(
15337 next_selected_range,
15338 last_selection.reversed,
15339 action.replace_newest,
15340 Some(Autoscroll::newest()),
15341 window,
15342 cx,
15343 );
15344 } else {
15345 select_prev_state.done = true;
15346 }
15347 }
15348
15349 self.select_prev_state = Some(select_prev_state);
15350 } else {
15351 let mut only_carets = true;
15352 let mut same_text_selected = true;
15353 let mut selected_text = None;
15354
15355 let mut selections_iter = selections.iter().peekable();
15356 while let Some(selection) = selections_iter.next() {
15357 if selection.start != selection.end {
15358 only_carets = false;
15359 }
15360
15361 if same_text_selected {
15362 if selected_text.is_none() {
15363 selected_text =
15364 Some(buffer.text_for_range(selection.range()).collect::<String>());
15365 }
15366
15367 if let Some(next_selection) = selections_iter.peek() {
15368 if next_selection.len() == selection.len() {
15369 let next_selected_text = buffer
15370 .text_for_range(next_selection.range())
15371 .collect::<String>();
15372 if Some(next_selected_text) != selected_text {
15373 same_text_selected = false;
15374 selected_text = None;
15375 }
15376 } else {
15377 same_text_selected = false;
15378 selected_text = None;
15379 }
15380 }
15381 }
15382 }
15383
15384 if only_carets {
15385 for selection in &mut selections {
15386 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15387 selection.start = word_range.start;
15388 selection.end = word_range.end;
15389 selection.goal = SelectionGoal::None;
15390 selection.reversed = false;
15391 self.select_match_ranges(
15392 selection.start..selection.end,
15393 selection.reversed,
15394 action.replace_newest,
15395 Some(Autoscroll::newest()),
15396 window,
15397 cx,
15398 );
15399 }
15400 if selections.len() == 1 {
15401 let selection = selections
15402 .last()
15403 .expect("ensured that there's only one selection");
15404 let query = buffer
15405 .text_for_range(selection.start..selection.end)
15406 .collect::<String>();
15407 let is_empty = query.is_empty();
15408 let select_state = SelectNextState {
15409 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15410 wordwise: true,
15411 done: is_empty,
15412 };
15413 self.select_prev_state = Some(select_state);
15414 } else {
15415 self.select_prev_state = None;
15416 }
15417 } else if let Some(selected_text) = selected_text {
15418 self.select_prev_state = Some(SelectNextState {
15419 query: self
15420 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15421 wordwise: false,
15422 done: false,
15423 });
15424 self.select_previous(action, window, cx)?;
15425 }
15426 }
15427 Ok(())
15428 }
15429
15430 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15431 /// setting the case sensitivity based on the global
15432 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15433 /// editor's settings.
15434 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15435 where
15436 I: IntoIterator<Item = P>,
15437 P: AsRef<[u8]>,
15438 {
15439 let case_sensitive = self
15440 .select_next_is_case_sensitive
15441 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15442
15443 let mut builder = AhoCorasickBuilder::new();
15444 builder.ascii_case_insensitive(!case_sensitive);
15445 builder.build(patterns)
15446 }
15447
15448 pub fn find_next_match(
15449 &mut self,
15450 _: &FindNextMatch,
15451 window: &mut Window,
15452 cx: &mut Context<Self>,
15453 ) -> Result<()> {
15454 let selections = self.selections.disjoint_anchors_arc();
15455 match selections.first() {
15456 Some(first) if selections.len() >= 2 => {
15457 self.change_selections(Default::default(), window, cx, |s| {
15458 s.select_ranges([first.range()]);
15459 });
15460 }
15461 _ => self.select_next(
15462 &SelectNext {
15463 replace_newest: true,
15464 },
15465 window,
15466 cx,
15467 )?,
15468 }
15469 Ok(())
15470 }
15471
15472 pub fn find_previous_match(
15473 &mut self,
15474 _: &FindPreviousMatch,
15475 window: &mut Window,
15476 cx: &mut Context<Self>,
15477 ) -> Result<()> {
15478 let selections = self.selections.disjoint_anchors_arc();
15479 match selections.last() {
15480 Some(last) if selections.len() >= 2 => {
15481 self.change_selections(Default::default(), window, cx, |s| {
15482 s.select_ranges([last.range()]);
15483 });
15484 }
15485 _ => self.select_previous(
15486 &SelectPrevious {
15487 replace_newest: true,
15488 },
15489 window,
15490 cx,
15491 )?,
15492 }
15493 Ok(())
15494 }
15495
15496 pub fn toggle_comments(
15497 &mut self,
15498 action: &ToggleComments,
15499 window: &mut Window,
15500 cx: &mut Context<Self>,
15501 ) {
15502 if self.read_only(cx) {
15503 return;
15504 }
15505 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15506 let text_layout_details = &self.text_layout_details(window);
15507 self.transact(window, cx, |this, window, cx| {
15508 let mut selections = this
15509 .selections
15510 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15511 let mut edits = Vec::new();
15512 let mut selection_edit_ranges = Vec::new();
15513 let mut last_toggled_row = None;
15514 let snapshot = this.buffer.read(cx).read(cx);
15515 let empty_str: Arc<str> = Arc::default();
15516 let mut suffixes_inserted = Vec::new();
15517 let ignore_indent = action.ignore_indent;
15518
15519 fn comment_prefix_range(
15520 snapshot: &MultiBufferSnapshot,
15521 row: MultiBufferRow,
15522 comment_prefix: &str,
15523 comment_prefix_whitespace: &str,
15524 ignore_indent: bool,
15525 ) -> Range<Point> {
15526 let indent_size = if ignore_indent {
15527 0
15528 } else {
15529 snapshot.indent_size_for_line(row).len
15530 };
15531
15532 let start = Point::new(row.0, indent_size);
15533
15534 let mut line_bytes = snapshot
15535 .bytes_in_range(start..snapshot.max_point())
15536 .flatten()
15537 .copied();
15538
15539 // If this line currently begins with the line comment prefix, then record
15540 // the range containing the prefix.
15541 if line_bytes
15542 .by_ref()
15543 .take(comment_prefix.len())
15544 .eq(comment_prefix.bytes())
15545 {
15546 // Include any whitespace that matches the comment prefix.
15547 let matching_whitespace_len = line_bytes
15548 .zip(comment_prefix_whitespace.bytes())
15549 .take_while(|(a, b)| a == b)
15550 .count() as u32;
15551 let end = Point::new(
15552 start.row,
15553 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15554 );
15555 start..end
15556 } else {
15557 start..start
15558 }
15559 }
15560
15561 fn comment_suffix_range(
15562 snapshot: &MultiBufferSnapshot,
15563 row: MultiBufferRow,
15564 comment_suffix: &str,
15565 comment_suffix_has_leading_space: bool,
15566 ) -> Range<Point> {
15567 let end = Point::new(row.0, snapshot.line_len(row));
15568 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15569
15570 let mut line_end_bytes = snapshot
15571 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15572 .flatten()
15573 .copied();
15574
15575 let leading_space_len = if suffix_start_column > 0
15576 && line_end_bytes.next() == Some(b' ')
15577 && comment_suffix_has_leading_space
15578 {
15579 1
15580 } else {
15581 0
15582 };
15583
15584 // If this line currently begins with the line comment prefix, then record
15585 // the range containing the prefix.
15586 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15587 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15588 start..end
15589 } else {
15590 end..end
15591 }
15592 }
15593
15594 // TODO: Handle selections that cross excerpts
15595 for selection in &mut selections {
15596 let start_column = snapshot
15597 .indent_size_for_line(MultiBufferRow(selection.start.row))
15598 .len;
15599 let language = if let Some(language) =
15600 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15601 {
15602 language
15603 } else {
15604 continue;
15605 };
15606
15607 selection_edit_ranges.clear();
15608
15609 // If multiple selections contain a given row, avoid processing that
15610 // row more than once.
15611 let mut start_row = MultiBufferRow(selection.start.row);
15612 if last_toggled_row == Some(start_row) {
15613 start_row = start_row.next_row();
15614 }
15615 let end_row =
15616 if selection.end.row > selection.start.row && selection.end.column == 0 {
15617 MultiBufferRow(selection.end.row - 1)
15618 } else {
15619 MultiBufferRow(selection.end.row)
15620 };
15621 last_toggled_row = Some(end_row);
15622
15623 if start_row > end_row {
15624 continue;
15625 }
15626
15627 // If the language has line comments, toggle those.
15628 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15629
15630 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15631 if ignore_indent {
15632 full_comment_prefixes = full_comment_prefixes
15633 .into_iter()
15634 .map(|s| Arc::from(s.trim_end()))
15635 .collect();
15636 }
15637
15638 if !full_comment_prefixes.is_empty() {
15639 let first_prefix = full_comment_prefixes
15640 .first()
15641 .expect("prefixes is non-empty");
15642 let prefix_trimmed_lengths = full_comment_prefixes
15643 .iter()
15644 .map(|p| p.trim_end_matches(' ').len())
15645 .collect::<SmallVec<[usize; 4]>>();
15646
15647 let mut all_selection_lines_are_comments = true;
15648
15649 for row in start_row.0..=end_row.0 {
15650 let row = MultiBufferRow(row);
15651 if start_row < end_row && snapshot.is_line_blank(row) {
15652 continue;
15653 }
15654
15655 let prefix_range = full_comment_prefixes
15656 .iter()
15657 .zip(prefix_trimmed_lengths.iter().copied())
15658 .map(|(prefix, trimmed_prefix_len)| {
15659 comment_prefix_range(
15660 snapshot.deref(),
15661 row,
15662 &prefix[..trimmed_prefix_len],
15663 &prefix[trimmed_prefix_len..],
15664 ignore_indent,
15665 )
15666 })
15667 .max_by_key(|range| range.end.column - range.start.column)
15668 .expect("prefixes is non-empty");
15669
15670 if prefix_range.is_empty() {
15671 all_selection_lines_are_comments = false;
15672 }
15673
15674 selection_edit_ranges.push(prefix_range);
15675 }
15676
15677 if all_selection_lines_are_comments {
15678 edits.extend(
15679 selection_edit_ranges
15680 .iter()
15681 .cloned()
15682 .map(|range| (range, empty_str.clone())),
15683 );
15684 } else {
15685 let min_column = selection_edit_ranges
15686 .iter()
15687 .map(|range| range.start.column)
15688 .min()
15689 .unwrap_or(0);
15690 edits.extend(selection_edit_ranges.iter().map(|range| {
15691 let position = Point::new(range.start.row, min_column);
15692 (position..position, first_prefix.clone())
15693 }));
15694 }
15695 } else if let Some(BlockCommentConfig {
15696 start: full_comment_prefix,
15697 end: comment_suffix,
15698 ..
15699 }) = language.block_comment()
15700 {
15701 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15702 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15703 let prefix_range = comment_prefix_range(
15704 snapshot.deref(),
15705 start_row,
15706 comment_prefix,
15707 comment_prefix_whitespace,
15708 ignore_indent,
15709 );
15710 let suffix_range = comment_suffix_range(
15711 snapshot.deref(),
15712 end_row,
15713 comment_suffix.trim_start_matches(' '),
15714 comment_suffix.starts_with(' '),
15715 );
15716
15717 if prefix_range.is_empty() || suffix_range.is_empty() {
15718 edits.push((
15719 prefix_range.start..prefix_range.start,
15720 full_comment_prefix.clone(),
15721 ));
15722 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15723 suffixes_inserted.push((end_row, comment_suffix.len()));
15724 } else {
15725 edits.push((prefix_range, empty_str.clone()));
15726 edits.push((suffix_range, empty_str.clone()));
15727 }
15728 } else {
15729 continue;
15730 }
15731 }
15732
15733 drop(snapshot);
15734 this.buffer.update(cx, |buffer, cx| {
15735 buffer.edit(edits, None, cx);
15736 });
15737
15738 // Adjust selections so that they end before any comment suffixes that
15739 // were inserted.
15740 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15741 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15742 let snapshot = this.buffer.read(cx).read(cx);
15743 for selection in &mut selections {
15744 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15745 match row.cmp(&MultiBufferRow(selection.end.row)) {
15746 Ordering::Less => {
15747 suffixes_inserted.next();
15748 continue;
15749 }
15750 Ordering::Greater => break,
15751 Ordering::Equal => {
15752 if selection.end.column == snapshot.line_len(row) {
15753 if selection.is_empty() {
15754 selection.start.column -= suffix_len as u32;
15755 }
15756 selection.end.column -= suffix_len as u32;
15757 }
15758 break;
15759 }
15760 }
15761 }
15762 }
15763
15764 drop(snapshot);
15765 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15766
15767 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15768 let selections_on_single_row = selections.windows(2).all(|selections| {
15769 selections[0].start.row == selections[1].start.row
15770 && selections[0].end.row == selections[1].end.row
15771 && selections[0].start.row == selections[0].end.row
15772 });
15773 let selections_selecting = selections
15774 .iter()
15775 .any(|selection| selection.start != selection.end);
15776 let advance_downwards = action.advance_downwards
15777 && selections_on_single_row
15778 && !selections_selecting
15779 && !matches!(this.mode, EditorMode::SingleLine);
15780
15781 if advance_downwards {
15782 let snapshot = this.buffer.read(cx).snapshot(cx);
15783
15784 this.change_selections(Default::default(), window, cx, |s| {
15785 s.move_cursors_with(|display_snapshot, display_point, _| {
15786 let mut point = display_point.to_point(display_snapshot);
15787 point.row += 1;
15788 point = snapshot.clip_point(point, Bias::Left);
15789 let display_point = point.to_display_point(display_snapshot);
15790 let goal = SelectionGoal::HorizontalPosition(
15791 display_snapshot
15792 .x_for_display_point(display_point, text_layout_details)
15793 .into(),
15794 );
15795 (display_point, goal)
15796 })
15797 });
15798 }
15799 });
15800 }
15801
15802 pub fn select_enclosing_symbol(
15803 &mut self,
15804 _: &SelectEnclosingSymbol,
15805 window: &mut Window,
15806 cx: &mut Context<Self>,
15807 ) {
15808 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15809
15810 let buffer = self.buffer.read(cx).snapshot(cx);
15811 let old_selections = self
15812 .selections
15813 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15814 .into_boxed_slice();
15815
15816 fn update_selection(
15817 selection: &Selection<MultiBufferOffset>,
15818 buffer_snap: &MultiBufferSnapshot,
15819 ) -> Option<Selection<MultiBufferOffset>> {
15820 let cursor = selection.head();
15821 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15822 for symbol in symbols.iter().rev() {
15823 let start = symbol.range.start.to_offset(buffer_snap);
15824 let end = symbol.range.end.to_offset(buffer_snap);
15825 let new_range = start..end;
15826 if start < selection.start || end > selection.end {
15827 return Some(Selection {
15828 id: selection.id,
15829 start: new_range.start,
15830 end: new_range.end,
15831 goal: SelectionGoal::None,
15832 reversed: selection.reversed,
15833 });
15834 }
15835 }
15836 None
15837 }
15838
15839 let mut selected_larger_symbol = false;
15840 let new_selections = old_selections
15841 .iter()
15842 .map(|selection| match update_selection(selection, &buffer) {
15843 Some(new_selection) => {
15844 if new_selection.range() != selection.range() {
15845 selected_larger_symbol = true;
15846 }
15847 new_selection
15848 }
15849 None => selection.clone(),
15850 })
15851 .collect::<Vec<_>>();
15852
15853 if selected_larger_symbol {
15854 self.change_selections(Default::default(), window, cx, |s| {
15855 s.select(new_selections);
15856 });
15857 }
15858 }
15859
15860 pub fn select_larger_syntax_node(
15861 &mut self,
15862 _: &SelectLargerSyntaxNode,
15863 window: &mut Window,
15864 cx: &mut Context<Self>,
15865 ) {
15866 let Some(visible_row_count) = self.visible_row_count() else {
15867 return;
15868 };
15869 let old_selections: Box<[_]> = self
15870 .selections
15871 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15872 .into();
15873 if old_selections.is_empty() {
15874 return;
15875 }
15876
15877 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15878
15879 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15880 let buffer = self.buffer.read(cx).snapshot(cx);
15881
15882 let mut selected_larger_node = false;
15883 let mut new_selections = old_selections
15884 .iter()
15885 .map(|selection| {
15886 let old_range = selection.start..selection.end;
15887
15888 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15889 // manually select word at selection
15890 if ["string_content", "inline"].contains(&node.kind()) {
15891 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15892 // ignore if word is already selected
15893 if !word_range.is_empty() && old_range != word_range {
15894 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15895 // only select word if start and end point belongs to same word
15896 if word_range == last_word_range {
15897 selected_larger_node = true;
15898 return Selection {
15899 id: selection.id,
15900 start: word_range.start,
15901 end: word_range.end,
15902 goal: SelectionGoal::None,
15903 reversed: selection.reversed,
15904 };
15905 }
15906 }
15907 }
15908 }
15909
15910 let mut new_range = old_range.clone();
15911 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15912 new_range = range;
15913 if !node.is_named() {
15914 continue;
15915 }
15916 if !display_map.intersects_fold(new_range.start)
15917 && !display_map.intersects_fold(new_range.end)
15918 {
15919 break;
15920 }
15921 }
15922
15923 selected_larger_node |= new_range != old_range;
15924 Selection {
15925 id: selection.id,
15926 start: new_range.start,
15927 end: new_range.end,
15928 goal: SelectionGoal::None,
15929 reversed: selection.reversed,
15930 }
15931 })
15932 .collect::<Vec<_>>();
15933
15934 if !selected_larger_node {
15935 return; // don't put this call in the history
15936 }
15937
15938 // scroll based on transformation done to the last selection created by the user
15939 let (last_old, last_new) = old_selections
15940 .last()
15941 .zip(new_selections.last().cloned())
15942 .expect("old_selections isn't empty");
15943
15944 // revert selection
15945 let is_selection_reversed = {
15946 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15947 new_selections.last_mut().expect("checked above").reversed =
15948 should_newest_selection_be_reversed;
15949 should_newest_selection_be_reversed
15950 };
15951
15952 if selected_larger_node {
15953 self.select_syntax_node_history.disable_clearing = true;
15954 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15955 s.select(new_selections.clone());
15956 });
15957 self.select_syntax_node_history.disable_clearing = false;
15958 }
15959
15960 let start_row = last_new.start.to_display_point(&display_map).row().0;
15961 let end_row = last_new.end.to_display_point(&display_map).row().0;
15962 let selection_height = end_row - start_row + 1;
15963 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15964
15965 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15966 let scroll_behavior = if fits_on_the_screen {
15967 self.request_autoscroll(Autoscroll::fit(), cx);
15968 SelectSyntaxNodeScrollBehavior::FitSelection
15969 } else if is_selection_reversed {
15970 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15971 SelectSyntaxNodeScrollBehavior::CursorTop
15972 } else {
15973 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15974 SelectSyntaxNodeScrollBehavior::CursorBottom
15975 };
15976
15977 self.select_syntax_node_history.push((
15978 old_selections,
15979 scroll_behavior,
15980 is_selection_reversed,
15981 ));
15982 }
15983
15984 pub fn select_smaller_syntax_node(
15985 &mut self,
15986 _: &SelectSmallerSyntaxNode,
15987 window: &mut Window,
15988 cx: &mut Context<Self>,
15989 ) {
15990 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15991
15992 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15993 self.select_syntax_node_history.pop()
15994 {
15995 if let Some(selection) = selections.last_mut() {
15996 selection.reversed = is_selection_reversed;
15997 }
15998
15999 self.select_syntax_node_history.disable_clearing = true;
16000 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16001 s.select(selections.to_vec());
16002 });
16003 self.select_syntax_node_history.disable_clearing = false;
16004
16005 match scroll_behavior {
16006 SelectSyntaxNodeScrollBehavior::CursorTop => {
16007 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16008 }
16009 SelectSyntaxNodeScrollBehavior::FitSelection => {
16010 self.request_autoscroll(Autoscroll::fit(), cx);
16011 }
16012 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16013 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16014 }
16015 }
16016 }
16017 }
16018
16019 pub fn unwrap_syntax_node(
16020 &mut self,
16021 _: &UnwrapSyntaxNode,
16022 window: &mut Window,
16023 cx: &mut Context<Self>,
16024 ) {
16025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16026
16027 let buffer = self.buffer.read(cx).snapshot(cx);
16028 let selections = self
16029 .selections
16030 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16031 .into_iter()
16032 // subtracting the offset requires sorting
16033 .sorted_by_key(|i| i.start);
16034
16035 let full_edits = selections
16036 .into_iter()
16037 .filter_map(|selection| {
16038 let child = if selection.is_empty()
16039 && let Some((_, ancestor_range)) =
16040 buffer.syntax_ancestor(selection.start..selection.end)
16041 {
16042 ancestor_range
16043 } else {
16044 selection.range()
16045 };
16046
16047 let mut parent = child.clone();
16048 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16049 parent = ancestor_range;
16050 if parent.start < child.start || parent.end > child.end {
16051 break;
16052 }
16053 }
16054
16055 if parent == child {
16056 return None;
16057 }
16058 let text = buffer.text_for_range(child).collect::<String>();
16059 Some((selection.id, parent, text))
16060 })
16061 .collect::<Vec<_>>();
16062 if full_edits.is_empty() {
16063 return;
16064 }
16065
16066 self.transact(window, cx, |this, window, cx| {
16067 this.buffer.update(cx, |buffer, cx| {
16068 buffer.edit(
16069 full_edits
16070 .iter()
16071 .map(|(_, p, t)| (p.clone(), t.clone()))
16072 .collect::<Vec<_>>(),
16073 None,
16074 cx,
16075 );
16076 });
16077 this.change_selections(Default::default(), window, cx, |s| {
16078 let mut offset = 0;
16079 let mut selections = vec![];
16080 for (id, parent, text) in full_edits {
16081 let start = parent.start - offset;
16082 offset += (parent.end - parent.start) - text.len();
16083 selections.push(Selection {
16084 id,
16085 start,
16086 end: start + text.len(),
16087 reversed: false,
16088 goal: Default::default(),
16089 });
16090 }
16091 s.select(selections);
16092 });
16093 });
16094 }
16095
16096 pub fn select_next_syntax_node(
16097 &mut self,
16098 _: &SelectNextSyntaxNode,
16099 window: &mut Window,
16100 cx: &mut Context<Self>,
16101 ) {
16102 let old_selections: Box<[_]> = self
16103 .selections
16104 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16105 .into();
16106 if old_selections.is_empty() {
16107 return;
16108 }
16109
16110 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16111
16112 let buffer = self.buffer.read(cx).snapshot(cx);
16113 let mut selected_sibling = false;
16114
16115 let new_selections = old_selections
16116 .iter()
16117 .map(|selection| {
16118 let old_range = selection.start..selection.end;
16119
16120 let old_range =
16121 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16122 let excerpt = buffer.excerpt_containing(old_range.clone());
16123
16124 if let Some(mut excerpt) = excerpt
16125 && let Some(node) = excerpt
16126 .buffer()
16127 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16128 {
16129 let new_range = excerpt.map_range_from_buffer(
16130 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16131 );
16132 selected_sibling = true;
16133 Selection {
16134 id: selection.id,
16135 start: new_range.start,
16136 end: new_range.end,
16137 goal: SelectionGoal::None,
16138 reversed: selection.reversed,
16139 }
16140 } else {
16141 selection.clone()
16142 }
16143 })
16144 .collect::<Vec<_>>();
16145
16146 if selected_sibling {
16147 self.change_selections(
16148 SelectionEffects::scroll(Autoscroll::fit()),
16149 window,
16150 cx,
16151 |s| {
16152 s.select(new_selections);
16153 },
16154 );
16155 }
16156 }
16157
16158 pub fn select_prev_syntax_node(
16159 &mut self,
16160 _: &SelectPreviousSyntaxNode,
16161 window: &mut Window,
16162 cx: &mut Context<Self>,
16163 ) {
16164 let old_selections: Box<[_]> = self
16165 .selections
16166 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16167 .into();
16168 if old_selections.is_empty() {
16169 return;
16170 }
16171
16172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16173
16174 let buffer = self.buffer.read(cx).snapshot(cx);
16175 let mut selected_sibling = false;
16176
16177 let new_selections = old_selections
16178 .iter()
16179 .map(|selection| {
16180 let old_range = selection.start..selection.end;
16181 let old_range =
16182 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16183 let excerpt = buffer.excerpt_containing(old_range.clone());
16184
16185 if let Some(mut excerpt) = excerpt
16186 && let Some(node) = excerpt
16187 .buffer()
16188 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16189 {
16190 let new_range = excerpt.map_range_from_buffer(
16191 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16192 );
16193 selected_sibling = true;
16194 Selection {
16195 id: selection.id,
16196 start: new_range.start,
16197 end: new_range.end,
16198 goal: SelectionGoal::None,
16199 reversed: selection.reversed,
16200 }
16201 } else {
16202 selection.clone()
16203 }
16204 })
16205 .collect::<Vec<_>>();
16206
16207 if selected_sibling {
16208 self.change_selections(
16209 SelectionEffects::scroll(Autoscroll::fit()),
16210 window,
16211 cx,
16212 |s| {
16213 s.select(new_selections);
16214 },
16215 );
16216 }
16217 }
16218
16219 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16220 if !EditorSettings::get_global(cx).gutter.runnables {
16221 self.clear_tasks();
16222 return Task::ready(());
16223 }
16224 let project = self.project().map(Entity::downgrade);
16225 let task_sources = self.lsp_task_sources(cx);
16226 let multi_buffer = self.buffer.downgrade();
16227 cx.spawn_in(window, async move |editor, cx| {
16228 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16229 let Some(project) = project.and_then(|p| p.upgrade()) else {
16230 return;
16231 };
16232 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16233 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16234 }) else {
16235 return;
16236 };
16237
16238 let hide_runnables = project
16239 .update(cx, |project, _| project.is_via_collab())
16240 .unwrap_or(true);
16241 if hide_runnables {
16242 return;
16243 }
16244 let new_rows =
16245 cx.background_spawn({
16246 let snapshot = display_snapshot.clone();
16247 async move {
16248 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16249 }
16250 })
16251 .await;
16252 let Ok(lsp_tasks) =
16253 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16254 else {
16255 return;
16256 };
16257 let lsp_tasks = lsp_tasks.await;
16258
16259 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16260 lsp_tasks
16261 .into_iter()
16262 .flat_map(|(kind, tasks)| {
16263 tasks.into_iter().filter_map(move |(location, task)| {
16264 Some((kind.clone(), location?, task))
16265 })
16266 })
16267 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16268 let buffer = location.target.buffer;
16269 let buffer_snapshot = buffer.read(cx).snapshot();
16270 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16271 |(excerpt_id, snapshot, _)| {
16272 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16273 display_snapshot
16274 .buffer_snapshot()
16275 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16276 } else {
16277 None
16278 }
16279 },
16280 );
16281 if let Some(offset) = offset {
16282 let task_buffer_range =
16283 location.target.range.to_point(&buffer_snapshot);
16284 let context_buffer_range =
16285 task_buffer_range.to_offset(&buffer_snapshot);
16286 let context_range = BufferOffset(context_buffer_range.start)
16287 ..BufferOffset(context_buffer_range.end);
16288
16289 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16290 .or_insert_with(|| RunnableTasks {
16291 templates: Vec::new(),
16292 offset,
16293 column: task_buffer_range.start.column,
16294 extra_variables: HashMap::default(),
16295 context_range,
16296 })
16297 .templates
16298 .push((kind, task.original_task().clone()));
16299 }
16300
16301 acc
16302 })
16303 }) else {
16304 return;
16305 };
16306
16307 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16308 buffer.language_settings(cx).tasks.prefer_lsp
16309 }) else {
16310 return;
16311 };
16312
16313 let rows = Self::runnable_rows(
16314 project,
16315 display_snapshot,
16316 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16317 new_rows,
16318 cx.clone(),
16319 )
16320 .await;
16321 editor
16322 .update(cx, |editor, _| {
16323 editor.clear_tasks();
16324 for (key, mut value) in rows {
16325 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16326 value.templates.extend(lsp_tasks.templates);
16327 }
16328
16329 editor.insert_tasks(key, value);
16330 }
16331 for (key, value) in lsp_tasks_by_rows {
16332 editor.insert_tasks(key, value);
16333 }
16334 })
16335 .ok();
16336 })
16337 }
16338 fn fetch_runnable_ranges(
16339 snapshot: &DisplaySnapshot,
16340 range: Range<Anchor>,
16341 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16342 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16343 }
16344
16345 fn runnable_rows(
16346 project: Entity<Project>,
16347 snapshot: DisplaySnapshot,
16348 prefer_lsp: bool,
16349 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16350 cx: AsyncWindowContext,
16351 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16352 cx.spawn(async move |cx| {
16353 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16354 for (run_range, mut runnable) in runnable_ranges {
16355 let Some(tasks) = cx
16356 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16357 .ok()
16358 else {
16359 continue;
16360 };
16361 let mut tasks = tasks.await;
16362
16363 if prefer_lsp {
16364 tasks.retain(|(task_kind, _)| {
16365 !matches!(task_kind, TaskSourceKind::Language { .. })
16366 });
16367 }
16368 if tasks.is_empty() {
16369 continue;
16370 }
16371
16372 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16373 let Some(row) = snapshot
16374 .buffer_snapshot()
16375 .buffer_line_for_row(MultiBufferRow(point.row))
16376 .map(|(_, range)| range.start.row)
16377 else {
16378 continue;
16379 };
16380
16381 let context_range =
16382 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16383 runnable_rows.push((
16384 (runnable.buffer_id, row),
16385 RunnableTasks {
16386 templates: tasks,
16387 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16388 context_range,
16389 column: point.column,
16390 extra_variables: runnable.extra_captures,
16391 },
16392 ));
16393 }
16394 runnable_rows
16395 })
16396 }
16397
16398 fn templates_with_tags(
16399 project: &Entity<Project>,
16400 runnable: &mut Runnable,
16401 cx: &mut App,
16402 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16403 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16404 let (worktree_id, file) = project
16405 .buffer_for_id(runnable.buffer, cx)
16406 .and_then(|buffer| buffer.read(cx).file())
16407 .map(|file| (file.worktree_id(cx), file.clone()))
16408 .unzip();
16409
16410 (
16411 project.task_store().read(cx).task_inventory().cloned(),
16412 worktree_id,
16413 file,
16414 )
16415 });
16416
16417 let tags = mem::take(&mut runnable.tags);
16418 let language = runnable.language.clone();
16419 cx.spawn(async move |cx| {
16420 let mut templates_with_tags = Vec::new();
16421 if let Some(inventory) = inventory {
16422 for RunnableTag(tag) in tags {
16423 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16424 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16425 }) else {
16426 return templates_with_tags;
16427 };
16428 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16429 move |(_, template)| {
16430 template.tags.iter().any(|source_tag| source_tag == &tag)
16431 },
16432 ));
16433 }
16434 }
16435 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16436
16437 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16438 // Strongest source wins; if we have worktree tag binding, prefer that to
16439 // global and language bindings;
16440 // if we have a global binding, prefer that to language binding.
16441 let first_mismatch = templates_with_tags
16442 .iter()
16443 .position(|(tag_source, _)| tag_source != leading_tag_source);
16444 if let Some(index) = first_mismatch {
16445 templates_with_tags.truncate(index);
16446 }
16447 }
16448
16449 templates_with_tags
16450 })
16451 }
16452
16453 pub fn move_to_enclosing_bracket(
16454 &mut self,
16455 _: &MoveToEnclosingBracket,
16456 window: &mut Window,
16457 cx: &mut Context<Self>,
16458 ) {
16459 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16460 self.change_selections(Default::default(), window, cx, |s| {
16461 s.move_offsets_with(|snapshot, selection| {
16462 let Some(enclosing_bracket_ranges) =
16463 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16464 else {
16465 return;
16466 };
16467
16468 let mut best_length = usize::MAX;
16469 let mut best_inside = false;
16470 let mut best_in_bracket_range = false;
16471 let mut best_destination = None;
16472 for (open, close) in enclosing_bracket_ranges {
16473 let close = close.to_inclusive();
16474 let length = *close.end() - open.start;
16475 let inside = selection.start >= open.end && selection.end <= *close.start();
16476 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16477 || close.contains(&selection.head());
16478
16479 // If best is next to a bracket and current isn't, skip
16480 if !in_bracket_range && best_in_bracket_range {
16481 continue;
16482 }
16483
16484 // Prefer smaller lengths unless best is inside and current isn't
16485 if length > best_length && (best_inside || !inside) {
16486 continue;
16487 }
16488
16489 best_length = length;
16490 best_inside = inside;
16491 best_in_bracket_range = in_bracket_range;
16492 best_destination = Some(
16493 if close.contains(&selection.start) && close.contains(&selection.end) {
16494 if inside { open.end } else { open.start }
16495 } else if inside {
16496 *close.start()
16497 } else {
16498 *close.end()
16499 },
16500 );
16501 }
16502
16503 if let Some(destination) = best_destination {
16504 selection.collapse_to(destination, SelectionGoal::None);
16505 }
16506 })
16507 });
16508 }
16509
16510 pub fn undo_selection(
16511 &mut self,
16512 _: &UndoSelection,
16513 window: &mut Window,
16514 cx: &mut Context<Self>,
16515 ) {
16516 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16517 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16518 self.selection_history.mode = SelectionHistoryMode::Undoing;
16519 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16520 this.end_selection(window, cx);
16521 this.change_selections(
16522 SelectionEffects::scroll(Autoscroll::newest()),
16523 window,
16524 cx,
16525 |s| s.select_anchors(entry.selections.to_vec()),
16526 );
16527 });
16528 self.selection_history.mode = SelectionHistoryMode::Normal;
16529
16530 self.select_next_state = entry.select_next_state;
16531 self.select_prev_state = entry.select_prev_state;
16532 self.add_selections_state = entry.add_selections_state;
16533 }
16534 }
16535
16536 pub fn redo_selection(
16537 &mut self,
16538 _: &RedoSelection,
16539 window: &mut Window,
16540 cx: &mut Context<Self>,
16541 ) {
16542 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16543 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16544 self.selection_history.mode = SelectionHistoryMode::Redoing;
16545 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16546 this.end_selection(window, cx);
16547 this.change_selections(
16548 SelectionEffects::scroll(Autoscroll::newest()),
16549 window,
16550 cx,
16551 |s| s.select_anchors(entry.selections.to_vec()),
16552 );
16553 });
16554 self.selection_history.mode = SelectionHistoryMode::Normal;
16555
16556 self.select_next_state = entry.select_next_state;
16557 self.select_prev_state = entry.select_prev_state;
16558 self.add_selections_state = entry.add_selections_state;
16559 }
16560 }
16561
16562 pub fn expand_excerpts(
16563 &mut self,
16564 action: &ExpandExcerpts,
16565 _: &mut Window,
16566 cx: &mut Context<Self>,
16567 ) {
16568 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16569 }
16570
16571 pub fn expand_excerpts_down(
16572 &mut self,
16573 action: &ExpandExcerptsDown,
16574 _: &mut Window,
16575 cx: &mut Context<Self>,
16576 ) {
16577 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16578 }
16579
16580 pub fn expand_excerpts_up(
16581 &mut self,
16582 action: &ExpandExcerptsUp,
16583 _: &mut Window,
16584 cx: &mut Context<Self>,
16585 ) {
16586 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16587 }
16588
16589 pub fn expand_excerpts_for_direction(
16590 &mut self,
16591 lines: u32,
16592 direction: ExpandExcerptDirection,
16593
16594 cx: &mut Context<Self>,
16595 ) {
16596 let selections = self.selections.disjoint_anchors_arc();
16597
16598 let lines = if lines == 0 {
16599 EditorSettings::get_global(cx).expand_excerpt_lines
16600 } else {
16601 lines
16602 };
16603
16604 self.buffer.update(cx, |buffer, cx| {
16605 let snapshot = buffer.snapshot(cx);
16606 let mut excerpt_ids = selections
16607 .iter()
16608 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16609 .collect::<Vec<_>>();
16610 excerpt_ids.sort();
16611 excerpt_ids.dedup();
16612 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16613 })
16614 }
16615
16616 pub fn expand_excerpt(
16617 &mut self,
16618 excerpt: ExcerptId,
16619 direction: ExpandExcerptDirection,
16620 window: &mut Window,
16621 cx: &mut Context<Self>,
16622 ) {
16623 let current_scroll_position = self.scroll_position(cx);
16624 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16625 let mut scroll = None;
16626
16627 if direction == ExpandExcerptDirection::Down {
16628 let multi_buffer = self.buffer.read(cx);
16629 let snapshot = multi_buffer.snapshot(cx);
16630 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16631 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16632 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16633 {
16634 let buffer_snapshot = buffer.read(cx).snapshot();
16635 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16636 let last_row = buffer_snapshot.max_point().row;
16637 let lines_below = last_row.saturating_sub(excerpt_end_row);
16638 if lines_below >= lines_to_expand {
16639 scroll = Some(
16640 current_scroll_position
16641 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16642 );
16643 }
16644 }
16645 }
16646 if direction == ExpandExcerptDirection::Up
16647 && self
16648 .buffer
16649 .read(cx)
16650 .snapshot(cx)
16651 .excerpt_before(excerpt)
16652 .is_none()
16653 {
16654 scroll = Some(current_scroll_position);
16655 }
16656
16657 self.buffer.update(cx, |buffer, cx| {
16658 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16659 });
16660
16661 if let Some(new_scroll_position) = scroll {
16662 self.set_scroll_position(new_scroll_position, window, cx);
16663 }
16664 }
16665
16666 pub fn go_to_singleton_buffer_point(
16667 &mut self,
16668 point: Point,
16669 window: &mut Window,
16670 cx: &mut Context<Self>,
16671 ) {
16672 self.go_to_singleton_buffer_range(point..point, window, cx);
16673 }
16674
16675 pub fn go_to_singleton_buffer_range(
16676 &mut self,
16677 range: Range<Point>,
16678 window: &mut Window,
16679 cx: &mut Context<Self>,
16680 ) {
16681 let multibuffer = self.buffer().read(cx);
16682 let Some(buffer) = multibuffer.as_singleton() else {
16683 return;
16684 };
16685 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16686 return;
16687 };
16688 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16689 return;
16690 };
16691 self.change_selections(
16692 SelectionEffects::default().nav_history(true),
16693 window,
16694 cx,
16695 |s| s.select_anchor_ranges([start..end]),
16696 );
16697 }
16698
16699 pub fn go_to_diagnostic(
16700 &mut self,
16701 action: &GoToDiagnostic,
16702 window: &mut Window,
16703 cx: &mut Context<Self>,
16704 ) {
16705 if !self.diagnostics_enabled() {
16706 return;
16707 }
16708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16709 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16710 }
16711
16712 pub fn go_to_prev_diagnostic(
16713 &mut self,
16714 action: &GoToPreviousDiagnostic,
16715 window: &mut Window,
16716 cx: &mut Context<Self>,
16717 ) {
16718 if !self.diagnostics_enabled() {
16719 return;
16720 }
16721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16722 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16723 }
16724
16725 pub fn go_to_diagnostic_impl(
16726 &mut self,
16727 direction: Direction,
16728 severity: GoToDiagnosticSeverityFilter,
16729 window: &mut Window,
16730 cx: &mut Context<Self>,
16731 ) {
16732 let buffer = self.buffer.read(cx).snapshot(cx);
16733 let selection = self
16734 .selections
16735 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16736
16737 let mut active_group_id = None;
16738 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16739 && active_group.active_range.start.to_offset(&buffer) == selection.start
16740 {
16741 active_group_id = Some(active_group.group_id);
16742 }
16743
16744 fn filtered<'a>(
16745 severity: GoToDiagnosticSeverityFilter,
16746 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16747 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16748 diagnostics
16749 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16750 .filter(|entry| entry.range.start != entry.range.end)
16751 .filter(|entry| !entry.diagnostic.is_unnecessary)
16752 }
16753
16754 let before = filtered(
16755 severity,
16756 buffer
16757 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16758 .filter(|entry| entry.range.start <= selection.start),
16759 );
16760 let after = filtered(
16761 severity,
16762 buffer
16763 .diagnostics_in_range(selection.start..buffer.len())
16764 .filter(|entry| entry.range.start >= selection.start),
16765 );
16766
16767 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16768 if direction == Direction::Prev {
16769 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16770 {
16771 for diagnostic in prev_diagnostics.into_iter().rev() {
16772 if diagnostic.range.start != selection.start
16773 || active_group_id
16774 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16775 {
16776 found = Some(diagnostic);
16777 break 'outer;
16778 }
16779 }
16780 }
16781 } else {
16782 for diagnostic in after.chain(before) {
16783 if diagnostic.range.start != selection.start
16784 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16785 {
16786 found = Some(diagnostic);
16787 break;
16788 }
16789 }
16790 }
16791 let Some(next_diagnostic) = found else {
16792 return;
16793 };
16794
16795 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16796 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16797 return;
16798 };
16799 let snapshot = self.snapshot(window, cx);
16800 if snapshot.intersects_fold(next_diagnostic.range.start) {
16801 self.unfold_ranges(
16802 std::slice::from_ref(&next_diagnostic.range),
16803 true,
16804 false,
16805 cx,
16806 );
16807 }
16808 self.change_selections(Default::default(), window, cx, |s| {
16809 s.select_ranges(vec![
16810 next_diagnostic.range.start..next_diagnostic.range.start,
16811 ])
16812 });
16813 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16814 self.refresh_edit_prediction(false, true, window, cx);
16815 }
16816
16817 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16818 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16819 let snapshot = self.snapshot(window, cx);
16820 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16821 self.go_to_hunk_before_or_after_position(
16822 &snapshot,
16823 selection.head(),
16824 Direction::Next,
16825 window,
16826 cx,
16827 );
16828 }
16829
16830 pub fn go_to_hunk_before_or_after_position(
16831 &mut self,
16832 snapshot: &EditorSnapshot,
16833 position: Point,
16834 direction: Direction,
16835 window: &mut Window,
16836 cx: &mut Context<Editor>,
16837 ) {
16838 let row = if direction == Direction::Next {
16839 self.hunk_after_position(snapshot, position)
16840 .map(|hunk| hunk.row_range.start)
16841 } else {
16842 self.hunk_before_position(snapshot, position)
16843 };
16844
16845 if let Some(row) = row {
16846 let destination = Point::new(row.0, 0);
16847 let autoscroll = Autoscroll::center();
16848
16849 self.unfold_ranges(&[destination..destination], false, false, cx);
16850 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16851 s.select_ranges([destination..destination]);
16852 });
16853 }
16854 }
16855
16856 fn hunk_after_position(
16857 &mut self,
16858 snapshot: &EditorSnapshot,
16859 position: Point,
16860 ) -> Option<MultiBufferDiffHunk> {
16861 snapshot
16862 .buffer_snapshot()
16863 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16864 .find(|hunk| hunk.row_range.start.0 > position.row)
16865 .or_else(|| {
16866 snapshot
16867 .buffer_snapshot()
16868 .diff_hunks_in_range(Point::zero()..position)
16869 .find(|hunk| hunk.row_range.end.0 < position.row)
16870 })
16871 }
16872
16873 fn go_to_prev_hunk(
16874 &mut self,
16875 _: &GoToPreviousHunk,
16876 window: &mut Window,
16877 cx: &mut Context<Self>,
16878 ) {
16879 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16880 let snapshot = self.snapshot(window, cx);
16881 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16882 self.go_to_hunk_before_or_after_position(
16883 &snapshot,
16884 selection.head(),
16885 Direction::Prev,
16886 window,
16887 cx,
16888 );
16889 }
16890
16891 fn hunk_before_position(
16892 &mut self,
16893 snapshot: &EditorSnapshot,
16894 position: Point,
16895 ) -> Option<MultiBufferRow> {
16896 snapshot
16897 .buffer_snapshot()
16898 .diff_hunk_before(position)
16899 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16900 }
16901
16902 fn go_to_next_change(
16903 &mut self,
16904 _: &GoToNextChange,
16905 window: &mut Window,
16906 cx: &mut Context<Self>,
16907 ) {
16908 if let Some(selections) = self
16909 .change_list
16910 .next_change(1, Direction::Next)
16911 .map(|s| s.to_vec())
16912 {
16913 self.change_selections(Default::default(), window, cx, |s| {
16914 let map = s.display_snapshot();
16915 s.select_display_ranges(selections.iter().map(|a| {
16916 let point = a.to_display_point(&map);
16917 point..point
16918 }))
16919 })
16920 }
16921 }
16922
16923 fn go_to_previous_change(
16924 &mut self,
16925 _: &GoToPreviousChange,
16926 window: &mut Window,
16927 cx: &mut Context<Self>,
16928 ) {
16929 if let Some(selections) = self
16930 .change_list
16931 .next_change(1, Direction::Prev)
16932 .map(|s| s.to_vec())
16933 {
16934 self.change_selections(Default::default(), window, cx, |s| {
16935 let map = s.display_snapshot();
16936 s.select_display_ranges(selections.iter().map(|a| {
16937 let point = a.to_display_point(&map);
16938 point..point
16939 }))
16940 })
16941 }
16942 }
16943
16944 pub fn go_to_next_document_highlight(
16945 &mut self,
16946 _: &GoToNextDocumentHighlight,
16947 window: &mut Window,
16948 cx: &mut Context<Self>,
16949 ) {
16950 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16951 }
16952
16953 pub fn go_to_prev_document_highlight(
16954 &mut self,
16955 _: &GoToPreviousDocumentHighlight,
16956 window: &mut Window,
16957 cx: &mut Context<Self>,
16958 ) {
16959 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16960 }
16961
16962 pub fn go_to_document_highlight_before_or_after_position(
16963 &mut self,
16964 direction: Direction,
16965 window: &mut Window,
16966 cx: &mut Context<Editor>,
16967 ) {
16968 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16969 let snapshot = self.snapshot(window, cx);
16970 let buffer = &snapshot.buffer_snapshot();
16971 let position = self
16972 .selections
16973 .newest::<Point>(&snapshot.display_snapshot)
16974 .head();
16975 let anchor_position = buffer.anchor_after(position);
16976
16977 // Get all document highlights (both read and write)
16978 let mut all_highlights = Vec::new();
16979
16980 if let Some((_, read_highlights)) = self
16981 .background_highlights
16982 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16983 {
16984 all_highlights.extend(read_highlights.iter());
16985 }
16986
16987 if let Some((_, write_highlights)) = self
16988 .background_highlights
16989 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16990 {
16991 all_highlights.extend(write_highlights.iter());
16992 }
16993
16994 if all_highlights.is_empty() {
16995 return;
16996 }
16997
16998 // Sort highlights by position
16999 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17000
17001 let target_highlight = match direction {
17002 Direction::Next => {
17003 // Find the first highlight after the current position
17004 all_highlights
17005 .iter()
17006 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17007 }
17008 Direction::Prev => {
17009 // Find the last highlight before the current position
17010 all_highlights
17011 .iter()
17012 .rev()
17013 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17014 }
17015 };
17016
17017 if let Some(highlight) = target_highlight {
17018 let destination = highlight.start.to_point(buffer);
17019 let autoscroll = Autoscroll::center();
17020
17021 self.unfold_ranges(&[destination..destination], false, false, cx);
17022 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17023 s.select_ranges([destination..destination]);
17024 });
17025 }
17026 }
17027
17028 fn go_to_line<T: 'static>(
17029 &mut self,
17030 position: Anchor,
17031 highlight_color: Option<Hsla>,
17032 window: &mut Window,
17033 cx: &mut Context<Self>,
17034 ) {
17035 let snapshot = self.snapshot(window, cx).display_snapshot;
17036 let position = position.to_point(&snapshot.buffer_snapshot());
17037 let start = snapshot
17038 .buffer_snapshot()
17039 .clip_point(Point::new(position.row, 0), Bias::Left);
17040 let end = start + Point::new(1, 0);
17041 let start = snapshot.buffer_snapshot().anchor_before(start);
17042 let end = snapshot.buffer_snapshot().anchor_before(end);
17043
17044 self.highlight_rows::<T>(
17045 start..end,
17046 highlight_color
17047 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17048 Default::default(),
17049 cx,
17050 );
17051
17052 if self.buffer.read(cx).is_singleton() {
17053 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17054 }
17055 }
17056
17057 pub fn go_to_definition(
17058 &mut self,
17059 _: &GoToDefinition,
17060 window: &mut Window,
17061 cx: &mut Context<Self>,
17062 ) -> Task<Result<Navigated>> {
17063 let definition =
17064 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17065 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17066 cx.spawn_in(window, async move |editor, cx| {
17067 if definition.await? == Navigated::Yes {
17068 return Ok(Navigated::Yes);
17069 }
17070 match fallback_strategy {
17071 GoToDefinitionFallback::None => Ok(Navigated::No),
17072 GoToDefinitionFallback::FindAllReferences => {
17073 match editor.update_in(cx, |editor, window, cx| {
17074 editor.find_all_references(&FindAllReferences::default(), window, cx)
17075 })? {
17076 Some(references) => references.await,
17077 None => Ok(Navigated::No),
17078 }
17079 }
17080 }
17081 })
17082 }
17083
17084 pub fn go_to_declaration(
17085 &mut self,
17086 _: &GoToDeclaration,
17087 window: &mut Window,
17088 cx: &mut Context<Self>,
17089 ) -> Task<Result<Navigated>> {
17090 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17091 }
17092
17093 pub fn go_to_declaration_split(
17094 &mut self,
17095 _: &GoToDeclaration,
17096 window: &mut Window,
17097 cx: &mut Context<Self>,
17098 ) -> Task<Result<Navigated>> {
17099 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17100 }
17101
17102 pub fn go_to_implementation(
17103 &mut self,
17104 _: &GoToImplementation,
17105 window: &mut Window,
17106 cx: &mut Context<Self>,
17107 ) -> Task<Result<Navigated>> {
17108 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17109 }
17110
17111 pub fn go_to_implementation_split(
17112 &mut self,
17113 _: &GoToImplementationSplit,
17114 window: &mut Window,
17115 cx: &mut Context<Self>,
17116 ) -> Task<Result<Navigated>> {
17117 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17118 }
17119
17120 pub fn go_to_type_definition(
17121 &mut self,
17122 _: &GoToTypeDefinition,
17123 window: &mut Window,
17124 cx: &mut Context<Self>,
17125 ) -> Task<Result<Navigated>> {
17126 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17127 }
17128
17129 pub fn go_to_definition_split(
17130 &mut self,
17131 _: &GoToDefinitionSplit,
17132 window: &mut Window,
17133 cx: &mut Context<Self>,
17134 ) -> Task<Result<Navigated>> {
17135 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17136 }
17137
17138 pub fn go_to_type_definition_split(
17139 &mut self,
17140 _: &GoToTypeDefinitionSplit,
17141 window: &mut Window,
17142 cx: &mut Context<Self>,
17143 ) -> Task<Result<Navigated>> {
17144 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17145 }
17146
17147 fn go_to_definition_of_kind(
17148 &mut self,
17149 kind: GotoDefinitionKind,
17150 split: bool,
17151 window: &mut Window,
17152 cx: &mut Context<Self>,
17153 ) -> Task<Result<Navigated>> {
17154 let Some(provider) = self.semantics_provider.clone() else {
17155 return Task::ready(Ok(Navigated::No));
17156 };
17157 let head = self
17158 .selections
17159 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17160 .head();
17161 let buffer = self.buffer.read(cx);
17162 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17163 return Task::ready(Ok(Navigated::No));
17164 };
17165 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17166 return Task::ready(Ok(Navigated::No));
17167 };
17168
17169 cx.spawn_in(window, async move |editor, cx| {
17170 let Some(definitions) = definitions.await? else {
17171 return Ok(Navigated::No);
17172 };
17173 let navigated = editor
17174 .update_in(cx, |editor, window, cx| {
17175 editor.navigate_to_hover_links(
17176 Some(kind),
17177 definitions
17178 .into_iter()
17179 .filter(|location| {
17180 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17181 })
17182 .map(HoverLink::Text)
17183 .collect::<Vec<_>>(),
17184 split,
17185 window,
17186 cx,
17187 )
17188 })?
17189 .await?;
17190 anyhow::Ok(navigated)
17191 })
17192 }
17193
17194 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17195 let selection = self.selections.newest_anchor();
17196 let head = selection.head();
17197 let tail = selection.tail();
17198
17199 let Some((buffer, start_position)) =
17200 self.buffer.read(cx).text_anchor_for_position(head, cx)
17201 else {
17202 return;
17203 };
17204
17205 let end_position = if head != tail {
17206 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17207 return;
17208 };
17209 Some(pos)
17210 } else {
17211 None
17212 };
17213
17214 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17215 let url = if let Some(end_pos) = end_position {
17216 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17217 } else {
17218 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17219 };
17220
17221 if let Some(url) = url {
17222 cx.update(|window, cx| {
17223 if parse_zed_link(&url, cx).is_some() {
17224 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17225 } else {
17226 cx.open_url(&url);
17227 }
17228 })?;
17229 }
17230
17231 anyhow::Ok(())
17232 });
17233
17234 url_finder.detach();
17235 }
17236
17237 pub fn open_selected_filename(
17238 &mut self,
17239 _: &OpenSelectedFilename,
17240 window: &mut Window,
17241 cx: &mut Context<Self>,
17242 ) {
17243 let Some(workspace) = self.workspace() else {
17244 return;
17245 };
17246
17247 let position = self.selections.newest_anchor().head();
17248
17249 let Some((buffer, buffer_position)) =
17250 self.buffer.read(cx).text_anchor_for_position(position, cx)
17251 else {
17252 return;
17253 };
17254
17255 let project = self.project.clone();
17256
17257 cx.spawn_in(window, async move |_, cx| {
17258 let result = find_file(&buffer, project, buffer_position, cx).await;
17259
17260 if let Some((_, path)) = result {
17261 workspace
17262 .update_in(cx, |workspace, window, cx| {
17263 workspace.open_resolved_path(path, window, cx)
17264 })?
17265 .await?;
17266 }
17267 anyhow::Ok(())
17268 })
17269 .detach();
17270 }
17271
17272 pub(crate) fn navigate_to_hover_links(
17273 &mut self,
17274 kind: Option<GotoDefinitionKind>,
17275 definitions: Vec<HoverLink>,
17276 split: bool,
17277 window: &mut Window,
17278 cx: &mut Context<Editor>,
17279 ) -> Task<Result<Navigated>> {
17280 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17281 let mut first_url_or_file = None;
17282 let definitions: Vec<_> = definitions
17283 .into_iter()
17284 .filter_map(|def| match def {
17285 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17286 HoverLink::InlayHint(lsp_location, server_id) => {
17287 let computation =
17288 self.compute_target_location(lsp_location, server_id, window, cx);
17289 Some(cx.background_spawn(computation))
17290 }
17291 HoverLink::Url(url) => {
17292 first_url_or_file = Some(Either::Left(url));
17293 None
17294 }
17295 HoverLink::File(path) => {
17296 first_url_or_file = Some(Either::Right(path));
17297 None
17298 }
17299 })
17300 .collect();
17301
17302 let workspace = self.workspace();
17303
17304 cx.spawn_in(window, async move |editor, cx| {
17305 let locations: Vec<Location> = future::join_all(definitions)
17306 .await
17307 .into_iter()
17308 .filter_map(|location| location.transpose())
17309 .collect::<Result<_>>()
17310 .context("location tasks")?;
17311 let mut locations = cx.update(|_, cx| {
17312 locations
17313 .into_iter()
17314 .map(|location| {
17315 let buffer = location.buffer.read(cx);
17316 (location.buffer, location.range.to_point(buffer))
17317 })
17318 .into_group_map()
17319 })?;
17320 let mut num_locations = 0;
17321 for ranges in locations.values_mut() {
17322 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17323 ranges.dedup();
17324 num_locations += ranges.len();
17325 }
17326
17327 if num_locations > 1 {
17328 let tab_kind = match kind {
17329 Some(GotoDefinitionKind::Implementation) => "Implementations",
17330 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17331 Some(GotoDefinitionKind::Declaration) => "Declarations",
17332 Some(GotoDefinitionKind::Type) => "Types",
17333 };
17334 let title = editor
17335 .update_in(cx, |_, _, cx| {
17336 let target = locations
17337 .iter()
17338 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17339 .map(|(buffer, location)| {
17340 buffer
17341 .read(cx)
17342 .text_for_range(location.clone())
17343 .collect::<String>()
17344 })
17345 .filter(|text| !text.contains('\n'))
17346 .unique()
17347 .take(3)
17348 .join(", ");
17349 if target.is_empty() {
17350 tab_kind.to_owned()
17351 } else {
17352 format!("{tab_kind} for {target}")
17353 }
17354 })
17355 .context("buffer title")?;
17356
17357 let Some(workspace) = workspace else {
17358 return Ok(Navigated::No);
17359 };
17360
17361 let opened = workspace
17362 .update_in(cx, |workspace, window, cx| {
17363 let allow_preview = PreviewTabsSettings::get_global(cx)
17364 .enable_preview_multibuffer_from_code_navigation;
17365 Self::open_locations_in_multibuffer(
17366 workspace,
17367 locations,
17368 title,
17369 split,
17370 allow_preview,
17371 MultibufferSelectionMode::First,
17372 window,
17373 cx,
17374 )
17375 })
17376 .is_ok();
17377
17378 anyhow::Ok(Navigated::from_bool(opened))
17379 } else if num_locations == 0 {
17380 // If there is one url or file, open it directly
17381 match first_url_or_file {
17382 Some(Either::Left(url)) => {
17383 cx.update(|window, cx| {
17384 if parse_zed_link(&url, cx).is_some() {
17385 window
17386 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17387 } else {
17388 cx.open_url(&url);
17389 }
17390 })?;
17391 Ok(Navigated::Yes)
17392 }
17393 Some(Either::Right(path)) => {
17394 // TODO(andrew): respect preview tab settings
17395 // `enable_keep_preview_on_code_navigation` and
17396 // `enable_preview_file_from_code_navigation`
17397 let Some(workspace) = workspace else {
17398 return Ok(Navigated::No);
17399 };
17400 workspace
17401 .update_in(cx, |workspace, window, cx| {
17402 workspace.open_resolved_path(path, window, cx)
17403 })?
17404 .await?;
17405 Ok(Navigated::Yes)
17406 }
17407 None => Ok(Navigated::No),
17408 }
17409 } else {
17410 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17411 let target_range = target_ranges.first().unwrap().clone();
17412
17413 editor.update_in(cx, |editor, window, cx| {
17414 let range = target_range.to_point(target_buffer.read(cx));
17415 let range = editor.range_for_match(&range);
17416 let range = collapse_multiline_range(range);
17417
17418 if !split
17419 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17420 {
17421 editor.go_to_singleton_buffer_range(range, window, cx);
17422 } else {
17423 let Some(workspace) = workspace else {
17424 return Navigated::No;
17425 };
17426 let pane = workspace.read(cx).active_pane().clone();
17427 window.defer(cx, move |window, cx| {
17428 let target_editor: Entity<Self> =
17429 workspace.update(cx, |workspace, cx| {
17430 let pane = if split {
17431 workspace.adjacent_pane(window, cx)
17432 } else {
17433 workspace.active_pane().clone()
17434 };
17435
17436 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17437 let keep_old_preview = preview_tabs_settings
17438 .enable_keep_preview_on_code_navigation;
17439 let allow_new_preview = preview_tabs_settings
17440 .enable_preview_file_from_code_navigation;
17441
17442 workspace.open_project_item(
17443 pane,
17444 target_buffer.clone(),
17445 true,
17446 true,
17447 keep_old_preview,
17448 allow_new_preview,
17449 window,
17450 cx,
17451 )
17452 });
17453 target_editor.update(cx, |target_editor, cx| {
17454 // When selecting a definition in a different buffer, disable the nav history
17455 // to avoid creating a history entry at the previous cursor location.
17456 pane.update(cx, |pane, _| pane.disable_history());
17457 target_editor.go_to_singleton_buffer_range(range, window, cx);
17458 pane.update(cx, |pane, _| pane.enable_history());
17459 });
17460 });
17461 }
17462 Navigated::Yes
17463 })
17464 }
17465 })
17466 }
17467
17468 fn compute_target_location(
17469 &self,
17470 lsp_location: lsp::Location,
17471 server_id: LanguageServerId,
17472 window: &mut Window,
17473 cx: &mut Context<Self>,
17474 ) -> Task<anyhow::Result<Option<Location>>> {
17475 let Some(project) = self.project.clone() else {
17476 return Task::ready(Ok(None));
17477 };
17478
17479 cx.spawn_in(window, async move |editor, cx| {
17480 let location_task = editor.update(cx, |_, cx| {
17481 project.update(cx, |project, cx| {
17482 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17483 })
17484 })?;
17485 let location = Some({
17486 let target_buffer_handle = location_task.await.context("open local buffer")?;
17487 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17488 let target_start = target_buffer
17489 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17490 let target_end = target_buffer
17491 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17492 target_buffer.anchor_after(target_start)
17493 ..target_buffer.anchor_before(target_end)
17494 })?;
17495 Location {
17496 buffer: target_buffer_handle,
17497 range,
17498 }
17499 });
17500 Ok(location)
17501 })
17502 }
17503
17504 fn go_to_next_reference(
17505 &mut self,
17506 _: &GoToNextReference,
17507 window: &mut Window,
17508 cx: &mut Context<Self>,
17509 ) {
17510 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17511 if let Some(task) = task {
17512 task.detach();
17513 };
17514 }
17515
17516 fn go_to_prev_reference(
17517 &mut self,
17518 _: &GoToPreviousReference,
17519 window: &mut Window,
17520 cx: &mut Context<Self>,
17521 ) {
17522 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17523 if let Some(task) = task {
17524 task.detach();
17525 };
17526 }
17527
17528 pub fn go_to_reference_before_or_after_position(
17529 &mut self,
17530 direction: Direction,
17531 count: usize,
17532 window: &mut Window,
17533 cx: &mut Context<Self>,
17534 ) -> Option<Task<Result<()>>> {
17535 let selection = self.selections.newest_anchor();
17536 let head = selection.head();
17537
17538 let multi_buffer = self.buffer.read(cx);
17539
17540 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17541 let workspace = self.workspace()?;
17542 let project = workspace.read(cx).project().clone();
17543 let references =
17544 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17545 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17546 let Some(locations) = references.await? else {
17547 return Ok(());
17548 };
17549
17550 if locations.is_empty() {
17551 // totally normal - the cursor may be on something which is not
17552 // a symbol (e.g. a keyword)
17553 log::info!("no references found under cursor");
17554 return Ok(());
17555 }
17556
17557 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17558
17559 let (locations, current_location_index) =
17560 multi_buffer.update(cx, |multi_buffer, cx| {
17561 let mut locations = locations
17562 .into_iter()
17563 .filter_map(|loc| {
17564 let start = multi_buffer.buffer_anchor_to_anchor(
17565 &loc.buffer,
17566 loc.range.start,
17567 cx,
17568 )?;
17569 let end = multi_buffer.buffer_anchor_to_anchor(
17570 &loc.buffer,
17571 loc.range.end,
17572 cx,
17573 )?;
17574 Some(start..end)
17575 })
17576 .collect::<Vec<_>>();
17577
17578 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17579 // There is an O(n) implementation, but given this list will be
17580 // small (usually <100 items), the extra O(log(n)) factor isn't
17581 // worth the (surprisingly large amount of) extra complexity.
17582 locations
17583 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17584
17585 let head_offset = head.to_offset(&multi_buffer_snapshot);
17586
17587 let current_location_index = locations.iter().position(|loc| {
17588 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17589 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17590 });
17591
17592 (locations, current_location_index)
17593 })?;
17594
17595 let Some(current_location_index) = current_location_index else {
17596 // This indicates something has gone wrong, because we already
17597 // handle the "no references" case above
17598 log::error!(
17599 "failed to find current reference under cursor. Total references: {}",
17600 locations.len()
17601 );
17602 return Ok(());
17603 };
17604
17605 let destination_location_index = match direction {
17606 Direction::Next => (current_location_index + count) % locations.len(),
17607 Direction::Prev => {
17608 (current_location_index + locations.len() - count % locations.len())
17609 % locations.len()
17610 }
17611 };
17612
17613 // TODO(cameron): is this needed?
17614 // the thinking is to avoid "jumping to the current location" (avoid
17615 // polluting "jumplist" in vim terms)
17616 if current_location_index == destination_location_index {
17617 return Ok(());
17618 }
17619
17620 let Range { start, end } = locations[destination_location_index];
17621
17622 editor.update_in(cx, |editor, window, cx| {
17623 let effects = SelectionEffects::default();
17624
17625 editor.unfold_ranges(&[start..end], false, false, cx);
17626 editor.change_selections(effects, window, cx, |s| {
17627 s.select_ranges([start..start]);
17628 });
17629 })?;
17630
17631 Ok(())
17632 }))
17633 }
17634
17635 pub fn find_all_references(
17636 &mut self,
17637 action: &FindAllReferences,
17638 window: &mut Window,
17639 cx: &mut Context<Self>,
17640 ) -> Option<Task<Result<Navigated>>> {
17641 let always_open_multibuffer = action.always_open_multibuffer;
17642 let selection = self.selections.newest_anchor();
17643 let multi_buffer = self.buffer.read(cx);
17644 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17645 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17646 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17647 let head = selection_offset.head();
17648
17649 let head_anchor = multi_buffer_snapshot.anchor_at(
17650 head,
17651 if head < selection_offset.tail() {
17652 Bias::Right
17653 } else {
17654 Bias::Left
17655 },
17656 );
17657
17658 match self
17659 .find_all_references_task_sources
17660 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17661 {
17662 Ok(_) => {
17663 log::info!(
17664 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17665 );
17666 return None;
17667 }
17668 Err(i) => {
17669 self.find_all_references_task_sources.insert(i, head_anchor);
17670 }
17671 }
17672
17673 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17674 let workspace = self.workspace()?;
17675 let project = workspace.read(cx).project().clone();
17676 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17677 Some(cx.spawn_in(window, async move |editor, cx| {
17678 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17679 if let Ok(i) = editor
17680 .find_all_references_task_sources
17681 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17682 {
17683 editor.find_all_references_task_sources.remove(i);
17684 }
17685 });
17686
17687 let Some(locations) = references.await? else {
17688 return anyhow::Ok(Navigated::No);
17689 };
17690 let mut locations = cx.update(|_, cx| {
17691 locations
17692 .into_iter()
17693 .map(|location| {
17694 let buffer = location.buffer.read(cx);
17695 (location.buffer, location.range.to_point(buffer))
17696 })
17697 // if special-casing the single-match case, remove ranges
17698 // that intersect current selection
17699 .filter(|(location_buffer, location)| {
17700 if always_open_multibuffer || &buffer != location_buffer {
17701 return true;
17702 }
17703
17704 !location.contains_inclusive(&selection_point.range())
17705 })
17706 .into_group_map()
17707 })?;
17708 if locations.is_empty() {
17709 return anyhow::Ok(Navigated::No);
17710 }
17711 for ranges in locations.values_mut() {
17712 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17713 ranges.dedup();
17714 }
17715 let mut num_locations = 0;
17716 for ranges in locations.values_mut() {
17717 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17718 ranges.dedup();
17719 num_locations += ranges.len();
17720 }
17721
17722 if num_locations == 1 && !always_open_multibuffer {
17723 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17724 let target_range = target_ranges.first().unwrap().clone();
17725
17726 return editor.update_in(cx, |editor, window, cx| {
17727 let range = target_range.to_point(target_buffer.read(cx));
17728 let range = editor.range_for_match(&range);
17729 let range = range.start..range.start;
17730
17731 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17732 editor.go_to_singleton_buffer_range(range, window, cx);
17733 } else {
17734 let pane = workspace.read(cx).active_pane().clone();
17735 window.defer(cx, move |window, cx| {
17736 let target_editor: Entity<Self> =
17737 workspace.update(cx, |workspace, cx| {
17738 let pane = workspace.active_pane().clone();
17739
17740 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17741 let keep_old_preview = preview_tabs_settings
17742 .enable_keep_preview_on_code_navigation;
17743 let allow_new_preview = preview_tabs_settings
17744 .enable_preview_file_from_code_navigation;
17745
17746 workspace.open_project_item(
17747 pane,
17748 target_buffer.clone(),
17749 true,
17750 true,
17751 keep_old_preview,
17752 allow_new_preview,
17753 window,
17754 cx,
17755 )
17756 });
17757 target_editor.update(cx, |target_editor, cx| {
17758 // When selecting a definition in a different buffer, disable the nav history
17759 // to avoid creating a history entry at the previous cursor location.
17760 pane.update(cx, |pane, _| pane.disable_history());
17761 target_editor.go_to_singleton_buffer_range(range, window, cx);
17762 pane.update(cx, |pane, _| pane.enable_history());
17763 });
17764 });
17765 }
17766 Navigated::No
17767 });
17768 }
17769
17770 workspace.update_in(cx, |workspace, window, cx| {
17771 let target = locations
17772 .iter()
17773 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17774 .map(|(buffer, location)| {
17775 buffer
17776 .read(cx)
17777 .text_for_range(location.clone())
17778 .collect::<String>()
17779 })
17780 .filter(|text| !text.contains('\n'))
17781 .unique()
17782 .take(3)
17783 .join(", ");
17784 let title = if target.is_empty() {
17785 "References".to_owned()
17786 } else {
17787 format!("References to {target}")
17788 };
17789 let allow_preview = PreviewTabsSettings::get_global(cx)
17790 .enable_preview_multibuffer_from_code_navigation;
17791 Self::open_locations_in_multibuffer(
17792 workspace,
17793 locations,
17794 title,
17795 false,
17796 allow_preview,
17797 MultibufferSelectionMode::First,
17798 window,
17799 cx,
17800 );
17801 Navigated::Yes
17802 })
17803 }))
17804 }
17805
17806 /// Opens a multibuffer with the given project locations in it.
17807 pub fn open_locations_in_multibuffer(
17808 workspace: &mut Workspace,
17809 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17810 title: String,
17811 split: bool,
17812 allow_preview: bool,
17813 multibuffer_selection_mode: MultibufferSelectionMode,
17814 window: &mut Window,
17815 cx: &mut Context<Workspace>,
17816 ) {
17817 if locations.is_empty() {
17818 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17819 return;
17820 }
17821
17822 let capability = workspace.project().read(cx).capability();
17823 let mut ranges = <Vec<Range<Anchor>>>::new();
17824
17825 // a key to find existing multibuffer editors with the same set of locations
17826 // to prevent us from opening more and more multibuffer tabs for searches and the like
17827 let mut key = (title.clone(), vec![]);
17828 let excerpt_buffer = cx.new(|cx| {
17829 let key = &mut key.1;
17830 let mut multibuffer = MultiBuffer::new(capability);
17831 for (buffer, mut ranges_for_buffer) in locations {
17832 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17833 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17834 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17835 PathKey::for_buffer(&buffer, cx),
17836 buffer.clone(),
17837 ranges_for_buffer,
17838 multibuffer_context_lines(cx),
17839 cx,
17840 );
17841 ranges.extend(new_ranges)
17842 }
17843
17844 multibuffer.with_title(title)
17845 });
17846 let existing = workspace.active_pane().update(cx, |pane, cx| {
17847 pane.items()
17848 .filter_map(|item| item.downcast::<Editor>())
17849 .find(|editor| {
17850 editor
17851 .read(cx)
17852 .lookup_key
17853 .as_ref()
17854 .and_then(|it| {
17855 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17856 })
17857 .is_some_and(|it| *it == key)
17858 })
17859 });
17860 let was_existing = existing.is_some();
17861 let editor = existing.unwrap_or_else(|| {
17862 cx.new(|cx| {
17863 let mut editor = Editor::for_multibuffer(
17864 excerpt_buffer,
17865 Some(workspace.project().clone()),
17866 window,
17867 cx,
17868 );
17869 editor.lookup_key = Some(Box::new(key));
17870 editor
17871 })
17872 });
17873 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17874 MultibufferSelectionMode::First => {
17875 if let Some(first_range) = ranges.first() {
17876 editor.change_selections(
17877 SelectionEffects::no_scroll(),
17878 window,
17879 cx,
17880 |selections| {
17881 selections.clear_disjoint();
17882 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17883 },
17884 );
17885 }
17886 editor.highlight_background::<Self>(
17887 &ranges,
17888 |_, theme| theme.colors().editor_highlighted_line_background,
17889 cx,
17890 );
17891 }
17892 MultibufferSelectionMode::All => {
17893 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17894 selections.clear_disjoint();
17895 selections.select_anchor_ranges(ranges);
17896 });
17897 }
17898 });
17899
17900 let item = Box::new(editor);
17901
17902 let pane = if split {
17903 workspace.adjacent_pane(window, cx)
17904 } else {
17905 workspace.active_pane().clone()
17906 };
17907 let activate_pane = split;
17908
17909 let mut destination_index = None;
17910 pane.update(cx, |pane, cx| {
17911 if allow_preview && !was_existing {
17912 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17913 }
17914 if was_existing && !allow_preview {
17915 pane.unpreview_item_if_preview(item.item_id());
17916 }
17917 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17918 });
17919 }
17920
17921 pub fn rename(
17922 &mut self,
17923 _: &Rename,
17924 window: &mut Window,
17925 cx: &mut Context<Self>,
17926 ) -> Option<Task<Result<()>>> {
17927 use language::ToOffset as _;
17928
17929 let provider = self.semantics_provider.clone()?;
17930 let selection = self.selections.newest_anchor().clone();
17931 let (cursor_buffer, cursor_buffer_position) = self
17932 .buffer
17933 .read(cx)
17934 .text_anchor_for_position(selection.head(), cx)?;
17935 let (tail_buffer, cursor_buffer_position_end) = self
17936 .buffer
17937 .read(cx)
17938 .text_anchor_for_position(selection.tail(), cx)?;
17939 if tail_buffer != cursor_buffer {
17940 return None;
17941 }
17942
17943 let snapshot = cursor_buffer.read(cx).snapshot();
17944 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17945 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17946 let prepare_rename = provider
17947 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17948 .unwrap_or_else(|| Task::ready(Ok(None)));
17949 drop(snapshot);
17950
17951 Some(cx.spawn_in(window, async move |this, cx| {
17952 let rename_range = if let Some(range) = prepare_rename.await? {
17953 Some(range)
17954 } else {
17955 this.update(cx, |this, cx| {
17956 let buffer = this.buffer.read(cx).snapshot(cx);
17957 let mut buffer_highlights = this
17958 .document_highlights_for_position(selection.head(), &buffer)
17959 .filter(|highlight| {
17960 highlight.start.excerpt_id == selection.head().excerpt_id
17961 && highlight.end.excerpt_id == selection.head().excerpt_id
17962 });
17963 buffer_highlights
17964 .next()
17965 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17966 })?
17967 };
17968 if let Some(rename_range) = rename_range {
17969 this.update_in(cx, |this, window, cx| {
17970 let snapshot = cursor_buffer.read(cx).snapshot();
17971 let rename_buffer_range = rename_range.to_offset(&snapshot);
17972 let cursor_offset_in_rename_range =
17973 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17974 let cursor_offset_in_rename_range_end =
17975 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17976
17977 this.take_rename(false, window, cx);
17978 let buffer = this.buffer.read(cx).read(cx);
17979 let cursor_offset = selection.head().to_offset(&buffer);
17980 let rename_start =
17981 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17982 let rename_end = rename_start + rename_buffer_range.len();
17983 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17984 let mut old_highlight_id = None;
17985 let old_name: Arc<str> = buffer
17986 .chunks(rename_start..rename_end, true)
17987 .map(|chunk| {
17988 if old_highlight_id.is_none() {
17989 old_highlight_id = chunk.syntax_highlight_id;
17990 }
17991 chunk.text
17992 })
17993 .collect::<String>()
17994 .into();
17995
17996 drop(buffer);
17997
17998 // Position the selection in the rename editor so that it matches the current selection.
17999 this.show_local_selections = false;
18000 let rename_editor = cx.new(|cx| {
18001 let mut editor = Editor::single_line(window, cx);
18002 editor.buffer.update(cx, |buffer, cx| {
18003 buffer.edit(
18004 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18005 None,
18006 cx,
18007 )
18008 });
18009 let cursor_offset_in_rename_range =
18010 MultiBufferOffset(cursor_offset_in_rename_range);
18011 let cursor_offset_in_rename_range_end =
18012 MultiBufferOffset(cursor_offset_in_rename_range_end);
18013 let rename_selection_range = match cursor_offset_in_rename_range
18014 .cmp(&cursor_offset_in_rename_range_end)
18015 {
18016 Ordering::Equal => {
18017 editor.select_all(&SelectAll, window, cx);
18018 return editor;
18019 }
18020 Ordering::Less => {
18021 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18022 }
18023 Ordering::Greater => {
18024 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18025 }
18026 };
18027 if rename_selection_range.end.0 > old_name.len() {
18028 editor.select_all(&SelectAll, window, cx);
18029 } else {
18030 editor.change_selections(Default::default(), window, cx, |s| {
18031 s.select_ranges([rename_selection_range]);
18032 });
18033 }
18034 editor
18035 });
18036 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18037 if e == &EditorEvent::Focused {
18038 cx.emit(EditorEvent::FocusedIn)
18039 }
18040 })
18041 .detach();
18042
18043 let write_highlights =
18044 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18045 let read_highlights =
18046 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18047 let ranges = write_highlights
18048 .iter()
18049 .flat_map(|(_, ranges)| ranges.iter())
18050 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18051 .cloned()
18052 .collect();
18053
18054 this.highlight_text::<Rename>(
18055 ranges,
18056 HighlightStyle {
18057 fade_out: Some(0.6),
18058 ..Default::default()
18059 },
18060 cx,
18061 );
18062 let rename_focus_handle = rename_editor.focus_handle(cx);
18063 window.focus(&rename_focus_handle, cx);
18064 let block_id = this.insert_blocks(
18065 [BlockProperties {
18066 style: BlockStyle::Flex,
18067 placement: BlockPlacement::Below(range.start),
18068 height: Some(1),
18069 render: Arc::new({
18070 let rename_editor = rename_editor.clone();
18071 move |cx: &mut BlockContext| {
18072 let mut text_style = cx.editor_style.text.clone();
18073 if let Some(highlight_style) = old_highlight_id
18074 .and_then(|h| h.style(&cx.editor_style.syntax))
18075 {
18076 text_style = text_style.highlight(highlight_style);
18077 }
18078 div()
18079 .block_mouse_except_scroll()
18080 .pl(cx.anchor_x)
18081 .child(EditorElement::new(
18082 &rename_editor,
18083 EditorStyle {
18084 background: cx.theme().system().transparent,
18085 local_player: cx.editor_style.local_player,
18086 text: text_style,
18087 scrollbar_width: cx.editor_style.scrollbar_width,
18088 syntax: cx.editor_style.syntax.clone(),
18089 status: cx.editor_style.status.clone(),
18090 inlay_hints_style: HighlightStyle {
18091 font_weight: Some(FontWeight::BOLD),
18092 ..make_inlay_hints_style(cx.app)
18093 },
18094 edit_prediction_styles: make_suggestion_styles(
18095 cx.app,
18096 ),
18097 ..EditorStyle::default()
18098 },
18099 ))
18100 .into_any_element()
18101 }
18102 }),
18103 priority: 0,
18104 }],
18105 Some(Autoscroll::fit()),
18106 cx,
18107 )[0];
18108 this.pending_rename = Some(RenameState {
18109 range,
18110 old_name,
18111 editor: rename_editor,
18112 block_id,
18113 });
18114 })?;
18115 }
18116
18117 Ok(())
18118 }))
18119 }
18120
18121 pub fn confirm_rename(
18122 &mut self,
18123 _: &ConfirmRename,
18124 window: &mut Window,
18125 cx: &mut Context<Self>,
18126 ) -> Option<Task<Result<()>>> {
18127 let rename = self.take_rename(false, window, cx)?;
18128 let workspace = self.workspace()?.downgrade();
18129 let (buffer, start) = self
18130 .buffer
18131 .read(cx)
18132 .text_anchor_for_position(rename.range.start, cx)?;
18133 let (end_buffer, _) = self
18134 .buffer
18135 .read(cx)
18136 .text_anchor_for_position(rename.range.end, cx)?;
18137 if buffer != end_buffer {
18138 return None;
18139 }
18140
18141 let old_name = rename.old_name;
18142 let new_name = rename.editor.read(cx).text(cx);
18143
18144 let rename = self.semantics_provider.as_ref()?.perform_rename(
18145 &buffer,
18146 start,
18147 new_name.clone(),
18148 cx,
18149 )?;
18150
18151 Some(cx.spawn_in(window, async move |editor, cx| {
18152 let project_transaction = rename.await?;
18153 Self::open_project_transaction(
18154 &editor,
18155 workspace,
18156 project_transaction,
18157 format!("Rename: {} → {}", old_name, new_name),
18158 cx,
18159 )
18160 .await?;
18161
18162 editor.update(cx, |editor, cx| {
18163 editor.refresh_document_highlights(cx);
18164 })?;
18165 Ok(())
18166 }))
18167 }
18168
18169 fn take_rename(
18170 &mut self,
18171 moving_cursor: bool,
18172 window: &mut Window,
18173 cx: &mut Context<Self>,
18174 ) -> Option<RenameState> {
18175 let rename = self.pending_rename.take()?;
18176 if rename.editor.focus_handle(cx).is_focused(window) {
18177 window.focus(&self.focus_handle, cx);
18178 }
18179
18180 self.remove_blocks(
18181 [rename.block_id].into_iter().collect(),
18182 Some(Autoscroll::fit()),
18183 cx,
18184 );
18185 self.clear_highlights::<Rename>(cx);
18186 self.show_local_selections = true;
18187
18188 if moving_cursor {
18189 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18190 editor
18191 .selections
18192 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18193 .head()
18194 });
18195
18196 // Update the selection to match the position of the selection inside
18197 // the rename editor.
18198 let snapshot = self.buffer.read(cx).read(cx);
18199 let rename_range = rename.range.to_offset(&snapshot);
18200 let cursor_in_editor = snapshot
18201 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18202 .min(rename_range.end);
18203 drop(snapshot);
18204
18205 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18206 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18207 });
18208 } else {
18209 self.refresh_document_highlights(cx);
18210 }
18211
18212 Some(rename)
18213 }
18214
18215 pub fn pending_rename(&self) -> Option<&RenameState> {
18216 self.pending_rename.as_ref()
18217 }
18218
18219 fn format(
18220 &mut self,
18221 _: &Format,
18222 window: &mut Window,
18223 cx: &mut Context<Self>,
18224 ) -> Option<Task<Result<()>>> {
18225 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18226
18227 let project = match &self.project {
18228 Some(project) => project.clone(),
18229 None => return None,
18230 };
18231
18232 Some(self.perform_format(
18233 project,
18234 FormatTrigger::Manual,
18235 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18236 window,
18237 cx,
18238 ))
18239 }
18240
18241 fn format_selections(
18242 &mut self,
18243 _: &FormatSelections,
18244 window: &mut Window,
18245 cx: &mut Context<Self>,
18246 ) -> Option<Task<Result<()>>> {
18247 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18248
18249 let project = match &self.project {
18250 Some(project) => project.clone(),
18251 None => return None,
18252 };
18253
18254 let ranges = self
18255 .selections
18256 .all_adjusted(&self.display_snapshot(cx))
18257 .into_iter()
18258 .map(|selection| selection.range())
18259 .collect_vec();
18260
18261 Some(self.perform_format(
18262 project,
18263 FormatTrigger::Manual,
18264 FormatTarget::Ranges(ranges),
18265 window,
18266 cx,
18267 ))
18268 }
18269
18270 fn perform_format(
18271 &mut self,
18272 project: Entity<Project>,
18273 trigger: FormatTrigger,
18274 target: FormatTarget,
18275 window: &mut Window,
18276 cx: &mut Context<Self>,
18277 ) -> Task<Result<()>> {
18278 let buffer = self.buffer.clone();
18279 let (buffers, target) = match target {
18280 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18281 FormatTarget::Ranges(selection_ranges) => {
18282 let multi_buffer = buffer.read(cx);
18283 let snapshot = multi_buffer.read(cx);
18284 let mut buffers = HashSet::default();
18285 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18286 BTreeMap::new();
18287 for selection_range in selection_ranges {
18288 for (buffer, buffer_range, _) in
18289 snapshot.range_to_buffer_ranges(selection_range)
18290 {
18291 let buffer_id = buffer.remote_id();
18292 let start = buffer.anchor_before(buffer_range.start);
18293 let end = buffer.anchor_after(buffer_range.end);
18294 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18295 buffer_id_to_ranges
18296 .entry(buffer_id)
18297 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18298 .or_insert_with(|| vec![start..end]);
18299 }
18300 }
18301 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18302 }
18303 };
18304
18305 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18306 let selections_prev = transaction_id_prev
18307 .and_then(|transaction_id_prev| {
18308 // default to selections as they were after the last edit, if we have them,
18309 // instead of how they are now.
18310 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18311 // will take you back to where you made the last edit, instead of staying where you scrolled
18312 self.selection_history
18313 .transaction(transaction_id_prev)
18314 .map(|t| t.0.clone())
18315 })
18316 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18317
18318 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18319 let format = project.update(cx, |project, cx| {
18320 project.format(buffers, target, true, trigger, cx)
18321 });
18322
18323 cx.spawn_in(window, async move |editor, cx| {
18324 let transaction = futures::select_biased! {
18325 transaction = format.log_err().fuse() => transaction,
18326 () = timeout => {
18327 log::warn!("timed out waiting for formatting");
18328 None
18329 }
18330 };
18331
18332 buffer
18333 .update(cx, |buffer, cx| {
18334 if let Some(transaction) = transaction
18335 && !buffer.is_singleton()
18336 {
18337 buffer.push_transaction(&transaction.0, cx);
18338 }
18339 cx.notify();
18340 })
18341 .ok();
18342
18343 if let Some(transaction_id_now) =
18344 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18345 {
18346 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18347 if has_new_transaction {
18348 _ = editor.update(cx, |editor, _| {
18349 editor
18350 .selection_history
18351 .insert_transaction(transaction_id_now, selections_prev);
18352 });
18353 }
18354 }
18355
18356 Ok(())
18357 })
18358 }
18359
18360 fn organize_imports(
18361 &mut self,
18362 _: &OrganizeImports,
18363 window: &mut Window,
18364 cx: &mut Context<Self>,
18365 ) -> Option<Task<Result<()>>> {
18366 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18367 let project = match &self.project {
18368 Some(project) => project.clone(),
18369 None => return None,
18370 };
18371 Some(self.perform_code_action_kind(
18372 project,
18373 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18374 window,
18375 cx,
18376 ))
18377 }
18378
18379 fn perform_code_action_kind(
18380 &mut self,
18381 project: Entity<Project>,
18382 kind: CodeActionKind,
18383 window: &mut Window,
18384 cx: &mut Context<Self>,
18385 ) -> Task<Result<()>> {
18386 let buffer = self.buffer.clone();
18387 let buffers = buffer.read(cx).all_buffers();
18388 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18389 let apply_action = project.update(cx, |project, cx| {
18390 project.apply_code_action_kind(buffers, kind, true, cx)
18391 });
18392 cx.spawn_in(window, async move |_, cx| {
18393 let transaction = futures::select_biased! {
18394 () = timeout => {
18395 log::warn!("timed out waiting for executing code action");
18396 None
18397 }
18398 transaction = apply_action.log_err().fuse() => transaction,
18399 };
18400 buffer
18401 .update(cx, |buffer, cx| {
18402 // check if we need this
18403 if let Some(transaction) = transaction
18404 && !buffer.is_singleton()
18405 {
18406 buffer.push_transaction(&transaction.0, cx);
18407 }
18408 cx.notify();
18409 })
18410 .ok();
18411 Ok(())
18412 })
18413 }
18414
18415 pub fn restart_language_server(
18416 &mut self,
18417 _: &RestartLanguageServer,
18418 _: &mut Window,
18419 cx: &mut Context<Self>,
18420 ) {
18421 if let Some(project) = self.project.clone() {
18422 self.buffer.update(cx, |multi_buffer, cx| {
18423 project.update(cx, |project, cx| {
18424 project.restart_language_servers_for_buffers(
18425 multi_buffer.all_buffers().into_iter().collect(),
18426 HashSet::default(),
18427 cx,
18428 );
18429 });
18430 })
18431 }
18432 }
18433
18434 pub fn stop_language_server(
18435 &mut self,
18436 _: &StopLanguageServer,
18437 _: &mut Window,
18438 cx: &mut Context<Self>,
18439 ) {
18440 if let Some(project) = self.project.clone() {
18441 self.buffer.update(cx, |multi_buffer, cx| {
18442 project.update(cx, |project, cx| {
18443 project.stop_language_servers_for_buffers(
18444 multi_buffer.all_buffers().into_iter().collect(),
18445 HashSet::default(),
18446 cx,
18447 );
18448 });
18449 });
18450 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18451 }
18452 }
18453
18454 fn cancel_language_server_work(
18455 workspace: &mut Workspace,
18456 _: &actions::CancelLanguageServerWork,
18457 _: &mut Window,
18458 cx: &mut Context<Workspace>,
18459 ) {
18460 let project = workspace.project();
18461 let buffers = workspace
18462 .active_item(cx)
18463 .and_then(|item| item.act_as::<Editor>(cx))
18464 .map_or(HashSet::default(), |editor| {
18465 editor.read(cx).buffer.read(cx).all_buffers()
18466 });
18467 project.update(cx, |project, cx| {
18468 project.cancel_language_server_work_for_buffers(buffers, cx);
18469 });
18470 }
18471
18472 fn show_character_palette(
18473 &mut self,
18474 _: &ShowCharacterPalette,
18475 window: &mut Window,
18476 _: &mut Context<Self>,
18477 ) {
18478 window.show_character_palette();
18479 }
18480
18481 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18482 if !self.diagnostics_enabled() {
18483 return;
18484 }
18485
18486 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18487 let buffer = self.buffer.read(cx).snapshot(cx);
18488 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18489 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18490 let is_valid = buffer
18491 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18492 .any(|entry| {
18493 entry.diagnostic.is_primary
18494 && !entry.range.is_empty()
18495 && entry.range.start == primary_range_start
18496 && entry.diagnostic.message == active_diagnostics.active_message
18497 });
18498
18499 if !is_valid {
18500 self.dismiss_diagnostics(cx);
18501 }
18502 }
18503 }
18504
18505 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18506 match &self.active_diagnostics {
18507 ActiveDiagnostic::Group(group) => Some(group),
18508 _ => None,
18509 }
18510 }
18511
18512 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18513 if !self.diagnostics_enabled() {
18514 return;
18515 }
18516 self.dismiss_diagnostics(cx);
18517 self.active_diagnostics = ActiveDiagnostic::All;
18518 }
18519
18520 fn activate_diagnostics(
18521 &mut self,
18522 buffer_id: BufferId,
18523 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18524 window: &mut Window,
18525 cx: &mut Context<Self>,
18526 ) {
18527 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18528 return;
18529 }
18530 self.dismiss_diagnostics(cx);
18531 let snapshot = self.snapshot(window, cx);
18532 let buffer = self.buffer.read(cx).snapshot(cx);
18533 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18534 return;
18535 };
18536
18537 let diagnostic_group = buffer
18538 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18539 .collect::<Vec<_>>();
18540
18541 let language_registry = self
18542 .project()
18543 .map(|project| project.read(cx).languages().clone());
18544
18545 let blocks = renderer.render_group(
18546 diagnostic_group,
18547 buffer_id,
18548 snapshot,
18549 cx.weak_entity(),
18550 language_registry,
18551 cx,
18552 );
18553
18554 let blocks = self.display_map.update(cx, |display_map, cx| {
18555 display_map.insert_blocks(blocks, cx).into_iter().collect()
18556 });
18557 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18558 active_range: buffer.anchor_before(diagnostic.range.start)
18559 ..buffer.anchor_after(diagnostic.range.end),
18560 active_message: diagnostic.diagnostic.message.clone(),
18561 group_id: diagnostic.diagnostic.group_id,
18562 blocks,
18563 });
18564 cx.notify();
18565 }
18566
18567 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18568 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18569 return;
18570 };
18571
18572 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18573 if let ActiveDiagnostic::Group(group) = prev {
18574 self.display_map.update(cx, |display_map, cx| {
18575 display_map.remove_blocks(group.blocks, cx);
18576 });
18577 cx.notify();
18578 }
18579 }
18580
18581 /// Disable inline diagnostics rendering for this editor.
18582 pub fn disable_inline_diagnostics(&mut self) {
18583 self.inline_diagnostics_enabled = false;
18584 self.inline_diagnostics_update = Task::ready(());
18585 self.inline_diagnostics.clear();
18586 }
18587
18588 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18589 self.diagnostics_enabled = false;
18590 self.dismiss_diagnostics(cx);
18591 self.inline_diagnostics_update = Task::ready(());
18592 self.inline_diagnostics.clear();
18593 }
18594
18595 pub fn disable_word_completions(&mut self) {
18596 self.word_completions_enabled = false;
18597 }
18598
18599 pub fn diagnostics_enabled(&self) -> bool {
18600 self.diagnostics_enabled && self.mode.is_full()
18601 }
18602
18603 pub fn inline_diagnostics_enabled(&self) -> bool {
18604 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18605 }
18606
18607 pub fn show_inline_diagnostics(&self) -> bool {
18608 self.show_inline_diagnostics
18609 }
18610
18611 pub fn toggle_inline_diagnostics(
18612 &mut self,
18613 _: &ToggleInlineDiagnostics,
18614 window: &mut Window,
18615 cx: &mut Context<Editor>,
18616 ) {
18617 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18618 self.refresh_inline_diagnostics(false, window, cx);
18619 }
18620
18621 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18622 self.diagnostics_max_severity = severity;
18623 self.display_map.update(cx, |display_map, _| {
18624 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18625 });
18626 }
18627
18628 pub fn toggle_diagnostics(
18629 &mut self,
18630 _: &ToggleDiagnostics,
18631 window: &mut Window,
18632 cx: &mut Context<Editor>,
18633 ) {
18634 if !self.diagnostics_enabled() {
18635 return;
18636 }
18637
18638 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18639 EditorSettings::get_global(cx)
18640 .diagnostics_max_severity
18641 .filter(|severity| severity != &DiagnosticSeverity::Off)
18642 .unwrap_or(DiagnosticSeverity::Hint)
18643 } else {
18644 DiagnosticSeverity::Off
18645 };
18646 self.set_max_diagnostics_severity(new_severity, cx);
18647 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18648 self.active_diagnostics = ActiveDiagnostic::None;
18649 self.inline_diagnostics_update = Task::ready(());
18650 self.inline_diagnostics.clear();
18651 } else {
18652 self.refresh_inline_diagnostics(false, window, cx);
18653 }
18654
18655 cx.notify();
18656 }
18657
18658 pub fn toggle_minimap(
18659 &mut self,
18660 _: &ToggleMinimap,
18661 window: &mut Window,
18662 cx: &mut Context<Editor>,
18663 ) {
18664 if self.supports_minimap(cx) {
18665 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18666 }
18667 }
18668
18669 fn refresh_inline_diagnostics(
18670 &mut self,
18671 debounce: bool,
18672 window: &mut Window,
18673 cx: &mut Context<Self>,
18674 ) {
18675 let max_severity = ProjectSettings::get_global(cx)
18676 .diagnostics
18677 .inline
18678 .max_severity
18679 .unwrap_or(self.diagnostics_max_severity);
18680
18681 if !self.inline_diagnostics_enabled()
18682 || !self.diagnostics_enabled()
18683 || !self.show_inline_diagnostics
18684 || max_severity == DiagnosticSeverity::Off
18685 {
18686 self.inline_diagnostics_update = Task::ready(());
18687 self.inline_diagnostics.clear();
18688 return;
18689 }
18690
18691 let debounce_ms = ProjectSettings::get_global(cx)
18692 .diagnostics
18693 .inline
18694 .update_debounce_ms;
18695 let debounce = if debounce && debounce_ms > 0 {
18696 Some(Duration::from_millis(debounce_ms))
18697 } else {
18698 None
18699 };
18700 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18701 if let Some(debounce) = debounce {
18702 cx.background_executor().timer(debounce).await;
18703 }
18704 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18705 editor
18706 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18707 .ok()
18708 }) else {
18709 return;
18710 };
18711
18712 let new_inline_diagnostics = cx
18713 .background_spawn(async move {
18714 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18715 for diagnostic_entry in
18716 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18717 {
18718 let message = diagnostic_entry
18719 .diagnostic
18720 .message
18721 .split_once('\n')
18722 .map(|(line, _)| line)
18723 .map(SharedString::new)
18724 .unwrap_or_else(|| {
18725 SharedString::new(&*diagnostic_entry.diagnostic.message)
18726 });
18727 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18728 let (Ok(i) | Err(i)) = inline_diagnostics
18729 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18730 inline_diagnostics.insert(
18731 i,
18732 (
18733 start_anchor,
18734 InlineDiagnostic {
18735 message,
18736 group_id: diagnostic_entry.diagnostic.group_id,
18737 start: diagnostic_entry.range.start.to_point(&snapshot),
18738 is_primary: diagnostic_entry.diagnostic.is_primary,
18739 severity: diagnostic_entry.diagnostic.severity,
18740 },
18741 ),
18742 );
18743 }
18744 inline_diagnostics
18745 })
18746 .await;
18747
18748 editor
18749 .update(cx, |editor, cx| {
18750 editor.inline_diagnostics = new_inline_diagnostics;
18751 cx.notify();
18752 })
18753 .ok();
18754 });
18755 }
18756
18757 fn pull_diagnostics(
18758 &mut self,
18759 buffer_id: Option<BufferId>,
18760 window: &Window,
18761 cx: &mut Context<Self>,
18762 ) -> Option<()> {
18763 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18764 return None;
18765 }
18766 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18767 .diagnostics
18768 .lsp_pull_diagnostics;
18769 if !pull_diagnostics_settings.enabled {
18770 return None;
18771 }
18772 let project = self.project()?.downgrade();
18773
18774 let mut edited_buffer_ids = HashSet::default();
18775 let mut edited_worktree_ids = HashSet::default();
18776 let edited_buffers = match buffer_id {
18777 Some(buffer_id) => {
18778 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18779 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18780 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18781 edited_worktree_ids.insert(worktree_id);
18782 vec![buffer]
18783 }
18784 None => self
18785 .buffer()
18786 .read(cx)
18787 .all_buffers()
18788 .into_iter()
18789 .filter(|buffer| {
18790 let buffer = buffer.read(cx);
18791 match buffer.file().map(|f| f.worktree_id(cx)) {
18792 Some(worktree_id) => {
18793 edited_buffer_ids.insert(buffer.remote_id());
18794 edited_worktree_ids.insert(worktree_id);
18795 true
18796 }
18797 None => false,
18798 }
18799 })
18800 .collect::<Vec<_>>(),
18801 };
18802
18803 if edited_buffers.is_empty() {
18804 self.pull_diagnostics_task = Task::ready(());
18805 self.pull_diagnostics_background_task = Task::ready(());
18806 return None;
18807 }
18808
18809 let mut already_used_buffers = HashSet::default();
18810 let related_open_buffers = self
18811 .workspace
18812 .as_ref()
18813 .and_then(|(workspace, _)| workspace.upgrade())
18814 .into_iter()
18815 .flat_map(|workspace| workspace.read(cx).panes())
18816 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18817 .filter(|editor| editor != &cx.entity())
18818 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18819 .filter(|buffer| {
18820 let buffer = buffer.read(cx);
18821 let buffer_id = buffer.remote_id();
18822 if already_used_buffers.insert(buffer_id) {
18823 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18824 return !edited_buffer_ids.contains(&buffer_id)
18825 && !edited_worktree_ids.contains(&worktree_id);
18826 }
18827 }
18828 false
18829 })
18830 .collect::<Vec<_>>();
18831
18832 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18833 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18834 if buffers.is_empty() {
18835 return Task::ready(());
18836 }
18837 let project_weak = project.clone();
18838 cx.spawn_in(window, async move |_, cx| {
18839 cx.background_executor().timer(delay).await;
18840
18841 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18842 buffers
18843 .into_iter()
18844 .filter_map(|buffer| {
18845 project_weak
18846 .update(cx, |project, cx| {
18847 project.lsp_store().update(cx, |lsp_store, cx| {
18848 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18849 })
18850 })
18851 .ok()
18852 })
18853 .collect::<FuturesUnordered<_>>()
18854 }) else {
18855 return;
18856 };
18857
18858 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18859 if let Err(e) = pull_task {
18860 log::error!("Failed to update project diagnostics: {e:#}");
18861 }
18862 }
18863 })
18864 };
18865
18866 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18867 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18868
18869 Some(())
18870 }
18871
18872 pub fn set_selections_from_remote(
18873 &mut self,
18874 selections: Vec<Selection<Anchor>>,
18875 pending_selection: Option<Selection<Anchor>>,
18876 window: &mut Window,
18877 cx: &mut Context<Self>,
18878 ) {
18879 let old_cursor_position = self.selections.newest_anchor().head();
18880 self.selections
18881 .change_with(&self.display_snapshot(cx), |s| {
18882 s.select_anchors(selections);
18883 if let Some(pending_selection) = pending_selection {
18884 s.set_pending(pending_selection, SelectMode::Character);
18885 } else {
18886 s.clear_pending();
18887 }
18888 });
18889 self.selections_did_change(
18890 false,
18891 &old_cursor_position,
18892 SelectionEffects::default(),
18893 window,
18894 cx,
18895 );
18896 }
18897
18898 pub fn transact(
18899 &mut self,
18900 window: &mut Window,
18901 cx: &mut Context<Self>,
18902 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18903 ) -> Option<TransactionId> {
18904 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18905 this.start_transaction_at(Instant::now(), window, cx);
18906 update(this, window, cx);
18907 this.end_transaction_at(Instant::now(), cx)
18908 })
18909 }
18910
18911 pub fn start_transaction_at(
18912 &mut self,
18913 now: Instant,
18914 window: &mut Window,
18915 cx: &mut Context<Self>,
18916 ) -> Option<TransactionId> {
18917 self.end_selection(window, cx);
18918 if let Some(tx_id) = self
18919 .buffer
18920 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18921 {
18922 self.selection_history
18923 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18924 cx.emit(EditorEvent::TransactionBegun {
18925 transaction_id: tx_id,
18926 });
18927 Some(tx_id)
18928 } else {
18929 None
18930 }
18931 }
18932
18933 pub fn end_transaction_at(
18934 &mut self,
18935 now: Instant,
18936 cx: &mut Context<Self>,
18937 ) -> Option<TransactionId> {
18938 if let Some(transaction_id) = self
18939 .buffer
18940 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18941 {
18942 if let Some((_, end_selections)) =
18943 self.selection_history.transaction_mut(transaction_id)
18944 {
18945 *end_selections = Some(self.selections.disjoint_anchors_arc());
18946 } else {
18947 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18948 }
18949
18950 cx.emit(EditorEvent::Edited { transaction_id });
18951 Some(transaction_id)
18952 } else {
18953 None
18954 }
18955 }
18956
18957 pub fn modify_transaction_selection_history(
18958 &mut self,
18959 transaction_id: TransactionId,
18960 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18961 ) -> bool {
18962 self.selection_history
18963 .transaction_mut(transaction_id)
18964 .map(modify)
18965 .is_some()
18966 }
18967
18968 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18969 if self.selection_mark_mode {
18970 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18971 s.move_with(|_, sel| {
18972 sel.collapse_to(sel.head(), SelectionGoal::None);
18973 });
18974 })
18975 }
18976 self.selection_mark_mode = true;
18977 cx.notify();
18978 }
18979
18980 pub fn swap_selection_ends(
18981 &mut self,
18982 _: &actions::SwapSelectionEnds,
18983 window: &mut Window,
18984 cx: &mut Context<Self>,
18985 ) {
18986 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18987 s.move_with(|_, sel| {
18988 if sel.start != sel.end {
18989 sel.reversed = !sel.reversed
18990 }
18991 });
18992 });
18993 self.request_autoscroll(Autoscroll::newest(), cx);
18994 cx.notify();
18995 }
18996
18997 pub fn toggle_focus(
18998 workspace: &mut Workspace,
18999 _: &actions::ToggleFocus,
19000 window: &mut Window,
19001 cx: &mut Context<Workspace>,
19002 ) {
19003 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19004 return;
19005 };
19006 workspace.activate_item(&item, true, true, window, cx);
19007 }
19008
19009 pub fn toggle_fold(
19010 &mut self,
19011 _: &actions::ToggleFold,
19012 window: &mut Window,
19013 cx: &mut Context<Self>,
19014 ) {
19015 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19016 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19017 let selection = self.selections.newest::<Point>(&display_map);
19018
19019 let range = if selection.is_empty() {
19020 let point = selection.head().to_display_point(&display_map);
19021 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19022 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19023 .to_point(&display_map);
19024 start..end
19025 } else {
19026 selection.range()
19027 };
19028 if display_map.folds_in_range(range).next().is_some() {
19029 self.unfold_lines(&Default::default(), window, cx)
19030 } else {
19031 self.fold(&Default::default(), window, cx)
19032 }
19033 } else {
19034 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19035 let buffer_ids: HashSet<_> = self
19036 .selections
19037 .disjoint_anchor_ranges()
19038 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19039 .collect();
19040
19041 let should_unfold = buffer_ids
19042 .iter()
19043 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19044
19045 for buffer_id in buffer_ids {
19046 if should_unfold {
19047 self.unfold_buffer(buffer_id, cx);
19048 } else {
19049 self.fold_buffer(buffer_id, cx);
19050 }
19051 }
19052 }
19053 }
19054
19055 pub fn toggle_fold_recursive(
19056 &mut self,
19057 _: &actions::ToggleFoldRecursive,
19058 window: &mut Window,
19059 cx: &mut Context<Self>,
19060 ) {
19061 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19062
19063 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19064 let range = if selection.is_empty() {
19065 let point = selection.head().to_display_point(&display_map);
19066 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19067 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19068 .to_point(&display_map);
19069 start..end
19070 } else {
19071 selection.range()
19072 };
19073 if display_map.folds_in_range(range).next().is_some() {
19074 self.unfold_recursive(&Default::default(), window, cx)
19075 } else {
19076 self.fold_recursive(&Default::default(), window, cx)
19077 }
19078 }
19079
19080 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19081 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19082 let mut to_fold = Vec::new();
19083 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19084 let selections = self.selections.all_adjusted(&display_map);
19085
19086 for selection in selections {
19087 let range = selection.range().sorted();
19088 let buffer_start_row = range.start.row;
19089
19090 if range.start.row != range.end.row {
19091 let mut found = false;
19092 let mut row = range.start.row;
19093 while row <= range.end.row {
19094 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19095 {
19096 found = true;
19097 row = crease.range().end.row + 1;
19098 to_fold.push(crease);
19099 } else {
19100 row += 1
19101 }
19102 }
19103 if found {
19104 continue;
19105 }
19106 }
19107
19108 for row in (0..=range.start.row).rev() {
19109 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19110 && crease.range().end.row >= buffer_start_row
19111 {
19112 to_fold.push(crease);
19113 if row <= range.start.row {
19114 break;
19115 }
19116 }
19117 }
19118 }
19119
19120 self.fold_creases(to_fold, true, window, cx);
19121 } else {
19122 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19123 let buffer_ids = self
19124 .selections
19125 .disjoint_anchor_ranges()
19126 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19127 .collect::<HashSet<_>>();
19128 for buffer_id in buffer_ids {
19129 self.fold_buffer(buffer_id, cx);
19130 }
19131 }
19132 }
19133
19134 pub fn toggle_fold_all(
19135 &mut self,
19136 _: &actions::ToggleFoldAll,
19137 window: &mut Window,
19138 cx: &mut Context<Self>,
19139 ) {
19140 if self.buffer.read(cx).is_singleton() {
19141 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19142 let has_folds = display_map
19143 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19144 .next()
19145 .is_some();
19146
19147 if has_folds {
19148 self.unfold_all(&actions::UnfoldAll, window, cx);
19149 } else {
19150 self.fold_all(&actions::FoldAll, window, cx);
19151 }
19152 } else {
19153 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19154 let should_unfold = buffer_ids
19155 .iter()
19156 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19157
19158 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19159 editor
19160 .update_in(cx, |editor, _, cx| {
19161 for buffer_id in buffer_ids {
19162 if should_unfold {
19163 editor.unfold_buffer(buffer_id, cx);
19164 } else {
19165 editor.fold_buffer(buffer_id, cx);
19166 }
19167 }
19168 })
19169 .ok();
19170 });
19171 }
19172 }
19173
19174 fn fold_at_level(
19175 &mut self,
19176 fold_at: &FoldAtLevel,
19177 window: &mut Window,
19178 cx: &mut Context<Self>,
19179 ) {
19180 if !self.buffer.read(cx).is_singleton() {
19181 return;
19182 }
19183
19184 let fold_at_level = fold_at.0;
19185 let snapshot = self.buffer.read(cx).snapshot(cx);
19186 let mut to_fold = Vec::new();
19187 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19188
19189 let row_ranges_to_keep: Vec<Range<u32>> = self
19190 .selections
19191 .all::<Point>(&self.display_snapshot(cx))
19192 .into_iter()
19193 .map(|sel| sel.start.row..sel.end.row)
19194 .collect();
19195
19196 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19197 while start_row < end_row {
19198 match self
19199 .snapshot(window, cx)
19200 .crease_for_buffer_row(MultiBufferRow(start_row))
19201 {
19202 Some(crease) => {
19203 let nested_start_row = crease.range().start.row + 1;
19204 let nested_end_row = crease.range().end.row;
19205
19206 if current_level < fold_at_level {
19207 stack.push((nested_start_row, nested_end_row, current_level + 1));
19208 } else if current_level == fold_at_level {
19209 // Fold iff there is no selection completely contained within the fold region
19210 if !row_ranges_to_keep.iter().any(|selection| {
19211 selection.end >= nested_start_row
19212 && selection.start <= nested_end_row
19213 }) {
19214 to_fold.push(crease);
19215 }
19216 }
19217
19218 start_row = nested_end_row + 1;
19219 }
19220 None => start_row += 1,
19221 }
19222 }
19223 }
19224
19225 self.fold_creases(to_fold, true, window, cx);
19226 }
19227
19228 pub fn fold_at_level_1(
19229 &mut self,
19230 _: &actions::FoldAtLevel1,
19231 window: &mut Window,
19232 cx: &mut Context<Self>,
19233 ) {
19234 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19235 }
19236
19237 pub fn fold_at_level_2(
19238 &mut self,
19239 _: &actions::FoldAtLevel2,
19240 window: &mut Window,
19241 cx: &mut Context<Self>,
19242 ) {
19243 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19244 }
19245
19246 pub fn fold_at_level_3(
19247 &mut self,
19248 _: &actions::FoldAtLevel3,
19249 window: &mut Window,
19250 cx: &mut Context<Self>,
19251 ) {
19252 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19253 }
19254
19255 pub fn fold_at_level_4(
19256 &mut self,
19257 _: &actions::FoldAtLevel4,
19258 window: &mut Window,
19259 cx: &mut Context<Self>,
19260 ) {
19261 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19262 }
19263
19264 pub fn fold_at_level_5(
19265 &mut self,
19266 _: &actions::FoldAtLevel5,
19267 window: &mut Window,
19268 cx: &mut Context<Self>,
19269 ) {
19270 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19271 }
19272
19273 pub fn fold_at_level_6(
19274 &mut self,
19275 _: &actions::FoldAtLevel6,
19276 window: &mut Window,
19277 cx: &mut Context<Self>,
19278 ) {
19279 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19280 }
19281
19282 pub fn fold_at_level_7(
19283 &mut self,
19284 _: &actions::FoldAtLevel7,
19285 window: &mut Window,
19286 cx: &mut Context<Self>,
19287 ) {
19288 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19289 }
19290
19291 pub fn fold_at_level_8(
19292 &mut self,
19293 _: &actions::FoldAtLevel8,
19294 window: &mut Window,
19295 cx: &mut Context<Self>,
19296 ) {
19297 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19298 }
19299
19300 pub fn fold_at_level_9(
19301 &mut self,
19302 _: &actions::FoldAtLevel9,
19303 window: &mut Window,
19304 cx: &mut Context<Self>,
19305 ) {
19306 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19307 }
19308
19309 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19310 if self.buffer.read(cx).is_singleton() {
19311 let mut fold_ranges = Vec::new();
19312 let snapshot = self.buffer.read(cx).snapshot(cx);
19313
19314 for row in 0..snapshot.max_row().0 {
19315 if let Some(foldable_range) = self
19316 .snapshot(window, cx)
19317 .crease_for_buffer_row(MultiBufferRow(row))
19318 {
19319 fold_ranges.push(foldable_range);
19320 }
19321 }
19322
19323 self.fold_creases(fold_ranges, true, window, cx);
19324 } else {
19325 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19326 editor
19327 .update_in(cx, |editor, _, cx| {
19328 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19329 editor.fold_buffer(buffer_id, cx);
19330 }
19331 })
19332 .ok();
19333 });
19334 }
19335 }
19336
19337 pub fn fold_function_bodies(
19338 &mut self,
19339 _: &actions::FoldFunctionBodies,
19340 window: &mut Window,
19341 cx: &mut Context<Self>,
19342 ) {
19343 let snapshot = self.buffer.read(cx).snapshot(cx);
19344
19345 let ranges = snapshot
19346 .text_object_ranges(
19347 MultiBufferOffset(0)..snapshot.len(),
19348 TreeSitterOptions::default(),
19349 )
19350 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19351 .collect::<Vec<_>>();
19352
19353 let creases = ranges
19354 .into_iter()
19355 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19356 .collect();
19357
19358 self.fold_creases(creases, true, window, cx);
19359 }
19360
19361 pub fn fold_recursive(
19362 &mut self,
19363 _: &actions::FoldRecursive,
19364 window: &mut Window,
19365 cx: &mut Context<Self>,
19366 ) {
19367 let mut to_fold = Vec::new();
19368 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19369 let selections = self.selections.all_adjusted(&display_map);
19370
19371 for selection in selections {
19372 let range = selection.range().sorted();
19373 let buffer_start_row = range.start.row;
19374
19375 if range.start.row != range.end.row {
19376 let mut found = false;
19377 for row in range.start.row..=range.end.row {
19378 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19379 found = true;
19380 to_fold.push(crease);
19381 }
19382 }
19383 if found {
19384 continue;
19385 }
19386 }
19387
19388 for row in (0..=range.start.row).rev() {
19389 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19390 if crease.range().end.row >= buffer_start_row {
19391 to_fold.push(crease);
19392 } else {
19393 break;
19394 }
19395 }
19396 }
19397 }
19398
19399 self.fold_creases(to_fold, true, window, cx);
19400 }
19401
19402 pub fn fold_at(
19403 &mut self,
19404 buffer_row: MultiBufferRow,
19405 window: &mut Window,
19406 cx: &mut Context<Self>,
19407 ) {
19408 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19409
19410 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19411 let autoscroll = self
19412 .selections
19413 .all::<Point>(&display_map)
19414 .iter()
19415 .any(|selection| crease.range().overlaps(&selection.range()));
19416
19417 self.fold_creases(vec![crease], autoscroll, window, cx);
19418 }
19419 }
19420
19421 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19422 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19423 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19424 let buffer = display_map.buffer_snapshot();
19425 let selections = self.selections.all::<Point>(&display_map);
19426 let ranges = selections
19427 .iter()
19428 .map(|s| {
19429 let range = s.display_range(&display_map).sorted();
19430 let mut start = range.start.to_point(&display_map);
19431 let mut end = range.end.to_point(&display_map);
19432 start.column = 0;
19433 end.column = buffer.line_len(MultiBufferRow(end.row));
19434 start..end
19435 })
19436 .collect::<Vec<_>>();
19437
19438 self.unfold_ranges(&ranges, true, true, cx);
19439 } else {
19440 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19441 let buffer_ids = self
19442 .selections
19443 .disjoint_anchor_ranges()
19444 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19445 .collect::<HashSet<_>>();
19446 for buffer_id in buffer_ids {
19447 self.unfold_buffer(buffer_id, cx);
19448 }
19449 }
19450 }
19451
19452 pub fn unfold_recursive(
19453 &mut self,
19454 _: &UnfoldRecursive,
19455 _window: &mut Window,
19456 cx: &mut Context<Self>,
19457 ) {
19458 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19459 let selections = self.selections.all::<Point>(&display_map);
19460 let ranges = selections
19461 .iter()
19462 .map(|s| {
19463 let mut range = s.display_range(&display_map).sorted();
19464 *range.start.column_mut() = 0;
19465 *range.end.column_mut() = display_map.line_len(range.end.row());
19466 let start = range.start.to_point(&display_map);
19467 let end = range.end.to_point(&display_map);
19468 start..end
19469 })
19470 .collect::<Vec<_>>();
19471
19472 self.unfold_ranges(&ranges, true, true, cx);
19473 }
19474
19475 pub fn unfold_at(
19476 &mut self,
19477 buffer_row: MultiBufferRow,
19478 _window: &mut Window,
19479 cx: &mut Context<Self>,
19480 ) {
19481 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19482
19483 let intersection_range = Point::new(buffer_row.0, 0)
19484 ..Point::new(
19485 buffer_row.0,
19486 display_map.buffer_snapshot().line_len(buffer_row),
19487 );
19488
19489 let autoscroll = self
19490 .selections
19491 .all::<Point>(&display_map)
19492 .iter()
19493 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19494
19495 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19496 }
19497
19498 pub fn unfold_all(
19499 &mut self,
19500 _: &actions::UnfoldAll,
19501 _window: &mut Window,
19502 cx: &mut Context<Self>,
19503 ) {
19504 if self.buffer.read(cx).is_singleton() {
19505 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19506 self.unfold_ranges(
19507 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19508 true,
19509 true,
19510 cx,
19511 );
19512 } else {
19513 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19514 editor
19515 .update(cx, |editor, cx| {
19516 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19517 editor.unfold_buffer(buffer_id, cx);
19518 }
19519 })
19520 .ok();
19521 });
19522 }
19523 }
19524
19525 pub fn fold_selected_ranges(
19526 &mut self,
19527 _: &FoldSelectedRanges,
19528 window: &mut Window,
19529 cx: &mut Context<Self>,
19530 ) {
19531 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19532 let selections = self.selections.all_adjusted(&display_map);
19533 let ranges = selections
19534 .into_iter()
19535 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19536 .collect::<Vec<_>>();
19537 self.fold_creases(ranges, true, window, cx);
19538 }
19539
19540 pub fn fold_ranges<T: ToOffset + Clone>(
19541 &mut self,
19542 ranges: Vec<Range<T>>,
19543 auto_scroll: bool,
19544 window: &mut Window,
19545 cx: &mut Context<Self>,
19546 ) {
19547 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19548 let ranges = ranges
19549 .into_iter()
19550 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19551 .collect::<Vec<_>>();
19552 self.fold_creases(ranges, auto_scroll, window, cx);
19553 }
19554
19555 pub fn fold_creases<T: ToOffset + Clone>(
19556 &mut self,
19557 creases: Vec<Crease<T>>,
19558 auto_scroll: bool,
19559 _window: &mut Window,
19560 cx: &mut Context<Self>,
19561 ) {
19562 if creases.is_empty() {
19563 return;
19564 }
19565
19566 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19567
19568 if auto_scroll {
19569 self.request_autoscroll(Autoscroll::fit(), cx);
19570 }
19571
19572 cx.notify();
19573
19574 self.scrollbar_marker_state.dirty = true;
19575 self.folds_did_change(cx);
19576 }
19577
19578 /// Removes any folds whose ranges intersect any of the given ranges.
19579 pub fn unfold_ranges<T: ToOffset + Clone>(
19580 &mut self,
19581 ranges: &[Range<T>],
19582 inclusive: bool,
19583 auto_scroll: bool,
19584 cx: &mut Context<Self>,
19585 ) {
19586 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19587 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19588 });
19589 self.folds_did_change(cx);
19590 }
19591
19592 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19593 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19594 return;
19595 }
19596
19597 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19598 self.display_map.update(cx, |display_map, cx| {
19599 display_map.fold_buffers([buffer_id], cx)
19600 });
19601
19602 let snapshot = self.display_snapshot(cx);
19603 self.selections.change_with(&snapshot, |selections| {
19604 selections.remove_selections_from_buffer(buffer_id);
19605 });
19606
19607 cx.emit(EditorEvent::BufferFoldToggled {
19608 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19609 folded: true,
19610 });
19611 cx.notify();
19612 }
19613
19614 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19615 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19616 return;
19617 }
19618 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19619 self.display_map.update(cx, |display_map, cx| {
19620 display_map.unfold_buffers([buffer_id], cx);
19621 });
19622 cx.emit(EditorEvent::BufferFoldToggled {
19623 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19624 folded: false,
19625 });
19626 cx.notify();
19627 }
19628
19629 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19630 self.display_map.read(cx).is_buffer_folded(buffer)
19631 }
19632
19633 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19634 self.display_map.read(cx).folded_buffers()
19635 }
19636
19637 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19638 self.display_map.update(cx, |display_map, cx| {
19639 display_map.disable_header_for_buffer(buffer_id, cx);
19640 });
19641 cx.notify();
19642 }
19643
19644 /// Removes any folds with the given ranges.
19645 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19646 &mut self,
19647 ranges: &[Range<T>],
19648 type_id: TypeId,
19649 auto_scroll: bool,
19650 cx: &mut Context<Self>,
19651 ) {
19652 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19653 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19654 });
19655 self.folds_did_change(cx);
19656 }
19657
19658 fn remove_folds_with<T: ToOffset + Clone>(
19659 &mut self,
19660 ranges: &[Range<T>],
19661 auto_scroll: bool,
19662 cx: &mut Context<Self>,
19663 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19664 ) {
19665 if ranges.is_empty() {
19666 return;
19667 }
19668
19669 let mut buffers_affected = HashSet::default();
19670 let multi_buffer = self.buffer().read(cx);
19671 for range in ranges {
19672 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19673 buffers_affected.insert(buffer.read(cx).remote_id());
19674 };
19675 }
19676
19677 self.display_map.update(cx, update);
19678
19679 if auto_scroll {
19680 self.request_autoscroll(Autoscroll::fit(), cx);
19681 }
19682
19683 cx.notify();
19684 self.scrollbar_marker_state.dirty = true;
19685 self.active_indent_guides_state.dirty = true;
19686 }
19687
19688 pub fn update_renderer_widths(
19689 &mut self,
19690 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19691 cx: &mut Context<Self>,
19692 ) -> bool {
19693 self.display_map
19694 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19695 }
19696
19697 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19698 self.display_map.read(cx).fold_placeholder.clone()
19699 }
19700
19701 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19702 self.use_base_text_line_numbers = show;
19703 }
19704
19705 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19706 self.buffer.update(cx, |buffer, cx| {
19707 buffer.set_all_diff_hunks_expanded(cx);
19708 });
19709 }
19710
19711 pub fn expand_all_diff_hunks(
19712 &mut self,
19713 _: &ExpandAllDiffHunks,
19714 _window: &mut Window,
19715 cx: &mut Context<Self>,
19716 ) {
19717 self.buffer.update(cx, |buffer, cx| {
19718 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19719 });
19720 }
19721
19722 pub fn collapse_all_diff_hunks(
19723 &mut self,
19724 _: &CollapseAllDiffHunks,
19725 _window: &mut Window,
19726 cx: &mut Context<Self>,
19727 ) {
19728 self.buffer.update(cx, |buffer, cx| {
19729 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19730 });
19731 }
19732
19733 pub fn toggle_selected_diff_hunks(
19734 &mut self,
19735 _: &ToggleSelectedDiffHunks,
19736 _window: &mut Window,
19737 cx: &mut Context<Self>,
19738 ) {
19739 let ranges: Vec<_> = self
19740 .selections
19741 .disjoint_anchors()
19742 .iter()
19743 .map(|s| s.range())
19744 .collect();
19745 self.toggle_diff_hunks_in_ranges(ranges, cx);
19746 }
19747
19748 pub fn diff_hunks_in_ranges<'a>(
19749 &'a self,
19750 ranges: &'a [Range<Anchor>],
19751 buffer: &'a MultiBufferSnapshot,
19752 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19753 ranges.iter().flat_map(move |range| {
19754 let end_excerpt_id = range.end.excerpt_id;
19755 let range = range.to_point(buffer);
19756 let mut peek_end = range.end;
19757 if range.end.row < buffer.max_row().0 {
19758 peek_end = Point::new(range.end.row + 1, 0);
19759 }
19760 buffer
19761 .diff_hunks_in_range(range.start..peek_end)
19762 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19763 })
19764 }
19765
19766 pub fn has_stageable_diff_hunks_in_ranges(
19767 &self,
19768 ranges: &[Range<Anchor>],
19769 snapshot: &MultiBufferSnapshot,
19770 ) -> bool {
19771 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19772 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19773 }
19774
19775 pub fn toggle_staged_selected_diff_hunks(
19776 &mut self,
19777 _: &::git::ToggleStaged,
19778 _: &mut Window,
19779 cx: &mut Context<Self>,
19780 ) {
19781 let snapshot = self.buffer.read(cx).snapshot(cx);
19782 let ranges: Vec<_> = self
19783 .selections
19784 .disjoint_anchors()
19785 .iter()
19786 .map(|s| s.range())
19787 .collect();
19788 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19789 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19790 }
19791
19792 pub fn set_render_diff_hunk_controls(
19793 &mut self,
19794 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19795 cx: &mut Context<Self>,
19796 ) {
19797 self.render_diff_hunk_controls = render_diff_hunk_controls;
19798 cx.notify();
19799 }
19800
19801 pub fn stage_and_next(
19802 &mut self,
19803 _: &::git::StageAndNext,
19804 window: &mut Window,
19805 cx: &mut Context<Self>,
19806 ) {
19807 self.do_stage_or_unstage_and_next(true, window, cx);
19808 }
19809
19810 pub fn unstage_and_next(
19811 &mut self,
19812 _: &::git::UnstageAndNext,
19813 window: &mut Window,
19814 cx: &mut Context<Self>,
19815 ) {
19816 self.do_stage_or_unstage_and_next(false, window, cx);
19817 }
19818
19819 pub fn stage_or_unstage_diff_hunks(
19820 &mut self,
19821 stage: bool,
19822 ranges: Vec<Range<Anchor>>,
19823 cx: &mut Context<Self>,
19824 ) {
19825 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19826 cx.spawn(async move |this, cx| {
19827 task.await?;
19828 this.update(cx, |this, cx| {
19829 let snapshot = this.buffer.read(cx).snapshot(cx);
19830 let chunk_by = this
19831 .diff_hunks_in_ranges(&ranges, &snapshot)
19832 .chunk_by(|hunk| hunk.buffer_id);
19833 for (buffer_id, hunks) in &chunk_by {
19834 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19835 }
19836 })
19837 })
19838 .detach_and_log_err(cx);
19839 }
19840
19841 fn save_buffers_for_ranges_if_needed(
19842 &mut self,
19843 ranges: &[Range<Anchor>],
19844 cx: &mut Context<Editor>,
19845 ) -> Task<Result<()>> {
19846 let multibuffer = self.buffer.read(cx);
19847 let snapshot = multibuffer.read(cx);
19848 let buffer_ids: HashSet<_> = ranges
19849 .iter()
19850 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19851 .collect();
19852 drop(snapshot);
19853
19854 let mut buffers = HashSet::default();
19855 for buffer_id in buffer_ids {
19856 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19857 let buffer = buffer_entity.read(cx);
19858 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19859 {
19860 buffers.insert(buffer_entity);
19861 }
19862 }
19863 }
19864
19865 if let Some(project) = &self.project {
19866 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19867 } else {
19868 Task::ready(Ok(()))
19869 }
19870 }
19871
19872 fn do_stage_or_unstage_and_next(
19873 &mut self,
19874 stage: bool,
19875 window: &mut Window,
19876 cx: &mut Context<Self>,
19877 ) {
19878 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19879
19880 if ranges.iter().any(|range| range.start != range.end) {
19881 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19882 return;
19883 }
19884
19885 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19886 let snapshot = self.snapshot(window, cx);
19887 let position = self
19888 .selections
19889 .newest::<Point>(&snapshot.display_snapshot)
19890 .head();
19891 let mut row = snapshot
19892 .buffer_snapshot()
19893 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19894 .find(|hunk| hunk.row_range.start.0 > position.row)
19895 .map(|hunk| hunk.row_range.start);
19896
19897 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19898 // Outside of the project diff editor, wrap around to the beginning.
19899 if !all_diff_hunks_expanded {
19900 row = row.or_else(|| {
19901 snapshot
19902 .buffer_snapshot()
19903 .diff_hunks_in_range(Point::zero()..position)
19904 .find(|hunk| hunk.row_range.end.0 < position.row)
19905 .map(|hunk| hunk.row_range.start)
19906 });
19907 }
19908
19909 if let Some(row) = row {
19910 let destination = Point::new(row.0, 0);
19911 let autoscroll = Autoscroll::center();
19912
19913 self.unfold_ranges(&[destination..destination], false, false, cx);
19914 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19915 s.select_ranges([destination..destination]);
19916 });
19917 }
19918 }
19919
19920 fn do_stage_or_unstage(
19921 &self,
19922 stage: bool,
19923 buffer_id: BufferId,
19924 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19925 cx: &mut App,
19926 ) -> Option<()> {
19927 let project = self.project()?;
19928 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19929 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19930 let buffer_snapshot = buffer.read(cx).snapshot();
19931 let file_exists = buffer_snapshot
19932 .file()
19933 .is_some_and(|file| file.disk_state().exists());
19934 diff.update(cx, |diff, cx| {
19935 diff.stage_or_unstage_hunks(
19936 stage,
19937 &hunks
19938 .map(|hunk| buffer_diff::DiffHunk {
19939 buffer_range: hunk.buffer_range,
19940 // We don't need to pass in word diffs here because they're only used for rendering and
19941 // this function changes internal state
19942 base_word_diffs: Vec::default(),
19943 buffer_word_diffs: Vec::default(),
19944 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19945 ..hunk.diff_base_byte_range.end.0,
19946 secondary_status: hunk.secondary_status,
19947 range: Point::zero()..Point::zero(), // unused
19948 })
19949 .collect::<Vec<_>>(),
19950 &buffer_snapshot,
19951 file_exists,
19952 cx,
19953 )
19954 });
19955 None
19956 }
19957
19958 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19959 let ranges: Vec<_> = self
19960 .selections
19961 .disjoint_anchors()
19962 .iter()
19963 .map(|s| s.range())
19964 .collect();
19965 self.buffer
19966 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19967 }
19968
19969 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19970 self.buffer.update(cx, |buffer, cx| {
19971 let ranges = vec![Anchor::min()..Anchor::max()];
19972 if !buffer.all_diff_hunks_expanded()
19973 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19974 {
19975 buffer.collapse_diff_hunks(ranges, cx);
19976 true
19977 } else {
19978 false
19979 }
19980 })
19981 }
19982
19983 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19984 if self.buffer.read(cx).all_diff_hunks_expanded() {
19985 return true;
19986 }
19987 let ranges = vec![Anchor::min()..Anchor::max()];
19988 self.buffer
19989 .read(cx)
19990 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19991 }
19992
19993 fn toggle_diff_hunks_in_ranges(
19994 &mut self,
19995 ranges: Vec<Range<Anchor>>,
19996 cx: &mut Context<Editor>,
19997 ) {
19998 self.buffer.update(cx, |buffer, cx| {
19999 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20000 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20001 })
20002 }
20003
20004 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20005 self.buffer.update(cx, |buffer, cx| {
20006 let snapshot = buffer.snapshot(cx);
20007 let excerpt_id = range.end.excerpt_id;
20008 let point_range = range.to_point(&snapshot);
20009 let expand = !buffer.single_hunk_is_expanded(range, cx);
20010 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20011 })
20012 }
20013
20014 pub(crate) fn apply_all_diff_hunks(
20015 &mut self,
20016 _: &ApplyAllDiffHunks,
20017 window: &mut Window,
20018 cx: &mut Context<Self>,
20019 ) {
20020 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20021
20022 let buffers = self.buffer.read(cx).all_buffers();
20023 for branch_buffer in buffers {
20024 branch_buffer.update(cx, |branch_buffer, cx| {
20025 branch_buffer.merge_into_base(Vec::new(), cx);
20026 });
20027 }
20028
20029 if let Some(project) = self.project.clone() {
20030 self.save(
20031 SaveOptions {
20032 format: true,
20033 autosave: false,
20034 },
20035 project,
20036 window,
20037 cx,
20038 )
20039 .detach_and_log_err(cx);
20040 }
20041 }
20042
20043 pub(crate) fn apply_selected_diff_hunks(
20044 &mut self,
20045 _: &ApplyDiffHunk,
20046 window: &mut Window,
20047 cx: &mut Context<Self>,
20048 ) {
20049 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20050 let snapshot = self.snapshot(window, cx);
20051 let hunks = snapshot.hunks_for_ranges(
20052 self.selections
20053 .all(&snapshot.display_snapshot)
20054 .into_iter()
20055 .map(|selection| selection.range()),
20056 );
20057 let mut ranges_by_buffer = HashMap::default();
20058 self.transact(window, cx, |editor, _window, cx| {
20059 for hunk in hunks {
20060 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20061 ranges_by_buffer
20062 .entry(buffer.clone())
20063 .or_insert_with(Vec::new)
20064 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20065 }
20066 }
20067
20068 for (buffer, ranges) in ranges_by_buffer {
20069 buffer.update(cx, |buffer, cx| {
20070 buffer.merge_into_base(ranges, cx);
20071 });
20072 }
20073 });
20074
20075 if let Some(project) = self.project.clone() {
20076 self.save(
20077 SaveOptions {
20078 format: true,
20079 autosave: false,
20080 },
20081 project,
20082 window,
20083 cx,
20084 )
20085 .detach_and_log_err(cx);
20086 }
20087 }
20088
20089 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20090 if hovered != self.gutter_hovered {
20091 self.gutter_hovered = hovered;
20092 cx.notify();
20093 }
20094 }
20095
20096 pub fn insert_blocks(
20097 &mut self,
20098 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20099 autoscroll: Option<Autoscroll>,
20100 cx: &mut Context<Self>,
20101 ) -> Vec<CustomBlockId> {
20102 let blocks = self
20103 .display_map
20104 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20105 if let Some(autoscroll) = autoscroll {
20106 self.request_autoscroll(autoscroll, cx);
20107 }
20108 cx.notify();
20109 blocks
20110 }
20111
20112 pub fn resize_blocks(
20113 &mut self,
20114 heights: HashMap<CustomBlockId, u32>,
20115 autoscroll: Option<Autoscroll>,
20116 cx: &mut Context<Self>,
20117 ) {
20118 self.display_map
20119 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20120 if let Some(autoscroll) = autoscroll {
20121 self.request_autoscroll(autoscroll, cx);
20122 }
20123 cx.notify();
20124 }
20125
20126 pub fn replace_blocks(
20127 &mut self,
20128 renderers: HashMap<CustomBlockId, RenderBlock>,
20129 autoscroll: Option<Autoscroll>,
20130 cx: &mut Context<Self>,
20131 ) {
20132 self.display_map
20133 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20134 if let Some(autoscroll) = autoscroll {
20135 self.request_autoscroll(autoscroll, cx);
20136 }
20137 cx.notify();
20138 }
20139
20140 pub fn remove_blocks(
20141 &mut self,
20142 block_ids: HashSet<CustomBlockId>,
20143 autoscroll: Option<Autoscroll>,
20144 cx: &mut Context<Self>,
20145 ) {
20146 self.display_map.update(cx, |display_map, cx| {
20147 display_map.remove_blocks(block_ids, cx)
20148 });
20149 if let Some(autoscroll) = autoscroll {
20150 self.request_autoscroll(autoscroll, cx);
20151 }
20152 cx.notify();
20153 }
20154
20155 pub fn row_for_block(
20156 &self,
20157 block_id: CustomBlockId,
20158 cx: &mut Context<Self>,
20159 ) -> Option<DisplayRow> {
20160 self.display_map
20161 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20162 }
20163
20164 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20165 self.focused_block = Some(focused_block);
20166 }
20167
20168 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20169 self.focused_block.take()
20170 }
20171
20172 pub fn insert_creases(
20173 &mut self,
20174 creases: impl IntoIterator<Item = Crease<Anchor>>,
20175 cx: &mut Context<Self>,
20176 ) -> Vec<CreaseId> {
20177 self.display_map
20178 .update(cx, |map, cx| map.insert_creases(creases, cx))
20179 }
20180
20181 pub fn remove_creases(
20182 &mut self,
20183 ids: impl IntoIterator<Item = CreaseId>,
20184 cx: &mut Context<Self>,
20185 ) -> Vec<(CreaseId, Range<Anchor>)> {
20186 self.display_map
20187 .update(cx, |map, cx| map.remove_creases(ids, cx))
20188 }
20189
20190 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20191 self.display_map
20192 .update(cx, |map, cx| map.snapshot(cx))
20193 .longest_row()
20194 }
20195
20196 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20197 self.display_map
20198 .update(cx, |map, cx| map.snapshot(cx))
20199 .max_point()
20200 }
20201
20202 pub fn text(&self, cx: &App) -> String {
20203 self.buffer.read(cx).read(cx).text()
20204 }
20205
20206 pub fn is_empty(&self, cx: &App) -> bool {
20207 self.buffer.read(cx).read(cx).is_empty()
20208 }
20209
20210 pub fn text_option(&self, cx: &App) -> Option<String> {
20211 let text = self.text(cx);
20212 let text = text.trim();
20213
20214 if text.is_empty() {
20215 return None;
20216 }
20217
20218 Some(text.to_string())
20219 }
20220
20221 pub fn set_text(
20222 &mut self,
20223 text: impl Into<Arc<str>>,
20224 window: &mut Window,
20225 cx: &mut Context<Self>,
20226 ) {
20227 self.transact(window, cx, |this, _, cx| {
20228 this.buffer
20229 .read(cx)
20230 .as_singleton()
20231 .expect("you can only call set_text on editors for singleton buffers")
20232 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20233 });
20234 }
20235
20236 pub fn display_text(&self, cx: &mut App) -> String {
20237 self.display_map
20238 .update(cx, |map, cx| map.snapshot(cx))
20239 .text()
20240 }
20241
20242 fn create_minimap(
20243 &self,
20244 minimap_settings: MinimapSettings,
20245 window: &mut Window,
20246 cx: &mut Context<Self>,
20247 ) -> Option<Entity<Self>> {
20248 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20249 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20250 }
20251
20252 fn initialize_new_minimap(
20253 &self,
20254 minimap_settings: MinimapSettings,
20255 window: &mut Window,
20256 cx: &mut Context<Self>,
20257 ) -> Entity<Self> {
20258 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20259
20260 let mut minimap = Editor::new_internal(
20261 EditorMode::Minimap {
20262 parent: cx.weak_entity(),
20263 },
20264 self.buffer.clone(),
20265 None,
20266 Some(self.display_map.clone()),
20267 window,
20268 cx,
20269 );
20270 minimap.scroll_manager.clone_state(&self.scroll_manager);
20271 minimap.set_text_style_refinement(TextStyleRefinement {
20272 font_size: Some(MINIMAP_FONT_SIZE),
20273 font_weight: Some(MINIMAP_FONT_WEIGHT),
20274 ..Default::default()
20275 });
20276 minimap.update_minimap_configuration(minimap_settings, cx);
20277 cx.new(|_| minimap)
20278 }
20279
20280 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20281 let current_line_highlight = minimap_settings
20282 .current_line_highlight
20283 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20284 self.set_current_line_highlight(Some(current_line_highlight));
20285 }
20286
20287 pub fn minimap(&self) -> Option<&Entity<Self>> {
20288 self.minimap
20289 .as_ref()
20290 .filter(|_| self.minimap_visibility.visible())
20291 }
20292
20293 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20294 let mut wrap_guides = smallvec![];
20295
20296 if self.show_wrap_guides == Some(false) {
20297 return wrap_guides;
20298 }
20299
20300 let settings = self.buffer.read(cx).language_settings(cx);
20301 if settings.show_wrap_guides {
20302 match self.soft_wrap_mode(cx) {
20303 SoftWrap::Column(soft_wrap) => {
20304 wrap_guides.push((soft_wrap as usize, true));
20305 }
20306 SoftWrap::Bounded(soft_wrap) => {
20307 wrap_guides.push((soft_wrap as usize, true));
20308 }
20309 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20310 }
20311 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20312 }
20313
20314 wrap_guides
20315 }
20316
20317 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20318 let settings = self.buffer.read(cx).language_settings(cx);
20319 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20320 match mode {
20321 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20322 SoftWrap::None
20323 }
20324 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20325 language_settings::SoftWrap::PreferredLineLength => {
20326 SoftWrap::Column(settings.preferred_line_length)
20327 }
20328 language_settings::SoftWrap::Bounded => {
20329 SoftWrap::Bounded(settings.preferred_line_length)
20330 }
20331 }
20332 }
20333
20334 pub fn set_soft_wrap_mode(
20335 &mut self,
20336 mode: language_settings::SoftWrap,
20337
20338 cx: &mut Context<Self>,
20339 ) {
20340 self.soft_wrap_mode_override = Some(mode);
20341 cx.notify();
20342 }
20343
20344 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20345 self.hard_wrap = hard_wrap;
20346 cx.notify();
20347 }
20348
20349 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20350 self.text_style_refinement = Some(style);
20351 }
20352
20353 /// called by the Element so we know what style we were most recently rendered with.
20354 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20355 // We intentionally do not inform the display map about the minimap style
20356 // so that wrapping is not recalculated and stays consistent for the editor
20357 // and its linked minimap.
20358 if !self.mode.is_minimap() {
20359 let font = style.text.font();
20360 let font_size = style.text.font_size.to_pixels(window.rem_size());
20361 let display_map = self
20362 .placeholder_display_map
20363 .as_ref()
20364 .filter(|_| self.is_empty(cx))
20365 .unwrap_or(&self.display_map);
20366
20367 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20368 }
20369 self.style = Some(style);
20370 }
20371
20372 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20373 if self.style.is_none() {
20374 self.style = Some(self.create_style(cx));
20375 }
20376 self.style.as_ref().unwrap()
20377 }
20378
20379 // Called by the element. This method is not designed to be called outside of the editor
20380 // element's layout code because it does not notify when rewrapping is computed synchronously.
20381 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20382 if self.is_empty(cx) {
20383 self.placeholder_display_map
20384 .as_ref()
20385 .map_or(false, |display_map| {
20386 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20387 })
20388 } else {
20389 self.display_map
20390 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20391 }
20392 }
20393
20394 pub fn set_soft_wrap(&mut self) {
20395 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20396 }
20397
20398 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20399 if self.soft_wrap_mode_override.is_some() {
20400 self.soft_wrap_mode_override.take();
20401 } else {
20402 let soft_wrap = match self.soft_wrap_mode(cx) {
20403 SoftWrap::GitDiff => return,
20404 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20405 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20406 language_settings::SoftWrap::None
20407 }
20408 };
20409 self.soft_wrap_mode_override = Some(soft_wrap);
20410 }
20411 cx.notify();
20412 }
20413
20414 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20415 let Some(workspace) = self.workspace() else {
20416 return;
20417 };
20418 let fs = workspace.read(cx).app_state().fs.clone();
20419 let current_show = TabBarSettings::get_global(cx).show;
20420 update_settings_file(fs, cx, move |setting, _| {
20421 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20422 });
20423 }
20424
20425 pub fn toggle_indent_guides(
20426 &mut self,
20427 _: &ToggleIndentGuides,
20428 _: &mut Window,
20429 cx: &mut Context<Self>,
20430 ) {
20431 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20432 self.buffer
20433 .read(cx)
20434 .language_settings(cx)
20435 .indent_guides
20436 .enabled
20437 });
20438 self.show_indent_guides = Some(!currently_enabled);
20439 cx.notify();
20440 }
20441
20442 fn should_show_indent_guides(&self) -> Option<bool> {
20443 self.show_indent_guides
20444 }
20445
20446 pub fn disable_indent_guides_for_buffer(
20447 &mut self,
20448 buffer_id: BufferId,
20449 cx: &mut Context<Self>,
20450 ) {
20451 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20452 cx.notify();
20453 }
20454
20455 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20456 self.buffers_with_disabled_indent_guides
20457 .contains(&buffer_id)
20458 }
20459
20460 pub fn toggle_line_numbers(
20461 &mut self,
20462 _: &ToggleLineNumbers,
20463 _: &mut Window,
20464 cx: &mut Context<Self>,
20465 ) {
20466 let mut editor_settings = EditorSettings::get_global(cx).clone();
20467 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20468 EditorSettings::override_global(editor_settings, cx);
20469 }
20470
20471 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20472 if let Some(show_line_numbers) = self.show_line_numbers {
20473 return show_line_numbers;
20474 }
20475 EditorSettings::get_global(cx).gutter.line_numbers
20476 }
20477
20478 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20479 match (
20480 self.use_relative_line_numbers,
20481 EditorSettings::get_global(cx).relative_line_numbers,
20482 ) {
20483 (None, setting) => setting,
20484 (Some(false), _) => RelativeLineNumbers::Disabled,
20485 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20486 (Some(true), _) => RelativeLineNumbers::Enabled,
20487 }
20488 }
20489
20490 pub fn toggle_relative_line_numbers(
20491 &mut self,
20492 _: &ToggleRelativeLineNumbers,
20493 _: &mut Window,
20494 cx: &mut Context<Self>,
20495 ) {
20496 let is_relative = self.relative_line_numbers(cx);
20497 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20498 }
20499
20500 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20501 self.use_relative_line_numbers = is_relative;
20502 cx.notify();
20503 }
20504
20505 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20506 self.show_gutter = show_gutter;
20507 cx.notify();
20508 }
20509
20510 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20511 self.show_scrollbars = ScrollbarAxes {
20512 horizontal: show,
20513 vertical: show,
20514 };
20515 cx.notify();
20516 }
20517
20518 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20519 self.show_scrollbars.vertical = show;
20520 cx.notify();
20521 }
20522
20523 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20524 self.show_scrollbars.horizontal = show;
20525 cx.notify();
20526 }
20527
20528 pub fn set_minimap_visibility(
20529 &mut self,
20530 minimap_visibility: MinimapVisibility,
20531 window: &mut Window,
20532 cx: &mut Context<Self>,
20533 ) {
20534 if self.minimap_visibility != minimap_visibility {
20535 if minimap_visibility.visible() && self.minimap.is_none() {
20536 let minimap_settings = EditorSettings::get_global(cx).minimap;
20537 self.minimap =
20538 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20539 }
20540 self.minimap_visibility = minimap_visibility;
20541 cx.notify();
20542 }
20543 }
20544
20545 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20546 self.set_show_scrollbars(false, cx);
20547 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20548 }
20549
20550 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20551 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20552 }
20553
20554 /// Normally the text in full mode and auto height editors is padded on the
20555 /// left side by roughly half a character width for improved hit testing.
20556 ///
20557 /// Use this method to disable this for cases where this is not wanted (e.g.
20558 /// if you want to align the editor text with some other text above or below)
20559 /// or if you want to add this padding to single-line editors.
20560 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20561 self.offset_content = offset_content;
20562 cx.notify();
20563 }
20564
20565 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20566 self.show_line_numbers = Some(show_line_numbers);
20567 cx.notify();
20568 }
20569
20570 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20571 self.disable_expand_excerpt_buttons = true;
20572 cx.notify();
20573 }
20574
20575 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20576 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20577 cx.notify();
20578 }
20579
20580 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20581 self.show_code_actions = Some(show_code_actions);
20582 cx.notify();
20583 }
20584
20585 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20586 self.show_runnables = Some(show_runnables);
20587 cx.notify();
20588 }
20589
20590 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20591 self.show_breakpoints = Some(show_breakpoints);
20592 cx.notify();
20593 }
20594
20595 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20596 if self.display_map.read(cx).masked != masked {
20597 self.display_map.update(cx, |map, _| map.masked = masked);
20598 }
20599 cx.notify()
20600 }
20601
20602 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20603 self.show_wrap_guides = Some(show_wrap_guides);
20604 cx.notify();
20605 }
20606
20607 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20608 self.show_indent_guides = Some(show_indent_guides);
20609 cx.notify();
20610 }
20611
20612 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20613 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20614 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20615 && let Some(dir) = file.abs_path(cx).parent()
20616 {
20617 return Some(dir.to_owned());
20618 }
20619 }
20620
20621 None
20622 }
20623
20624 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20625 self.active_excerpt(cx)?
20626 .1
20627 .read(cx)
20628 .file()
20629 .and_then(|f| f.as_local())
20630 }
20631
20632 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20633 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20634 let buffer = buffer.read(cx);
20635 if let Some(project_path) = buffer.project_path(cx) {
20636 let project = self.project()?.read(cx);
20637 project.absolute_path(&project_path, cx)
20638 } else {
20639 buffer
20640 .file()
20641 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20642 }
20643 })
20644 }
20645
20646 pub fn reveal_in_finder(
20647 &mut self,
20648 _: &RevealInFileManager,
20649 _window: &mut Window,
20650 cx: &mut Context<Self>,
20651 ) {
20652 if let Some(target) = self.target_file(cx) {
20653 cx.reveal_path(&target.abs_path(cx));
20654 }
20655 }
20656
20657 pub fn copy_path(
20658 &mut self,
20659 _: &zed_actions::workspace::CopyPath,
20660 _window: &mut Window,
20661 cx: &mut Context<Self>,
20662 ) {
20663 if let Some(path) = self.target_file_abs_path(cx)
20664 && let Some(path) = path.to_str()
20665 {
20666 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20667 } else {
20668 cx.propagate();
20669 }
20670 }
20671
20672 pub fn copy_relative_path(
20673 &mut self,
20674 _: &zed_actions::workspace::CopyRelativePath,
20675 _window: &mut Window,
20676 cx: &mut Context<Self>,
20677 ) {
20678 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20679 let project = self.project()?.read(cx);
20680 let path = buffer.read(cx).file()?.path();
20681 let path = path.display(project.path_style(cx));
20682 Some(path)
20683 }) {
20684 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20685 } else {
20686 cx.propagate();
20687 }
20688 }
20689
20690 /// Returns the project path for the editor's buffer, if any buffer is
20691 /// opened in the editor.
20692 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20693 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20694 buffer.read(cx).project_path(cx)
20695 } else {
20696 None
20697 }
20698 }
20699
20700 // Returns true if the editor handled a go-to-line request
20701 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20702 maybe!({
20703 let breakpoint_store = self.breakpoint_store.as_ref()?;
20704
20705 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20706 else {
20707 self.clear_row_highlights::<ActiveDebugLine>();
20708 return None;
20709 };
20710
20711 let position = active_stack_frame.position;
20712 let buffer_id = position.buffer_id?;
20713 let snapshot = self
20714 .project
20715 .as_ref()?
20716 .read(cx)
20717 .buffer_for_id(buffer_id, cx)?
20718 .read(cx)
20719 .snapshot();
20720
20721 let mut handled = false;
20722 for (id, ExcerptRange { context, .. }) in
20723 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20724 {
20725 if context.start.cmp(&position, &snapshot).is_ge()
20726 || context.end.cmp(&position, &snapshot).is_lt()
20727 {
20728 continue;
20729 }
20730 let snapshot = self.buffer.read(cx).snapshot(cx);
20731 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20732
20733 handled = true;
20734 self.clear_row_highlights::<ActiveDebugLine>();
20735
20736 self.go_to_line::<ActiveDebugLine>(
20737 multibuffer_anchor,
20738 Some(cx.theme().colors().editor_debugger_active_line_background),
20739 window,
20740 cx,
20741 );
20742
20743 cx.notify();
20744 }
20745
20746 handled.then_some(())
20747 })
20748 .is_some()
20749 }
20750
20751 pub fn copy_file_name_without_extension(
20752 &mut self,
20753 _: &CopyFileNameWithoutExtension,
20754 _: &mut Window,
20755 cx: &mut Context<Self>,
20756 ) {
20757 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20758 let file = buffer.read(cx).file()?;
20759 file.path().file_stem()
20760 }) {
20761 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20762 }
20763 }
20764
20765 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20766 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20767 let file = buffer.read(cx).file()?;
20768 Some(file.file_name(cx))
20769 }) {
20770 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20771 }
20772 }
20773
20774 pub fn toggle_git_blame(
20775 &mut self,
20776 _: &::git::Blame,
20777 window: &mut Window,
20778 cx: &mut Context<Self>,
20779 ) {
20780 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20781
20782 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20783 self.start_git_blame(true, window, cx);
20784 }
20785
20786 cx.notify();
20787 }
20788
20789 pub fn toggle_git_blame_inline(
20790 &mut self,
20791 _: &ToggleGitBlameInline,
20792 window: &mut Window,
20793 cx: &mut Context<Self>,
20794 ) {
20795 self.toggle_git_blame_inline_internal(true, window, cx);
20796 cx.notify();
20797 }
20798
20799 pub fn open_git_blame_commit(
20800 &mut self,
20801 _: &OpenGitBlameCommit,
20802 window: &mut Window,
20803 cx: &mut Context<Self>,
20804 ) {
20805 self.open_git_blame_commit_internal(window, cx);
20806 }
20807
20808 fn open_git_blame_commit_internal(
20809 &mut self,
20810 window: &mut Window,
20811 cx: &mut Context<Self>,
20812 ) -> Option<()> {
20813 let blame = self.blame.as_ref()?;
20814 let snapshot = self.snapshot(window, cx);
20815 let cursor = self
20816 .selections
20817 .newest::<Point>(&snapshot.display_snapshot)
20818 .head();
20819 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20820 let (_, blame_entry) = blame
20821 .update(cx, |blame, cx| {
20822 blame
20823 .blame_for_rows(
20824 &[RowInfo {
20825 buffer_id: Some(buffer.remote_id()),
20826 buffer_row: Some(point.row),
20827 ..Default::default()
20828 }],
20829 cx,
20830 )
20831 .next()
20832 })
20833 .flatten()?;
20834 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20835 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20836 let workspace = self.workspace()?.downgrade();
20837 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20838 None
20839 }
20840
20841 pub fn git_blame_inline_enabled(&self) -> bool {
20842 self.git_blame_inline_enabled
20843 }
20844
20845 pub fn toggle_selection_menu(
20846 &mut self,
20847 _: &ToggleSelectionMenu,
20848 _: &mut Window,
20849 cx: &mut Context<Self>,
20850 ) {
20851 self.show_selection_menu = self
20852 .show_selection_menu
20853 .map(|show_selections_menu| !show_selections_menu)
20854 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20855
20856 cx.notify();
20857 }
20858
20859 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20860 self.show_selection_menu
20861 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20862 }
20863
20864 fn start_git_blame(
20865 &mut self,
20866 user_triggered: bool,
20867 window: &mut Window,
20868 cx: &mut Context<Self>,
20869 ) {
20870 if let Some(project) = self.project() {
20871 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20872 && buffer.read(cx).file().is_none()
20873 {
20874 return;
20875 }
20876
20877 let focused = self.focus_handle(cx).contains_focused(window, cx);
20878
20879 let project = project.clone();
20880 let blame = cx
20881 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20882 self.blame_subscription =
20883 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20884 self.blame = Some(blame);
20885 }
20886 }
20887
20888 fn toggle_git_blame_inline_internal(
20889 &mut self,
20890 user_triggered: bool,
20891 window: &mut Window,
20892 cx: &mut Context<Self>,
20893 ) {
20894 if self.git_blame_inline_enabled {
20895 self.git_blame_inline_enabled = false;
20896 self.show_git_blame_inline = false;
20897 self.show_git_blame_inline_delay_task.take();
20898 } else {
20899 self.git_blame_inline_enabled = true;
20900 self.start_git_blame_inline(user_triggered, window, cx);
20901 }
20902
20903 cx.notify();
20904 }
20905
20906 fn start_git_blame_inline(
20907 &mut self,
20908 user_triggered: bool,
20909 window: &mut Window,
20910 cx: &mut Context<Self>,
20911 ) {
20912 self.start_git_blame(user_triggered, window, cx);
20913
20914 if ProjectSettings::get_global(cx)
20915 .git
20916 .inline_blame_delay()
20917 .is_some()
20918 {
20919 self.start_inline_blame_timer(window, cx);
20920 } else {
20921 self.show_git_blame_inline = true
20922 }
20923 }
20924
20925 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20926 self.blame.as_ref()
20927 }
20928
20929 pub fn show_git_blame_gutter(&self) -> bool {
20930 self.show_git_blame_gutter
20931 }
20932
20933 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20934 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20935 }
20936
20937 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20938 self.show_git_blame_inline
20939 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20940 && !self.newest_selection_head_on_empty_line(cx)
20941 && self.has_blame_entries(cx)
20942 }
20943
20944 fn has_blame_entries(&self, cx: &App) -> bool {
20945 self.blame()
20946 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20947 }
20948
20949 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20950 let cursor_anchor = self.selections.newest_anchor().head();
20951
20952 let snapshot = self.buffer.read(cx).snapshot(cx);
20953 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20954
20955 snapshot.line_len(buffer_row) == 0
20956 }
20957
20958 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20959 let buffer_and_selection = maybe!({
20960 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20961 let selection_range = selection.range();
20962
20963 let multi_buffer = self.buffer().read(cx);
20964 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20965 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20966
20967 let (buffer, range, _) = if selection.reversed {
20968 buffer_ranges.first()
20969 } else {
20970 buffer_ranges.last()
20971 }?;
20972
20973 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
20974 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
20975
20976 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
20977 let selection = start_row_in_buffer..end_row_in_buffer;
20978
20979 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
20980 };
20981
20982 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
20983
20984 Some((
20985 multi_buffer.buffer(buffer.remote_id()).unwrap(),
20986 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, buffer)
20987 ..buffer_diff_snapshot.row_to_base_text_row(end_row_in_buffer, buffer),
20988 ))
20989 });
20990
20991 let Some((buffer, selection)) = buffer_and_selection else {
20992 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20993 };
20994
20995 let Some(project) = self.project() else {
20996 return Task::ready(Err(anyhow!("editor does not have project")));
20997 };
20998
20999 project.update(cx, |project, cx| {
21000 project.get_permalink_to_line(&buffer, selection, cx)
21001 })
21002 }
21003
21004 pub fn copy_permalink_to_line(
21005 &mut self,
21006 _: &CopyPermalinkToLine,
21007 window: &mut Window,
21008 cx: &mut Context<Self>,
21009 ) {
21010 let permalink_task = self.get_permalink_to_line(cx);
21011 let workspace = self.workspace();
21012
21013 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21014 Ok(permalink) => {
21015 cx.update(|_, cx| {
21016 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21017 })
21018 .ok();
21019 }
21020 Err(err) => {
21021 let message = format!("Failed to copy permalink: {err}");
21022
21023 anyhow::Result::<()>::Err(err).log_err();
21024
21025 if let Some(workspace) = workspace {
21026 workspace
21027 .update_in(cx, |workspace, _, cx| {
21028 struct CopyPermalinkToLine;
21029
21030 workspace.show_toast(
21031 Toast::new(
21032 NotificationId::unique::<CopyPermalinkToLine>(),
21033 message,
21034 ),
21035 cx,
21036 )
21037 })
21038 .ok();
21039 }
21040 }
21041 })
21042 .detach();
21043 }
21044
21045 pub fn copy_file_location(
21046 &mut self,
21047 _: &CopyFileLocation,
21048 _: &mut Window,
21049 cx: &mut Context<Self>,
21050 ) {
21051 let selection = self
21052 .selections
21053 .newest::<Point>(&self.display_snapshot(cx))
21054 .start
21055 .row
21056 + 1;
21057 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21058 let project = self.project()?.read(cx);
21059 let file = buffer.read(cx).file()?;
21060 let path = file.path().display(project.path_style(cx));
21061
21062 Some(format!("{path}:{selection}"))
21063 }) {
21064 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21065 }
21066 }
21067
21068 pub fn open_permalink_to_line(
21069 &mut self,
21070 _: &OpenPermalinkToLine,
21071 window: &mut Window,
21072 cx: &mut Context<Self>,
21073 ) {
21074 let permalink_task = self.get_permalink_to_line(cx);
21075 let workspace = self.workspace();
21076
21077 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21078 Ok(permalink) => {
21079 cx.update(|_, cx| {
21080 cx.open_url(permalink.as_ref());
21081 })
21082 .ok();
21083 }
21084 Err(err) => {
21085 let message = format!("Failed to open permalink: {err}");
21086
21087 anyhow::Result::<()>::Err(err).log_err();
21088
21089 if let Some(workspace) = workspace {
21090 workspace
21091 .update(cx, |workspace, cx| {
21092 struct OpenPermalinkToLine;
21093
21094 workspace.show_toast(
21095 Toast::new(
21096 NotificationId::unique::<OpenPermalinkToLine>(),
21097 message,
21098 ),
21099 cx,
21100 )
21101 })
21102 .ok();
21103 }
21104 }
21105 })
21106 .detach();
21107 }
21108
21109 pub fn insert_uuid_v4(
21110 &mut self,
21111 _: &InsertUuidV4,
21112 window: &mut Window,
21113 cx: &mut Context<Self>,
21114 ) {
21115 self.insert_uuid(UuidVersion::V4, window, cx);
21116 }
21117
21118 pub fn insert_uuid_v7(
21119 &mut self,
21120 _: &InsertUuidV7,
21121 window: &mut Window,
21122 cx: &mut Context<Self>,
21123 ) {
21124 self.insert_uuid(UuidVersion::V7, window, cx);
21125 }
21126
21127 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21128 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21129 self.transact(window, cx, |this, window, cx| {
21130 let edits = this
21131 .selections
21132 .all::<Point>(&this.display_snapshot(cx))
21133 .into_iter()
21134 .map(|selection| {
21135 let uuid = match version {
21136 UuidVersion::V4 => uuid::Uuid::new_v4(),
21137 UuidVersion::V7 => uuid::Uuid::now_v7(),
21138 };
21139
21140 (selection.range(), uuid.to_string())
21141 });
21142 this.edit(edits, cx);
21143 this.refresh_edit_prediction(true, false, window, cx);
21144 });
21145 }
21146
21147 pub fn open_selections_in_multibuffer(
21148 &mut self,
21149 _: &OpenSelectionsInMultibuffer,
21150 window: &mut Window,
21151 cx: &mut Context<Self>,
21152 ) {
21153 let multibuffer = self.buffer.read(cx);
21154
21155 let Some(buffer) = multibuffer.as_singleton() else {
21156 return;
21157 };
21158
21159 let Some(workspace) = self.workspace() else {
21160 return;
21161 };
21162
21163 let title = multibuffer.title(cx).to_string();
21164
21165 let locations = self
21166 .selections
21167 .all_anchors(&self.display_snapshot(cx))
21168 .iter()
21169 .map(|selection| {
21170 (
21171 buffer.clone(),
21172 (selection.start.text_anchor..selection.end.text_anchor)
21173 .to_point(buffer.read(cx)),
21174 )
21175 })
21176 .into_group_map();
21177
21178 cx.spawn_in(window, async move |_, cx| {
21179 workspace.update_in(cx, |workspace, window, cx| {
21180 Self::open_locations_in_multibuffer(
21181 workspace,
21182 locations,
21183 format!("Selections for '{title}'"),
21184 false,
21185 false,
21186 MultibufferSelectionMode::All,
21187 window,
21188 cx,
21189 );
21190 })
21191 })
21192 .detach();
21193 }
21194
21195 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21196 /// last highlight added will be used.
21197 ///
21198 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21199 pub fn highlight_rows<T: 'static>(
21200 &mut self,
21201 range: Range<Anchor>,
21202 color: Hsla,
21203 options: RowHighlightOptions,
21204 cx: &mut Context<Self>,
21205 ) {
21206 let snapshot = self.buffer().read(cx).snapshot(cx);
21207 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21208 let ix = row_highlights.binary_search_by(|highlight| {
21209 Ordering::Equal
21210 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21211 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21212 });
21213
21214 if let Err(mut ix) = ix {
21215 let index = post_inc(&mut self.highlight_order);
21216
21217 // If this range intersects with the preceding highlight, then merge it with
21218 // the preceding highlight. Otherwise insert a new highlight.
21219 let mut merged = false;
21220 if ix > 0 {
21221 let prev_highlight = &mut row_highlights[ix - 1];
21222 if prev_highlight
21223 .range
21224 .end
21225 .cmp(&range.start, &snapshot)
21226 .is_ge()
21227 {
21228 ix -= 1;
21229 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21230 prev_highlight.range.end = range.end;
21231 }
21232 merged = true;
21233 prev_highlight.index = index;
21234 prev_highlight.color = color;
21235 prev_highlight.options = options;
21236 }
21237 }
21238
21239 if !merged {
21240 row_highlights.insert(
21241 ix,
21242 RowHighlight {
21243 range,
21244 index,
21245 color,
21246 options,
21247 type_id: TypeId::of::<T>(),
21248 },
21249 );
21250 }
21251
21252 // If any of the following highlights intersect with this one, merge them.
21253 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21254 let highlight = &row_highlights[ix];
21255 if next_highlight
21256 .range
21257 .start
21258 .cmp(&highlight.range.end, &snapshot)
21259 .is_le()
21260 {
21261 if next_highlight
21262 .range
21263 .end
21264 .cmp(&highlight.range.end, &snapshot)
21265 .is_gt()
21266 {
21267 row_highlights[ix].range.end = next_highlight.range.end;
21268 }
21269 row_highlights.remove(ix + 1);
21270 } else {
21271 break;
21272 }
21273 }
21274 }
21275 }
21276
21277 /// Remove any highlighted row ranges of the given type that intersect the
21278 /// given ranges.
21279 pub fn remove_highlighted_rows<T: 'static>(
21280 &mut self,
21281 ranges_to_remove: Vec<Range<Anchor>>,
21282 cx: &mut Context<Self>,
21283 ) {
21284 let snapshot = self.buffer().read(cx).snapshot(cx);
21285 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21286 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21287 row_highlights.retain(|highlight| {
21288 while let Some(range_to_remove) = ranges_to_remove.peek() {
21289 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21290 Ordering::Less | Ordering::Equal => {
21291 ranges_to_remove.next();
21292 }
21293 Ordering::Greater => {
21294 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21295 Ordering::Less | Ordering::Equal => {
21296 return false;
21297 }
21298 Ordering::Greater => break,
21299 }
21300 }
21301 }
21302 }
21303
21304 true
21305 })
21306 }
21307
21308 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21309 pub fn clear_row_highlights<T: 'static>(&mut self) {
21310 self.highlighted_rows.remove(&TypeId::of::<T>());
21311 }
21312
21313 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21314 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21315 self.highlighted_rows
21316 .get(&TypeId::of::<T>())
21317 .map_or(&[] as &[_], |vec| vec.as_slice())
21318 .iter()
21319 .map(|highlight| (highlight.range.clone(), highlight.color))
21320 }
21321
21322 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21323 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21324 /// Allows to ignore certain kinds of highlights.
21325 pub fn highlighted_display_rows(
21326 &self,
21327 window: &mut Window,
21328 cx: &mut App,
21329 ) -> BTreeMap<DisplayRow, LineHighlight> {
21330 let snapshot = self.snapshot(window, cx);
21331 let mut used_highlight_orders = HashMap::default();
21332 self.highlighted_rows
21333 .iter()
21334 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21335 .fold(
21336 BTreeMap::<DisplayRow, LineHighlight>::new(),
21337 |mut unique_rows, highlight| {
21338 let start = highlight.range.start.to_display_point(&snapshot);
21339 let end = highlight.range.end.to_display_point(&snapshot);
21340 let start_row = start.row().0;
21341 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21342 {
21343 end.row().0.saturating_sub(1)
21344 } else {
21345 end.row().0
21346 };
21347 for row in start_row..=end_row {
21348 let used_index =
21349 used_highlight_orders.entry(row).or_insert(highlight.index);
21350 if highlight.index >= *used_index {
21351 *used_index = highlight.index;
21352 unique_rows.insert(
21353 DisplayRow(row),
21354 LineHighlight {
21355 include_gutter: highlight.options.include_gutter,
21356 border: None,
21357 background: highlight.color.into(),
21358 type_id: Some(highlight.type_id),
21359 },
21360 );
21361 }
21362 }
21363 unique_rows
21364 },
21365 )
21366 }
21367
21368 pub fn highlighted_display_row_for_autoscroll(
21369 &self,
21370 snapshot: &DisplaySnapshot,
21371 ) -> Option<DisplayRow> {
21372 self.highlighted_rows
21373 .values()
21374 .flat_map(|highlighted_rows| highlighted_rows.iter())
21375 .filter_map(|highlight| {
21376 if highlight.options.autoscroll {
21377 Some(highlight.range.start.to_display_point(snapshot).row())
21378 } else {
21379 None
21380 }
21381 })
21382 .min()
21383 }
21384
21385 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21386 self.highlight_background::<SearchWithinRange>(
21387 ranges,
21388 |_, colors| colors.colors().editor_document_highlight_read_background,
21389 cx,
21390 )
21391 }
21392
21393 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21394 self.breadcrumb_header = Some(new_header);
21395 }
21396
21397 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21398 self.clear_background_highlights::<SearchWithinRange>(cx);
21399 }
21400
21401 pub fn highlight_background<T: 'static>(
21402 &mut self,
21403 ranges: &[Range<Anchor>],
21404 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21405 cx: &mut Context<Self>,
21406 ) {
21407 self.background_highlights.insert(
21408 HighlightKey::Type(TypeId::of::<T>()),
21409 (Arc::new(color_fetcher), Arc::from(ranges)),
21410 );
21411 self.scrollbar_marker_state.dirty = true;
21412 cx.notify();
21413 }
21414
21415 pub fn highlight_background_key<T: 'static>(
21416 &mut self,
21417 key: usize,
21418 ranges: &[Range<Anchor>],
21419 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21420 cx: &mut Context<Self>,
21421 ) {
21422 self.background_highlights.insert(
21423 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21424 (Arc::new(color_fetcher), Arc::from(ranges)),
21425 );
21426 self.scrollbar_marker_state.dirty = true;
21427 cx.notify();
21428 }
21429
21430 pub fn clear_background_highlights<T: 'static>(
21431 &mut self,
21432 cx: &mut Context<Self>,
21433 ) -> Option<BackgroundHighlight> {
21434 let text_highlights = self
21435 .background_highlights
21436 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21437 if !text_highlights.1.is_empty() {
21438 self.scrollbar_marker_state.dirty = true;
21439 cx.notify();
21440 }
21441 Some(text_highlights)
21442 }
21443
21444 pub fn highlight_gutter<T: 'static>(
21445 &mut self,
21446 ranges: impl Into<Vec<Range<Anchor>>>,
21447 color_fetcher: fn(&App) -> Hsla,
21448 cx: &mut Context<Self>,
21449 ) {
21450 self.gutter_highlights
21451 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21452 cx.notify();
21453 }
21454
21455 pub fn clear_gutter_highlights<T: 'static>(
21456 &mut self,
21457 cx: &mut Context<Self>,
21458 ) -> Option<GutterHighlight> {
21459 cx.notify();
21460 self.gutter_highlights.remove(&TypeId::of::<T>())
21461 }
21462
21463 pub fn insert_gutter_highlight<T: 'static>(
21464 &mut self,
21465 range: Range<Anchor>,
21466 color_fetcher: fn(&App) -> Hsla,
21467 cx: &mut Context<Self>,
21468 ) {
21469 let snapshot = self.buffer().read(cx).snapshot(cx);
21470 let mut highlights = self
21471 .gutter_highlights
21472 .remove(&TypeId::of::<T>())
21473 .map(|(_, highlights)| highlights)
21474 .unwrap_or_default();
21475 let ix = highlights.binary_search_by(|highlight| {
21476 Ordering::Equal
21477 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21478 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21479 });
21480 if let Err(ix) = ix {
21481 highlights.insert(ix, range);
21482 }
21483 self.gutter_highlights
21484 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21485 }
21486
21487 pub fn remove_gutter_highlights<T: 'static>(
21488 &mut self,
21489 ranges_to_remove: Vec<Range<Anchor>>,
21490 cx: &mut Context<Self>,
21491 ) {
21492 let snapshot = self.buffer().read(cx).snapshot(cx);
21493 let Some((color_fetcher, mut gutter_highlights)) =
21494 self.gutter_highlights.remove(&TypeId::of::<T>())
21495 else {
21496 return;
21497 };
21498 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21499 gutter_highlights.retain(|highlight| {
21500 while let Some(range_to_remove) = ranges_to_remove.peek() {
21501 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21502 Ordering::Less | Ordering::Equal => {
21503 ranges_to_remove.next();
21504 }
21505 Ordering::Greater => {
21506 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21507 Ordering::Less | Ordering::Equal => {
21508 return false;
21509 }
21510 Ordering::Greater => break,
21511 }
21512 }
21513 }
21514 }
21515
21516 true
21517 });
21518 self.gutter_highlights
21519 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21520 }
21521
21522 #[cfg(feature = "test-support")]
21523 pub fn all_text_highlights(
21524 &self,
21525 window: &mut Window,
21526 cx: &mut Context<Self>,
21527 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21528 let snapshot = self.snapshot(window, cx);
21529 self.display_map.update(cx, |display_map, _| {
21530 display_map
21531 .all_text_highlights()
21532 .map(|highlight| {
21533 let (style, ranges) = highlight.as_ref();
21534 (
21535 *style,
21536 ranges
21537 .iter()
21538 .map(|range| range.clone().to_display_points(&snapshot))
21539 .collect(),
21540 )
21541 })
21542 .collect()
21543 })
21544 }
21545
21546 #[cfg(feature = "test-support")]
21547 pub fn all_text_background_highlights(
21548 &self,
21549 window: &mut Window,
21550 cx: &mut Context<Self>,
21551 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21552 let snapshot = self.snapshot(window, cx);
21553 let buffer = &snapshot.buffer_snapshot();
21554 let start = buffer.anchor_before(MultiBufferOffset(0));
21555 let end = buffer.anchor_after(buffer.len());
21556 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21557 }
21558
21559 #[cfg(any(test, feature = "test-support"))]
21560 pub fn sorted_background_highlights_in_range(
21561 &self,
21562 search_range: Range<Anchor>,
21563 display_snapshot: &DisplaySnapshot,
21564 theme: &Theme,
21565 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21566 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21567 res.sort_by(|a, b| {
21568 a.0.start
21569 .cmp(&b.0.start)
21570 .then_with(|| a.0.end.cmp(&b.0.end))
21571 .then_with(|| a.1.cmp(&b.1))
21572 });
21573 res
21574 }
21575
21576 #[cfg(feature = "test-support")]
21577 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21578 let snapshot = self.buffer().read(cx).snapshot(cx);
21579
21580 let highlights = self
21581 .background_highlights
21582 .get(&HighlightKey::Type(TypeId::of::<
21583 items::BufferSearchHighlights,
21584 >()));
21585
21586 if let Some((_color, ranges)) = highlights {
21587 ranges
21588 .iter()
21589 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21590 .collect_vec()
21591 } else {
21592 vec![]
21593 }
21594 }
21595
21596 fn document_highlights_for_position<'a>(
21597 &'a self,
21598 position: Anchor,
21599 buffer: &'a MultiBufferSnapshot,
21600 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21601 let read_highlights = self
21602 .background_highlights
21603 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21604 .map(|h| &h.1);
21605 let write_highlights = self
21606 .background_highlights
21607 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21608 .map(|h| &h.1);
21609 let left_position = position.bias_left(buffer);
21610 let right_position = position.bias_right(buffer);
21611 read_highlights
21612 .into_iter()
21613 .chain(write_highlights)
21614 .flat_map(move |ranges| {
21615 let start_ix = match ranges.binary_search_by(|probe| {
21616 let cmp = probe.end.cmp(&left_position, buffer);
21617 if cmp.is_ge() {
21618 Ordering::Greater
21619 } else {
21620 Ordering::Less
21621 }
21622 }) {
21623 Ok(i) | Err(i) => i,
21624 };
21625
21626 ranges[start_ix..]
21627 .iter()
21628 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21629 })
21630 }
21631
21632 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21633 self.background_highlights
21634 .get(&HighlightKey::Type(TypeId::of::<T>()))
21635 .is_some_and(|(_, highlights)| !highlights.is_empty())
21636 }
21637
21638 /// Returns all background highlights for a given range.
21639 ///
21640 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21641 pub fn background_highlights_in_range(
21642 &self,
21643 search_range: Range<Anchor>,
21644 display_snapshot: &DisplaySnapshot,
21645 theme: &Theme,
21646 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21647 let mut results = Vec::new();
21648 for (color_fetcher, ranges) in self.background_highlights.values() {
21649 let start_ix = match ranges.binary_search_by(|probe| {
21650 let cmp = probe
21651 .end
21652 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21653 if cmp.is_gt() {
21654 Ordering::Greater
21655 } else {
21656 Ordering::Less
21657 }
21658 }) {
21659 Ok(i) | Err(i) => i,
21660 };
21661 for (index, range) in ranges[start_ix..].iter().enumerate() {
21662 if range
21663 .start
21664 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21665 .is_ge()
21666 {
21667 break;
21668 }
21669
21670 let color = color_fetcher(&(start_ix + index), theme);
21671 let start = range.start.to_display_point(display_snapshot);
21672 let end = range.end.to_display_point(display_snapshot);
21673 results.push((start..end, color))
21674 }
21675 }
21676 results
21677 }
21678
21679 pub fn gutter_highlights_in_range(
21680 &self,
21681 search_range: Range<Anchor>,
21682 display_snapshot: &DisplaySnapshot,
21683 cx: &App,
21684 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21685 let mut results = Vec::new();
21686 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21687 let color = color_fetcher(cx);
21688 let start_ix = match ranges.binary_search_by(|probe| {
21689 let cmp = probe
21690 .end
21691 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21692 if cmp.is_gt() {
21693 Ordering::Greater
21694 } else {
21695 Ordering::Less
21696 }
21697 }) {
21698 Ok(i) | Err(i) => i,
21699 };
21700 for range in &ranges[start_ix..] {
21701 if range
21702 .start
21703 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21704 .is_ge()
21705 {
21706 break;
21707 }
21708
21709 let start = range.start.to_display_point(display_snapshot);
21710 let end = range.end.to_display_point(display_snapshot);
21711 results.push((start..end, color))
21712 }
21713 }
21714 results
21715 }
21716
21717 /// Get the text ranges corresponding to the redaction query
21718 pub fn redacted_ranges(
21719 &self,
21720 search_range: Range<Anchor>,
21721 display_snapshot: &DisplaySnapshot,
21722 cx: &App,
21723 ) -> Vec<Range<DisplayPoint>> {
21724 display_snapshot
21725 .buffer_snapshot()
21726 .redacted_ranges(search_range, |file| {
21727 if let Some(file) = file {
21728 file.is_private()
21729 && EditorSettings::get(
21730 Some(SettingsLocation {
21731 worktree_id: file.worktree_id(cx),
21732 path: file.path().as_ref(),
21733 }),
21734 cx,
21735 )
21736 .redact_private_values
21737 } else {
21738 false
21739 }
21740 })
21741 .map(|range| {
21742 range.start.to_display_point(display_snapshot)
21743 ..range.end.to_display_point(display_snapshot)
21744 })
21745 .collect()
21746 }
21747
21748 pub fn highlight_text_key<T: 'static>(
21749 &mut self,
21750 key: usize,
21751 ranges: Vec<Range<Anchor>>,
21752 style: HighlightStyle,
21753 merge: bool,
21754 cx: &mut Context<Self>,
21755 ) {
21756 self.display_map.update(cx, |map, cx| {
21757 map.highlight_text(
21758 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21759 ranges,
21760 style,
21761 merge,
21762 cx,
21763 );
21764 });
21765 cx.notify();
21766 }
21767
21768 pub fn highlight_text<T: 'static>(
21769 &mut self,
21770 ranges: Vec<Range<Anchor>>,
21771 style: HighlightStyle,
21772 cx: &mut Context<Self>,
21773 ) {
21774 self.display_map.update(cx, |map, cx| {
21775 map.highlight_text(
21776 HighlightKey::Type(TypeId::of::<T>()),
21777 ranges,
21778 style,
21779 false,
21780 cx,
21781 )
21782 });
21783 cx.notify();
21784 }
21785
21786 pub fn text_highlights<'a, T: 'static>(
21787 &'a self,
21788 cx: &'a App,
21789 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21790 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21791 }
21792
21793 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21794 let cleared = self
21795 .display_map
21796 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21797 if cleared {
21798 cx.notify();
21799 }
21800 }
21801
21802 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21803 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21804 && self.focus_handle.is_focused(window)
21805 }
21806
21807 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21808 self.show_cursor_when_unfocused = is_enabled;
21809 cx.notify();
21810 }
21811
21812 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21813 cx.notify();
21814 }
21815
21816 fn on_debug_session_event(
21817 &mut self,
21818 _session: Entity<Session>,
21819 event: &SessionEvent,
21820 cx: &mut Context<Self>,
21821 ) {
21822 if let SessionEvent::InvalidateInlineValue = event {
21823 self.refresh_inline_values(cx);
21824 }
21825 }
21826
21827 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21828 let Some(project) = self.project.clone() else {
21829 return;
21830 };
21831
21832 if !self.inline_value_cache.enabled {
21833 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21834 self.splice_inlays(&inlays, Vec::new(), cx);
21835 return;
21836 }
21837
21838 let current_execution_position = self
21839 .highlighted_rows
21840 .get(&TypeId::of::<ActiveDebugLine>())
21841 .and_then(|lines| lines.last().map(|line| line.range.end));
21842
21843 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21844 let inline_values = editor
21845 .update(cx, |editor, cx| {
21846 let Some(current_execution_position) = current_execution_position else {
21847 return Some(Task::ready(Ok(Vec::new())));
21848 };
21849
21850 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21851 let snapshot = buffer.snapshot(cx);
21852
21853 let excerpt = snapshot.excerpt_containing(
21854 current_execution_position..current_execution_position,
21855 )?;
21856
21857 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21858 })?;
21859
21860 let range =
21861 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21862
21863 project.inline_values(buffer, range, cx)
21864 })
21865 .ok()
21866 .flatten()?
21867 .await
21868 .context("refreshing debugger inlays")
21869 .log_err()?;
21870
21871 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21872
21873 for (buffer_id, inline_value) in inline_values
21874 .into_iter()
21875 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21876 {
21877 buffer_inline_values
21878 .entry(buffer_id)
21879 .or_default()
21880 .push(inline_value);
21881 }
21882
21883 editor
21884 .update(cx, |editor, cx| {
21885 let snapshot = editor.buffer.read(cx).snapshot(cx);
21886 let mut new_inlays = Vec::default();
21887
21888 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21889 let buffer_id = buffer_snapshot.remote_id();
21890 buffer_inline_values
21891 .get(&buffer_id)
21892 .into_iter()
21893 .flatten()
21894 .for_each(|hint| {
21895 let inlay = Inlay::debugger(
21896 post_inc(&mut editor.next_inlay_id),
21897 Anchor::in_buffer(excerpt_id, hint.position),
21898 hint.text(),
21899 );
21900 if !inlay.text().chars().contains(&'\n') {
21901 new_inlays.push(inlay);
21902 }
21903 });
21904 }
21905
21906 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21907 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21908
21909 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21910 })
21911 .ok()?;
21912 Some(())
21913 });
21914 }
21915
21916 fn on_buffer_event(
21917 &mut self,
21918 multibuffer: &Entity<MultiBuffer>,
21919 event: &multi_buffer::Event,
21920 window: &mut Window,
21921 cx: &mut Context<Self>,
21922 ) {
21923 match event {
21924 multi_buffer::Event::Edited { edited_buffer } => {
21925 self.scrollbar_marker_state.dirty = true;
21926 self.active_indent_guides_state.dirty = true;
21927 self.refresh_active_diagnostics(cx);
21928 self.refresh_code_actions(window, cx);
21929 self.refresh_single_line_folds(window, cx);
21930 self.refresh_matching_bracket_highlights(window, cx);
21931 if self.has_active_edit_prediction() {
21932 self.update_visible_edit_prediction(window, cx);
21933 }
21934
21935 if let Some(buffer) = edited_buffer {
21936 if buffer.read(cx).file().is_none() {
21937 cx.emit(EditorEvent::TitleChanged);
21938 }
21939
21940 if self.project.is_some() {
21941 let buffer_id = buffer.read(cx).remote_id();
21942 self.register_buffer(buffer_id, cx);
21943 self.update_lsp_data(Some(buffer_id), window, cx);
21944 self.refresh_inlay_hints(
21945 InlayHintRefreshReason::BufferEdited(buffer_id),
21946 cx,
21947 );
21948 }
21949 }
21950
21951 cx.emit(EditorEvent::BufferEdited);
21952 cx.emit(SearchEvent::MatchesInvalidated);
21953
21954 let Some(project) = &self.project else { return };
21955 let (telemetry, is_via_ssh) = {
21956 let project = project.read(cx);
21957 let telemetry = project.client().telemetry().clone();
21958 let is_via_ssh = project.is_via_remote_server();
21959 (telemetry, is_via_ssh)
21960 };
21961 telemetry.log_edit_event("editor", is_via_ssh);
21962 }
21963 multi_buffer::Event::ExcerptsAdded {
21964 buffer,
21965 predecessor,
21966 excerpts,
21967 } => {
21968 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21969 let buffer_id = buffer.read(cx).remote_id();
21970 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21971 && let Some(project) = &self.project
21972 {
21973 update_uncommitted_diff_for_buffer(
21974 cx.entity(),
21975 project,
21976 [buffer.clone()],
21977 self.buffer.clone(),
21978 cx,
21979 )
21980 .detach();
21981 }
21982 self.update_lsp_data(Some(buffer_id), window, cx);
21983 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21984 self.colorize_brackets(false, cx);
21985 cx.emit(EditorEvent::ExcerptsAdded {
21986 buffer: buffer.clone(),
21987 predecessor: *predecessor,
21988 excerpts: excerpts.clone(),
21989 });
21990 }
21991 multi_buffer::Event::ExcerptsRemoved {
21992 ids,
21993 removed_buffer_ids,
21994 } => {
21995 if let Some(inlay_hints) = &mut self.inlay_hints {
21996 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21997 }
21998 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21999 for buffer_id in removed_buffer_ids {
22000 self.registered_buffers.remove(buffer_id);
22001 }
22002 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22003 cx.emit(EditorEvent::ExcerptsRemoved {
22004 ids: ids.clone(),
22005 removed_buffer_ids: removed_buffer_ids.clone(),
22006 });
22007 }
22008 multi_buffer::Event::ExcerptsEdited {
22009 excerpt_ids,
22010 buffer_ids,
22011 } => {
22012 self.display_map.update(cx, |map, cx| {
22013 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22014 });
22015 cx.emit(EditorEvent::ExcerptsEdited {
22016 ids: excerpt_ids.clone(),
22017 });
22018 }
22019 multi_buffer::Event::ExcerptsExpanded { ids } => {
22020 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22021 self.refresh_document_highlights(cx);
22022 for id in ids {
22023 self.fetched_tree_sitter_chunks.remove(id);
22024 }
22025 self.colorize_brackets(false, cx);
22026 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22027 }
22028 multi_buffer::Event::Reparsed(buffer_id) => {
22029 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22030 self.refresh_selected_text_highlights(true, window, cx);
22031 self.colorize_brackets(true, cx);
22032 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22033
22034 cx.emit(EditorEvent::Reparsed(*buffer_id));
22035 }
22036 multi_buffer::Event::DiffHunksToggled => {
22037 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22038 }
22039 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22040 if !is_fresh_language {
22041 self.registered_buffers.remove(&buffer_id);
22042 }
22043 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22044 cx.emit(EditorEvent::Reparsed(*buffer_id));
22045 cx.notify();
22046 }
22047 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22048 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22049 multi_buffer::Event::FileHandleChanged
22050 | multi_buffer::Event::Reloaded
22051 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22052 multi_buffer::Event::DiagnosticsUpdated => {
22053 self.update_diagnostics_state(window, cx);
22054 }
22055 _ => {}
22056 };
22057 }
22058
22059 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22060 if !self.diagnostics_enabled() {
22061 return;
22062 }
22063 self.refresh_active_diagnostics(cx);
22064 self.refresh_inline_diagnostics(true, window, cx);
22065 self.scrollbar_marker_state.dirty = true;
22066 cx.notify();
22067 }
22068
22069 pub fn start_temporary_diff_override(&mut self) {
22070 self.load_diff_task.take();
22071 self.temporary_diff_override = true;
22072 }
22073
22074 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22075 self.temporary_diff_override = false;
22076 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22077 self.buffer.update(cx, |buffer, cx| {
22078 buffer.set_all_diff_hunks_collapsed(cx);
22079 });
22080
22081 if let Some(project) = self.project.clone() {
22082 self.load_diff_task = Some(
22083 update_uncommitted_diff_for_buffer(
22084 cx.entity(),
22085 &project,
22086 self.buffer.read(cx).all_buffers(),
22087 self.buffer.clone(),
22088 cx,
22089 )
22090 .shared(),
22091 );
22092 }
22093 }
22094
22095 fn on_display_map_changed(
22096 &mut self,
22097 _: Entity<DisplayMap>,
22098 _: &mut Window,
22099 cx: &mut Context<Self>,
22100 ) {
22101 cx.notify();
22102 }
22103
22104 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22105 if !self.mode.is_full() {
22106 return None;
22107 }
22108
22109 let theme_settings = theme::ThemeSettings::get_global(cx);
22110 let theme = cx.theme();
22111 let accent_colors = theme.accents().clone();
22112
22113 let accent_overrides = theme_settings
22114 .theme_overrides
22115 .get(theme.name.as_ref())
22116 .map(|theme_style| &theme_style.accents)
22117 .into_iter()
22118 .flatten()
22119 .chain(
22120 theme_settings
22121 .experimental_theme_overrides
22122 .as_ref()
22123 .map(|overrides| &overrides.accents)
22124 .into_iter()
22125 .flatten(),
22126 )
22127 .flat_map(|accent| accent.0.clone())
22128 .collect();
22129
22130 Some(AccentData {
22131 colors: accent_colors,
22132 overrides: accent_overrides,
22133 })
22134 }
22135
22136 fn fetch_applicable_language_settings(
22137 &self,
22138 cx: &App,
22139 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22140 if !self.mode.is_full() {
22141 return HashMap::default();
22142 }
22143
22144 self.buffer().read(cx).all_buffers().into_iter().fold(
22145 HashMap::default(),
22146 |mut acc, buffer| {
22147 let buffer = buffer.read(cx);
22148 let language = buffer.language().map(|language| language.name());
22149 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22150 let file = buffer.file();
22151 v.insert(language_settings(language, file, cx).into_owned());
22152 }
22153 acc
22154 },
22155 )
22156 }
22157
22158 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22159 let new_language_settings = self.fetch_applicable_language_settings(cx);
22160 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22161 self.applicable_language_settings = new_language_settings;
22162
22163 let new_accents = self.fetch_accent_data(cx);
22164 let accents_changed = new_accents != self.accent_data;
22165 self.accent_data = new_accents;
22166
22167 if self.diagnostics_enabled() {
22168 let new_severity = EditorSettings::get_global(cx)
22169 .diagnostics_max_severity
22170 .unwrap_or(DiagnosticSeverity::Hint);
22171 self.set_max_diagnostics_severity(new_severity, cx);
22172 }
22173 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22174 self.update_edit_prediction_settings(cx);
22175 self.refresh_edit_prediction(true, false, window, cx);
22176 self.refresh_inline_values(cx);
22177 self.refresh_inlay_hints(
22178 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22179 self.selections.newest_anchor().head(),
22180 &self.buffer.read(cx).snapshot(cx),
22181 cx,
22182 )),
22183 cx,
22184 );
22185
22186 let old_cursor_shape = self.cursor_shape;
22187 let old_show_breadcrumbs = self.show_breadcrumbs;
22188
22189 {
22190 let editor_settings = EditorSettings::get_global(cx);
22191 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22192 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22193 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22194 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22195 }
22196
22197 if old_cursor_shape != self.cursor_shape {
22198 cx.emit(EditorEvent::CursorShapeChanged);
22199 }
22200
22201 if old_show_breadcrumbs != self.show_breadcrumbs {
22202 cx.emit(EditorEvent::BreadcrumbsChanged);
22203 }
22204
22205 let project_settings = ProjectSettings::get_global(cx);
22206 self.buffer_serialization = self
22207 .should_serialize_buffer()
22208 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22209
22210 if self.mode.is_full() {
22211 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22212 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22213 if self.show_inline_diagnostics != show_inline_diagnostics {
22214 self.show_inline_diagnostics = show_inline_diagnostics;
22215 self.refresh_inline_diagnostics(false, window, cx);
22216 }
22217
22218 if self.git_blame_inline_enabled != inline_blame_enabled {
22219 self.toggle_git_blame_inline_internal(false, window, cx);
22220 }
22221
22222 let minimap_settings = EditorSettings::get_global(cx).minimap;
22223 if self.minimap_visibility != MinimapVisibility::Disabled {
22224 if self.minimap_visibility.settings_visibility()
22225 != minimap_settings.minimap_enabled()
22226 {
22227 self.set_minimap_visibility(
22228 MinimapVisibility::for_mode(self.mode(), cx),
22229 window,
22230 cx,
22231 );
22232 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22233 minimap_entity.update(cx, |minimap_editor, cx| {
22234 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22235 })
22236 }
22237 }
22238
22239 if language_settings_changed || accents_changed {
22240 self.colorize_brackets(true, cx);
22241 }
22242
22243 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22244 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22245 }) {
22246 if !inlay_splice.is_empty() {
22247 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22248 }
22249 self.refresh_colors_for_visible_range(None, window, cx);
22250 }
22251 }
22252
22253 cx.notify();
22254 }
22255
22256 pub fn set_searchable(&mut self, searchable: bool) {
22257 self.searchable = searchable;
22258 }
22259
22260 pub fn searchable(&self) -> bool {
22261 self.searchable
22262 }
22263
22264 pub fn open_excerpts_in_split(
22265 &mut self,
22266 _: &OpenExcerptsSplit,
22267 window: &mut Window,
22268 cx: &mut Context<Self>,
22269 ) {
22270 self.open_excerpts_common(None, true, window, cx)
22271 }
22272
22273 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22274 self.open_excerpts_common(None, false, window, cx)
22275 }
22276
22277 fn open_excerpts_common(
22278 &mut self,
22279 jump_data: Option<JumpData>,
22280 split: bool,
22281 window: &mut Window,
22282 cx: &mut Context<Self>,
22283 ) {
22284 let Some(workspace) = self.workspace() else {
22285 cx.propagate();
22286 return;
22287 };
22288
22289 if self.buffer.read(cx).is_singleton() {
22290 cx.propagate();
22291 return;
22292 }
22293
22294 let mut new_selections_by_buffer = HashMap::default();
22295 match &jump_data {
22296 Some(JumpData::MultiBufferPoint {
22297 excerpt_id,
22298 position,
22299 anchor,
22300 line_offset_from_top,
22301 }) => {
22302 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22303 if let Some(buffer) = multi_buffer_snapshot
22304 .buffer_id_for_excerpt(*excerpt_id)
22305 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22306 {
22307 let buffer_snapshot = buffer.read(cx).snapshot();
22308 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22309 language::ToPoint::to_point(anchor, &buffer_snapshot)
22310 } else {
22311 buffer_snapshot.clip_point(*position, Bias::Left)
22312 };
22313 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22314 new_selections_by_buffer.insert(
22315 buffer,
22316 (
22317 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22318 Some(*line_offset_from_top),
22319 ),
22320 );
22321 }
22322 }
22323 Some(JumpData::MultiBufferRow {
22324 row,
22325 line_offset_from_top,
22326 }) => {
22327 let point = MultiBufferPoint::new(row.0, 0);
22328 if let Some((buffer, buffer_point, _)) =
22329 self.buffer.read(cx).point_to_buffer_point(point, cx)
22330 {
22331 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22332 new_selections_by_buffer
22333 .entry(buffer)
22334 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22335 .0
22336 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22337 }
22338 }
22339 None => {
22340 let selections = self
22341 .selections
22342 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22343 let multi_buffer = self.buffer.read(cx);
22344 for selection in selections {
22345 for (snapshot, range, _, anchor) in multi_buffer
22346 .snapshot(cx)
22347 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22348 {
22349 if let Some(anchor) = anchor {
22350 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22351 else {
22352 continue;
22353 };
22354 let offset = text::ToOffset::to_offset(
22355 &anchor.text_anchor,
22356 &buffer_handle.read(cx).snapshot(),
22357 );
22358 let range = BufferOffset(offset)..BufferOffset(offset);
22359 new_selections_by_buffer
22360 .entry(buffer_handle)
22361 .or_insert((Vec::new(), None))
22362 .0
22363 .push(range)
22364 } else {
22365 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22366 else {
22367 continue;
22368 };
22369 new_selections_by_buffer
22370 .entry(buffer_handle)
22371 .or_insert((Vec::new(), None))
22372 .0
22373 .push(range)
22374 }
22375 }
22376 }
22377 }
22378 }
22379
22380 new_selections_by_buffer
22381 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22382
22383 if new_selections_by_buffer.is_empty() {
22384 return;
22385 }
22386
22387 // We defer the pane interaction because we ourselves are a workspace item
22388 // and activating a new item causes the pane to call a method on us reentrantly,
22389 // which panics if we're on the stack.
22390 window.defer(cx, move |window, cx| {
22391 workspace.update(cx, |workspace, cx| {
22392 let pane = if split {
22393 workspace.adjacent_pane(window, cx)
22394 } else {
22395 workspace.active_pane().clone()
22396 };
22397
22398 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22399 let buffer_read = buffer.read(cx);
22400 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22401 (true, project::File::from_dyn(Some(file)).is_some())
22402 } else {
22403 (false, false)
22404 };
22405
22406 // If project file is none workspace.open_project_item will fail to open the excerpt
22407 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22408 // so we check if there's a tab match in that case first
22409 let editor = (!has_file || !is_project_file)
22410 .then(|| {
22411 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22412 // so `workspace.open_project_item` will never find them, always opening a new editor.
22413 // Instead, we try to activate the existing editor in the pane first.
22414 let (editor, pane_item_index, pane_item_id) =
22415 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22416 let editor = item.downcast::<Editor>()?;
22417 let singleton_buffer =
22418 editor.read(cx).buffer().read(cx).as_singleton()?;
22419 if singleton_buffer == buffer {
22420 Some((editor, i, item.item_id()))
22421 } else {
22422 None
22423 }
22424 })?;
22425 pane.update(cx, |pane, cx| {
22426 pane.activate_item(pane_item_index, true, true, window, cx);
22427 if !PreviewTabsSettings::get_global(cx)
22428 .enable_preview_from_multibuffer
22429 {
22430 pane.unpreview_item_if_preview(pane_item_id);
22431 }
22432 });
22433 Some(editor)
22434 })
22435 .flatten()
22436 .unwrap_or_else(|| {
22437 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22438 .enable_keep_preview_on_code_navigation;
22439 let allow_new_preview =
22440 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22441 workspace.open_project_item::<Self>(
22442 pane.clone(),
22443 buffer,
22444 true,
22445 true,
22446 keep_old_preview,
22447 allow_new_preview,
22448 window,
22449 cx,
22450 )
22451 });
22452
22453 editor.update(cx, |editor, cx| {
22454 if has_file && !is_project_file {
22455 editor.set_read_only(true);
22456 }
22457 let autoscroll = match scroll_offset {
22458 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22459 None => Autoscroll::newest(),
22460 };
22461 let nav_history = editor.nav_history.take();
22462 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22463 let Some((&excerpt_id, _, buffer_snapshot)) =
22464 multibuffer_snapshot.as_singleton()
22465 else {
22466 return;
22467 };
22468 editor.change_selections(
22469 SelectionEffects::scroll(autoscroll),
22470 window,
22471 cx,
22472 |s| {
22473 s.select_ranges(ranges.into_iter().map(|range| {
22474 let range = buffer_snapshot.anchor_before(range.start)
22475 ..buffer_snapshot.anchor_after(range.end);
22476 multibuffer_snapshot
22477 .anchor_range_in_excerpt(excerpt_id, range)
22478 .unwrap()
22479 }));
22480 },
22481 );
22482 editor.nav_history = nav_history;
22483 });
22484 }
22485 })
22486 });
22487 }
22488
22489 // Allow opening excerpts for buffers that either belong to the current project
22490 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22491 // are also supported so tests and other in-memory views keep working.
22492 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22493 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22494 }
22495
22496 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22497 let snapshot = self.buffer.read(cx).read(cx);
22498 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22499 Some(
22500 ranges
22501 .iter()
22502 .map(move |range| {
22503 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22504 })
22505 .collect(),
22506 )
22507 }
22508
22509 fn selection_replacement_ranges(
22510 &self,
22511 range: Range<MultiBufferOffsetUtf16>,
22512 cx: &mut App,
22513 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22514 let selections = self
22515 .selections
22516 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22517 let newest_selection = selections
22518 .iter()
22519 .max_by_key(|selection| selection.id)
22520 .unwrap();
22521 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22522 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22523 let snapshot = self.buffer.read(cx).read(cx);
22524 selections
22525 .into_iter()
22526 .map(|mut selection| {
22527 selection.start.0.0 =
22528 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22529 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22530 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22531 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22532 })
22533 .collect()
22534 }
22535
22536 fn report_editor_event(
22537 &self,
22538 reported_event: ReportEditorEvent,
22539 file_extension: Option<String>,
22540 cx: &App,
22541 ) {
22542 if cfg!(any(test, feature = "test-support")) {
22543 return;
22544 }
22545
22546 let Some(project) = &self.project else { return };
22547
22548 // If None, we are in a file without an extension
22549 let file = self
22550 .buffer
22551 .read(cx)
22552 .as_singleton()
22553 .and_then(|b| b.read(cx).file());
22554 let file_extension = file_extension.or(file
22555 .as_ref()
22556 .and_then(|file| Path::new(file.file_name(cx)).extension())
22557 .and_then(|e| e.to_str())
22558 .map(|a| a.to_string()));
22559
22560 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22561 .map(|vim_mode| vim_mode.0)
22562 .unwrap_or(false);
22563
22564 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22565 let copilot_enabled = edit_predictions_provider
22566 == language::language_settings::EditPredictionProvider::Copilot;
22567 let copilot_enabled_for_language = self
22568 .buffer
22569 .read(cx)
22570 .language_settings(cx)
22571 .show_edit_predictions;
22572
22573 let project = project.read(cx);
22574 let event_type = reported_event.event_type();
22575
22576 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22577 telemetry::event!(
22578 event_type,
22579 type = if auto_saved {"autosave"} else {"manual"},
22580 file_extension,
22581 vim_mode,
22582 copilot_enabled,
22583 copilot_enabled_for_language,
22584 edit_predictions_provider,
22585 is_via_ssh = project.is_via_remote_server(),
22586 );
22587 } else {
22588 telemetry::event!(
22589 event_type,
22590 file_extension,
22591 vim_mode,
22592 copilot_enabled,
22593 copilot_enabled_for_language,
22594 edit_predictions_provider,
22595 is_via_ssh = project.is_via_remote_server(),
22596 );
22597 };
22598 }
22599
22600 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22601 /// with each line being an array of {text, highlight} objects.
22602 fn copy_highlight_json(
22603 &mut self,
22604 _: &CopyHighlightJson,
22605 window: &mut Window,
22606 cx: &mut Context<Self>,
22607 ) {
22608 #[derive(Serialize)]
22609 struct Chunk<'a> {
22610 text: String,
22611 highlight: Option<&'a str>,
22612 }
22613
22614 let snapshot = self.buffer.read(cx).snapshot(cx);
22615 let range = self
22616 .selected_text_range(false, window, cx)
22617 .and_then(|selection| {
22618 if selection.range.is_empty() {
22619 None
22620 } else {
22621 Some(
22622 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22623 selection.range.start,
22624 )))
22625 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22626 selection.range.end,
22627 ))),
22628 )
22629 }
22630 })
22631 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22632
22633 let chunks = snapshot.chunks(range, true);
22634 let mut lines = Vec::new();
22635 let mut line: VecDeque<Chunk> = VecDeque::new();
22636
22637 let Some(style) = self.style.as_ref() else {
22638 return;
22639 };
22640
22641 for chunk in chunks {
22642 let highlight = chunk
22643 .syntax_highlight_id
22644 .and_then(|id| id.name(&style.syntax));
22645 let mut chunk_lines = chunk.text.split('\n').peekable();
22646 while let Some(text) = chunk_lines.next() {
22647 let mut merged_with_last_token = false;
22648 if let Some(last_token) = line.back_mut()
22649 && last_token.highlight == highlight
22650 {
22651 last_token.text.push_str(text);
22652 merged_with_last_token = true;
22653 }
22654
22655 if !merged_with_last_token {
22656 line.push_back(Chunk {
22657 text: text.into(),
22658 highlight,
22659 });
22660 }
22661
22662 if chunk_lines.peek().is_some() {
22663 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22664 line.pop_front();
22665 }
22666 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22667 line.pop_back();
22668 }
22669
22670 lines.push(mem::take(&mut line));
22671 }
22672 }
22673 }
22674
22675 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22676 return;
22677 };
22678 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22679 }
22680
22681 pub fn open_context_menu(
22682 &mut self,
22683 _: &OpenContextMenu,
22684 window: &mut Window,
22685 cx: &mut Context<Self>,
22686 ) {
22687 self.request_autoscroll(Autoscroll::newest(), cx);
22688 let position = self
22689 .selections
22690 .newest_display(&self.display_snapshot(cx))
22691 .start;
22692 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22693 }
22694
22695 pub fn replay_insert_event(
22696 &mut self,
22697 text: &str,
22698 relative_utf16_range: Option<Range<isize>>,
22699 window: &mut Window,
22700 cx: &mut Context<Self>,
22701 ) {
22702 if !self.input_enabled {
22703 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22704 return;
22705 }
22706 if let Some(relative_utf16_range) = relative_utf16_range {
22707 let selections = self
22708 .selections
22709 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22710 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22711 let new_ranges = selections.into_iter().map(|range| {
22712 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22713 range
22714 .head()
22715 .0
22716 .0
22717 .saturating_add_signed(relative_utf16_range.start),
22718 ));
22719 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22720 range
22721 .head()
22722 .0
22723 .0
22724 .saturating_add_signed(relative_utf16_range.end),
22725 ));
22726 start..end
22727 });
22728 s.select_ranges(new_ranges);
22729 });
22730 }
22731
22732 self.handle_input(text, window, cx);
22733 }
22734
22735 pub fn is_focused(&self, window: &Window) -> bool {
22736 self.focus_handle.is_focused(window)
22737 }
22738
22739 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22740 cx.emit(EditorEvent::Focused);
22741
22742 if let Some(descendant) = self
22743 .last_focused_descendant
22744 .take()
22745 .and_then(|descendant| descendant.upgrade())
22746 {
22747 window.focus(&descendant, cx);
22748 } else {
22749 if let Some(blame) = self.blame.as_ref() {
22750 blame.update(cx, GitBlame::focus)
22751 }
22752
22753 self.blink_manager.update(cx, BlinkManager::enable);
22754 self.show_cursor_names(window, cx);
22755 self.buffer.update(cx, |buffer, cx| {
22756 buffer.finalize_last_transaction(cx);
22757 if self.leader_id.is_none() {
22758 buffer.set_active_selections(
22759 &self.selections.disjoint_anchors_arc(),
22760 self.selections.line_mode(),
22761 self.cursor_shape,
22762 cx,
22763 );
22764 }
22765 });
22766
22767 if let Some(position_map) = self.last_position_map.clone() {
22768 EditorElement::mouse_moved(
22769 self,
22770 &MouseMoveEvent {
22771 position: window.mouse_position(),
22772 pressed_button: None,
22773 modifiers: window.modifiers(),
22774 },
22775 &position_map,
22776 window,
22777 cx,
22778 );
22779 }
22780 }
22781 }
22782
22783 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22784 cx.emit(EditorEvent::FocusedIn)
22785 }
22786
22787 fn handle_focus_out(
22788 &mut self,
22789 event: FocusOutEvent,
22790 _window: &mut Window,
22791 cx: &mut Context<Self>,
22792 ) {
22793 if event.blurred != self.focus_handle {
22794 self.last_focused_descendant = Some(event.blurred);
22795 }
22796 self.selection_drag_state = SelectionDragState::None;
22797 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22798 }
22799
22800 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22801 self.blink_manager.update(cx, BlinkManager::disable);
22802 self.buffer
22803 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22804
22805 if let Some(blame) = self.blame.as_ref() {
22806 blame.update(cx, GitBlame::blur)
22807 }
22808 if !self.hover_state.focused(window, cx) {
22809 hide_hover(self, cx);
22810 }
22811 if !self
22812 .context_menu
22813 .borrow()
22814 .as_ref()
22815 .is_some_and(|context_menu| context_menu.focused(window, cx))
22816 {
22817 self.hide_context_menu(window, cx);
22818 }
22819 self.take_active_edit_prediction(cx);
22820 cx.emit(EditorEvent::Blurred);
22821 cx.notify();
22822 }
22823
22824 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22825 let mut pending: String = window
22826 .pending_input_keystrokes()
22827 .into_iter()
22828 .flatten()
22829 .filter_map(|keystroke| keystroke.key_char.clone())
22830 .collect();
22831
22832 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22833 pending = "".to_string();
22834 }
22835
22836 let existing_pending = self
22837 .text_highlights::<PendingInput>(cx)
22838 .map(|(_, ranges)| ranges.to_vec());
22839 if existing_pending.is_none() && pending.is_empty() {
22840 return;
22841 }
22842 let transaction =
22843 self.transact(window, cx, |this, window, cx| {
22844 let selections = this
22845 .selections
22846 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22847 let edits = selections
22848 .iter()
22849 .map(|selection| (selection.end..selection.end, pending.clone()));
22850 this.edit(edits, cx);
22851 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22852 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22853 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22854 }));
22855 });
22856 if let Some(existing_ranges) = existing_pending {
22857 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22858 this.edit(edits, cx);
22859 }
22860 });
22861
22862 let snapshot = self.snapshot(window, cx);
22863 let ranges = self
22864 .selections
22865 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22866 .into_iter()
22867 .map(|selection| {
22868 snapshot.buffer_snapshot().anchor_after(selection.end)
22869 ..snapshot
22870 .buffer_snapshot()
22871 .anchor_before(selection.end + pending.len())
22872 })
22873 .collect();
22874
22875 if pending.is_empty() {
22876 self.clear_highlights::<PendingInput>(cx);
22877 } else {
22878 self.highlight_text::<PendingInput>(
22879 ranges,
22880 HighlightStyle {
22881 underline: Some(UnderlineStyle {
22882 thickness: px(1.),
22883 color: None,
22884 wavy: false,
22885 }),
22886 ..Default::default()
22887 },
22888 cx,
22889 );
22890 }
22891
22892 self.ime_transaction = self.ime_transaction.or(transaction);
22893 if let Some(transaction) = self.ime_transaction {
22894 self.buffer.update(cx, |buffer, cx| {
22895 buffer.group_until_transaction(transaction, cx);
22896 });
22897 }
22898
22899 if self.text_highlights::<PendingInput>(cx).is_none() {
22900 self.ime_transaction.take();
22901 }
22902 }
22903
22904 pub fn register_action_renderer(
22905 &mut self,
22906 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22907 ) -> Subscription {
22908 let id = self.next_editor_action_id.post_inc();
22909 self.editor_actions
22910 .borrow_mut()
22911 .insert(id, Box::new(listener));
22912
22913 let editor_actions = self.editor_actions.clone();
22914 Subscription::new(move || {
22915 editor_actions.borrow_mut().remove(&id);
22916 })
22917 }
22918
22919 pub fn register_action<A: Action>(
22920 &mut self,
22921 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22922 ) -> Subscription {
22923 let id = self.next_editor_action_id.post_inc();
22924 let listener = Arc::new(listener);
22925 self.editor_actions.borrow_mut().insert(
22926 id,
22927 Box::new(move |_, window, _| {
22928 let listener = listener.clone();
22929 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22930 let action = action.downcast_ref().unwrap();
22931 if phase == DispatchPhase::Bubble {
22932 listener(action, window, cx)
22933 }
22934 })
22935 }),
22936 );
22937
22938 let editor_actions = self.editor_actions.clone();
22939 Subscription::new(move || {
22940 editor_actions.borrow_mut().remove(&id);
22941 })
22942 }
22943
22944 pub fn file_header_size(&self) -> u32 {
22945 FILE_HEADER_HEIGHT
22946 }
22947
22948 pub fn restore(
22949 &mut self,
22950 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22951 window: &mut Window,
22952 cx: &mut Context<Self>,
22953 ) {
22954 self.buffer().update(cx, |multi_buffer, cx| {
22955 for (buffer_id, changes) in revert_changes {
22956 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22957 buffer.update(cx, |buffer, cx| {
22958 buffer.edit(
22959 changes
22960 .into_iter()
22961 .map(|(range, text)| (range, text.to_string())),
22962 None,
22963 cx,
22964 );
22965 });
22966 }
22967 }
22968 });
22969 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22970 selections.refresh()
22971 });
22972 }
22973
22974 pub fn to_pixel_point(
22975 &mut self,
22976 source: multi_buffer::Anchor,
22977 editor_snapshot: &EditorSnapshot,
22978 window: &mut Window,
22979 cx: &App,
22980 ) -> Option<gpui::Point<Pixels>> {
22981 let source_point = source.to_display_point(editor_snapshot);
22982 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
22983 }
22984
22985 pub fn display_to_pixel_point(
22986 &mut self,
22987 source: DisplayPoint,
22988 editor_snapshot: &EditorSnapshot,
22989 window: &mut Window,
22990 cx: &App,
22991 ) -> Option<gpui::Point<Pixels>> {
22992 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
22993 let text_layout_details = self.text_layout_details(window);
22994 let scroll_top = text_layout_details
22995 .scroll_anchor
22996 .scroll_position(editor_snapshot)
22997 .y;
22998
22999 if source.row().as_f64() < scroll_top.floor() {
23000 return None;
23001 }
23002 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23003 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23004 Some(gpui::Point::new(source_x, source_y))
23005 }
23006
23007 pub fn has_visible_completions_menu(&self) -> bool {
23008 !self.edit_prediction_preview_is_active()
23009 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23010 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23011 })
23012 }
23013
23014 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23015 if self.mode.is_minimap() {
23016 return;
23017 }
23018 self.addons
23019 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23020 }
23021
23022 pub fn unregister_addon<T: Addon>(&mut self) {
23023 self.addons.remove(&std::any::TypeId::of::<T>());
23024 }
23025
23026 pub fn addon<T: Addon>(&self) -> Option<&T> {
23027 let type_id = std::any::TypeId::of::<T>();
23028 self.addons
23029 .get(&type_id)
23030 .and_then(|item| item.to_any().downcast_ref::<T>())
23031 }
23032
23033 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23034 let type_id = std::any::TypeId::of::<T>();
23035 self.addons
23036 .get_mut(&type_id)
23037 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23038 }
23039
23040 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23041 let text_layout_details = self.text_layout_details(window);
23042 let style = &text_layout_details.editor_style;
23043 let font_id = window.text_system().resolve_font(&style.text.font());
23044 let font_size = style.text.font_size.to_pixels(window.rem_size());
23045 let line_height = style.text.line_height_in_pixels(window.rem_size());
23046 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23047 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23048
23049 CharacterDimensions {
23050 em_width,
23051 em_advance,
23052 line_height,
23053 }
23054 }
23055
23056 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23057 self.load_diff_task.clone()
23058 }
23059
23060 fn read_metadata_from_db(
23061 &mut self,
23062 item_id: u64,
23063 workspace_id: WorkspaceId,
23064 window: &mut Window,
23065 cx: &mut Context<Editor>,
23066 ) {
23067 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23068 && !self.mode.is_minimap()
23069 && WorkspaceSettings::get(None, cx).restore_on_startup
23070 != RestoreOnStartupBehavior::EmptyTab
23071 {
23072 let buffer_snapshot = OnceCell::new();
23073
23074 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23075 && !folds.is_empty()
23076 {
23077 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23078 self.fold_ranges(
23079 folds
23080 .into_iter()
23081 .map(|(start, end)| {
23082 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23083 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23084 })
23085 .collect(),
23086 false,
23087 window,
23088 cx,
23089 );
23090 }
23091
23092 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23093 && !selections.is_empty()
23094 {
23095 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23096 // skip adding the initial selection to selection history
23097 self.selection_history.mode = SelectionHistoryMode::Skipping;
23098 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23099 s.select_ranges(selections.into_iter().map(|(start, end)| {
23100 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23101 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23102 }));
23103 });
23104 self.selection_history.mode = SelectionHistoryMode::Normal;
23105 };
23106 }
23107
23108 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23109 }
23110
23111 fn update_lsp_data(
23112 &mut self,
23113 for_buffer: Option<BufferId>,
23114 window: &mut Window,
23115 cx: &mut Context<'_, Self>,
23116 ) {
23117 self.pull_diagnostics(for_buffer, window, cx);
23118 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23119 }
23120
23121 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23122 if self.ignore_lsp_data() {
23123 return;
23124 }
23125 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23126 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23127 }
23128 }
23129
23130 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23131 if self.ignore_lsp_data() {
23132 return;
23133 }
23134
23135 if !self.registered_buffers.contains_key(&buffer_id)
23136 && let Some(project) = self.project.as_ref()
23137 {
23138 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23139 project.update(cx, |project, cx| {
23140 self.registered_buffers.insert(
23141 buffer_id,
23142 project.register_buffer_with_language_servers(&buffer, cx),
23143 );
23144 });
23145 } else {
23146 self.registered_buffers.remove(&buffer_id);
23147 }
23148 }
23149 }
23150
23151 fn ignore_lsp_data(&self) -> bool {
23152 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23153 // skip any LSP updates for it.
23154 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23155 }
23156
23157 fn create_style(&self, cx: &App) -> EditorStyle {
23158 let settings = ThemeSettings::get_global(cx);
23159
23160 let mut text_style = match self.mode {
23161 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23162 color: cx.theme().colors().editor_foreground,
23163 font_family: settings.ui_font.family.clone(),
23164 font_features: settings.ui_font.features.clone(),
23165 font_fallbacks: settings.ui_font.fallbacks.clone(),
23166 font_size: rems(0.875).into(),
23167 font_weight: settings.ui_font.weight,
23168 line_height: relative(settings.buffer_line_height.value()),
23169 ..Default::default()
23170 },
23171 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23172 color: cx.theme().colors().editor_foreground,
23173 font_family: settings.buffer_font.family.clone(),
23174 font_features: settings.buffer_font.features.clone(),
23175 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23176 font_size: settings.buffer_font_size(cx).into(),
23177 font_weight: settings.buffer_font.weight,
23178 line_height: relative(settings.buffer_line_height.value()),
23179 ..Default::default()
23180 },
23181 };
23182 if let Some(text_style_refinement) = &self.text_style_refinement {
23183 text_style.refine(text_style_refinement)
23184 }
23185
23186 let background = match self.mode {
23187 EditorMode::SingleLine => cx.theme().system().transparent,
23188 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23189 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23190 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23191 };
23192
23193 EditorStyle {
23194 background,
23195 border: cx.theme().colors().border,
23196 local_player: cx.theme().players().local(),
23197 text: text_style,
23198 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23199 syntax: cx.theme().syntax().clone(),
23200 status: cx.theme().status().clone(),
23201 inlay_hints_style: make_inlay_hints_style(cx),
23202 edit_prediction_styles: make_suggestion_styles(cx),
23203 unnecessary_code_fade: settings.unnecessary_code_fade,
23204 show_underlines: self.diagnostics_enabled(),
23205 }
23206 }
23207}
23208
23209fn edit_for_markdown_paste<'a>(
23210 buffer: &MultiBufferSnapshot,
23211 range: Range<MultiBufferOffset>,
23212 to_insert: &'a str,
23213 url: Option<url::Url>,
23214) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23215 if url.is_none() {
23216 return (range, Cow::Borrowed(to_insert));
23217 };
23218
23219 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23220
23221 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23222 Cow::Borrowed(to_insert)
23223 } else {
23224 Cow::Owned(format!("[{old_text}]({to_insert})"))
23225 };
23226 (range, new_text)
23227}
23228
23229fn process_completion_for_edit(
23230 completion: &Completion,
23231 intent: CompletionIntent,
23232 buffer: &Entity<Buffer>,
23233 cursor_position: &text::Anchor,
23234 cx: &mut Context<Editor>,
23235) -> CompletionEdit {
23236 let buffer = buffer.read(cx);
23237 let buffer_snapshot = buffer.snapshot();
23238 let (snippet, new_text) = if completion.is_snippet() {
23239 let mut snippet_source = completion.new_text.clone();
23240 // Workaround for typescript language server issues so that methods don't expand within
23241 // strings and functions with type expressions. The previous point is used because the query
23242 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23243 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23244 let previous_point = if previous_point.column > 0 {
23245 cursor_position.to_previous_offset(&buffer_snapshot)
23246 } else {
23247 cursor_position.to_offset(&buffer_snapshot)
23248 };
23249 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23250 && scope.prefers_label_for_snippet_in_completion()
23251 && let Some(label) = completion.label()
23252 && matches!(
23253 completion.kind(),
23254 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23255 )
23256 {
23257 snippet_source = label;
23258 }
23259 match Snippet::parse(&snippet_source).log_err() {
23260 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23261 None => (None, completion.new_text.clone()),
23262 }
23263 } else {
23264 (None, completion.new_text.clone())
23265 };
23266
23267 let mut range_to_replace = {
23268 let replace_range = &completion.replace_range;
23269 if let CompletionSource::Lsp {
23270 insert_range: Some(insert_range),
23271 ..
23272 } = &completion.source
23273 {
23274 debug_assert_eq!(
23275 insert_range.start, replace_range.start,
23276 "insert_range and replace_range should start at the same position"
23277 );
23278 debug_assert!(
23279 insert_range
23280 .start
23281 .cmp(cursor_position, &buffer_snapshot)
23282 .is_le(),
23283 "insert_range should start before or at cursor position"
23284 );
23285 debug_assert!(
23286 replace_range
23287 .start
23288 .cmp(cursor_position, &buffer_snapshot)
23289 .is_le(),
23290 "replace_range should start before or at cursor position"
23291 );
23292
23293 let should_replace = match intent {
23294 CompletionIntent::CompleteWithInsert => false,
23295 CompletionIntent::CompleteWithReplace => true,
23296 CompletionIntent::Complete | CompletionIntent::Compose => {
23297 let insert_mode =
23298 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23299 .completions
23300 .lsp_insert_mode;
23301 match insert_mode {
23302 LspInsertMode::Insert => false,
23303 LspInsertMode::Replace => true,
23304 LspInsertMode::ReplaceSubsequence => {
23305 let mut text_to_replace = buffer.chars_for_range(
23306 buffer.anchor_before(replace_range.start)
23307 ..buffer.anchor_after(replace_range.end),
23308 );
23309 let mut current_needle = text_to_replace.next();
23310 for haystack_ch in completion.label.text.chars() {
23311 if let Some(needle_ch) = current_needle
23312 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23313 {
23314 current_needle = text_to_replace.next();
23315 }
23316 }
23317 current_needle.is_none()
23318 }
23319 LspInsertMode::ReplaceSuffix => {
23320 if replace_range
23321 .end
23322 .cmp(cursor_position, &buffer_snapshot)
23323 .is_gt()
23324 {
23325 let range_after_cursor = *cursor_position..replace_range.end;
23326 let text_after_cursor = buffer
23327 .text_for_range(
23328 buffer.anchor_before(range_after_cursor.start)
23329 ..buffer.anchor_after(range_after_cursor.end),
23330 )
23331 .collect::<String>()
23332 .to_ascii_lowercase();
23333 completion
23334 .label
23335 .text
23336 .to_ascii_lowercase()
23337 .ends_with(&text_after_cursor)
23338 } else {
23339 true
23340 }
23341 }
23342 }
23343 }
23344 };
23345
23346 if should_replace {
23347 replace_range.clone()
23348 } else {
23349 insert_range.clone()
23350 }
23351 } else {
23352 replace_range.clone()
23353 }
23354 };
23355
23356 if range_to_replace
23357 .end
23358 .cmp(cursor_position, &buffer_snapshot)
23359 .is_lt()
23360 {
23361 range_to_replace.end = *cursor_position;
23362 }
23363
23364 let replace_range = range_to_replace.to_offset(buffer);
23365 CompletionEdit {
23366 new_text,
23367 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23368 snippet,
23369 }
23370}
23371
23372struct CompletionEdit {
23373 new_text: String,
23374 replace_range: Range<BufferOffset>,
23375 snippet: Option<Snippet>,
23376}
23377
23378fn comment_delimiter_for_newline(
23379 start_point: &Point,
23380 buffer: &MultiBufferSnapshot,
23381 language: &LanguageScope,
23382) -> Option<Arc<str>> {
23383 let delimiters = language.line_comment_prefixes();
23384 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
23385 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23386
23387 let num_of_whitespaces = snapshot
23388 .chars_for_range(range.clone())
23389 .take_while(|c| c.is_whitespace())
23390 .count();
23391 let comment_candidate = snapshot
23392 .chars_for_range(range.clone())
23393 .skip(num_of_whitespaces)
23394 .take(max_len_of_delimiter)
23395 .collect::<String>();
23396 let (delimiter, trimmed_len) = delimiters
23397 .iter()
23398 .filter_map(|delimiter| {
23399 let prefix = delimiter.trim_end();
23400 if comment_candidate.starts_with(prefix) {
23401 Some((delimiter, prefix.len()))
23402 } else {
23403 None
23404 }
23405 })
23406 .max_by_key(|(_, len)| *len)?;
23407
23408 if let Some(BlockCommentConfig {
23409 start: block_start, ..
23410 }) = language.block_comment()
23411 {
23412 let block_start_trimmed = block_start.trim_end();
23413 if block_start_trimmed.starts_with(delimiter.trim_end()) {
23414 let line_content = snapshot
23415 .chars_for_range(range)
23416 .skip(num_of_whitespaces)
23417 .take(block_start_trimmed.len())
23418 .collect::<String>();
23419
23420 if line_content.starts_with(block_start_trimmed) {
23421 return None;
23422 }
23423 }
23424 }
23425
23426 let cursor_is_placed_after_comment_marker =
23427 num_of_whitespaces + trimmed_len <= start_point.column as usize;
23428 if cursor_is_placed_after_comment_marker {
23429 Some(delimiter.clone())
23430 } else {
23431 None
23432 }
23433}
23434
23435fn documentation_delimiter_for_newline(
23436 start_point: &Point,
23437 buffer: &MultiBufferSnapshot,
23438 language: &LanguageScope,
23439 newline_config: &mut NewlineConfig,
23440) -> Option<Arc<str>> {
23441 let BlockCommentConfig {
23442 start: start_tag,
23443 end: end_tag,
23444 prefix: delimiter,
23445 tab_size: len,
23446 } = language.documentation_comment()?;
23447 let is_within_block_comment = buffer
23448 .language_scope_at(*start_point)
23449 .is_some_and(|scope| scope.override_name() == Some("comment"));
23450 if !is_within_block_comment {
23451 return None;
23452 }
23453
23454 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23455
23456 let num_of_whitespaces = snapshot
23457 .chars_for_range(range.clone())
23458 .take_while(|c| c.is_whitespace())
23459 .count();
23460
23461 // 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.
23462 let column = start_point.column;
23463 let cursor_is_after_start_tag = {
23464 let start_tag_len = start_tag.len();
23465 let start_tag_line = snapshot
23466 .chars_for_range(range.clone())
23467 .skip(num_of_whitespaces)
23468 .take(start_tag_len)
23469 .collect::<String>();
23470 if start_tag_line.starts_with(start_tag.as_ref()) {
23471 num_of_whitespaces + start_tag_len <= column as usize
23472 } else {
23473 false
23474 }
23475 };
23476
23477 let cursor_is_after_delimiter = {
23478 let delimiter_trim = delimiter.trim_end();
23479 let delimiter_line = snapshot
23480 .chars_for_range(range.clone())
23481 .skip(num_of_whitespaces)
23482 .take(delimiter_trim.len())
23483 .collect::<String>();
23484 if delimiter_line.starts_with(delimiter_trim) {
23485 num_of_whitespaces + delimiter_trim.len() <= column as usize
23486 } else {
23487 false
23488 }
23489 };
23490
23491 let mut needs_extra_line = false;
23492 let mut extra_line_additional_indent = IndentSize::spaces(0);
23493
23494 let cursor_is_before_end_tag_if_exists = {
23495 let mut char_position = 0u32;
23496 let mut end_tag_offset = None;
23497
23498 'outer: for chunk in snapshot.text_for_range(range) {
23499 if let Some(byte_pos) = chunk.find(&**end_tag) {
23500 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
23501 end_tag_offset = Some(char_position + chars_before_match);
23502 break 'outer;
23503 }
23504 char_position += chunk.chars().count() as u32;
23505 }
23506
23507 if let Some(end_tag_offset) = end_tag_offset {
23508 let cursor_is_before_end_tag = column <= end_tag_offset;
23509 if cursor_is_after_start_tag {
23510 if cursor_is_before_end_tag {
23511 needs_extra_line = true;
23512 }
23513 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
23514 if cursor_is_at_start_of_end_tag {
23515 extra_line_additional_indent.len = *len;
23516 }
23517 }
23518 cursor_is_before_end_tag
23519 } else {
23520 true
23521 }
23522 };
23523
23524 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
23525 && cursor_is_before_end_tag_if_exists
23526 {
23527 let additional_indent = if cursor_is_after_start_tag {
23528 IndentSize::spaces(*len)
23529 } else {
23530 IndentSize::spaces(0)
23531 };
23532
23533 *newline_config = NewlineConfig::Newline {
23534 additional_indent,
23535 extra_line_additional_indent: if needs_extra_line {
23536 Some(extra_line_additional_indent)
23537 } else {
23538 None
23539 },
23540 prevent_auto_indent: true,
23541 };
23542 Some(delimiter.clone())
23543 } else {
23544 None
23545 }
23546}
23547
23548const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
23549
23550fn list_delimiter_for_newline(
23551 start_point: &Point,
23552 buffer: &MultiBufferSnapshot,
23553 language: &LanguageScope,
23554 newline_config: &mut NewlineConfig,
23555) -> Option<Arc<str>> {
23556 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23557
23558 let num_of_whitespaces = snapshot
23559 .chars_for_range(range.clone())
23560 .take_while(|c| c.is_whitespace())
23561 .count();
23562
23563 let task_list_entries: Vec<_> = language
23564 .task_list()
23565 .into_iter()
23566 .flat_map(|config| {
23567 config
23568 .prefixes
23569 .iter()
23570 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
23571 })
23572 .collect();
23573 let unordered_list_entries: Vec<_> = language
23574 .unordered_list()
23575 .iter()
23576 .map(|marker| (marker.as_ref(), marker.as_ref()))
23577 .collect();
23578
23579 let all_entries: Vec<_> = task_list_entries
23580 .into_iter()
23581 .chain(unordered_list_entries)
23582 .collect();
23583
23584 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
23585 let candidate: String = snapshot
23586 .chars_for_range(range.clone())
23587 .skip(num_of_whitespaces)
23588 .take(max_prefix_len)
23589 .collect();
23590
23591 if let Some((prefix, continuation)) = all_entries
23592 .iter()
23593 .filter(|(prefix, _)| candidate.starts_with(*prefix))
23594 .max_by_key(|(prefix, _)| prefix.len())
23595 {
23596 let end_of_prefix = num_of_whitespaces + prefix.len();
23597 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
23598 let has_content_after_marker = snapshot
23599 .chars_for_range(range)
23600 .skip(end_of_prefix)
23601 .any(|c| !c.is_whitespace());
23602
23603 if has_content_after_marker && cursor_is_after_prefix {
23604 return Some((*continuation).into());
23605 }
23606
23607 if start_point.column as usize == end_of_prefix {
23608 if num_of_whitespaces == 0 {
23609 *newline_config = NewlineConfig::ClearCurrentLine;
23610 } else {
23611 *newline_config = NewlineConfig::UnindentCurrentLine {
23612 continuation: (*continuation).into(),
23613 };
23614 }
23615 }
23616
23617 return None;
23618 }
23619 }
23620
23621 let candidate: String = snapshot
23622 .chars_for_range(range.clone())
23623 .skip(num_of_whitespaces)
23624 .take(ORDERED_LIST_MAX_MARKER_LEN)
23625 .collect();
23626
23627 for ordered_config in language.ordered_list() {
23628 let regex = match Regex::new(&ordered_config.pattern) {
23629 Ok(r) => r,
23630 Err(_) => continue,
23631 };
23632
23633 if let Some(captures) = regex.captures(&candidate) {
23634 let full_match = captures.get(0)?;
23635 let marker_len = full_match.len();
23636 let end_of_prefix = num_of_whitespaces + marker_len;
23637 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
23638
23639 let has_content_after_marker = snapshot
23640 .chars_for_range(range)
23641 .skip(end_of_prefix)
23642 .any(|c| !c.is_whitespace());
23643
23644 if has_content_after_marker && cursor_is_after_prefix {
23645 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
23646 let continuation = ordered_config
23647 .format
23648 .replace("{1}", &(number + 1).to_string());
23649 return Some(continuation.into());
23650 }
23651
23652 if start_point.column as usize == end_of_prefix {
23653 let continuation = ordered_config.format.replace("{1}", "1");
23654 if num_of_whitespaces == 0 {
23655 *newline_config = NewlineConfig::ClearCurrentLine;
23656 } else {
23657 *newline_config = NewlineConfig::UnindentCurrentLine {
23658 continuation: continuation.into(),
23659 };
23660 }
23661 }
23662
23663 return None;
23664 }
23665 }
23666
23667 None
23668}
23669
23670fn is_list_prefix_row(
23671 row: MultiBufferRow,
23672 buffer: &MultiBufferSnapshot,
23673 language: &LanguageScope,
23674) -> bool {
23675 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
23676 return false;
23677 };
23678
23679 let num_of_whitespaces = snapshot
23680 .chars_for_range(range.clone())
23681 .take_while(|c| c.is_whitespace())
23682 .count();
23683
23684 let task_list_prefixes: Vec<_> = language
23685 .task_list()
23686 .into_iter()
23687 .flat_map(|config| {
23688 config
23689 .prefixes
23690 .iter()
23691 .map(|p| p.as_ref())
23692 .collect::<Vec<_>>()
23693 })
23694 .collect();
23695 let unordered_list_markers: Vec<_> = language
23696 .unordered_list()
23697 .iter()
23698 .map(|marker| marker.as_ref())
23699 .collect();
23700 let all_prefixes: Vec<_> = task_list_prefixes
23701 .into_iter()
23702 .chain(unordered_list_markers)
23703 .collect();
23704 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
23705 let candidate: String = snapshot
23706 .chars_for_range(range.clone())
23707 .skip(num_of_whitespaces)
23708 .take(max_prefix_len)
23709 .collect();
23710 if all_prefixes
23711 .iter()
23712 .any(|prefix| candidate.starts_with(*prefix))
23713 {
23714 return true;
23715 }
23716 }
23717
23718 let ordered_list_candidate: String = snapshot
23719 .chars_for_range(range)
23720 .skip(num_of_whitespaces)
23721 .take(ORDERED_LIST_MAX_MARKER_LEN)
23722 .collect();
23723 for ordered_config in language.ordered_list() {
23724 let regex = match Regex::new(&ordered_config.pattern) {
23725 Ok(r) => r,
23726 Err(_) => continue,
23727 };
23728 if let Some(captures) = regex.captures(&ordered_list_candidate) {
23729 return captures.get(0).is_some();
23730 }
23731 }
23732
23733 false
23734}
23735
23736#[derive(Debug)]
23737enum NewlineConfig {
23738 /// Insert newline with optional additional indent and optional extra blank line
23739 Newline {
23740 additional_indent: IndentSize,
23741 extra_line_additional_indent: Option<IndentSize>,
23742 prevent_auto_indent: bool,
23743 },
23744 /// Clear the current line
23745 ClearCurrentLine,
23746 /// Unindent the current line and add continuation
23747 UnindentCurrentLine { continuation: Arc<str> },
23748}
23749
23750impl NewlineConfig {
23751 fn has_extra_line(&self) -> bool {
23752 matches!(
23753 self,
23754 Self::Newline {
23755 extra_line_additional_indent: Some(_),
23756 ..
23757 }
23758 )
23759 }
23760
23761 fn insert_extra_newline_brackets(
23762 buffer: &MultiBufferSnapshot,
23763 range: Range<MultiBufferOffset>,
23764 language: &language::LanguageScope,
23765 ) -> bool {
23766 let leading_whitespace_len = buffer
23767 .reversed_chars_at(range.start)
23768 .take_while(|c| c.is_whitespace() && *c != '\n')
23769 .map(|c| c.len_utf8())
23770 .sum::<usize>();
23771 let trailing_whitespace_len = buffer
23772 .chars_at(range.end)
23773 .take_while(|c| c.is_whitespace() && *c != '\n')
23774 .map(|c| c.len_utf8())
23775 .sum::<usize>();
23776 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23777
23778 language.brackets().any(|(pair, enabled)| {
23779 let pair_start = pair.start.trim_end();
23780 let pair_end = pair.end.trim_start();
23781
23782 enabled
23783 && pair.newline
23784 && buffer.contains_str_at(range.end, pair_end)
23785 && buffer.contains_str_at(
23786 range.start.saturating_sub_usize(pair_start.len()),
23787 pair_start,
23788 )
23789 })
23790 }
23791
23792 fn insert_extra_newline_tree_sitter(
23793 buffer: &MultiBufferSnapshot,
23794 range: Range<MultiBufferOffset>,
23795 ) -> bool {
23796 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23797 [(buffer, range, _)] => (*buffer, range.clone()),
23798 _ => return false,
23799 };
23800 let pair = {
23801 let mut result: Option<BracketMatch<usize>> = None;
23802
23803 for pair in buffer
23804 .all_bracket_ranges(range.start.0..range.end.0)
23805 .filter(move |pair| {
23806 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23807 })
23808 {
23809 let len = pair.close_range.end - pair.open_range.start;
23810
23811 if let Some(existing) = &result {
23812 let existing_len = existing.close_range.end - existing.open_range.start;
23813 if len > existing_len {
23814 continue;
23815 }
23816 }
23817
23818 result = Some(pair);
23819 }
23820
23821 result
23822 };
23823 let Some(pair) = pair else {
23824 return false;
23825 };
23826 pair.newline_only
23827 && buffer
23828 .chars_for_range(pair.open_range.end..range.start.0)
23829 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23830 .all(|c| c.is_whitespace() && c != '\n')
23831 }
23832}
23833
23834fn update_uncommitted_diff_for_buffer(
23835 editor: Entity<Editor>,
23836 project: &Entity<Project>,
23837 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23838 buffer: Entity<MultiBuffer>,
23839 cx: &mut App,
23840) -> Task<()> {
23841 let mut tasks = Vec::new();
23842 project.update(cx, |project, cx| {
23843 for buffer in buffers {
23844 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23845 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23846 }
23847 }
23848 });
23849 cx.spawn(async move |cx| {
23850 let diffs = future::join_all(tasks).await;
23851 if editor
23852 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23853 .unwrap_or(false)
23854 {
23855 return;
23856 }
23857
23858 buffer
23859 .update(cx, |buffer, cx| {
23860 for diff in diffs.into_iter().flatten() {
23861 buffer.add_diff(diff, cx);
23862 }
23863 })
23864 .ok();
23865 })
23866}
23867
23868fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23869 let tab_size = tab_size.get() as usize;
23870 let mut width = offset;
23871
23872 for ch in text.chars() {
23873 width += if ch == '\t' {
23874 tab_size - (width % tab_size)
23875 } else {
23876 1
23877 };
23878 }
23879
23880 width - offset
23881}
23882
23883#[cfg(test)]
23884mod tests {
23885 use super::*;
23886
23887 #[test]
23888 fn test_string_size_with_expanded_tabs() {
23889 let nz = |val| NonZeroU32::new(val).unwrap();
23890 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23891 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23892 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23893 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23894 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23895 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23896 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23897 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23898 }
23899}
23900
23901/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23902struct WordBreakingTokenizer<'a> {
23903 input: &'a str,
23904}
23905
23906impl<'a> WordBreakingTokenizer<'a> {
23907 fn new(input: &'a str) -> Self {
23908 Self { input }
23909 }
23910}
23911
23912fn is_char_ideographic(ch: char) -> bool {
23913 use unicode_script::Script::*;
23914 use unicode_script::UnicodeScript;
23915 matches!(ch.script(), Han | Tangut | Yi)
23916}
23917
23918fn is_grapheme_ideographic(text: &str) -> bool {
23919 text.chars().any(is_char_ideographic)
23920}
23921
23922fn is_grapheme_whitespace(text: &str) -> bool {
23923 text.chars().any(|x| x.is_whitespace())
23924}
23925
23926fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23927 text.chars()
23928 .next()
23929 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23930}
23931
23932#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23933enum WordBreakToken<'a> {
23934 Word { token: &'a str, grapheme_len: usize },
23935 InlineWhitespace { token: &'a str, grapheme_len: usize },
23936 Newline,
23937}
23938
23939impl<'a> Iterator for WordBreakingTokenizer<'a> {
23940 /// Yields a span, the count of graphemes in the token, and whether it was
23941 /// whitespace. Note that it also breaks at word boundaries.
23942 type Item = WordBreakToken<'a>;
23943
23944 fn next(&mut self) -> Option<Self::Item> {
23945 use unicode_segmentation::UnicodeSegmentation;
23946 if self.input.is_empty() {
23947 return None;
23948 }
23949
23950 let mut iter = self.input.graphemes(true).peekable();
23951 let mut offset = 0;
23952 let mut grapheme_len = 0;
23953 if let Some(first_grapheme) = iter.next() {
23954 let is_newline = first_grapheme == "\n";
23955 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23956 offset += first_grapheme.len();
23957 grapheme_len += 1;
23958 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23959 if let Some(grapheme) = iter.peek().copied()
23960 && should_stay_with_preceding_ideograph(grapheme)
23961 {
23962 offset += grapheme.len();
23963 grapheme_len += 1;
23964 }
23965 } else {
23966 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23967 let mut next_word_bound = words.peek().copied();
23968 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23969 next_word_bound = words.next();
23970 }
23971 while let Some(grapheme) = iter.peek().copied() {
23972 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23973 break;
23974 };
23975 if is_grapheme_whitespace(grapheme) != is_whitespace
23976 || (grapheme == "\n") != is_newline
23977 {
23978 break;
23979 };
23980 offset += grapheme.len();
23981 grapheme_len += 1;
23982 iter.next();
23983 }
23984 }
23985 let token = &self.input[..offset];
23986 self.input = &self.input[offset..];
23987 if token == "\n" {
23988 Some(WordBreakToken::Newline)
23989 } else if is_whitespace {
23990 Some(WordBreakToken::InlineWhitespace {
23991 token,
23992 grapheme_len,
23993 })
23994 } else {
23995 Some(WordBreakToken::Word {
23996 token,
23997 grapheme_len,
23998 })
23999 }
24000 } else {
24001 None
24002 }
24003 }
24004}
24005
24006#[test]
24007fn test_word_breaking_tokenizer() {
24008 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
24009 ("", &[]),
24010 (" ", &[whitespace(" ", 2)]),
24011 ("Ʒ", &[word("Ʒ", 1)]),
24012 ("Ǽ", &[word("Ǽ", 1)]),
24013 ("⋑", &[word("⋑", 1)]),
24014 ("⋑⋑", &[word("⋑⋑", 2)]),
24015 (
24016 "原理,进而",
24017 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
24018 ),
24019 (
24020 "hello world",
24021 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
24022 ),
24023 (
24024 "hello, world",
24025 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
24026 ),
24027 (
24028 " hello world",
24029 &[
24030 whitespace(" ", 2),
24031 word("hello", 5),
24032 whitespace(" ", 1),
24033 word("world", 5),
24034 ],
24035 ),
24036 (
24037 "这是什么 \n 钢笔",
24038 &[
24039 word("这", 1),
24040 word("是", 1),
24041 word("什", 1),
24042 word("么", 1),
24043 whitespace(" ", 1),
24044 newline(),
24045 whitespace(" ", 1),
24046 word("钢", 1),
24047 word("笔", 1),
24048 ],
24049 ),
24050 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
24051 ];
24052
24053 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24054 WordBreakToken::Word {
24055 token,
24056 grapheme_len,
24057 }
24058 }
24059
24060 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24061 WordBreakToken::InlineWhitespace {
24062 token,
24063 grapheme_len,
24064 }
24065 }
24066
24067 fn newline() -> WordBreakToken<'static> {
24068 WordBreakToken::Newline
24069 }
24070
24071 for (input, result) in tests {
24072 assert_eq!(
24073 WordBreakingTokenizer::new(input)
24074 .collect::<Vec<_>>()
24075 .as_slice(),
24076 *result,
24077 );
24078 }
24079}
24080
24081fn wrap_with_prefix(
24082 first_line_prefix: String,
24083 subsequent_lines_prefix: String,
24084 unwrapped_text: String,
24085 wrap_column: usize,
24086 tab_size: NonZeroU32,
24087 preserve_existing_whitespace: bool,
24088) -> String {
24089 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
24090 let subsequent_lines_prefix_len =
24091 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
24092 let mut wrapped_text = String::new();
24093 let mut current_line = first_line_prefix;
24094 let mut is_first_line = true;
24095
24096 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
24097 let mut current_line_len = first_line_prefix_len;
24098 let mut in_whitespace = false;
24099 for token in tokenizer {
24100 let have_preceding_whitespace = in_whitespace;
24101 match token {
24102 WordBreakToken::Word {
24103 token,
24104 grapheme_len,
24105 } => {
24106 in_whitespace = false;
24107 let current_prefix_len = if is_first_line {
24108 first_line_prefix_len
24109 } else {
24110 subsequent_lines_prefix_len
24111 };
24112 if current_line_len + grapheme_len > wrap_column
24113 && current_line_len != current_prefix_len
24114 {
24115 wrapped_text.push_str(current_line.trim_end());
24116 wrapped_text.push('\n');
24117 is_first_line = false;
24118 current_line = subsequent_lines_prefix.clone();
24119 current_line_len = subsequent_lines_prefix_len;
24120 }
24121 current_line.push_str(token);
24122 current_line_len += grapheme_len;
24123 }
24124 WordBreakToken::InlineWhitespace {
24125 mut token,
24126 mut grapheme_len,
24127 } => {
24128 in_whitespace = true;
24129 if have_preceding_whitespace && !preserve_existing_whitespace {
24130 continue;
24131 }
24132 if !preserve_existing_whitespace {
24133 // Keep a single whitespace grapheme as-is
24134 if let Some(first) =
24135 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
24136 {
24137 token = first;
24138 } else {
24139 token = " ";
24140 }
24141 grapheme_len = 1;
24142 }
24143 let current_prefix_len = if is_first_line {
24144 first_line_prefix_len
24145 } else {
24146 subsequent_lines_prefix_len
24147 };
24148 if current_line_len + grapheme_len > wrap_column {
24149 wrapped_text.push_str(current_line.trim_end());
24150 wrapped_text.push('\n');
24151 is_first_line = false;
24152 current_line = subsequent_lines_prefix.clone();
24153 current_line_len = subsequent_lines_prefix_len;
24154 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
24155 current_line.push_str(token);
24156 current_line_len += grapheme_len;
24157 }
24158 }
24159 WordBreakToken::Newline => {
24160 in_whitespace = true;
24161 let current_prefix_len = if is_first_line {
24162 first_line_prefix_len
24163 } else {
24164 subsequent_lines_prefix_len
24165 };
24166 if preserve_existing_whitespace {
24167 wrapped_text.push_str(current_line.trim_end());
24168 wrapped_text.push('\n');
24169 is_first_line = false;
24170 current_line = subsequent_lines_prefix.clone();
24171 current_line_len = subsequent_lines_prefix_len;
24172 } else if have_preceding_whitespace {
24173 continue;
24174 } else if current_line_len + 1 > wrap_column
24175 && current_line_len != current_prefix_len
24176 {
24177 wrapped_text.push_str(current_line.trim_end());
24178 wrapped_text.push('\n');
24179 is_first_line = false;
24180 current_line = subsequent_lines_prefix.clone();
24181 current_line_len = subsequent_lines_prefix_len;
24182 } else if current_line_len != current_prefix_len {
24183 current_line.push(' ');
24184 current_line_len += 1;
24185 }
24186 }
24187 }
24188 }
24189
24190 if !current_line.is_empty() {
24191 wrapped_text.push_str(¤t_line);
24192 }
24193 wrapped_text
24194}
24195
24196#[test]
24197fn test_wrap_with_prefix() {
24198 assert_eq!(
24199 wrap_with_prefix(
24200 "# ".to_string(),
24201 "# ".to_string(),
24202 "abcdefg".to_string(),
24203 4,
24204 NonZeroU32::new(4).unwrap(),
24205 false,
24206 ),
24207 "# abcdefg"
24208 );
24209 assert_eq!(
24210 wrap_with_prefix(
24211 "".to_string(),
24212 "".to_string(),
24213 "\thello world".to_string(),
24214 8,
24215 NonZeroU32::new(4).unwrap(),
24216 false,
24217 ),
24218 "hello\nworld"
24219 );
24220 assert_eq!(
24221 wrap_with_prefix(
24222 "// ".to_string(),
24223 "// ".to_string(),
24224 "xx \nyy zz aa bb cc".to_string(),
24225 12,
24226 NonZeroU32::new(4).unwrap(),
24227 false,
24228 ),
24229 "// xx yy zz\n// aa bb cc"
24230 );
24231 assert_eq!(
24232 wrap_with_prefix(
24233 String::new(),
24234 String::new(),
24235 "这是什么 \n 钢笔".to_string(),
24236 3,
24237 NonZeroU32::new(4).unwrap(),
24238 false,
24239 ),
24240 "这是什\n么 钢\n笔"
24241 );
24242 assert_eq!(
24243 wrap_with_prefix(
24244 String::new(),
24245 String::new(),
24246 format!("foo{}bar", '\u{2009}'), // thin space
24247 80,
24248 NonZeroU32::new(4).unwrap(),
24249 false,
24250 ),
24251 format!("foo{}bar", '\u{2009}')
24252 );
24253}
24254
24255pub trait CollaborationHub {
24256 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
24257 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
24258 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
24259}
24260
24261impl CollaborationHub for Entity<Project> {
24262 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
24263 self.read(cx).collaborators()
24264 }
24265
24266 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
24267 self.read(cx).user_store().read(cx).participant_indices()
24268 }
24269
24270 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
24271 let this = self.read(cx);
24272 let user_ids = this.collaborators().values().map(|c| c.user_id);
24273 this.user_store().read(cx).participant_names(user_ids, cx)
24274 }
24275}
24276
24277pub trait SemanticsProvider {
24278 fn hover(
24279 &self,
24280 buffer: &Entity<Buffer>,
24281 position: text::Anchor,
24282 cx: &mut App,
24283 ) -> Option<Task<Option<Vec<project::Hover>>>>;
24284
24285 fn inline_values(
24286 &self,
24287 buffer_handle: Entity<Buffer>,
24288 range: Range<text::Anchor>,
24289 cx: &mut App,
24290 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24291
24292 fn applicable_inlay_chunks(
24293 &self,
24294 buffer: &Entity<Buffer>,
24295 ranges: &[Range<text::Anchor>],
24296 cx: &mut App,
24297 ) -> Vec<Range<BufferRow>>;
24298
24299 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24300
24301 fn inlay_hints(
24302 &self,
24303 invalidate: InvalidationStrategy,
24304 buffer: Entity<Buffer>,
24305 ranges: Vec<Range<text::Anchor>>,
24306 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24307 cx: &mut App,
24308 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24309
24310 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24311
24312 fn document_highlights(
24313 &self,
24314 buffer: &Entity<Buffer>,
24315 position: text::Anchor,
24316 cx: &mut App,
24317 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24318
24319 fn definitions(
24320 &self,
24321 buffer: &Entity<Buffer>,
24322 position: text::Anchor,
24323 kind: GotoDefinitionKind,
24324 cx: &mut App,
24325 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24326
24327 fn range_for_rename(
24328 &self,
24329 buffer: &Entity<Buffer>,
24330 position: text::Anchor,
24331 cx: &mut App,
24332 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24333
24334 fn perform_rename(
24335 &self,
24336 buffer: &Entity<Buffer>,
24337 position: text::Anchor,
24338 new_name: String,
24339 cx: &mut App,
24340 ) -> Option<Task<Result<ProjectTransaction>>>;
24341}
24342
24343pub trait CompletionProvider {
24344 fn completions(
24345 &self,
24346 excerpt_id: ExcerptId,
24347 buffer: &Entity<Buffer>,
24348 buffer_position: text::Anchor,
24349 trigger: CompletionContext,
24350 window: &mut Window,
24351 cx: &mut Context<Editor>,
24352 ) -> Task<Result<Vec<CompletionResponse>>>;
24353
24354 fn resolve_completions(
24355 &self,
24356 _buffer: Entity<Buffer>,
24357 _completion_indices: Vec<usize>,
24358 _completions: Rc<RefCell<Box<[Completion]>>>,
24359 _cx: &mut Context<Editor>,
24360 ) -> Task<Result<bool>> {
24361 Task::ready(Ok(false))
24362 }
24363
24364 fn apply_additional_edits_for_completion(
24365 &self,
24366 _buffer: Entity<Buffer>,
24367 _completions: Rc<RefCell<Box<[Completion]>>>,
24368 _completion_index: usize,
24369 _push_to_history: bool,
24370 _cx: &mut Context<Editor>,
24371 ) -> Task<Result<Option<language::Transaction>>> {
24372 Task::ready(Ok(None))
24373 }
24374
24375 fn is_completion_trigger(
24376 &self,
24377 buffer: &Entity<Buffer>,
24378 position: language::Anchor,
24379 text: &str,
24380 trigger_in_words: bool,
24381 cx: &mut Context<Editor>,
24382 ) -> bool;
24383
24384 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24385
24386 fn sort_completions(&self) -> bool {
24387 true
24388 }
24389
24390 fn filter_completions(&self) -> bool {
24391 true
24392 }
24393
24394 fn show_snippets(&self) -> bool {
24395 false
24396 }
24397}
24398
24399pub trait CodeActionProvider {
24400 fn id(&self) -> Arc<str>;
24401
24402 fn code_actions(
24403 &self,
24404 buffer: &Entity<Buffer>,
24405 range: Range<text::Anchor>,
24406 window: &mut Window,
24407 cx: &mut App,
24408 ) -> Task<Result<Vec<CodeAction>>>;
24409
24410 fn apply_code_action(
24411 &self,
24412 buffer_handle: Entity<Buffer>,
24413 action: CodeAction,
24414 excerpt_id: ExcerptId,
24415 push_to_history: bool,
24416 window: &mut Window,
24417 cx: &mut App,
24418 ) -> Task<Result<ProjectTransaction>>;
24419}
24420
24421impl CodeActionProvider for Entity<Project> {
24422 fn id(&self) -> Arc<str> {
24423 "project".into()
24424 }
24425
24426 fn code_actions(
24427 &self,
24428 buffer: &Entity<Buffer>,
24429 range: Range<text::Anchor>,
24430 _window: &mut Window,
24431 cx: &mut App,
24432 ) -> Task<Result<Vec<CodeAction>>> {
24433 self.update(cx, |project, cx| {
24434 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24435 let code_actions = project.code_actions(buffer, range, None, cx);
24436 cx.background_spawn(async move {
24437 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24438 Ok(code_lens_actions
24439 .context("code lens fetch")?
24440 .into_iter()
24441 .flatten()
24442 .chain(
24443 code_actions
24444 .context("code action fetch")?
24445 .into_iter()
24446 .flatten(),
24447 )
24448 .collect())
24449 })
24450 })
24451 }
24452
24453 fn apply_code_action(
24454 &self,
24455 buffer_handle: Entity<Buffer>,
24456 action: CodeAction,
24457 _excerpt_id: ExcerptId,
24458 push_to_history: bool,
24459 _window: &mut Window,
24460 cx: &mut App,
24461 ) -> Task<Result<ProjectTransaction>> {
24462 self.update(cx, |project, cx| {
24463 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24464 })
24465 }
24466}
24467
24468fn snippet_completions(
24469 project: &Project,
24470 buffer: &Entity<Buffer>,
24471 buffer_anchor: text::Anchor,
24472 classifier: CharClassifier,
24473 cx: &mut App,
24474) -> Task<Result<CompletionResponse>> {
24475 let languages = buffer.read(cx).languages_at(buffer_anchor);
24476 let snippet_store = project.snippets().read(cx);
24477
24478 let scopes: Vec<_> = languages
24479 .iter()
24480 .filter_map(|language| {
24481 let language_name = language.lsp_id();
24482 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24483
24484 if snippets.is_empty() {
24485 None
24486 } else {
24487 Some((language.default_scope(), snippets))
24488 }
24489 })
24490 .collect();
24491
24492 if scopes.is_empty() {
24493 return Task::ready(Ok(CompletionResponse {
24494 completions: vec![],
24495 display_options: CompletionDisplayOptions::default(),
24496 is_incomplete: false,
24497 }));
24498 }
24499
24500 let snapshot = buffer.read(cx).text_snapshot();
24501 let executor = cx.background_executor().clone();
24502
24503 cx.background_spawn(async move {
24504 let is_word_char = |c| classifier.is_word(c);
24505
24506 let mut is_incomplete = false;
24507 let mut completions: Vec<Completion> = Vec::new();
24508
24509 const MAX_PREFIX_LEN: usize = 128;
24510 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24511 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24512 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24513
24514 let max_buffer_window: String = snapshot
24515 .text_for_range(window_start..buffer_offset)
24516 .collect();
24517
24518 if max_buffer_window.is_empty() {
24519 return Ok(CompletionResponse {
24520 completions: vec![],
24521 display_options: CompletionDisplayOptions::default(),
24522 is_incomplete: true,
24523 });
24524 }
24525
24526 for (_scope, snippets) in scopes.into_iter() {
24527 // Sort snippets by word count to match longer snippet prefixes first.
24528 let mut sorted_snippet_candidates = snippets
24529 .iter()
24530 .enumerate()
24531 .flat_map(|(snippet_ix, snippet)| {
24532 snippet
24533 .prefix
24534 .iter()
24535 .enumerate()
24536 .map(move |(prefix_ix, prefix)| {
24537 let word_count =
24538 snippet_candidate_suffixes(prefix, is_word_char).count();
24539 ((snippet_ix, prefix_ix), prefix, word_count)
24540 })
24541 })
24542 .collect_vec();
24543 sorted_snippet_candidates
24544 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24545
24546 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24547
24548 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24549 .take(
24550 sorted_snippet_candidates
24551 .first()
24552 .map(|(_, _, word_count)| *word_count)
24553 .unwrap_or_default(),
24554 )
24555 .collect_vec();
24556
24557 const MAX_RESULTS: usize = 100;
24558 // Each match also remembers how many characters from the buffer it consumed
24559 let mut matches: Vec<(StringMatch, usize)> = vec![];
24560
24561 let mut snippet_list_cutoff_index = 0;
24562 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24563 let word_count = buffer_index + 1;
24564 // Increase `snippet_list_cutoff_index` until we have all of the
24565 // snippets with sufficiently many words.
24566 while sorted_snippet_candidates
24567 .get(snippet_list_cutoff_index)
24568 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24569 *snippet_word_count >= word_count
24570 })
24571 {
24572 snippet_list_cutoff_index += 1;
24573 }
24574
24575 // Take only the candidates with at least `word_count` many words
24576 let snippet_candidates_at_word_len =
24577 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24578
24579 let candidates = snippet_candidates_at_word_len
24580 .iter()
24581 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24582 .enumerate() // index in `sorted_snippet_candidates`
24583 // First char must match
24584 .filter(|(_ix, prefix)| {
24585 itertools::equal(
24586 prefix
24587 .chars()
24588 .next()
24589 .into_iter()
24590 .flat_map(|c| c.to_lowercase()),
24591 buffer_window
24592 .chars()
24593 .next()
24594 .into_iter()
24595 .flat_map(|c| c.to_lowercase()),
24596 )
24597 })
24598 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24599 .collect::<Vec<StringMatchCandidate>>();
24600
24601 matches.extend(
24602 fuzzy::match_strings(
24603 &candidates,
24604 &buffer_window,
24605 buffer_window.chars().any(|c| c.is_uppercase()),
24606 true,
24607 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24608 &Default::default(),
24609 executor.clone(),
24610 )
24611 .await
24612 .into_iter()
24613 .map(|string_match| (string_match, buffer_window.len())),
24614 );
24615
24616 if matches.len() >= MAX_RESULTS {
24617 break;
24618 }
24619 }
24620
24621 let to_lsp = |point: &text::Anchor| {
24622 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24623 point_to_lsp(end)
24624 };
24625 let lsp_end = to_lsp(&buffer_anchor);
24626
24627 if matches.len() >= MAX_RESULTS {
24628 is_incomplete = true;
24629 }
24630
24631 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24632 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24633 sorted_snippet_candidates[string_match.candidate_id];
24634 let snippet = &snippets[snippet_index];
24635 let start = buffer_offset - buffer_window_len;
24636 let start = snapshot.anchor_before(start);
24637 let range = start..buffer_anchor;
24638 let lsp_start = to_lsp(&start);
24639 let lsp_range = lsp::Range {
24640 start: lsp_start,
24641 end: lsp_end,
24642 };
24643 Completion {
24644 replace_range: range,
24645 new_text: snippet.body.clone(),
24646 source: CompletionSource::Lsp {
24647 insert_range: None,
24648 server_id: LanguageServerId(usize::MAX),
24649 resolved: true,
24650 lsp_completion: Box::new(lsp::CompletionItem {
24651 label: snippet.prefix.first().unwrap().clone(),
24652 kind: Some(CompletionItemKind::SNIPPET),
24653 label_details: snippet.description.as_ref().map(|description| {
24654 lsp::CompletionItemLabelDetails {
24655 detail: Some(description.clone()),
24656 description: None,
24657 }
24658 }),
24659 insert_text_format: Some(InsertTextFormat::SNIPPET),
24660 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24661 lsp::InsertReplaceEdit {
24662 new_text: snippet.body.clone(),
24663 insert: lsp_range,
24664 replace: lsp_range,
24665 },
24666 )),
24667 filter_text: Some(snippet.body.clone()),
24668 sort_text: Some(char::MAX.to_string()),
24669 ..lsp::CompletionItem::default()
24670 }),
24671 lsp_defaults: None,
24672 },
24673 label: CodeLabel {
24674 text: matching_prefix.clone(),
24675 runs: Vec::new(),
24676 filter_range: 0..matching_prefix.len(),
24677 },
24678 icon_path: None,
24679 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24680 single_line: snippet.name.clone().into(),
24681 plain_text: snippet
24682 .description
24683 .clone()
24684 .map(|description| description.into()),
24685 }),
24686 insert_text_mode: None,
24687 confirm: None,
24688 match_start: Some(start),
24689 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24690 }
24691 }));
24692 }
24693
24694 Ok(CompletionResponse {
24695 completions,
24696 display_options: CompletionDisplayOptions::default(),
24697 is_incomplete,
24698 })
24699 })
24700}
24701
24702impl CompletionProvider for Entity<Project> {
24703 fn completions(
24704 &self,
24705 _excerpt_id: ExcerptId,
24706 buffer: &Entity<Buffer>,
24707 buffer_position: text::Anchor,
24708 options: CompletionContext,
24709 _window: &mut Window,
24710 cx: &mut Context<Editor>,
24711 ) -> Task<Result<Vec<CompletionResponse>>> {
24712 self.update(cx, |project, cx| {
24713 let task = project.completions(buffer, buffer_position, options, cx);
24714 cx.background_spawn(task)
24715 })
24716 }
24717
24718 fn resolve_completions(
24719 &self,
24720 buffer: Entity<Buffer>,
24721 completion_indices: Vec<usize>,
24722 completions: Rc<RefCell<Box<[Completion]>>>,
24723 cx: &mut Context<Editor>,
24724 ) -> Task<Result<bool>> {
24725 self.update(cx, |project, cx| {
24726 project.lsp_store().update(cx, |lsp_store, cx| {
24727 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24728 })
24729 })
24730 }
24731
24732 fn apply_additional_edits_for_completion(
24733 &self,
24734 buffer: Entity<Buffer>,
24735 completions: Rc<RefCell<Box<[Completion]>>>,
24736 completion_index: usize,
24737 push_to_history: bool,
24738 cx: &mut Context<Editor>,
24739 ) -> Task<Result<Option<language::Transaction>>> {
24740 self.update(cx, |project, cx| {
24741 project.lsp_store().update(cx, |lsp_store, cx| {
24742 lsp_store.apply_additional_edits_for_completion(
24743 buffer,
24744 completions,
24745 completion_index,
24746 push_to_history,
24747 cx,
24748 )
24749 })
24750 })
24751 }
24752
24753 fn is_completion_trigger(
24754 &self,
24755 buffer: &Entity<Buffer>,
24756 position: language::Anchor,
24757 text: &str,
24758 trigger_in_words: bool,
24759 cx: &mut Context<Editor>,
24760 ) -> bool {
24761 let mut chars = text.chars();
24762 let char = if let Some(char) = chars.next() {
24763 char
24764 } else {
24765 return false;
24766 };
24767 if chars.next().is_some() {
24768 return false;
24769 }
24770
24771 let buffer = buffer.read(cx);
24772 let snapshot = buffer.snapshot();
24773 let classifier = snapshot
24774 .char_classifier_at(position)
24775 .scope_context(Some(CharScopeContext::Completion));
24776 if trigger_in_words && classifier.is_word(char) {
24777 return true;
24778 }
24779
24780 buffer.completion_triggers().contains(text)
24781 }
24782
24783 fn show_snippets(&self) -> bool {
24784 true
24785 }
24786}
24787
24788impl SemanticsProvider for Entity<Project> {
24789 fn hover(
24790 &self,
24791 buffer: &Entity<Buffer>,
24792 position: text::Anchor,
24793 cx: &mut App,
24794 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24795 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24796 }
24797
24798 fn document_highlights(
24799 &self,
24800 buffer: &Entity<Buffer>,
24801 position: text::Anchor,
24802 cx: &mut App,
24803 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24804 Some(self.update(cx, |project, cx| {
24805 project.document_highlights(buffer, position, cx)
24806 }))
24807 }
24808
24809 fn definitions(
24810 &self,
24811 buffer: &Entity<Buffer>,
24812 position: text::Anchor,
24813 kind: GotoDefinitionKind,
24814 cx: &mut App,
24815 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24816 Some(self.update(cx, |project, cx| match kind {
24817 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24818 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24819 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24820 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24821 }))
24822 }
24823
24824 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24825 self.update(cx, |project, cx| {
24826 if project
24827 .active_debug_session(cx)
24828 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24829 {
24830 return true;
24831 }
24832
24833 buffer.update(cx, |buffer, cx| {
24834 project.any_language_server_supports_inlay_hints(buffer, cx)
24835 })
24836 })
24837 }
24838
24839 fn inline_values(
24840 &self,
24841 buffer_handle: Entity<Buffer>,
24842 range: Range<text::Anchor>,
24843 cx: &mut App,
24844 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24845 self.update(cx, |project, cx| {
24846 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24847
24848 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24849 })
24850 }
24851
24852 fn applicable_inlay_chunks(
24853 &self,
24854 buffer: &Entity<Buffer>,
24855 ranges: &[Range<text::Anchor>],
24856 cx: &mut App,
24857 ) -> Vec<Range<BufferRow>> {
24858 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24859 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24860 })
24861 }
24862
24863 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24864 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24865 lsp_store.invalidate_inlay_hints(for_buffers)
24866 });
24867 }
24868
24869 fn inlay_hints(
24870 &self,
24871 invalidate: InvalidationStrategy,
24872 buffer: Entity<Buffer>,
24873 ranges: Vec<Range<text::Anchor>>,
24874 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24875 cx: &mut App,
24876 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24877 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24878 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24879 }))
24880 }
24881
24882 fn range_for_rename(
24883 &self,
24884 buffer: &Entity<Buffer>,
24885 position: text::Anchor,
24886 cx: &mut App,
24887 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24888 Some(self.update(cx, |project, cx| {
24889 let buffer = buffer.clone();
24890 let task = project.prepare_rename(buffer.clone(), position, cx);
24891 cx.spawn(async move |_, cx| {
24892 Ok(match task.await? {
24893 PrepareRenameResponse::Success(range) => Some(range),
24894 PrepareRenameResponse::InvalidPosition => None,
24895 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24896 // Fallback on using TreeSitter info to determine identifier range
24897 buffer.read_with(cx, |buffer, _| {
24898 let snapshot = buffer.snapshot();
24899 let (range, kind) = snapshot.surrounding_word(position, None);
24900 if kind != Some(CharKind::Word) {
24901 return None;
24902 }
24903 Some(
24904 snapshot.anchor_before(range.start)
24905 ..snapshot.anchor_after(range.end),
24906 )
24907 })?
24908 }
24909 })
24910 })
24911 }))
24912 }
24913
24914 fn perform_rename(
24915 &self,
24916 buffer: &Entity<Buffer>,
24917 position: text::Anchor,
24918 new_name: String,
24919 cx: &mut App,
24920 ) -> Option<Task<Result<ProjectTransaction>>> {
24921 Some(self.update(cx, |project, cx| {
24922 project.perform_rename(buffer.clone(), position, new_name, cx)
24923 }))
24924 }
24925}
24926
24927fn consume_contiguous_rows(
24928 contiguous_row_selections: &mut Vec<Selection<Point>>,
24929 selection: &Selection<Point>,
24930 display_map: &DisplaySnapshot,
24931 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24932) -> (MultiBufferRow, MultiBufferRow) {
24933 contiguous_row_selections.push(selection.clone());
24934 let start_row = starting_row(selection, display_map);
24935 let mut end_row = ending_row(selection, display_map);
24936
24937 while let Some(next_selection) = selections.peek() {
24938 if next_selection.start.row <= end_row.0 {
24939 end_row = ending_row(next_selection, display_map);
24940 contiguous_row_selections.push(selections.next().unwrap().clone());
24941 } else {
24942 break;
24943 }
24944 }
24945 (start_row, end_row)
24946}
24947
24948fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24949 if selection.start.column > 0 {
24950 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24951 } else {
24952 MultiBufferRow(selection.start.row)
24953 }
24954}
24955
24956fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24957 if next_selection.end.column > 0 || next_selection.is_empty() {
24958 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24959 } else {
24960 MultiBufferRow(next_selection.end.row)
24961 }
24962}
24963
24964impl EditorSnapshot {
24965 pub fn remote_selections_in_range<'a>(
24966 &'a self,
24967 range: &'a Range<Anchor>,
24968 collaboration_hub: &dyn CollaborationHub,
24969 cx: &'a App,
24970 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24971 let participant_names = collaboration_hub.user_names(cx);
24972 let participant_indices = collaboration_hub.user_participant_indices(cx);
24973 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24974 let collaborators_by_replica_id = collaborators_by_peer_id
24975 .values()
24976 .map(|collaborator| (collaborator.replica_id, collaborator))
24977 .collect::<HashMap<_, _>>();
24978 self.buffer_snapshot()
24979 .selections_in_range(range, false)
24980 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24981 if replica_id == ReplicaId::AGENT {
24982 Some(RemoteSelection {
24983 replica_id,
24984 selection,
24985 cursor_shape,
24986 line_mode,
24987 collaborator_id: CollaboratorId::Agent,
24988 user_name: Some("Agent".into()),
24989 color: cx.theme().players().agent(),
24990 })
24991 } else {
24992 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24993 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24994 let user_name = participant_names.get(&collaborator.user_id).cloned();
24995 Some(RemoteSelection {
24996 replica_id,
24997 selection,
24998 cursor_shape,
24999 line_mode,
25000 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
25001 user_name,
25002 color: if let Some(index) = participant_index {
25003 cx.theme().players().color_for_participant(index.0)
25004 } else {
25005 cx.theme().players().absent()
25006 },
25007 })
25008 }
25009 })
25010 }
25011
25012 pub fn hunks_for_ranges(
25013 &self,
25014 ranges: impl IntoIterator<Item = Range<Point>>,
25015 ) -> Vec<MultiBufferDiffHunk> {
25016 let mut hunks = Vec::new();
25017 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
25018 HashMap::default();
25019 for query_range in ranges {
25020 let query_rows =
25021 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
25022 for hunk in self.buffer_snapshot().diff_hunks_in_range(
25023 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
25024 ) {
25025 // Include deleted hunks that are adjacent to the query range, because
25026 // otherwise they would be missed.
25027 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
25028 if hunk.status().is_deleted() {
25029 intersects_range |= hunk.row_range.start == query_rows.end;
25030 intersects_range |= hunk.row_range.end == query_rows.start;
25031 }
25032 if intersects_range {
25033 if !processed_buffer_rows
25034 .entry(hunk.buffer_id)
25035 .or_default()
25036 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
25037 {
25038 continue;
25039 }
25040 hunks.push(hunk);
25041 }
25042 }
25043 }
25044
25045 hunks
25046 }
25047
25048 fn display_diff_hunks_for_rows<'a>(
25049 &'a self,
25050 display_rows: Range<DisplayRow>,
25051 folded_buffers: &'a HashSet<BufferId>,
25052 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
25053 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
25054 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
25055
25056 self.buffer_snapshot()
25057 .diff_hunks_in_range(buffer_start..buffer_end)
25058 .filter_map(|hunk| {
25059 if folded_buffers.contains(&hunk.buffer_id) {
25060 return None;
25061 }
25062
25063 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
25064 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
25065
25066 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
25067 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
25068
25069 let display_hunk = if hunk_display_start.column() != 0 {
25070 DisplayDiffHunk::Folded {
25071 display_row: hunk_display_start.row(),
25072 }
25073 } else {
25074 let mut end_row = hunk_display_end.row();
25075 if hunk_display_end.column() > 0 {
25076 end_row.0 += 1;
25077 }
25078 let is_created_file = hunk.is_created_file();
25079
25080 DisplayDiffHunk::Unfolded {
25081 status: hunk.status(),
25082 diff_base_byte_range: hunk.diff_base_byte_range.start.0
25083 ..hunk.diff_base_byte_range.end.0,
25084 word_diffs: hunk.word_diffs,
25085 display_row_range: hunk_display_start.row()..end_row,
25086 multi_buffer_range: Anchor::range_in_buffer(
25087 hunk.excerpt_id,
25088 hunk.buffer_range,
25089 ),
25090 is_created_file,
25091 }
25092 };
25093
25094 Some(display_hunk)
25095 })
25096 }
25097
25098 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
25099 self.display_snapshot
25100 .buffer_snapshot()
25101 .language_at(position)
25102 }
25103
25104 pub fn is_focused(&self) -> bool {
25105 self.is_focused
25106 }
25107
25108 pub fn placeholder_text(&self) -> Option<String> {
25109 self.placeholder_display_snapshot
25110 .as_ref()
25111 .map(|display_map| display_map.text())
25112 }
25113
25114 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
25115 self.scroll_anchor.scroll_position(&self.display_snapshot)
25116 }
25117
25118 pub fn gutter_dimensions(
25119 &self,
25120 font_id: FontId,
25121 font_size: Pixels,
25122 style: &EditorStyle,
25123 window: &mut Window,
25124 cx: &App,
25125 ) -> GutterDimensions {
25126 if self.show_gutter
25127 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
25128 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
25129 {
25130 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
25131 matches!(
25132 ProjectSettings::get_global(cx).git.git_gutter,
25133 GitGutterSetting::TrackedFiles
25134 )
25135 });
25136 let gutter_settings = EditorSettings::get_global(cx).gutter;
25137 let show_line_numbers = self
25138 .show_line_numbers
25139 .unwrap_or(gutter_settings.line_numbers);
25140 let line_gutter_width = if show_line_numbers {
25141 // Avoid flicker-like gutter resizes when the line number gains another digit by
25142 // only resizing the gutter on files with > 10**min_line_number_digits lines.
25143 let min_width_for_number_on_gutter =
25144 ch_advance * gutter_settings.min_line_number_digits as f32;
25145 self.max_line_number_width(style, window)
25146 .max(min_width_for_number_on_gutter)
25147 } else {
25148 0.0.into()
25149 };
25150
25151 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
25152 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
25153
25154 let git_blame_entries_width =
25155 self.git_blame_gutter_max_author_length
25156 .map(|max_author_length| {
25157 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
25158 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
25159
25160 /// The number of characters to dedicate to gaps and margins.
25161 const SPACING_WIDTH: usize = 4;
25162
25163 let max_char_count = max_author_length.min(renderer.max_author_length())
25164 + ::git::SHORT_SHA_LENGTH
25165 + MAX_RELATIVE_TIMESTAMP.len()
25166 + SPACING_WIDTH;
25167
25168 ch_advance * max_char_count
25169 });
25170
25171 let is_singleton = self.buffer_snapshot().is_singleton();
25172
25173 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
25174 left_padding += if !is_singleton {
25175 ch_width * 4.0
25176 } else if show_runnables || show_breakpoints {
25177 ch_width * 3.0
25178 } else if show_git_gutter && show_line_numbers {
25179 ch_width * 2.0
25180 } else if show_git_gutter || show_line_numbers {
25181 ch_width
25182 } else {
25183 px(0.)
25184 };
25185
25186 let shows_folds = is_singleton && gutter_settings.folds;
25187
25188 let right_padding = if shows_folds && show_line_numbers {
25189 ch_width * 4.0
25190 } else if shows_folds || (!is_singleton && show_line_numbers) {
25191 ch_width * 3.0
25192 } else if show_line_numbers {
25193 ch_width
25194 } else {
25195 px(0.)
25196 };
25197
25198 GutterDimensions {
25199 left_padding,
25200 right_padding,
25201 width: line_gutter_width + left_padding + right_padding,
25202 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
25203 git_blame_entries_width,
25204 }
25205 } else if self.offset_content {
25206 GutterDimensions::default_with_margin(font_id, font_size, cx)
25207 } else {
25208 GutterDimensions::default()
25209 }
25210 }
25211
25212 pub fn render_crease_toggle(
25213 &self,
25214 buffer_row: MultiBufferRow,
25215 row_contains_cursor: bool,
25216 editor: Entity<Editor>,
25217 window: &mut Window,
25218 cx: &mut App,
25219 ) -> Option<AnyElement> {
25220 let folded = self.is_line_folded(buffer_row);
25221 let mut is_foldable = false;
25222
25223 if let Some(crease) = self
25224 .crease_snapshot
25225 .query_row(buffer_row, self.buffer_snapshot())
25226 {
25227 is_foldable = true;
25228 match crease {
25229 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
25230 if let Some(render_toggle) = render_toggle {
25231 let toggle_callback =
25232 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
25233 if folded {
25234 editor.update(cx, |editor, cx| {
25235 editor.fold_at(buffer_row, window, cx)
25236 });
25237 } else {
25238 editor.update(cx, |editor, cx| {
25239 editor.unfold_at(buffer_row, window, cx)
25240 });
25241 }
25242 });
25243 return Some((render_toggle)(
25244 buffer_row,
25245 folded,
25246 toggle_callback,
25247 window,
25248 cx,
25249 ));
25250 }
25251 }
25252 }
25253 }
25254
25255 is_foldable |= self.starts_indent(buffer_row);
25256
25257 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
25258 Some(
25259 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
25260 .toggle_state(folded)
25261 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
25262 if folded {
25263 this.unfold_at(buffer_row, window, cx);
25264 } else {
25265 this.fold_at(buffer_row, window, cx);
25266 }
25267 }))
25268 .into_any_element(),
25269 )
25270 } else {
25271 None
25272 }
25273 }
25274
25275 pub fn render_crease_trailer(
25276 &self,
25277 buffer_row: MultiBufferRow,
25278 window: &mut Window,
25279 cx: &mut App,
25280 ) -> Option<AnyElement> {
25281 let folded = self.is_line_folded(buffer_row);
25282 if let Crease::Inline { render_trailer, .. } = self
25283 .crease_snapshot
25284 .query_row(buffer_row, self.buffer_snapshot())?
25285 {
25286 let render_trailer = render_trailer.as_ref()?;
25287 Some(render_trailer(buffer_row, folded, window, cx))
25288 } else {
25289 None
25290 }
25291 }
25292
25293 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25294 let digit_count = self.widest_line_number().ilog10() + 1;
25295 column_pixels(style, digit_count as usize, window)
25296 }
25297}
25298
25299pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25300 let font_size = style.text.font_size.to_pixels(window.rem_size());
25301 let layout = window.text_system().shape_line(
25302 SharedString::from(" ".repeat(column)),
25303 font_size,
25304 &[TextRun {
25305 len: column,
25306 font: style.text.font(),
25307 color: Hsla::default(),
25308 ..Default::default()
25309 }],
25310 None,
25311 );
25312
25313 layout.width
25314}
25315
25316impl Deref for EditorSnapshot {
25317 type Target = DisplaySnapshot;
25318
25319 fn deref(&self) -> &Self::Target {
25320 &self.display_snapshot
25321 }
25322}
25323
25324#[derive(Clone, Debug, PartialEq, Eq)]
25325pub enum EditorEvent {
25326 InputIgnored {
25327 text: Arc<str>,
25328 },
25329 InputHandled {
25330 utf16_range_to_replace: Option<Range<isize>>,
25331 text: Arc<str>,
25332 },
25333 ExcerptsAdded {
25334 buffer: Entity<Buffer>,
25335 predecessor: ExcerptId,
25336 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25337 },
25338 ExcerptsRemoved {
25339 ids: Vec<ExcerptId>,
25340 removed_buffer_ids: Vec<BufferId>,
25341 },
25342 BufferFoldToggled {
25343 ids: Vec<ExcerptId>,
25344 folded: bool,
25345 },
25346 ExcerptsEdited {
25347 ids: Vec<ExcerptId>,
25348 },
25349 ExcerptsExpanded {
25350 ids: Vec<ExcerptId>,
25351 },
25352 BufferEdited,
25353 Edited {
25354 transaction_id: clock::Lamport,
25355 },
25356 Reparsed(BufferId),
25357 Focused,
25358 FocusedIn,
25359 Blurred,
25360 DirtyChanged,
25361 Saved,
25362 TitleChanged,
25363 SelectionsChanged {
25364 local: bool,
25365 },
25366 ScrollPositionChanged {
25367 local: bool,
25368 autoscroll: bool,
25369 },
25370 TransactionUndone {
25371 transaction_id: clock::Lamport,
25372 },
25373 TransactionBegun {
25374 transaction_id: clock::Lamport,
25375 },
25376 CursorShapeChanged,
25377 BreadcrumbsChanged,
25378 PushedToNavHistory {
25379 anchor: Anchor,
25380 is_deactivate: bool,
25381 },
25382}
25383
25384impl EventEmitter<EditorEvent> for Editor {}
25385
25386impl Focusable for Editor {
25387 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25388 self.focus_handle.clone()
25389 }
25390}
25391
25392impl Render for Editor {
25393 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25394 EditorElement::new(&cx.entity(), self.create_style(cx))
25395 }
25396}
25397
25398impl EntityInputHandler for Editor {
25399 fn text_for_range(
25400 &mut self,
25401 range_utf16: Range<usize>,
25402 adjusted_range: &mut Option<Range<usize>>,
25403 _: &mut Window,
25404 cx: &mut Context<Self>,
25405 ) -> Option<String> {
25406 let snapshot = self.buffer.read(cx).read(cx);
25407 let start = snapshot.clip_offset_utf16(
25408 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25409 Bias::Left,
25410 );
25411 let end = snapshot.clip_offset_utf16(
25412 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25413 Bias::Right,
25414 );
25415 if (start.0.0..end.0.0) != range_utf16 {
25416 adjusted_range.replace(start.0.0..end.0.0);
25417 }
25418 Some(snapshot.text_for_range(start..end).collect())
25419 }
25420
25421 fn selected_text_range(
25422 &mut self,
25423 ignore_disabled_input: bool,
25424 _: &mut Window,
25425 cx: &mut Context<Self>,
25426 ) -> Option<UTF16Selection> {
25427 // Prevent the IME menu from appearing when holding down an alphabetic key
25428 // while input is disabled.
25429 if !ignore_disabled_input && !self.input_enabled {
25430 return None;
25431 }
25432
25433 let selection = self
25434 .selections
25435 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25436 let range = selection.range();
25437
25438 Some(UTF16Selection {
25439 range: range.start.0.0..range.end.0.0,
25440 reversed: selection.reversed,
25441 })
25442 }
25443
25444 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25445 let snapshot = self.buffer.read(cx).read(cx);
25446 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25447 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25448 }
25449
25450 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25451 self.clear_highlights::<InputComposition>(cx);
25452 self.ime_transaction.take();
25453 }
25454
25455 fn replace_text_in_range(
25456 &mut self,
25457 range_utf16: Option<Range<usize>>,
25458 text: &str,
25459 window: &mut Window,
25460 cx: &mut Context<Self>,
25461 ) {
25462 if !self.input_enabled {
25463 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25464 return;
25465 }
25466
25467 self.transact(window, cx, |this, window, cx| {
25468 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25469 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25470 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25471 Some(this.selection_replacement_ranges(range_utf16, cx))
25472 } else {
25473 this.marked_text_ranges(cx)
25474 };
25475
25476 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25477 let newest_selection_id = this.selections.newest_anchor().id;
25478 this.selections
25479 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25480 .iter()
25481 .zip(ranges_to_replace.iter())
25482 .find_map(|(selection, range)| {
25483 if selection.id == newest_selection_id {
25484 Some(
25485 (range.start.0.0 as isize - selection.head().0.0 as isize)
25486 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25487 )
25488 } else {
25489 None
25490 }
25491 })
25492 });
25493
25494 cx.emit(EditorEvent::InputHandled {
25495 utf16_range_to_replace: range_to_replace,
25496 text: text.into(),
25497 });
25498
25499 if let Some(new_selected_ranges) = new_selected_ranges {
25500 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25501 selections.select_ranges(new_selected_ranges)
25502 });
25503 this.backspace(&Default::default(), window, cx);
25504 }
25505
25506 this.handle_input(text, window, cx);
25507 });
25508
25509 if let Some(transaction) = self.ime_transaction {
25510 self.buffer.update(cx, |buffer, cx| {
25511 buffer.group_until_transaction(transaction, cx);
25512 });
25513 }
25514
25515 self.unmark_text(window, cx);
25516 }
25517
25518 fn replace_and_mark_text_in_range(
25519 &mut self,
25520 range_utf16: Option<Range<usize>>,
25521 text: &str,
25522 new_selected_range_utf16: Option<Range<usize>>,
25523 window: &mut Window,
25524 cx: &mut Context<Self>,
25525 ) {
25526 if !self.input_enabled {
25527 return;
25528 }
25529
25530 let transaction = self.transact(window, cx, |this, window, cx| {
25531 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25532 let snapshot = this.buffer.read(cx).read(cx);
25533 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25534 for marked_range in &mut marked_ranges {
25535 marked_range.end = marked_range.start + relative_range_utf16.end;
25536 marked_range.start += relative_range_utf16.start;
25537 marked_range.start =
25538 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25539 marked_range.end =
25540 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25541 }
25542 }
25543 Some(marked_ranges)
25544 } else if let Some(range_utf16) = range_utf16 {
25545 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25546 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25547 Some(this.selection_replacement_ranges(range_utf16, cx))
25548 } else {
25549 None
25550 };
25551
25552 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25553 let newest_selection_id = this.selections.newest_anchor().id;
25554 this.selections
25555 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25556 .iter()
25557 .zip(ranges_to_replace.iter())
25558 .find_map(|(selection, range)| {
25559 if selection.id == newest_selection_id {
25560 Some(
25561 (range.start.0.0 as isize - selection.head().0.0 as isize)
25562 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25563 )
25564 } else {
25565 None
25566 }
25567 })
25568 });
25569
25570 cx.emit(EditorEvent::InputHandled {
25571 utf16_range_to_replace: range_to_replace,
25572 text: text.into(),
25573 });
25574
25575 if let Some(ranges) = ranges_to_replace {
25576 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25577 s.select_ranges(ranges)
25578 });
25579 }
25580
25581 let marked_ranges = {
25582 let snapshot = this.buffer.read(cx).read(cx);
25583 this.selections
25584 .disjoint_anchors_arc()
25585 .iter()
25586 .map(|selection| {
25587 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25588 })
25589 .collect::<Vec<_>>()
25590 };
25591
25592 if text.is_empty() {
25593 this.unmark_text(window, cx);
25594 } else {
25595 this.highlight_text::<InputComposition>(
25596 marked_ranges.clone(),
25597 HighlightStyle {
25598 underline: Some(UnderlineStyle {
25599 thickness: px(1.),
25600 color: None,
25601 wavy: false,
25602 }),
25603 ..Default::default()
25604 },
25605 cx,
25606 );
25607 }
25608
25609 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25610 let use_autoclose = this.use_autoclose;
25611 let use_auto_surround = this.use_auto_surround;
25612 this.set_use_autoclose(false);
25613 this.set_use_auto_surround(false);
25614 this.handle_input(text, window, cx);
25615 this.set_use_autoclose(use_autoclose);
25616 this.set_use_auto_surround(use_auto_surround);
25617
25618 if let Some(new_selected_range) = new_selected_range_utf16 {
25619 let snapshot = this.buffer.read(cx).read(cx);
25620 let new_selected_ranges = marked_ranges
25621 .into_iter()
25622 .map(|marked_range| {
25623 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25624 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25625 insertion_start.0 + new_selected_range.start,
25626 ));
25627 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25628 insertion_start.0 + new_selected_range.end,
25629 ));
25630 snapshot.clip_offset_utf16(new_start, Bias::Left)
25631 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25632 })
25633 .collect::<Vec<_>>();
25634
25635 drop(snapshot);
25636 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25637 selections.select_ranges(new_selected_ranges)
25638 });
25639 }
25640 });
25641
25642 self.ime_transaction = self.ime_transaction.or(transaction);
25643 if let Some(transaction) = self.ime_transaction {
25644 self.buffer.update(cx, |buffer, cx| {
25645 buffer.group_until_transaction(transaction, cx);
25646 });
25647 }
25648
25649 if self.text_highlights::<InputComposition>(cx).is_none() {
25650 self.ime_transaction.take();
25651 }
25652 }
25653
25654 fn bounds_for_range(
25655 &mut self,
25656 range_utf16: Range<usize>,
25657 element_bounds: gpui::Bounds<Pixels>,
25658 window: &mut Window,
25659 cx: &mut Context<Self>,
25660 ) -> Option<gpui::Bounds<Pixels>> {
25661 let text_layout_details = self.text_layout_details(window);
25662 let CharacterDimensions {
25663 em_width,
25664 em_advance,
25665 line_height,
25666 } = self.character_dimensions(window);
25667
25668 let snapshot = self.snapshot(window, cx);
25669 let scroll_position = snapshot.scroll_position();
25670 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25671
25672 let start =
25673 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25674 let x = Pixels::from(
25675 ScrollOffset::from(
25676 snapshot.x_for_display_point(start, &text_layout_details)
25677 + self.gutter_dimensions.full_width(),
25678 ) - scroll_left,
25679 );
25680 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25681
25682 Some(Bounds {
25683 origin: element_bounds.origin + point(x, y),
25684 size: size(em_width, line_height),
25685 })
25686 }
25687
25688 fn character_index_for_point(
25689 &mut self,
25690 point: gpui::Point<Pixels>,
25691 _window: &mut Window,
25692 _cx: &mut Context<Self>,
25693 ) -> Option<usize> {
25694 let position_map = self.last_position_map.as_ref()?;
25695 if !position_map.text_hitbox.contains(&point) {
25696 return None;
25697 }
25698 let display_point = position_map.point_for_position(point).previous_valid;
25699 let anchor = position_map
25700 .snapshot
25701 .display_point_to_anchor(display_point, Bias::Left);
25702 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25703 Some(utf16_offset.0.0)
25704 }
25705
25706 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25707 self.input_enabled
25708 }
25709}
25710
25711trait SelectionExt {
25712 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25713 fn spanned_rows(
25714 &self,
25715 include_end_if_at_line_start: bool,
25716 map: &DisplaySnapshot,
25717 ) -> Range<MultiBufferRow>;
25718}
25719
25720impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25721 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25722 let start = self
25723 .start
25724 .to_point(map.buffer_snapshot())
25725 .to_display_point(map);
25726 let end = self
25727 .end
25728 .to_point(map.buffer_snapshot())
25729 .to_display_point(map);
25730 if self.reversed {
25731 end..start
25732 } else {
25733 start..end
25734 }
25735 }
25736
25737 fn spanned_rows(
25738 &self,
25739 include_end_if_at_line_start: bool,
25740 map: &DisplaySnapshot,
25741 ) -> Range<MultiBufferRow> {
25742 let start = self.start.to_point(map.buffer_snapshot());
25743 let mut end = self.end.to_point(map.buffer_snapshot());
25744 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25745 end.row -= 1;
25746 }
25747
25748 let buffer_start = map.prev_line_boundary(start).0;
25749 let buffer_end = map.next_line_boundary(end).0;
25750 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25751 }
25752}
25753
25754impl<T: InvalidationRegion> InvalidationStack<T> {
25755 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25756 where
25757 S: Clone + ToOffset,
25758 {
25759 while let Some(region) = self.last() {
25760 let all_selections_inside_invalidation_ranges =
25761 if selections.len() == region.ranges().len() {
25762 selections
25763 .iter()
25764 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25765 .all(|(selection, invalidation_range)| {
25766 let head = selection.head().to_offset(buffer);
25767 invalidation_range.start <= head && invalidation_range.end >= head
25768 })
25769 } else {
25770 false
25771 };
25772
25773 if all_selections_inside_invalidation_ranges {
25774 break;
25775 } else {
25776 self.pop();
25777 }
25778 }
25779 }
25780}
25781
25782impl<T> Default for InvalidationStack<T> {
25783 fn default() -> Self {
25784 Self(Default::default())
25785 }
25786}
25787
25788impl<T> Deref for InvalidationStack<T> {
25789 type Target = Vec<T>;
25790
25791 fn deref(&self) -> &Self::Target {
25792 &self.0
25793 }
25794}
25795
25796impl<T> DerefMut for InvalidationStack<T> {
25797 fn deref_mut(&mut self) -> &mut Self::Target {
25798 &mut self.0
25799 }
25800}
25801
25802impl InvalidationRegion for SnippetState {
25803 fn ranges(&self) -> &[Range<Anchor>] {
25804 &self.ranges[self.active_index]
25805 }
25806}
25807
25808fn edit_prediction_edit_text(
25809 current_snapshot: &BufferSnapshot,
25810 edits: &[(Range<Anchor>, impl AsRef<str>)],
25811 edit_preview: &EditPreview,
25812 include_deletions: bool,
25813 cx: &App,
25814) -> HighlightedText {
25815 let edits = edits
25816 .iter()
25817 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25818 .collect::<Vec<_>>();
25819
25820 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25821}
25822
25823fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25824 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25825 // Just show the raw edit text with basic styling
25826 let mut text = String::new();
25827 let mut highlights = Vec::new();
25828
25829 let insertion_highlight_style = HighlightStyle {
25830 color: Some(cx.theme().colors().text),
25831 ..Default::default()
25832 };
25833
25834 for (_, edit_text) in edits {
25835 let start_offset = text.len();
25836 text.push_str(edit_text);
25837 let end_offset = text.len();
25838
25839 if start_offset < end_offset {
25840 highlights.push((start_offset..end_offset, insertion_highlight_style));
25841 }
25842 }
25843
25844 HighlightedText {
25845 text: text.into(),
25846 highlights,
25847 }
25848}
25849
25850pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25851 match severity {
25852 lsp::DiagnosticSeverity::ERROR => colors.error,
25853 lsp::DiagnosticSeverity::WARNING => colors.warning,
25854 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25855 lsp::DiagnosticSeverity::HINT => colors.info,
25856 _ => colors.ignored,
25857 }
25858}
25859
25860pub fn styled_runs_for_code_label<'a>(
25861 label: &'a CodeLabel,
25862 syntax_theme: &'a theme::SyntaxTheme,
25863 local_player: &'a theme::PlayerColor,
25864) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25865 let fade_out = HighlightStyle {
25866 fade_out: Some(0.35),
25867 ..Default::default()
25868 };
25869
25870 let mut prev_end = label.filter_range.end;
25871 label
25872 .runs
25873 .iter()
25874 .enumerate()
25875 .flat_map(move |(ix, (range, highlight_id))| {
25876 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25877 HighlightStyle {
25878 color: Some(local_player.cursor),
25879 ..Default::default()
25880 }
25881 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25882 HighlightStyle {
25883 background_color: Some(local_player.selection),
25884 ..Default::default()
25885 }
25886 } else if let Some(style) = highlight_id.style(syntax_theme) {
25887 style
25888 } else {
25889 return Default::default();
25890 };
25891 let muted_style = style.highlight(fade_out);
25892
25893 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25894 if range.start >= label.filter_range.end {
25895 if range.start > prev_end {
25896 runs.push((prev_end..range.start, fade_out));
25897 }
25898 runs.push((range.clone(), muted_style));
25899 } else if range.end <= label.filter_range.end {
25900 runs.push((range.clone(), style));
25901 } else {
25902 runs.push((range.start..label.filter_range.end, style));
25903 runs.push((label.filter_range.end..range.end, muted_style));
25904 }
25905 prev_end = cmp::max(prev_end, range.end);
25906
25907 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25908 runs.push((prev_end..label.text.len(), fade_out));
25909 }
25910
25911 runs
25912 })
25913}
25914
25915pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25916 let mut prev_index = 0;
25917 let mut prev_codepoint: Option<char> = None;
25918 text.char_indices()
25919 .chain([(text.len(), '\0')])
25920 .filter_map(move |(index, codepoint)| {
25921 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25922 let is_boundary = index == text.len()
25923 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25924 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25925 if is_boundary {
25926 let chunk = &text[prev_index..index];
25927 prev_index = index;
25928 Some(chunk)
25929 } else {
25930 None
25931 }
25932 })
25933}
25934
25935/// Given a string of text immediately before the cursor, iterates over possible
25936/// strings a snippet could match to. More precisely: returns an iterator over
25937/// suffixes of `text` created by splitting at word boundaries (before & after
25938/// every non-word character).
25939///
25940/// Shorter suffixes are returned first.
25941pub(crate) fn snippet_candidate_suffixes(
25942 text: &str,
25943 is_word_char: impl Fn(char) -> bool,
25944) -> impl std::iter::Iterator<Item = &str> {
25945 let mut prev_index = text.len();
25946 let mut prev_codepoint = None;
25947 text.char_indices()
25948 .rev()
25949 .chain([(0, '\0')])
25950 .filter_map(move |(index, codepoint)| {
25951 let prev_index = std::mem::replace(&mut prev_index, index);
25952 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25953 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25954 None
25955 } else {
25956 let chunk = &text[prev_index..]; // go to end of string
25957 Some(chunk)
25958 }
25959 })
25960}
25961
25962pub trait RangeToAnchorExt: Sized {
25963 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25964
25965 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25966 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25967 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25968 }
25969}
25970
25971impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25972 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25973 let start_offset = self.start.to_offset(snapshot);
25974 let end_offset = self.end.to_offset(snapshot);
25975 if start_offset == end_offset {
25976 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25977 } else {
25978 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25979 }
25980 }
25981}
25982
25983pub trait RowExt {
25984 fn as_f64(&self) -> f64;
25985
25986 fn next_row(&self) -> Self;
25987
25988 fn previous_row(&self) -> Self;
25989
25990 fn minus(&self, other: Self) -> u32;
25991}
25992
25993impl RowExt for DisplayRow {
25994 fn as_f64(&self) -> f64 {
25995 self.0 as _
25996 }
25997
25998 fn next_row(&self) -> Self {
25999 Self(self.0 + 1)
26000 }
26001
26002 fn previous_row(&self) -> Self {
26003 Self(self.0.saturating_sub(1))
26004 }
26005
26006 fn minus(&self, other: Self) -> u32 {
26007 self.0 - other.0
26008 }
26009}
26010
26011impl RowExt for MultiBufferRow {
26012 fn as_f64(&self) -> f64 {
26013 self.0 as _
26014 }
26015
26016 fn next_row(&self) -> Self {
26017 Self(self.0 + 1)
26018 }
26019
26020 fn previous_row(&self) -> Self {
26021 Self(self.0.saturating_sub(1))
26022 }
26023
26024 fn minus(&self, other: Self) -> u32 {
26025 self.0 - other.0
26026 }
26027}
26028
26029trait RowRangeExt {
26030 type Row;
26031
26032 fn len(&self) -> usize;
26033
26034 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
26035}
26036
26037impl RowRangeExt for Range<MultiBufferRow> {
26038 type Row = MultiBufferRow;
26039
26040 fn len(&self) -> usize {
26041 (self.end.0 - self.start.0) as usize
26042 }
26043
26044 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
26045 (self.start.0..self.end.0).map(MultiBufferRow)
26046 }
26047}
26048
26049impl RowRangeExt for Range<DisplayRow> {
26050 type Row = DisplayRow;
26051
26052 fn len(&self) -> usize {
26053 (self.end.0 - self.start.0) as usize
26054 }
26055
26056 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
26057 (self.start.0..self.end.0).map(DisplayRow)
26058 }
26059}
26060
26061/// If select range has more than one line, we
26062/// just point the cursor to range.start.
26063fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
26064 if range.start.row == range.end.row {
26065 range
26066 } else {
26067 range.start..range.start
26068 }
26069}
26070pub struct KillRing(ClipboardItem);
26071impl Global for KillRing {}
26072
26073const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
26074
26075enum BreakpointPromptEditAction {
26076 Log,
26077 Condition,
26078 HitCondition,
26079}
26080
26081struct BreakpointPromptEditor {
26082 pub(crate) prompt: Entity<Editor>,
26083 editor: WeakEntity<Editor>,
26084 breakpoint_anchor: Anchor,
26085 breakpoint: Breakpoint,
26086 edit_action: BreakpointPromptEditAction,
26087 block_ids: HashSet<CustomBlockId>,
26088 editor_margins: Arc<Mutex<EditorMargins>>,
26089 _subscriptions: Vec<Subscription>,
26090}
26091
26092impl BreakpointPromptEditor {
26093 const MAX_LINES: u8 = 4;
26094
26095 fn new(
26096 editor: WeakEntity<Editor>,
26097 breakpoint_anchor: Anchor,
26098 breakpoint: Breakpoint,
26099 edit_action: BreakpointPromptEditAction,
26100 window: &mut Window,
26101 cx: &mut Context<Self>,
26102 ) -> Self {
26103 let base_text = match edit_action {
26104 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
26105 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
26106 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
26107 }
26108 .map(|msg| msg.to_string())
26109 .unwrap_or_default();
26110
26111 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
26112 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26113
26114 let prompt = cx.new(|cx| {
26115 let mut prompt = Editor::new(
26116 EditorMode::AutoHeight {
26117 min_lines: 1,
26118 max_lines: Some(Self::MAX_LINES as usize),
26119 },
26120 buffer,
26121 None,
26122 window,
26123 cx,
26124 );
26125 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
26126 prompt.set_show_cursor_when_unfocused(false, cx);
26127 prompt.set_placeholder_text(
26128 match edit_action {
26129 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
26130 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
26131 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
26132 },
26133 window,
26134 cx,
26135 );
26136
26137 prompt
26138 });
26139
26140 Self {
26141 prompt,
26142 editor,
26143 breakpoint_anchor,
26144 breakpoint,
26145 edit_action,
26146 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
26147 block_ids: Default::default(),
26148 _subscriptions: vec![],
26149 }
26150 }
26151
26152 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
26153 self.block_ids.extend(block_ids)
26154 }
26155
26156 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
26157 if let Some(editor) = self.editor.upgrade() {
26158 let message = self
26159 .prompt
26160 .read(cx)
26161 .buffer
26162 .read(cx)
26163 .as_singleton()
26164 .expect("A multi buffer in breakpoint prompt isn't possible")
26165 .read(cx)
26166 .as_rope()
26167 .to_string();
26168
26169 editor.update(cx, |editor, cx| {
26170 editor.edit_breakpoint_at_anchor(
26171 self.breakpoint_anchor,
26172 self.breakpoint.clone(),
26173 match self.edit_action {
26174 BreakpointPromptEditAction::Log => {
26175 BreakpointEditAction::EditLogMessage(message.into())
26176 }
26177 BreakpointPromptEditAction::Condition => {
26178 BreakpointEditAction::EditCondition(message.into())
26179 }
26180 BreakpointPromptEditAction::HitCondition => {
26181 BreakpointEditAction::EditHitCondition(message.into())
26182 }
26183 },
26184 cx,
26185 );
26186
26187 editor.remove_blocks(self.block_ids.clone(), None, cx);
26188 cx.focus_self(window);
26189 });
26190 }
26191 }
26192
26193 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
26194 self.editor
26195 .update(cx, |editor, cx| {
26196 editor.remove_blocks(self.block_ids.clone(), None, cx);
26197 window.focus(&editor.focus_handle, cx);
26198 })
26199 .log_err();
26200 }
26201
26202 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
26203 let settings = ThemeSettings::get_global(cx);
26204 let text_style = TextStyle {
26205 color: if self.prompt.read(cx).read_only(cx) {
26206 cx.theme().colors().text_disabled
26207 } else {
26208 cx.theme().colors().text
26209 },
26210 font_family: settings.buffer_font.family.clone(),
26211 font_fallbacks: settings.buffer_font.fallbacks.clone(),
26212 font_size: settings.buffer_font_size(cx).into(),
26213 font_weight: settings.buffer_font.weight,
26214 line_height: relative(settings.buffer_line_height.value()),
26215 ..Default::default()
26216 };
26217 EditorElement::new(
26218 &self.prompt,
26219 EditorStyle {
26220 background: cx.theme().colors().editor_background,
26221 local_player: cx.theme().players().local(),
26222 text: text_style,
26223 ..Default::default()
26224 },
26225 )
26226 }
26227}
26228
26229impl Render for BreakpointPromptEditor {
26230 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26231 let editor_margins = *self.editor_margins.lock();
26232 let gutter_dimensions = editor_margins.gutter;
26233 h_flex()
26234 .key_context("Editor")
26235 .bg(cx.theme().colors().editor_background)
26236 .border_y_1()
26237 .border_color(cx.theme().status().info_border)
26238 .size_full()
26239 .py(window.line_height() / 2.5)
26240 .on_action(cx.listener(Self::confirm))
26241 .on_action(cx.listener(Self::cancel))
26242 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
26243 .child(div().flex_1().child(self.render_prompt_editor(cx)))
26244 }
26245}
26246
26247impl Focusable for BreakpointPromptEditor {
26248 fn focus_handle(&self, cx: &App) -> FocusHandle {
26249 self.prompt.focus_handle(cx)
26250 }
26251}
26252
26253fn all_edits_insertions_or_deletions(
26254 edits: &Vec<(Range<Anchor>, Arc<str>)>,
26255 snapshot: &MultiBufferSnapshot,
26256) -> bool {
26257 let mut all_insertions = true;
26258 let mut all_deletions = true;
26259
26260 for (range, new_text) in edits.iter() {
26261 let range_is_empty = range.to_offset(snapshot).is_empty();
26262 let text_is_empty = new_text.is_empty();
26263
26264 if range_is_empty != text_is_empty {
26265 if range_is_empty {
26266 all_deletions = false;
26267 } else {
26268 all_insertions = false;
26269 }
26270 } else {
26271 return false;
26272 }
26273
26274 if !all_insertions && !all_deletions {
26275 return false;
26276 }
26277 }
26278 all_insertions || all_deletions
26279}
26280
26281struct MissingEditPredictionKeybindingTooltip;
26282
26283impl Render for MissingEditPredictionKeybindingTooltip {
26284 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26285 ui::tooltip_container(cx, |container, cx| {
26286 container
26287 .flex_shrink_0()
26288 .max_w_80()
26289 .min_h(rems_from_px(124.))
26290 .justify_between()
26291 .child(
26292 v_flex()
26293 .flex_1()
26294 .text_ui_sm(cx)
26295 .child(Label::new("Conflict with Accept Keybinding"))
26296 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26297 )
26298 .child(
26299 h_flex()
26300 .pb_1()
26301 .gap_1()
26302 .items_end()
26303 .w_full()
26304 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26305 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26306 }))
26307 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26308 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26309 })),
26310 )
26311 })
26312 }
26313}
26314
26315#[derive(Debug, Clone, Copy, PartialEq)]
26316pub struct LineHighlight {
26317 pub background: Background,
26318 pub border: Option<gpui::Hsla>,
26319 pub include_gutter: bool,
26320 pub type_id: Option<TypeId>,
26321}
26322
26323struct LineManipulationResult {
26324 pub new_text: String,
26325 pub line_count_before: usize,
26326 pub line_count_after: usize,
26327}
26328
26329fn render_diff_hunk_controls(
26330 row: u32,
26331 status: &DiffHunkStatus,
26332 hunk_range: Range<Anchor>,
26333 is_created_file: bool,
26334 line_height: Pixels,
26335 editor: &Entity<Editor>,
26336 _window: &mut Window,
26337 cx: &mut App,
26338) -> AnyElement {
26339 h_flex()
26340 .h(line_height)
26341 .mr_1()
26342 .gap_1()
26343 .px_0p5()
26344 .pb_1()
26345 .border_x_1()
26346 .border_b_1()
26347 .border_color(cx.theme().colors().border_variant)
26348 .rounded_b_lg()
26349 .bg(cx.theme().colors().editor_background)
26350 .gap_1()
26351 .block_mouse_except_scroll()
26352 .shadow_md()
26353 .child(if status.has_secondary_hunk() {
26354 Button::new(("stage", row as u64), "Stage")
26355 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26356 .tooltip({
26357 let focus_handle = editor.focus_handle(cx);
26358 move |_window, cx| {
26359 Tooltip::for_action_in(
26360 "Stage Hunk",
26361 &::git::ToggleStaged,
26362 &focus_handle,
26363 cx,
26364 )
26365 }
26366 })
26367 .on_click({
26368 let editor = editor.clone();
26369 move |_event, _window, cx| {
26370 editor.update(cx, |editor, cx| {
26371 editor.stage_or_unstage_diff_hunks(
26372 true,
26373 vec![hunk_range.start..hunk_range.start],
26374 cx,
26375 );
26376 });
26377 }
26378 })
26379 } else {
26380 Button::new(("unstage", row as u64), "Unstage")
26381 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26382 .tooltip({
26383 let focus_handle = editor.focus_handle(cx);
26384 move |_window, cx| {
26385 Tooltip::for_action_in(
26386 "Unstage Hunk",
26387 &::git::ToggleStaged,
26388 &focus_handle,
26389 cx,
26390 )
26391 }
26392 })
26393 .on_click({
26394 let editor = editor.clone();
26395 move |_event, _window, cx| {
26396 editor.update(cx, |editor, cx| {
26397 editor.stage_or_unstage_diff_hunks(
26398 false,
26399 vec![hunk_range.start..hunk_range.start],
26400 cx,
26401 );
26402 });
26403 }
26404 })
26405 })
26406 .child(
26407 Button::new(("restore", row as u64), "Restore")
26408 .tooltip({
26409 let focus_handle = editor.focus_handle(cx);
26410 move |_window, cx| {
26411 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26412 }
26413 })
26414 .on_click({
26415 let editor = editor.clone();
26416 move |_event, window, cx| {
26417 editor.update(cx, |editor, cx| {
26418 let snapshot = editor.snapshot(window, cx);
26419 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26420 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26421 });
26422 }
26423 })
26424 .disabled(is_created_file),
26425 )
26426 .when(
26427 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26428 |el| {
26429 el.child(
26430 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26431 .shape(IconButtonShape::Square)
26432 .icon_size(IconSize::Small)
26433 // .disabled(!has_multiple_hunks)
26434 .tooltip({
26435 let focus_handle = editor.focus_handle(cx);
26436 move |_window, cx| {
26437 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26438 }
26439 })
26440 .on_click({
26441 let editor = editor.clone();
26442 move |_event, window, cx| {
26443 editor.update(cx, |editor, cx| {
26444 let snapshot = editor.snapshot(window, cx);
26445 let position =
26446 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26447 editor.go_to_hunk_before_or_after_position(
26448 &snapshot,
26449 position,
26450 Direction::Next,
26451 window,
26452 cx,
26453 );
26454 editor.expand_selected_diff_hunks(cx);
26455 });
26456 }
26457 }),
26458 )
26459 .child(
26460 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26461 .shape(IconButtonShape::Square)
26462 .icon_size(IconSize::Small)
26463 // .disabled(!has_multiple_hunks)
26464 .tooltip({
26465 let focus_handle = editor.focus_handle(cx);
26466 move |_window, cx| {
26467 Tooltip::for_action_in(
26468 "Previous Hunk",
26469 &GoToPreviousHunk,
26470 &focus_handle,
26471 cx,
26472 )
26473 }
26474 })
26475 .on_click({
26476 let editor = editor.clone();
26477 move |_event, window, cx| {
26478 editor.update(cx, |editor, cx| {
26479 let snapshot = editor.snapshot(window, cx);
26480 let point =
26481 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26482 editor.go_to_hunk_before_or_after_position(
26483 &snapshot,
26484 point,
26485 Direction::Prev,
26486 window,
26487 cx,
26488 );
26489 editor.expand_selected_diff_hunks(cx);
26490 });
26491 }
26492 }),
26493 )
26494 },
26495 )
26496 .into_any_element()
26497}
26498
26499pub fn multibuffer_context_lines(cx: &App) -> u32 {
26500 EditorSettings::try_get(cx)
26501 .map(|settings| settings.excerpt_context_lines)
26502 .unwrap_or(2)
26503 .min(32)
26504}