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 render_breadcrumb_text,
62};
63pub use git::blame::BlameRenderer;
64pub use hover_popover::hover_markdown_style;
65pub use inlays::Inlay;
66pub use items::MAX_TAB_TITLE_LEN;
67pub use lsp::CompletionContext;
68pub use lsp_ext::lsp_tasks;
69pub use multi_buffer::{
70 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
71 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
72 ToPoint,
73};
74pub use split::SplittableEditor;
75pub use text::Bias;
76
77use ::git::{Restore, blame::BlameEntry, commit::ParsedCommitMessage, status::FileStatus};
78use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
79use anyhow::{Context as _, Result, anyhow, bail};
80use blink_manager::BlinkManager;
81use buffer_diff::DiffHunkStatus;
82use client::{Collaborator, ParticipantIndex, parse_zed_link};
83use clock::ReplicaId;
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use collections::{BTreeMap, HashMap, HashSet, VecDeque};
89use convert_case::{Case, Casing};
90use dap::TelemetrySpawnLocation;
91use display_map::*;
92use edit_prediction_types::{
93 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionGranularity,
94};
95use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
96use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
97use futures::{
98 FutureExt, StreamExt as _,
99 future::{self, Shared, join},
100 stream::FuturesUnordered,
101};
102use fuzzy::{StringMatch, StringMatchCandidate};
103use git::blame::{GitBlame, GlobalBlameRenderer};
104use gpui::{
105 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
106 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
107 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
108 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
109 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
110 Render, ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextRun,
111 TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
112 WeakEntity, WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative,
113 size,
114};
115use hover_links::{HoverLink, HoveredLinkState, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
125 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
126 TreeSitterOptions, WordsQuery,
127 language_settings::{
128 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
129 all_language_settings, language_settings,
130 },
131 point_from_lsp, point_to_lsp, text_diff_with_options,
132};
133use linked_editing_ranges::refresh_linked_ranges;
134use lsp::{
135 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
136 LanguageServerId,
137};
138use lsp_colors::LspColorData;
139use markdown::Markdown;
140use mouse_context_menu::MouseContextMenu;
141use movement::TextLayoutDetails;
142use multi_buffer::{
143 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
144};
145use parking_lot::Mutex;
146use persistence::DB;
147use project::{
148 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
149 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
150 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
151 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
152 debugger::{
153 breakpoint_store::{
154 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
155 BreakpointStore, BreakpointStoreEvent,
156 },
157 session::{Session, SessionEvent},
158 },
159 git_store::GitStoreEvent,
160 lsp_store::{
161 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
162 OpenLspBufferHandle,
163 },
164 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
165};
166use rand::seq::SliceRandom;
167use regex::Regex;
168use rpc::{ErrorCode, ErrorExt, proto::PeerId};
169use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
170use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
171use serde::{Deserialize, Serialize};
172use settings::{
173 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
174 update_settings_file,
175};
176use smallvec::{SmallVec, smallvec};
177use snippet::Snippet;
178use std::{
179 any::{Any, TypeId},
180 borrow::Cow,
181 cell::{OnceCell, RefCell},
182 cmp::{self, Ordering, Reverse},
183 collections::hash_map,
184 iter::{self, Peekable},
185 mem,
186 num::NonZeroU32,
187 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
188 path::{Path, PathBuf},
189 rc::Rc,
190 sync::Arc,
191 time::{Duration, Instant},
192};
193use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
194use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
195use theme::{
196 AccentColors, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
197 observe_buffer_font_size_adjustment,
198};
199use ui::{
200 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
201 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
202};
203use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
204use workspace::{
205 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
206 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
207 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
208 item::{BreadcrumbText, ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
209 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
210 searchable::{CollapseDirection, SearchEvent},
211};
212
213use crate::{
214 code_context_menus::CompletionsMenuSource,
215 editor_settings::MultiCursorModifier,
216 hover_links::{find_url, find_url_from_range},
217 inlays::{
218 InlineValueCache,
219 inlay_hints::{LspInlayHintData, inlay_hint_settings},
220 },
221 scroll::{ScrollOffset, ScrollPixelOffset},
222 selections_collection::resolve_selections_wrapping_blocks,
223 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
224};
225
226pub const FILE_HEADER_HEIGHT: u32 = 2;
227pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
228const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
229const MAX_LINE_LEN: usize = 1024;
230const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
231const MAX_SELECTION_HISTORY_LEN: usize = 1024;
232pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
233#[doc(hidden)]
234pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
235pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
236
237pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
238pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
240pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
241
242pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
243pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
244pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
245
246pub type RenderDiffHunkControlsFn = Arc<
247 dyn Fn(
248 u32,
249 &DiffHunkStatus,
250 Range<Anchor>,
251 bool,
252 Pixels,
253 &Entity<Editor>,
254 &mut Window,
255 &mut App,
256 ) -> AnyElement,
257>;
258
259enum ReportEditorEvent {
260 Saved { auto_saved: bool },
261 EditorOpened,
262 Closed,
263}
264
265impl ReportEditorEvent {
266 pub fn event_type(&self) -> &'static str {
267 match self {
268 Self::Saved { .. } => "Editor Saved",
269 Self::EditorOpened => "Editor Opened",
270 Self::Closed => "Editor Closed",
271 }
272 }
273}
274
275pub enum ActiveDebugLine {}
276pub enum DebugStackFrameLine {}
277enum DocumentHighlightRead {}
278enum DocumentHighlightWrite {}
279enum InputComposition {}
280pub enum PendingInput {}
281enum SelectedTextHighlight {}
282
283pub enum ConflictsOuter {}
284pub enum ConflictsOurs {}
285pub enum ConflictsTheirs {}
286pub enum ConflictsOursMarker {}
287pub enum ConflictsTheirsMarker {}
288
289pub struct HunkAddedColor;
290pub struct HunkRemovedColor;
291
292#[derive(Debug, Copy, Clone, PartialEq, Eq)]
293pub enum Navigated {
294 Yes,
295 No,
296}
297
298impl Navigated {
299 pub fn from_bool(yes: bool) -> Navigated {
300 if yes { Navigated::Yes } else { Navigated::No }
301 }
302}
303
304#[derive(Debug, Clone, PartialEq, Eq)]
305enum DisplayDiffHunk {
306 Folded {
307 display_row: DisplayRow,
308 },
309 Unfolded {
310 is_created_file: bool,
311 diff_base_byte_range: Range<usize>,
312 display_row_range: Range<DisplayRow>,
313 multi_buffer_range: Range<Anchor>,
314 status: DiffHunkStatus,
315 word_diffs: Vec<Range<MultiBufferOffset>>,
316 },
317}
318
319pub enum HideMouseCursorOrigin {
320 TypingAction,
321 MovementAction,
322}
323
324pub fn init(cx: &mut App) {
325 cx.set_global(GlobalBlameRenderer(Arc::new(())));
326
327 workspace::register_project_item::<Editor>(cx);
328 workspace::FollowableViewRegistry::register::<Editor>(cx);
329 workspace::register_serializable_item::<Editor>(cx);
330
331 cx.observe_new(
332 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
333 workspace.register_action(Editor::new_file);
334 workspace.register_action(Editor::new_file_split);
335 workspace.register_action(Editor::new_file_vertical);
336 workspace.register_action(Editor::new_file_horizontal);
337 workspace.register_action(Editor::cancel_language_server_work);
338 workspace.register_action(Editor::toggle_focus);
339 },
340 )
341 .detach();
342
343 cx.on_action(move |_: &workspace::NewFile, cx| {
344 let app_state = workspace::AppState::global(cx);
345 if let Some(app_state) = app_state.upgrade() {
346 workspace::open_new(
347 Default::default(),
348 app_state,
349 cx,
350 |workspace, window, cx| {
351 Editor::new_file(workspace, &Default::default(), window, cx)
352 },
353 )
354 .detach();
355 }
356 })
357 .on_action(move |_: &workspace::NewWindow, cx| {
358 let app_state = workspace::AppState::global(cx);
359 if let Some(app_state) = app_state.upgrade() {
360 workspace::open_new(
361 Default::default(),
362 app_state,
363 cx,
364 |workspace, window, cx| {
365 cx.activate(true);
366 Editor::new_file(workspace, &Default::default(), window, cx)
367 },
368 )
369 .detach();
370 }
371 });
372}
373
374pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
375 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
376}
377
378pub trait DiagnosticRenderer {
379 fn render_group(
380 &self,
381 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
382 buffer_id: BufferId,
383 snapshot: EditorSnapshot,
384 editor: WeakEntity<Editor>,
385 language_registry: Option<Arc<LanguageRegistry>>,
386 cx: &mut App,
387 ) -> Vec<BlockProperties<Anchor>>;
388
389 fn render_hover(
390 &self,
391 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
392 range: Range<Point>,
393 buffer_id: BufferId,
394 language_registry: Option<Arc<LanguageRegistry>>,
395 cx: &mut App,
396 ) -> Option<Entity<markdown::Markdown>>;
397
398 fn open_link(
399 &self,
400 editor: &mut Editor,
401 link: SharedString,
402 window: &mut Window,
403 cx: &mut Context<Editor>,
404 );
405}
406
407pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
408
409impl GlobalDiagnosticRenderer {
410 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
411 cx.try_global::<Self>().map(|g| g.0.clone())
412 }
413}
414
415impl gpui::Global for GlobalDiagnosticRenderer {}
416pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
417 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
418}
419
420pub struct SearchWithinRange;
421
422trait InvalidationRegion {
423 fn ranges(&self) -> &[Range<Anchor>];
424}
425
426#[derive(Clone, Debug, PartialEq)]
427pub enum SelectPhase {
428 Begin {
429 position: DisplayPoint,
430 add: bool,
431 click_count: usize,
432 },
433 BeginColumnar {
434 position: DisplayPoint,
435 reset: bool,
436 mode: ColumnarMode,
437 goal_column: u32,
438 },
439 Extend {
440 position: DisplayPoint,
441 click_count: usize,
442 },
443 Update {
444 position: DisplayPoint,
445 goal_column: u32,
446 scroll_delta: gpui::Point<f32>,
447 },
448 End,
449}
450
451#[derive(Clone, Debug, PartialEq)]
452pub enum ColumnarMode {
453 FromMouse,
454 FromSelection,
455}
456
457#[derive(Clone, Debug)]
458pub enum SelectMode {
459 Character,
460 Word(Range<Anchor>),
461 Line(Range<Anchor>),
462 All,
463}
464
465#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
466pub enum SizingBehavior {
467 /// The editor will layout itself using `size_full` and will include the vertical
468 /// scroll margin as requested by user settings.
469 #[default]
470 Default,
471 /// The editor will layout itself using `size_full`, but will not have any
472 /// vertical overscroll.
473 ExcludeOverscrollMargin,
474 /// The editor will request a vertical size according to its content and will be
475 /// layouted without a vertical scroll margin.
476 SizeByContent,
477}
478
479#[derive(Clone, PartialEq, Eq, Debug)]
480pub enum EditorMode {
481 SingleLine,
482 AutoHeight {
483 min_lines: usize,
484 max_lines: Option<usize>,
485 },
486 Full {
487 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
488 scale_ui_elements_with_buffer_font_size: bool,
489 /// When set to `true`, the editor will render a background for the active line.
490 show_active_line_background: bool,
491 /// Determines the sizing behavior for this editor
492 sizing_behavior: SizingBehavior,
493 },
494 Minimap {
495 parent: WeakEntity<Editor>,
496 },
497}
498
499impl EditorMode {
500 pub fn full() -> Self {
501 Self::Full {
502 scale_ui_elements_with_buffer_font_size: true,
503 show_active_line_background: true,
504 sizing_behavior: SizingBehavior::Default,
505 }
506 }
507
508 #[inline]
509 pub fn is_full(&self) -> bool {
510 matches!(self, Self::Full { .. })
511 }
512
513 #[inline]
514 pub fn is_single_line(&self) -> bool {
515 matches!(self, Self::SingleLine { .. })
516 }
517
518 #[inline]
519 fn is_minimap(&self) -> bool {
520 matches!(self, Self::Minimap { .. })
521 }
522}
523
524#[derive(Copy, Clone, Debug)]
525pub enum SoftWrap {
526 /// Prefer not to wrap at all.
527 ///
528 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
529 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
530 GitDiff,
531 /// Prefer a single line generally, unless an overly long line is encountered.
532 None,
533 /// Soft wrap lines that exceed the editor width.
534 EditorWidth,
535 /// Soft wrap lines at the preferred line length.
536 Column(u32),
537 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
538 Bounded(u32),
539}
540
541#[derive(Clone)]
542pub struct EditorStyle {
543 pub background: Hsla,
544 pub border: Hsla,
545 pub local_player: PlayerColor,
546 pub text: TextStyle,
547 pub scrollbar_width: Pixels,
548 pub syntax: Arc<SyntaxTheme>,
549 pub status: StatusColors,
550 pub inlay_hints_style: HighlightStyle,
551 pub edit_prediction_styles: EditPredictionStyles,
552 pub unnecessary_code_fade: f32,
553 pub show_underlines: bool,
554}
555
556impl Default for EditorStyle {
557 fn default() -> Self {
558 Self {
559 background: Hsla::default(),
560 border: Hsla::default(),
561 local_player: PlayerColor::default(),
562 text: TextStyle::default(),
563 scrollbar_width: Pixels::default(),
564 syntax: Default::default(),
565 // HACK: Status colors don't have a real default.
566 // We should look into removing the status colors from the editor
567 // style and retrieve them directly from the theme.
568 status: StatusColors::dark(),
569 inlay_hints_style: HighlightStyle::default(),
570 edit_prediction_styles: EditPredictionStyles {
571 insertion: HighlightStyle::default(),
572 whitespace: HighlightStyle::default(),
573 },
574 unnecessary_code_fade: Default::default(),
575 show_underlines: true,
576 }
577 }
578}
579
580pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
581 let show_background = language_settings::language_settings(None, None, cx)
582 .inlay_hints
583 .show_background;
584
585 let mut style = cx.theme().syntax().get("hint");
586
587 if style.color.is_none() {
588 style.color = Some(cx.theme().status().hint);
589 }
590
591 if !show_background {
592 style.background_color = None;
593 return style;
594 }
595
596 if style.background_color.is_none() {
597 style.background_color = Some(cx.theme().status().hint_background);
598 }
599
600 style
601}
602
603pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
604 EditPredictionStyles {
605 insertion: HighlightStyle {
606 color: Some(cx.theme().status().predictive),
607 ..HighlightStyle::default()
608 },
609 whitespace: HighlightStyle {
610 background_color: Some(cx.theme().status().created_background),
611 ..HighlightStyle::default()
612 },
613 }
614}
615
616type CompletionId = usize;
617
618pub(crate) enum EditDisplayMode {
619 TabAccept,
620 DiffPopover,
621 Inline,
622}
623
624enum EditPrediction {
625 Edit {
626 edits: Vec<(Range<Anchor>, Arc<str>)>,
627 edit_preview: Option<EditPreview>,
628 display_mode: EditDisplayMode,
629 snapshot: BufferSnapshot,
630 },
631 /// Move to a specific location in the active editor
632 MoveWithin {
633 target: Anchor,
634 snapshot: BufferSnapshot,
635 },
636 /// Move to a specific location in a different editor (not the active one)
637 MoveOutside {
638 target: language::Anchor,
639 snapshot: BufferSnapshot,
640 },
641}
642
643struct EditPredictionState {
644 inlay_ids: Vec<InlayId>,
645 completion: EditPrediction,
646 completion_id: Option<SharedString>,
647 invalidation_range: Option<Range<Anchor>>,
648}
649
650enum EditPredictionSettings {
651 Disabled,
652 Enabled {
653 show_in_menu: bool,
654 preview_requires_modifier: bool,
655 },
656}
657
658enum EditPredictionHighlight {}
659
660#[derive(Debug, Clone)]
661struct InlineDiagnostic {
662 message: SharedString,
663 group_id: usize,
664 is_primary: bool,
665 start: Point,
666 severity: lsp::DiagnosticSeverity,
667}
668
669pub enum MenuEditPredictionsPolicy {
670 Never,
671 ByProvider,
672}
673
674pub enum EditPredictionPreview {
675 /// Modifier is not pressed
676 Inactive { released_too_fast: bool },
677 /// Modifier pressed
678 Active {
679 since: Instant,
680 previous_scroll_position: Option<ScrollAnchor>,
681 },
682}
683
684impl EditPredictionPreview {
685 pub fn released_too_fast(&self) -> bool {
686 match self {
687 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
688 EditPredictionPreview::Active { .. } => false,
689 }
690 }
691
692 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
693 if let EditPredictionPreview::Active {
694 previous_scroll_position,
695 ..
696 } = self
697 {
698 *previous_scroll_position = scroll_position;
699 }
700 }
701}
702
703pub struct ContextMenuOptions {
704 pub min_entries_visible: usize,
705 pub max_entries_visible: usize,
706 pub placement: Option<ContextMenuPlacement>,
707}
708
709#[derive(Debug, Clone, PartialEq, Eq)]
710pub enum ContextMenuPlacement {
711 Above,
712 Below,
713}
714
715#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
716struct EditorActionId(usize);
717
718impl EditorActionId {
719 pub fn post_inc(&mut self) -> Self {
720 let answer = self.0;
721
722 *self = Self(answer + 1);
723
724 Self(answer)
725 }
726}
727
728// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
729// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
730
731type BackgroundHighlight = (
732 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
733 Arc<[Range<Anchor>]>,
734);
735type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
736
737#[derive(Default)]
738struct ScrollbarMarkerState {
739 scrollbar_size: Size<Pixels>,
740 dirty: bool,
741 markers: Arc<[PaintQuad]>,
742 pending_refresh: Option<Task<Result<()>>>,
743}
744
745impl ScrollbarMarkerState {
746 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
747 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
748 }
749}
750
751#[derive(Clone, Copy, PartialEq, Eq)]
752pub enum MinimapVisibility {
753 Disabled,
754 Enabled {
755 /// The configuration currently present in the users settings.
756 setting_configuration: bool,
757 /// Whether to override the currently set visibility from the users setting.
758 toggle_override: bool,
759 },
760}
761
762impl MinimapVisibility {
763 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
764 if mode.is_full() {
765 Self::Enabled {
766 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
767 toggle_override: false,
768 }
769 } else {
770 Self::Disabled
771 }
772 }
773
774 fn hidden(&self) -> Self {
775 match *self {
776 Self::Enabled {
777 setting_configuration,
778 ..
779 } => Self::Enabled {
780 setting_configuration,
781 toggle_override: setting_configuration,
782 },
783 Self::Disabled => Self::Disabled,
784 }
785 }
786
787 fn disabled(&self) -> bool {
788 matches!(*self, Self::Disabled)
789 }
790
791 fn settings_visibility(&self) -> bool {
792 match *self {
793 Self::Enabled {
794 setting_configuration,
795 ..
796 } => setting_configuration,
797 _ => false,
798 }
799 }
800
801 fn visible(&self) -> bool {
802 match *self {
803 Self::Enabled {
804 setting_configuration,
805 toggle_override,
806 } => setting_configuration ^ toggle_override,
807 _ => false,
808 }
809 }
810
811 fn toggle_visibility(&self) -> Self {
812 match *self {
813 Self::Enabled {
814 toggle_override,
815 setting_configuration,
816 } => Self::Enabled {
817 setting_configuration,
818 toggle_override: !toggle_override,
819 },
820 Self::Disabled => Self::Disabled,
821 }
822 }
823}
824
825#[derive(Debug, Clone, Copy, PartialEq, Eq)]
826pub enum BufferSerialization {
827 All,
828 NonDirtyBuffers,
829}
830
831impl BufferSerialization {
832 fn new(restore_unsaved_buffers: bool) -> Self {
833 if restore_unsaved_buffers {
834 Self::All
835 } else {
836 Self::NonDirtyBuffers
837 }
838 }
839}
840
841#[derive(Clone, Debug)]
842struct RunnableTasks {
843 templates: Vec<(TaskSourceKind, TaskTemplate)>,
844 offset: multi_buffer::Anchor,
845 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
846 column: u32,
847 // Values of all named captures, including those starting with '_'
848 extra_variables: HashMap<String, String>,
849 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
850 context_range: Range<BufferOffset>,
851}
852
853impl RunnableTasks {
854 fn resolve<'a>(
855 &'a self,
856 cx: &'a task::TaskContext,
857 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
858 self.templates.iter().filter_map(|(kind, template)| {
859 template
860 .resolve_task(&kind.to_id_base(), cx)
861 .map(|task| (kind.clone(), task))
862 })
863 }
864}
865
866#[derive(Clone)]
867pub struct ResolvedTasks {
868 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
869 position: Anchor,
870}
871
872/// Addons allow storing per-editor state in other crates (e.g. Vim)
873pub trait Addon: 'static {
874 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
875
876 fn render_buffer_header_controls(
877 &self,
878 _: &ExcerptInfo,
879 _: &Window,
880 _: &App,
881 ) -> Option<AnyElement> {
882 None
883 }
884
885 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
886 None
887 }
888
889 fn to_any(&self) -> &dyn std::any::Any;
890
891 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
892 None
893 }
894}
895
896struct ChangeLocation {
897 current: Option<Vec<Anchor>>,
898 original: Vec<Anchor>,
899}
900impl ChangeLocation {
901 fn locations(&self) -> &[Anchor] {
902 self.current.as_ref().unwrap_or(&self.original)
903 }
904}
905
906/// A set of caret positions, registered when the editor was edited.
907pub struct ChangeList {
908 changes: Vec<ChangeLocation>,
909 /// Currently "selected" change.
910 position: Option<usize>,
911}
912
913impl ChangeList {
914 pub fn new() -> Self {
915 Self {
916 changes: Vec::new(),
917 position: None,
918 }
919 }
920
921 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
922 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
923 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
924 if self.changes.is_empty() {
925 return None;
926 }
927
928 let prev = self.position.unwrap_or(self.changes.len());
929 let next = if direction == Direction::Prev {
930 prev.saturating_sub(count)
931 } else {
932 (prev + count).min(self.changes.len() - 1)
933 };
934 self.position = Some(next);
935 self.changes.get(next).map(|change| change.locations())
936 }
937
938 /// Adds a new change to the list, resetting the change list position.
939 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
940 self.position.take();
941 if let Some(last) = self.changes.last_mut()
942 && group
943 {
944 last.current = Some(new_positions)
945 } else {
946 self.changes.push(ChangeLocation {
947 original: new_positions,
948 current: None,
949 });
950 }
951 }
952
953 pub fn last(&self) -> Option<&[Anchor]> {
954 self.changes.last().map(|change| change.locations())
955 }
956
957 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
958 self.changes.last().map(|change| change.original.as_slice())
959 }
960
961 pub fn invert_last_group(&mut self) {
962 if let Some(last) = self.changes.last_mut()
963 && let Some(current) = last.current.as_mut()
964 {
965 mem::swap(&mut last.original, current);
966 }
967 }
968}
969
970#[derive(Clone)]
971struct InlineBlamePopoverState {
972 scroll_handle: ScrollHandle,
973 commit_message: Option<ParsedCommitMessage>,
974 markdown: Entity<Markdown>,
975}
976
977struct InlineBlamePopover {
978 position: gpui::Point<Pixels>,
979 hide_task: Option<Task<()>>,
980 popover_bounds: Option<Bounds<Pixels>>,
981 popover_state: InlineBlamePopoverState,
982 keyboard_grace: bool,
983}
984
985enum SelectionDragState {
986 /// State when no drag related activity is detected.
987 None,
988 /// State when the mouse is down on a selection that is about to be dragged.
989 ReadyToDrag {
990 selection: Selection<Anchor>,
991 click_position: gpui::Point<Pixels>,
992 mouse_down_time: Instant,
993 },
994 /// State when the mouse is dragging the selection in the editor.
995 Dragging {
996 selection: Selection<Anchor>,
997 drop_cursor: Selection<Anchor>,
998 hide_drop_cursor: bool,
999 },
1000}
1001
1002enum ColumnarSelectionState {
1003 FromMouse {
1004 selection_tail: Anchor,
1005 display_point: Option<DisplayPoint>,
1006 },
1007 FromSelection {
1008 selection_tail: Anchor,
1009 },
1010}
1011
1012/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1013/// a breakpoint on them.
1014#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1015struct PhantomBreakpointIndicator {
1016 display_row: DisplayRow,
1017 /// There's a small debounce between hovering over the line and showing the indicator.
1018 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1019 is_active: bool,
1020 collides_with_existing_breakpoint: bool,
1021}
1022
1023/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1024///
1025/// See the [module level documentation](self) for more information.
1026pub struct Editor {
1027 focus_handle: FocusHandle,
1028 last_focused_descendant: Option<WeakFocusHandle>,
1029 /// The text buffer being edited
1030 buffer: Entity<MultiBuffer>,
1031 /// Map of how text in the buffer should be displayed.
1032 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1033 pub display_map: Entity<DisplayMap>,
1034 placeholder_display_map: Option<Entity<DisplayMap>>,
1035 pub selections: SelectionsCollection,
1036 pub scroll_manager: ScrollManager,
1037 /// When inline assist editors are linked, they all render cursors because
1038 /// typing enters text into each of them, even the ones that aren't focused.
1039 pub(crate) show_cursor_when_unfocused: bool,
1040 columnar_selection_state: Option<ColumnarSelectionState>,
1041 add_selections_state: Option<AddSelectionsState>,
1042 select_next_state: Option<SelectNextState>,
1043 select_prev_state: Option<SelectNextState>,
1044 selection_history: SelectionHistory,
1045 defer_selection_effects: bool,
1046 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1047 autoclose_regions: Vec<AutocloseRegion>,
1048 snippet_stack: InvalidationStack<SnippetState>,
1049 select_syntax_node_history: SelectSyntaxNodeHistory,
1050 ime_transaction: Option<TransactionId>,
1051 pub diagnostics_max_severity: DiagnosticSeverity,
1052 active_diagnostics: ActiveDiagnostic,
1053 show_inline_diagnostics: bool,
1054 inline_diagnostics_update: Task<()>,
1055 inline_diagnostics_enabled: bool,
1056 diagnostics_enabled: bool,
1057 word_completions_enabled: bool,
1058 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1059 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1060 hard_wrap: Option<usize>,
1061 project: Option<Entity<Project>>,
1062 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1063 completion_provider: Option<Rc<dyn CompletionProvider>>,
1064 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1065 blink_manager: Entity<BlinkManager>,
1066 show_cursor_names: bool,
1067 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1068 pub show_local_selections: bool,
1069 mode: EditorMode,
1070 show_breadcrumbs: bool,
1071 show_gutter: bool,
1072 show_scrollbars: ScrollbarAxes,
1073 minimap_visibility: MinimapVisibility,
1074 offset_content: bool,
1075 disable_expand_excerpt_buttons: bool,
1076 show_line_numbers: Option<bool>,
1077 use_relative_line_numbers: Option<bool>,
1078 show_git_diff_gutter: Option<bool>,
1079 show_code_actions: Option<bool>,
1080 show_runnables: Option<bool>,
1081 show_breakpoints: Option<bool>,
1082 show_wrap_guides: Option<bool>,
1083 show_indent_guides: Option<bool>,
1084 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1085 highlight_order: usize,
1086 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1087 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1088 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1089 scrollbar_marker_state: ScrollbarMarkerState,
1090 active_indent_guides_state: ActiveIndentGuidesState,
1091 nav_history: Option<ItemNavHistory>,
1092 context_menu: RefCell<Option<CodeContextMenu>>,
1093 context_menu_options: Option<ContextMenuOptions>,
1094 mouse_context_menu: Option<MouseContextMenu>,
1095 completion_tasks: Vec<(CompletionId, Task<()>)>,
1096 inline_blame_popover: Option<InlineBlamePopover>,
1097 inline_blame_popover_show_task: Option<Task<()>>,
1098 signature_help_state: SignatureHelpState,
1099 auto_signature_help: Option<bool>,
1100 find_all_references_task_sources: Vec<Anchor>,
1101 next_completion_id: CompletionId,
1102 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1103 code_actions_task: Option<Task<Result<()>>>,
1104 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1105 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1106 document_highlights_task: Option<Task<()>>,
1107 linked_editing_range_task: Option<Task<Option<()>>>,
1108 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1109 pending_rename: Option<RenameState>,
1110 searchable: bool,
1111 cursor_shape: CursorShape,
1112 /// Whether the cursor is offset one character to the left when something is
1113 /// selected (needed for vim visual mode)
1114 cursor_offset_on_selection: bool,
1115 current_line_highlight: Option<CurrentLineHighlight>,
1116 pub collapse_matches: bool,
1117 autoindent_mode: Option<AutoindentMode>,
1118 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1119 input_enabled: bool,
1120 use_modal_editing: bool,
1121 read_only: bool,
1122 leader_id: Option<CollaboratorId>,
1123 remote_id: Option<ViewId>,
1124 pub hover_state: HoverState,
1125 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1126 prev_pressure_stage: Option<PressureStage>,
1127 gutter_hovered: bool,
1128 hovered_link_state: Option<HoveredLinkState>,
1129 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1130 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1131 active_edit_prediction: Option<EditPredictionState>,
1132 /// Used to prevent flickering as the user types while the menu is open
1133 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1134 edit_prediction_settings: EditPredictionSettings,
1135 edit_predictions_hidden_for_vim_mode: bool,
1136 show_edit_predictions_override: Option<bool>,
1137 show_completions_on_input_override: Option<bool>,
1138 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1139 edit_prediction_preview: EditPredictionPreview,
1140 edit_prediction_indent_conflict: bool,
1141 edit_prediction_requires_modifier_in_indent_conflict: bool,
1142 next_inlay_id: usize,
1143 next_color_inlay_id: usize,
1144 _subscriptions: Vec<Subscription>,
1145 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1146 gutter_dimensions: GutterDimensions,
1147 style: Option<EditorStyle>,
1148 text_style_refinement: Option<TextStyleRefinement>,
1149 next_editor_action_id: EditorActionId,
1150 editor_actions: Rc<
1151 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1152 >,
1153 use_autoclose: bool,
1154 use_auto_surround: bool,
1155 auto_replace_emoji_shortcode: bool,
1156 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1157 show_git_blame_gutter: bool,
1158 show_git_blame_inline: bool,
1159 show_git_blame_inline_delay_task: Option<Task<()>>,
1160 git_blame_inline_enabled: bool,
1161 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1162 buffer_serialization: Option<BufferSerialization>,
1163 show_selection_menu: Option<bool>,
1164 blame: Option<Entity<GitBlame>>,
1165 blame_subscription: Option<Subscription>,
1166 custom_context_menu: Option<
1167 Box<
1168 dyn 'static
1169 + Fn(
1170 &mut Self,
1171 DisplayPoint,
1172 &mut Window,
1173 &mut Context<Self>,
1174 ) -> Option<Entity<ui::ContextMenu>>,
1175 >,
1176 >,
1177 last_bounds: Option<Bounds<Pixels>>,
1178 last_position_map: Option<Rc<PositionMap>>,
1179 expect_bounds_change: Option<Bounds<Pixels>>,
1180 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1181 tasks_update_task: Option<Task<()>>,
1182 breakpoint_store: Option<Entity<BreakpointStore>>,
1183 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1184 hovered_diff_hunk_row: Option<DisplayRow>,
1185 pull_diagnostics_task: Task<()>,
1186 pull_diagnostics_background_task: Task<()>,
1187 in_project_search: bool,
1188 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1189 breadcrumb_header: Option<String>,
1190 focused_block: Option<FocusedBlock>,
1191 next_scroll_position: NextScrollCursorCenterTopBottom,
1192 addons: HashMap<TypeId, Box<dyn Addon>>,
1193 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1194 load_diff_task: Option<Shared<Task<()>>>,
1195 /// Whether we are temporarily displaying a diff other than git's
1196 temporary_diff_override: bool,
1197 selection_mark_mode: bool,
1198 toggle_fold_multiple_buffers: Task<()>,
1199 _scroll_cursor_center_top_bottom_task: Task<()>,
1200 serialize_selections: Task<()>,
1201 serialize_folds: Task<()>,
1202 mouse_cursor_hidden: bool,
1203 minimap: Option<Entity<Self>>,
1204 hide_mouse_mode: HideMouseMode,
1205 pub change_list: ChangeList,
1206 inline_value_cache: InlineValueCache,
1207
1208 selection_drag_state: SelectionDragState,
1209 colors: Option<LspColorData>,
1210 post_scroll_update: Task<()>,
1211 refresh_colors_task: Task<()>,
1212 inlay_hints: Option<LspInlayHintData>,
1213 folding_newlines: Task<()>,
1214 select_next_is_case_sensitive: Option<bool>,
1215 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1216 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1217 accent_data: Option<AccentData>,
1218 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1219 use_base_text_line_numbers: bool,
1220}
1221
1222#[derive(Debug, PartialEq)]
1223struct AccentData {
1224 colors: AccentColors,
1225 overrides: Vec<SharedString>,
1226}
1227
1228fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1229 if debounce_ms > 0 {
1230 Some(Duration::from_millis(debounce_ms))
1231 } else {
1232 None
1233 }
1234}
1235
1236#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1237enum NextScrollCursorCenterTopBottom {
1238 #[default]
1239 Center,
1240 Top,
1241 Bottom,
1242}
1243
1244impl NextScrollCursorCenterTopBottom {
1245 fn next(&self) -> Self {
1246 match self {
1247 Self::Center => Self::Top,
1248 Self::Top => Self::Bottom,
1249 Self::Bottom => Self::Center,
1250 }
1251 }
1252}
1253
1254#[derive(Clone)]
1255pub struct EditorSnapshot {
1256 pub mode: EditorMode,
1257 show_gutter: bool,
1258 offset_content: bool,
1259 show_line_numbers: Option<bool>,
1260 show_git_diff_gutter: Option<bool>,
1261 show_code_actions: Option<bool>,
1262 show_runnables: Option<bool>,
1263 show_breakpoints: Option<bool>,
1264 git_blame_gutter_max_author_length: Option<usize>,
1265 pub display_snapshot: DisplaySnapshot,
1266 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1267 is_focused: bool,
1268 scroll_anchor: ScrollAnchor,
1269 ongoing_scroll: OngoingScroll,
1270 current_line_highlight: CurrentLineHighlight,
1271 gutter_hovered: bool,
1272}
1273
1274#[derive(Default, Debug, Clone, Copy)]
1275pub struct GutterDimensions {
1276 pub left_padding: Pixels,
1277 pub right_padding: Pixels,
1278 pub width: Pixels,
1279 pub margin: Pixels,
1280 pub git_blame_entries_width: Option<Pixels>,
1281}
1282
1283impl GutterDimensions {
1284 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1285 Self {
1286 margin: Self::default_gutter_margin(font_id, font_size, cx),
1287 ..Default::default()
1288 }
1289 }
1290
1291 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1292 -cx.text_system().descent(font_id, font_size)
1293 }
1294 /// The full width of the space taken up by the gutter.
1295 pub fn full_width(&self) -> Pixels {
1296 self.margin + self.width
1297 }
1298
1299 /// The width of the space reserved for the fold indicators,
1300 /// use alongside 'justify_end' and `gutter_width` to
1301 /// right align content with the line numbers
1302 pub fn fold_area_width(&self) -> Pixels {
1303 self.margin + self.right_padding
1304 }
1305}
1306
1307struct CharacterDimensions {
1308 em_width: Pixels,
1309 em_advance: Pixels,
1310 line_height: Pixels,
1311}
1312
1313#[derive(Debug)]
1314pub struct RemoteSelection {
1315 pub replica_id: ReplicaId,
1316 pub selection: Selection<Anchor>,
1317 pub cursor_shape: CursorShape,
1318 pub collaborator_id: CollaboratorId,
1319 pub line_mode: bool,
1320 pub user_name: Option<SharedString>,
1321 pub color: PlayerColor,
1322}
1323
1324#[derive(Clone, Debug)]
1325struct SelectionHistoryEntry {
1326 selections: Arc<[Selection<Anchor>]>,
1327 select_next_state: Option<SelectNextState>,
1328 select_prev_state: Option<SelectNextState>,
1329 add_selections_state: Option<AddSelectionsState>,
1330}
1331
1332#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1333enum SelectionHistoryMode {
1334 #[default]
1335 Normal,
1336 Undoing,
1337 Redoing,
1338 Skipping,
1339}
1340
1341#[derive(Clone, PartialEq, Eq, Hash)]
1342struct HoveredCursor {
1343 replica_id: ReplicaId,
1344 selection_id: usize,
1345}
1346
1347#[derive(Debug)]
1348/// SelectionEffects controls the side-effects of updating the selection.
1349///
1350/// The default behaviour does "what you mostly want":
1351/// - it pushes to the nav history if the cursor moved by >10 lines
1352/// - it re-triggers completion requests
1353/// - it scrolls to fit
1354///
1355/// You might want to modify these behaviours. For example when doing a "jump"
1356/// like go to definition, we always want to add to nav history; but when scrolling
1357/// in vim mode we never do.
1358///
1359/// Similarly, you might want to disable scrolling if you don't want the viewport to
1360/// move.
1361#[derive(Clone)]
1362pub struct SelectionEffects {
1363 nav_history: Option<bool>,
1364 completions: bool,
1365 scroll: Option<Autoscroll>,
1366}
1367
1368impl Default for SelectionEffects {
1369 fn default() -> Self {
1370 Self {
1371 nav_history: None,
1372 completions: true,
1373 scroll: Some(Autoscroll::fit()),
1374 }
1375 }
1376}
1377impl SelectionEffects {
1378 pub fn scroll(scroll: Autoscroll) -> Self {
1379 Self {
1380 scroll: Some(scroll),
1381 ..Default::default()
1382 }
1383 }
1384
1385 pub fn no_scroll() -> Self {
1386 Self {
1387 scroll: None,
1388 ..Default::default()
1389 }
1390 }
1391
1392 pub fn completions(self, completions: bool) -> Self {
1393 Self {
1394 completions,
1395 ..self
1396 }
1397 }
1398
1399 pub fn nav_history(self, nav_history: bool) -> Self {
1400 Self {
1401 nav_history: Some(nav_history),
1402 ..self
1403 }
1404 }
1405}
1406
1407struct DeferredSelectionEffectsState {
1408 changed: bool,
1409 effects: SelectionEffects,
1410 old_cursor_position: Anchor,
1411 history_entry: SelectionHistoryEntry,
1412}
1413
1414#[derive(Default)]
1415struct SelectionHistory {
1416 #[allow(clippy::type_complexity)]
1417 selections_by_transaction:
1418 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1419 mode: SelectionHistoryMode,
1420 undo_stack: VecDeque<SelectionHistoryEntry>,
1421 redo_stack: VecDeque<SelectionHistoryEntry>,
1422}
1423
1424impl SelectionHistory {
1425 #[track_caller]
1426 fn insert_transaction(
1427 &mut self,
1428 transaction_id: TransactionId,
1429 selections: Arc<[Selection<Anchor>]>,
1430 ) {
1431 if selections.is_empty() {
1432 log::error!(
1433 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1434 std::panic::Location::caller()
1435 );
1436 return;
1437 }
1438 self.selections_by_transaction
1439 .insert(transaction_id, (selections, None));
1440 }
1441
1442 #[allow(clippy::type_complexity)]
1443 fn transaction(
1444 &self,
1445 transaction_id: TransactionId,
1446 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1447 self.selections_by_transaction.get(&transaction_id)
1448 }
1449
1450 #[allow(clippy::type_complexity)]
1451 fn transaction_mut(
1452 &mut self,
1453 transaction_id: TransactionId,
1454 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1455 self.selections_by_transaction.get_mut(&transaction_id)
1456 }
1457
1458 fn push(&mut self, entry: SelectionHistoryEntry) {
1459 if !entry.selections.is_empty() {
1460 match self.mode {
1461 SelectionHistoryMode::Normal => {
1462 self.push_undo(entry);
1463 self.redo_stack.clear();
1464 }
1465 SelectionHistoryMode::Undoing => self.push_redo(entry),
1466 SelectionHistoryMode::Redoing => self.push_undo(entry),
1467 SelectionHistoryMode::Skipping => {}
1468 }
1469 }
1470 }
1471
1472 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1473 if self
1474 .undo_stack
1475 .back()
1476 .is_none_or(|e| e.selections != entry.selections)
1477 {
1478 self.undo_stack.push_back(entry);
1479 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1480 self.undo_stack.pop_front();
1481 }
1482 }
1483 }
1484
1485 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1486 if self
1487 .redo_stack
1488 .back()
1489 .is_none_or(|e| e.selections != entry.selections)
1490 {
1491 self.redo_stack.push_back(entry);
1492 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1493 self.redo_stack.pop_front();
1494 }
1495 }
1496 }
1497}
1498
1499#[derive(Clone, Copy)]
1500pub struct RowHighlightOptions {
1501 pub autoscroll: bool,
1502 pub include_gutter: bool,
1503}
1504
1505impl Default for RowHighlightOptions {
1506 fn default() -> Self {
1507 Self {
1508 autoscroll: Default::default(),
1509 include_gutter: true,
1510 }
1511 }
1512}
1513
1514struct RowHighlight {
1515 index: usize,
1516 range: Range<Anchor>,
1517 color: Hsla,
1518 options: RowHighlightOptions,
1519 type_id: TypeId,
1520}
1521
1522#[derive(Clone, Debug)]
1523struct AddSelectionsState {
1524 groups: Vec<AddSelectionsGroup>,
1525}
1526
1527#[derive(Clone, Debug)]
1528struct AddSelectionsGroup {
1529 above: bool,
1530 stack: Vec<usize>,
1531}
1532
1533#[derive(Clone)]
1534struct SelectNextState {
1535 query: AhoCorasick,
1536 wordwise: bool,
1537 done: bool,
1538}
1539
1540impl std::fmt::Debug for SelectNextState {
1541 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1542 f.debug_struct(std::any::type_name::<Self>())
1543 .field("wordwise", &self.wordwise)
1544 .field("done", &self.done)
1545 .finish()
1546 }
1547}
1548
1549#[derive(Debug)]
1550struct AutocloseRegion {
1551 selection_id: usize,
1552 range: Range<Anchor>,
1553 pair: BracketPair,
1554}
1555
1556#[derive(Debug)]
1557struct SnippetState {
1558 ranges: Vec<Vec<Range<Anchor>>>,
1559 active_index: usize,
1560 choices: Vec<Option<Vec<String>>>,
1561}
1562
1563#[doc(hidden)]
1564pub struct RenameState {
1565 pub range: Range<Anchor>,
1566 pub old_name: Arc<str>,
1567 pub editor: Entity<Editor>,
1568 block_id: CustomBlockId,
1569}
1570
1571struct InvalidationStack<T>(Vec<T>);
1572
1573struct RegisteredEditPredictionDelegate {
1574 provider: Arc<dyn EditPredictionDelegateHandle>,
1575 _subscription: Subscription,
1576}
1577
1578#[derive(Debug, PartialEq, Eq)]
1579pub struct ActiveDiagnosticGroup {
1580 pub active_range: Range<Anchor>,
1581 pub active_message: String,
1582 pub group_id: usize,
1583 pub blocks: HashSet<CustomBlockId>,
1584}
1585
1586#[derive(Debug, PartialEq, Eq)]
1587
1588pub(crate) enum ActiveDiagnostic {
1589 None,
1590 All,
1591 Group(ActiveDiagnosticGroup),
1592}
1593
1594#[derive(Serialize, Deserialize, Clone, Debug)]
1595pub struct ClipboardSelection {
1596 /// The number of bytes in this selection.
1597 pub len: usize,
1598 /// Whether this was a full-line selection.
1599 pub is_entire_line: bool,
1600 /// The indentation of the first line when this content was originally copied.
1601 pub first_line_indent: u32,
1602 #[serde(default)]
1603 pub file_path: Option<PathBuf>,
1604 #[serde(default)]
1605 pub line_range: Option<RangeInclusive<u32>>,
1606}
1607
1608impl ClipboardSelection {
1609 pub fn for_buffer(
1610 len: usize,
1611 is_entire_line: bool,
1612 range: Range<Point>,
1613 buffer: &MultiBufferSnapshot,
1614 project: Option<&Entity<Project>>,
1615 cx: &App,
1616 ) -> Self {
1617 let first_line_indent = buffer
1618 .indent_size_for_line(MultiBufferRow(range.start.row))
1619 .len;
1620
1621 let file_path = util::maybe!({
1622 let project = project?.read(cx);
1623 let file = buffer.file_at(range.start)?;
1624 let project_path = ProjectPath {
1625 worktree_id: file.worktree_id(cx),
1626 path: file.path().clone(),
1627 };
1628 project.absolute_path(&project_path, cx)
1629 });
1630
1631 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1632
1633 Self {
1634 len,
1635 is_entire_line,
1636 first_line_indent,
1637 file_path,
1638 line_range,
1639 }
1640 }
1641}
1642
1643// selections, scroll behavior, was newest selection reversed
1644type SelectSyntaxNodeHistoryState = (
1645 Box<[Selection<MultiBufferOffset>]>,
1646 SelectSyntaxNodeScrollBehavior,
1647 bool,
1648);
1649
1650#[derive(Default)]
1651struct SelectSyntaxNodeHistory {
1652 stack: Vec<SelectSyntaxNodeHistoryState>,
1653 // disable temporarily to allow changing selections without losing the stack
1654 pub disable_clearing: bool,
1655}
1656
1657impl SelectSyntaxNodeHistory {
1658 pub fn try_clear(&mut self) {
1659 if !self.disable_clearing {
1660 self.stack.clear();
1661 }
1662 }
1663
1664 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1665 self.stack.push(selection);
1666 }
1667
1668 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1669 self.stack.pop()
1670 }
1671}
1672
1673enum SelectSyntaxNodeScrollBehavior {
1674 CursorTop,
1675 FitSelection,
1676 CursorBottom,
1677}
1678
1679#[derive(Debug)]
1680pub(crate) struct NavigationData {
1681 cursor_anchor: Anchor,
1682 cursor_position: Point,
1683 scroll_anchor: ScrollAnchor,
1684 scroll_top_row: u32,
1685}
1686
1687#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1688pub enum GotoDefinitionKind {
1689 Symbol,
1690 Declaration,
1691 Type,
1692 Implementation,
1693}
1694
1695pub enum FormatTarget {
1696 Buffers(HashSet<Entity<Buffer>>),
1697 Ranges(Vec<Range<MultiBufferPoint>>),
1698}
1699
1700pub(crate) struct FocusedBlock {
1701 id: BlockId,
1702 focus_handle: WeakFocusHandle,
1703}
1704
1705#[derive(Clone, Debug)]
1706enum JumpData {
1707 MultiBufferRow {
1708 row: MultiBufferRow,
1709 line_offset_from_top: u32,
1710 },
1711 MultiBufferPoint {
1712 excerpt_id: ExcerptId,
1713 position: Point,
1714 anchor: text::Anchor,
1715 line_offset_from_top: u32,
1716 },
1717}
1718
1719pub enum MultibufferSelectionMode {
1720 First,
1721 All,
1722}
1723
1724#[derive(Clone, Copy, Debug, Default)]
1725pub struct RewrapOptions {
1726 pub override_language_settings: bool,
1727 pub preserve_existing_whitespace: bool,
1728}
1729
1730impl Editor {
1731 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1732 let buffer = cx.new(|cx| Buffer::local("", cx));
1733 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1734 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1735 }
1736
1737 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1738 let buffer = cx.new(|cx| Buffer::local("", cx));
1739 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1740 Self::new(EditorMode::full(), buffer, None, window, cx)
1741 }
1742
1743 pub fn auto_height(
1744 min_lines: usize,
1745 max_lines: usize,
1746 window: &mut Window,
1747 cx: &mut Context<Self>,
1748 ) -> Self {
1749 let buffer = cx.new(|cx| Buffer::local("", cx));
1750 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1751 Self::new(
1752 EditorMode::AutoHeight {
1753 min_lines,
1754 max_lines: Some(max_lines),
1755 },
1756 buffer,
1757 None,
1758 window,
1759 cx,
1760 )
1761 }
1762
1763 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1764 /// The editor grows as tall as needed to fit its content.
1765 pub fn auto_height_unbounded(
1766 min_lines: usize,
1767 window: &mut Window,
1768 cx: &mut Context<Self>,
1769 ) -> Self {
1770 let buffer = cx.new(|cx| Buffer::local("", cx));
1771 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1772 Self::new(
1773 EditorMode::AutoHeight {
1774 min_lines,
1775 max_lines: None,
1776 },
1777 buffer,
1778 None,
1779 window,
1780 cx,
1781 )
1782 }
1783
1784 pub fn for_buffer(
1785 buffer: Entity<Buffer>,
1786 project: Option<Entity<Project>>,
1787 window: &mut Window,
1788 cx: &mut Context<Self>,
1789 ) -> Self {
1790 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1791 Self::new(EditorMode::full(), buffer, project, window, cx)
1792 }
1793
1794 pub fn for_multibuffer(
1795 buffer: Entity<MultiBuffer>,
1796 project: Option<Entity<Project>>,
1797 window: &mut Window,
1798 cx: &mut Context<Self>,
1799 ) -> Self {
1800 Self::new(EditorMode::full(), buffer, project, window, cx)
1801 }
1802
1803 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1804 let mut clone = Self::new(
1805 self.mode.clone(),
1806 self.buffer.clone(),
1807 self.project.clone(),
1808 window,
1809 cx,
1810 );
1811 self.display_map.update(cx, |display_map, cx| {
1812 let snapshot = display_map.snapshot(cx);
1813 clone.display_map.update(cx, |display_map, cx| {
1814 display_map.set_state(&snapshot, cx);
1815 });
1816 });
1817 clone.folds_did_change(cx);
1818 clone.selections.clone_state(&self.selections);
1819 clone.scroll_manager.clone_state(&self.scroll_manager);
1820 clone.searchable = self.searchable;
1821 clone.read_only = self.read_only;
1822 clone
1823 }
1824
1825 pub fn new(
1826 mode: EditorMode,
1827 buffer: Entity<MultiBuffer>,
1828 project: Option<Entity<Project>>,
1829 window: &mut Window,
1830 cx: &mut Context<Self>,
1831 ) -> Self {
1832 Editor::new_internal(mode, buffer, project, None, window, cx)
1833 }
1834
1835 pub fn sticky_headers(
1836 &self,
1837 style: &EditorStyle,
1838 cx: &App,
1839 ) -> Option<Vec<OutlineItem<Anchor>>> {
1840 let multi_buffer = self.buffer().read(cx);
1841 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1842 let multi_buffer_visible_start = self
1843 .scroll_manager
1844 .anchor()
1845 .anchor
1846 .to_point(&multi_buffer_snapshot);
1847 let max_row = multi_buffer_snapshot.max_point().row;
1848
1849 let start_row = (multi_buffer_visible_start.row).min(max_row);
1850 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1851
1852 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1853 let outline_items = buffer
1854 .outline_items_containing(
1855 Point::new(start_row, 0)..Point::new(end_row, 0),
1856 true,
1857 Some(style.syntax.as_ref()),
1858 )
1859 .into_iter()
1860 .map(|outline_item| OutlineItem {
1861 depth: outline_item.depth,
1862 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1863 source_range_for_text: Anchor::range_in_buffer(
1864 *excerpt_id,
1865 outline_item.source_range_for_text,
1866 ),
1867 text: outline_item.text,
1868 highlight_ranges: outline_item.highlight_ranges,
1869 name_ranges: outline_item.name_ranges,
1870 body_range: outline_item
1871 .body_range
1872 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1873 annotation_range: outline_item
1874 .annotation_range
1875 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1876 });
1877 return Some(outline_items.collect());
1878 }
1879
1880 None
1881 }
1882
1883 fn new_internal(
1884 mode: EditorMode,
1885 multi_buffer: Entity<MultiBuffer>,
1886 project: Option<Entity<Project>>,
1887 display_map: Option<Entity<DisplayMap>>,
1888 window: &mut Window,
1889 cx: &mut Context<Self>,
1890 ) -> Self {
1891 debug_assert!(
1892 display_map.is_none() || mode.is_minimap(),
1893 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1894 );
1895
1896 let full_mode = mode.is_full();
1897 let is_minimap = mode.is_minimap();
1898 let diagnostics_max_severity = if full_mode {
1899 EditorSettings::get_global(cx)
1900 .diagnostics_max_severity
1901 .unwrap_or(DiagnosticSeverity::Hint)
1902 } else {
1903 DiagnosticSeverity::Off
1904 };
1905 let style = window.text_style();
1906 let font_size = style.font_size.to_pixels(window.rem_size());
1907 let editor = cx.entity().downgrade();
1908 let fold_placeholder = FoldPlaceholder {
1909 constrain_width: false,
1910 render: Arc::new(move |fold_id, fold_range, cx| {
1911 let editor = editor.clone();
1912 div()
1913 .id(fold_id)
1914 .bg(cx.theme().colors().ghost_element_background)
1915 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1916 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1917 .rounded_xs()
1918 .size_full()
1919 .cursor_pointer()
1920 .child("⋯")
1921 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1922 .on_click(move |_, _window, cx| {
1923 editor
1924 .update(cx, |editor, cx| {
1925 editor.unfold_ranges(
1926 &[fold_range.start..fold_range.end],
1927 true,
1928 false,
1929 cx,
1930 );
1931 cx.stop_propagation();
1932 })
1933 .ok();
1934 })
1935 .into_any()
1936 }),
1937 merge_adjacent: true,
1938 ..FoldPlaceholder::default()
1939 };
1940 let display_map = display_map.unwrap_or_else(|| {
1941 cx.new(|cx| {
1942 DisplayMap::new(
1943 multi_buffer.clone(),
1944 style.font(),
1945 font_size,
1946 None,
1947 FILE_HEADER_HEIGHT,
1948 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1949 fold_placeholder,
1950 diagnostics_max_severity,
1951 cx,
1952 )
1953 })
1954 });
1955
1956 let selections = SelectionsCollection::new();
1957
1958 let blink_manager = cx.new(|cx| {
1959 let mut blink_manager = BlinkManager::new(
1960 CURSOR_BLINK_INTERVAL,
1961 |cx| EditorSettings::get_global(cx).cursor_blink,
1962 cx,
1963 );
1964 if is_minimap {
1965 blink_manager.disable(cx);
1966 }
1967 blink_manager
1968 });
1969
1970 let soft_wrap_mode_override =
1971 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1972
1973 let mut project_subscriptions = Vec::new();
1974 if full_mode && let Some(project) = project.as_ref() {
1975 project_subscriptions.push(cx.subscribe_in(
1976 project,
1977 window,
1978 |editor, _, event, window, cx| match event {
1979 project::Event::RefreshCodeLens => {
1980 // we always query lens with actions, without storing them, always refreshing them
1981 }
1982 project::Event::RefreshInlayHints {
1983 server_id,
1984 request_id,
1985 } => {
1986 editor.refresh_inlay_hints(
1987 InlayHintRefreshReason::RefreshRequested {
1988 server_id: *server_id,
1989 request_id: *request_id,
1990 },
1991 cx,
1992 );
1993 }
1994 project::Event::LanguageServerRemoved(..) => {
1995 if editor.tasks_update_task.is_none() {
1996 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1997 }
1998 editor.registered_buffers.clear();
1999 editor.register_visible_buffers(cx);
2000 }
2001 project::Event::LanguageServerAdded(..) => {
2002 if editor.tasks_update_task.is_none() {
2003 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2004 }
2005 }
2006 project::Event::SnippetEdit(id, snippet_edits) => {
2007 // todo(lw): Non singletons
2008 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2009 let snapshot = buffer.read(cx).snapshot();
2010 let focus_handle = editor.focus_handle(cx);
2011 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2012 for (range, snippet) in snippet_edits {
2013 let buffer_range =
2014 language::range_from_lsp(*range).to_offset(&snapshot);
2015 editor
2016 .insert_snippet(
2017 &[MultiBufferOffset(buffer_range.start)
2018 ..MultiBufferOffset(buffer_range.end)],
2019 snippet.clone(),
2020 window,
2021 cx,
2022 )
2023 .ok();
2024 }
2025 }
2026 }
2027 }
2028 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2029 let buffer_id = *buffer_id;
2030 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2031 editor.register_buffer(buffer_id, cx);
2032 editor.update_lsp_data(Some(buffer_id), window, cx);
2033 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2034 refresh_linked_ranges(editor, window, cx);
2035 editor.refresh_code_actions(window, cx);
2036 editor.refresh_document_highlights(cx);
2037 }
2038 }
2039
2040 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2041 let Some(workspace) = editor.workspace() else {
2042 return;
2043 };
2044 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2045 else {
2046 return;
2047 };
2048
2049 if active_editor.entity_id() == cx.entity_id() {
2050 let entity_id = cx.entity_id();
2051 workspace.update(cx, |this, cx| {
2052 this.panes_mut()
2053 .iter_mut()
2054 .filter(|pane| pane.entity_id() != entity_id)
2055 .for_each(|p| {
2056 p.update(cx, |pane, _| {
2057 pane.nav_history_mut().rename_item(
2058 entity_id,
2059 project_path.clone(),
2060 abs_path.clone().into(),
2061 );
2062 })
2063 });
2064 });
2065
2066 Self::open_transaction_for_hidden_buffers(
2067 workspace,
2068 transaction.clone(),
2069 "Rename".to_string(),
2070 window,
2071 cx,
2072 );
2073 }
2074 }
2075
2076 project::Event::WorkspaceEditApplied(transaction) => {
2077 let Some(workspace) = editor.workspace() else {
2078 return;
2079 };
2080 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2081 else {
2082 return;
2083 };
2084
2085 if active_editor.entity_id() == cx.entity_id() {
2086 Self::open_transaction_for_hidden_buffers(
2087 workspace,
2088 transaction.clone(),
2089 "LSP Edit".to_string(),
2090 window,
2091 cx,
2092 );
2093 }
2094 }
2095
2096 _ => {}
2097 },
2098 ));
2099 if let Some(task_inventory) = project
2100 .read(cx)
2101 .task_store()
2102 .read(cx)
2103 .task_inventory()
2104 .cloned()
2105 {
2106 project_subscriptions.push(cx.observe_in(
2107 &task_inventory,
2108 window,
2109 |editor, _, window, cx| {
2110 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2111 },
2112 ));
2113 };
2114
2115 project_subscriptions.push(cx.subscribe_in(
2116 &project.read(cx).breakpoint_store(),
2117 window,
2118 |editor, _, event, window, cx| match event {
2119 BreakpointStoreEvent::ClearDebugLines => {
2120 editor.clear_row_highlights::<ActiveDebugLine>();
2121 editor.refresh_inline_values(cx);
2122 }
2123 BreakpointStoreEvent::SetDebugLine => {
2124 if editor.go_to_active_debug_line(window, cx) {
2125 cx.stop_propagation();
2126 }
2127
2128 editor.refresh_inline_values(cx);
2129 }
2130 _ => {}
2131 },
2132 ));
2133 let git_store = project.read(cx).git_store().clone();
2134 let project = project.clone();
2135 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2136 if let GitStoreEvent::RepositoryAdded = event {
2137 this.load_diff_task = Some(
2138 update_uncommitted_diff_for_buffer(
2139 cx.entity(),
2140 &project,
2141 this.buffer.read(cx).all_buffers(),
2142 this.buffer.clone(),
2143 cx,
2144 )
2145 .shared(),
2146 );
2147 }
2148 }));
2149 }
2150
2151 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2152
2153 let inlay_hint_settings =
2154 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2155 let focus_handle = cx.focus_handle();
2156 if !is_minimap {
2157 cx.on_focus(&focus_handle, window, Self::handle_focus)
2158 .detach();
2159 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2160 .detach();
2161 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2162 .detach();
2163 cx.on_blur(&focus_handle, window, Self::handle_blur)
2164 .detach();
2165 cx.observe_pending_input(window, Self::observe_pending_input)
2166 .detach();
2167 }
2168
2169 let show_indent_guides =
2170 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2171 Some(false)
2172 } else {
2173 None
2174 };
2175
2176 let breakpoint_store = match (&mode, project.as_ref()) {
2177 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2178 _ => None,
2179 };
2180
2181 let mut code_action_providers = Vec::new();
2182 let mut load_uncommitted_diff = None;
2183 if let Some(project) = project.clone() {
2184 load_uncommitted_diff = Some(
2185 update_uncommitted_diff_for_buffer(
2186 cx.entity(),
2187 &project,
2188 multi_buffer.read(cx).all_buffers(),
2189 multi_buffer.clone(),
2190 cx,
2191 )
2192 .shared(),
2193 );
2194 code_action_providers.push(Rc::new(project) as Rc<_>);
2195 }
2196
2197 let mut editor = Self {
2198 focus_handle,
2199 show_cursor_when_unfocused: false,
2200 last_focused_descendant: None,
2201 buffer: multi_buffer.clone(),
2202 display_map: display_map.clone(),
2203 placeholder_display_map: None,
2204 selections,
2205 scroll_manager: ScrollManager::new(cx),
2206 columnar_selection_state: None,
2207 add_selections_state: None,
2208 select_next_state: None,
2209 select_prev_state: None,
2210 selection_history: SelectionHistory::default(),
2211 defer_selection_effects: false,
2212 deferred_selection_effects_state: None,
2213 autoclose_regions: Vec::new(),
2214 snippet_stack: InvalidationStack::default(),
2215 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2216 ime_transaction: None,
2217 active_diagnostics: ActiveDiagnostic::None,
2218 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2219 inline_diagnostics_update: Task::ready(()),
2220 inline_diagnostics: Vec::new(),
2221 soft_wrap_mode_override,
2222 diagnostics_max_severity,
2223 hard_wrap: None,
2224 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2225 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2226 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2227 project,
2228 blink_manager: blink_manager.clone(),
2229 show_local_selections: true,
2230 show_scrollbars: ScrollbarAxes {
2231 horizontal: full_mode,
2232 vertical: full_mode,
2233 },
2234 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2235 offset_content: !matches!(mode, EditorMode::SingleLine),
2236 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2237 show_gutter: full_mode,
2238 show_line_numbers: (!full_mode).then_some(false),
2239 use_relative_line_numbers: None,
2240 disable_expand_excerpt_buttons: !full_mode,
2241 show_git_diff_gutter: None,
2242 show_code_actions: None,
2243 show_runnables: None,
2244 show_breakpoints: None,
2245 show_wrap_guides: None,
2246 show_indent_guides,
2247 buffers_with_disabled_indent_guides: HashSet::default(),
2248 highlight_order: 0,
2249 highlighted_rows: HashMap::default(),
2250 background_highlights: HashMap::default(),
2251 gutter_highlights: HashMap::default(),
2252 scrollbar_marker_state: ScrollbarMarkerState::default(),
2253 active_indent_guides_state: ActiveIndentGuidesState::default(),
2254 nav_history: None,
2255 context_menu: RefCell::new(None),
2256 context_menu_options: None,
2257 mouse_context_menu: None,
2258 completion_tasks: Vec::new(),
2259 inline_blame_popover: None,
2260 inline_blame_popover_show_task: None,
2261 signature_help_state: SignatureHelpState::default(),
2262 auto_signature_help: None,
2263 find_all_references_task_sources: Vec::new(),
2264 next_completion_id: 0,
2265 next_inlay_id: 0,
2266 code_action_providers,
2267 available_code_actions: None,
2268 code_actions_task: None,
2269 quick_selection_highlight_task: None,
2270 debounced_selection_highlight_task: None,
2271 document_highlights_task: None,
2272 linked_editing_range_task: None,
2273 pending_rename: None,
2274 searchable: !is_minimap,
2275 cursor_shape: EditorSettings::get_global(cx)
2276 .cursor_shape
2277 .unwrap_or_default(),
2278 cursor_offset_on_selection: false,
2279 current_line_highlight: None,
2280 autoindent_mode: Some(AutoindentMode::EachLine),
2281 collapse_matches: false,
2282 workspace: None,
2283 input_enabled: !is_minimap,
2284 use_modal_editing: full_mode,
2285 read_only: is_minimap,
2286 use_autoclose: true,
2287 use_auto_surround: true,
2288 auto_replace_emoji_shortcode: false,
2289 jsx_tag_auto_close_enabled_in_any_buffer: false,
2290 leader_id: None,
2291 remote_id: None,
2292 hover_state: HoverState::default(),
2293 pending_mouse_down: None,
2294 prev_pressure_stage: None,
2295 hovered_link_state: None,
2296 edit_prediction_provider: None,
2297 active_edit_prediction: None,
2298 stale_edit_prediction_in_menu: None,
2299 edit_prediction_preview: EditPredictionPreview::Inactive {
2300 released_too_fast: false,
2301 },
2302 inline_diagnostics_enabled: full_mode,
2303 diagnostics_enabled: full_mode,
2304 word_completions_enabled: full_mode,
2305 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2306 gutter_hovered: false,
2307 pixel_position_of_newest_cursor: None,
2308 last_bounds: None,
2309 last_position_map: None,
2310 expect_bounds_change: None,
2311 gutter_dimensions: GutterDimensions::default(),
2312 style: None,
2313 show_cursor_names: false,
2314 hovered_cursors: HashMap::default(),
2315 next_editor_action_id: EditorActionId::default(),
2316 editor_actions: Rc::default(),
2317 edit_predictions_hidden_for_vim_mode: false,
2318 show_edit_predictions_override: None,
2319 show_completions_on_input_override: None,
2320 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2321 edit_prediction_settings: EditPredictionSettings::Disabled,
2322 edit_prediction_indent_conflict: false,
2323 edit_prediction_requires_modifier_in_indent_conflict: true,
2324 custom_context_menu: None,
2325 show_git_blame_gutter: false,
2326 show_git_blame_inline: false,
2327 show_selection_menu: None,
2328 show_git_blame_inline_delay_task: None,
2329 git_blame_inline_enabled: full_mode
2330 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2331 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2332 buffer_serialization: is_minimap.not().then(|| {
2333 BufferSerialization::new(
2334 ProjectSettings::get_global(cx)
2335 .session
2336 .restore_unsaved_buffers,
2337 )
2338 }),
2339 blame: None,
2340 blame_subscription: None,
2341 tasks: BTreeMap::default(),
2342
2343 breakpoint_store,
2344 gutter_breakpoint_indicator: (None, None),
2345 hovered_diff_hunk_row: None,
2346 _subscriptions: (!is_minimap)
2347 .then(|| {
2348 vec![
2349 cx.observe(&multi_buffer, Self::on_buffer_changed),
2350 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2351 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2352 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2353 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2354 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2355 cx.observe_window_activation(window, |editor, window, cx| {
2356 let active = window.is_window_active();
2357 editor.blink_manager.update(cx, |blink_manager, cx| {
2358 if active {
2359 blink_manager.enable(cx);
2360 } else {
2361 blink_manager.disable(cx);
2362 }
2363 });
2364 if active {
2365 editor.show_mouse_cursor(cx);
2366 }
2367 }),
2368 ]
2369 })
2370 .unwrap_or_default(),
2371 tasks_update_task: None,
2372 pull_diagnostics_task: Task::ready(()),
2373 pull_diagnostics_background_task: Task::ready(()),
2374 colors: None,
2375 refresh_colors_task: Task::ready(()),
2376 inlay_hints: None,
2377 next_color_inlay_id: 0,
2378 post_scroll_update: Task::ready(()),
2379 linked_edit_ranges: Default::default(),
2380 in_project_search: false,
2381 previous_search_ranges: None,
2382 breadcrumb_header: None,
2383 focused_block: None,
2384 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2385 addons: HashMap::default(),
2386 registered_buffers: HashMap::default(),
2387 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2388 selection_mark_mode: false,
2389 toggle_fold_multiple_buffers: Task::ready(()),
2390 serialize_selections: Task::ready(()),
2391 serialize_folds: Task::ready(()),
2392 text_style_refinement: None,
2393 load_diff_task: load_uncommitted_diff,
2394 temporary_diff_override: false,
2395 mouse_cursor_hidden: false,
2396 minimap: None,
2397 hide_mouse_mode: EditorSettings::get_global(cx)
2398 .hide_mouse
2399 .unwrap_or_default(),
2400 change_list: ChangeList::new(),
2401 mode,
2402 selection_drag_state: SelectionDragState::None,
2403 folding_newlines: Task::ready(()),
2404 lookup_key: None,
2405 select_next_is_case_sensitive: None,
2406 applicable_language_settings: HashMap::default(),
2407 accent_data: None,
2408 fetched_tree_sitter_chunks: HashMap::default(),
2409 use_base_text_line_numbers: false,
2410 };
2411
2412 if is_minimap {
2413 return editor;
2414 }
2415
2416 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2417 editor.accent_data = editor.fetch_accent_data(cx);
2418
2419 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2420 editor
2421 ._subscriptions
2422 .push(cx.observe(breakpoints, |_, _, cx| {
2423 cx.notify();
2424 }));
2425 }
2426 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2427 editor._subscriptions.extend(project_subscriptions);
2428
2429 editor._subscriptions.push(cx.subscribe_in(
2430 &cx.entity(),
2431 window,
2432 |editor, _, e: &EditorEvent, window, cx| match e {
2433 EditorEvent::ScrollPositionChanged { local, .. } => {
2434 if *local {
2435 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2436 editor.inline_blame_popover.take();
2437 let new_anchor = editor.scroll_manager.anchor();
2438 let snapshot = editor.snapshot(window, cx);
2439 editor.update_restoration_data(cx, move |data| {
2440 data.scroll_position = (
2441 new_anchor.top_row(snapshot.buffer_snapshot()),
2442 new_anchor.offset,
2443 );
2444 });
2445
2446 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2447 cx.background_executor()
2448 .timer(Duration::from_millis(50))
2449 .await;
2450 editor
2451 .update_in(cx, |editor, window, cx| {
2452 editor.register_visible_buffers(cx);
2453 editor.refresh_colors_for_visible_range(None, window, cx);
2454 editor.refresh_inlay_hints(
2455 InlayHintRefreshReason::NewLinesShown,
2456 cx,
2457 );
2458 editor.colorize_brackets(false, cx);
2459 })
2460 .ok();
2461 });
2462 }
2463 }
2464 EditorEvent::Edited { .. } => {
2465 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2466 .map(|vim_mode| vim_mode.0)
2467 .unwrap_or(false);
2468 if !vim_mode {
2469 let display_map = editor.display_snapshot(cx);
2470 let selections = editor.selections.all_adjusted_display(&display_map);
2471 let pop_state = editor
2472 .change_list
2473 .last()
2474 .map(|previous| {
2475 previous.len() == selections.len()
2476 && previous.iter().enumerate().all(|(ix, p)| {
2477 p.to_display_point(&display_map).row()
2478 == selections[ix].head().row()
2479 })
2480 })
2481 .unwrap_or(false);
2482 let new_positions = selections
2483 .into_iter()
2484 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2485 .collect();
2486 editor
2487 .change_list
2488 .push_to_change_list(pop_state, new_positions);
2489 }
2490 }
2491 _ => (),
2492 },
2493 ));
2494
2495 if let Some(dap_store) = editor
2496 .project
2497 .as_ref()
2498 .map(|project| project.read(cx).dap_store())
2499 {
2500 let weak_editor = cx.weak_entity();
2501
2502 editor
2503 ._subscriptions
2504 .push(
2505 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2506 let session_entity = cx.entity();
2507 weak_editor
2508 .update(cx, |editor, cx| {
2509 editor._subscriptions.push(
2510 cx.subscribe(&session_entity, Self::on_debug_session_event),
2511 );
2512 })
2513 .ok();
2514 }),
2515 );
2516
2517 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2518 editor
2519 ._subscriptions
2520 .push(cx.subscribe(&session, Self::on_debug_session_event));
2521 }
2522 }
2523
2524 // skip adding the initial selection to selection history
2525 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2526 editor.end_selection(window, cx);
2527 editor.selection_history.mode = SelectionHistoryMode::Normal;
2528
2529 editor.scroll_manager.show_scrollbars(window, cx);
2530 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2531
2532 if full_mode {
2533 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2534 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2535
2536 if editor.git_blame_inline_enabled {
2537 editor.start_git_blame_inline(false, window, cx);
2538 }
2539
2540 editor.go_to_active_debug_line(window, cx);
2541
2542 editor.minimap =
2543 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2544 editor.colors = Some(LspColorData::new(cx));
2545 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2546
2547 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2548 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2549 }
2550 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2551 }
2552
2553 editor
2554 }
2555
2556 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2557 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2558 }
2559
2560 pub fn deploy_mouse_context_menu(
2561 &mut self,
2562 position: gpui::Point<Pixels>,
2563 context_menu: Entity<ContextMenu>,
2564 window: &mut Window,
2565 cx: &mut Context<Self>,
2566 ) {
2567 self.mouse_context_menu = Some(MouseContextMenu::new(
2568 self,
2569 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2570 context_menu,
2571 window,
2572 cx,
2573 ));
2574 }
2575
2576 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2577 self.mouse_context_menu
2578 .as_ref()
2579 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2580 }
2581
2582 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2583 if self
2584 .selections
2585 .pending_anchor()
2586 .is_some_and(|pending_selection| {
2587 let snapshot = self.buffer().read(cx).snapshot(cx);
2588 pending_selection.range().includes(range, &snapshot)
2589 })
2590 {
2591 return true;
2592 }
2593
2594 self.selections
2595 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2596 .into_iter()
2597 .any(|selection| {
2598 // This is needed to cover a corner case, if we just check for an existing
2599 // selection in the fold range, having a cursor at the start of the fold
2600 // marks it as selected. Non-empty selections don't cause this.
2601 let length = selection.end - selection.start;
2602 length > 0
2603 })
2604 }
2605
2606 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2607 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2608 }
2609
2610 fn key_context_internal(
2611 &self,
2612 has_active_edit_prediction: bool,
2613 window: &mut Window,
2614 cx: &mut App,
2615 ) -> KeyContext {
2616 let mut key_context = KeyContext::new_with_defaults();
2617 key_context.add("Editor");
2618 let mode = match self.mode {
2619 EditorMode::SingleLine => "single_line",
2620 EditorMode::AutoHeight { .. } => "auto_height",
2621 EditorMode::Minimap { .. } => "minimap",
2622 EditorMode::Full { .. } => "full",
2623 };
2624
2625 if EditorSettings::jupyter_enabled(cx) {
2626 key_context.add("jupyter");
2627 }
2628
2629 key_context.set("mode", mode);
2630 if self.pending_rename.is_some() {
2631 key_context.add("renaming");
2632 }
2633
2634 if let Some(snippet_stack) = self.snippet_stack.last() {
2635 key_context.add("in_snippet");
2636
2637 if snippet_stack.active_index > 0 {
2638 key_context.add("has_previous_tabstop");
2639 }
2640
2641 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2642 key_context.add("has_next_tabstop");
2643 }
2644 }
2645
2646 match self.context_menu.borrow().as_ref() {
2647 Some(CodeContextMenu::Completions(menu)) => {
2648 if menu.visible() {
2649 key_context.add("menu");
2650 key_context.add("showing_completions");
2651 }
2652 }
2653 Some(CodeContextMenu::CodeActions(menu)) => {
2654 if menu.visible() {
2655 key_context.add("menu");
2656 key_context.add("showing_code_actions")
2657 }
2658 }
2659 None => {}
2660 }
2661
2662 if self.signature_help_state.has_multiple_signatures() {
2663 key_context.add("showing_signature_help");
2664 }
2665
2666 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2667 if !self.focus_handle(cx).contains_focused(window, cx)
2668 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2669 {
2670 for addon in self.addons.values() {
2671 addon.extend_key_context(&mut key_context, cx)
2672 }
2673 }
2674
2675 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2676 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2677 Some(
2678 file.full_path(cx)
2679 .extension()?
2680 .to_string_lossy()
2681 .into_owned(),
2682 )
2683 }) {
2684 key_context.set("extension", extension);
2685 }
2686 } else {
2687 key_context.add("multibuffer");
2688 }
2689
2690 if has_active_edit_prediction {
2691 if self.edit_prediction_in_conflict() {
2692 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2693 } else {
2694 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2695 key_context.add("copilot_suggestion");
2696 }
2697 }
2698
2699 if self.selection_mark_mode {
2700 key_context.add("selection_mode");
2701 }
2702
2703 let disjoint = self.selections.disjoint_anchors();
2704 let snapshot = self.snapshot(window, cx);
2705 let snapshot = snapshot.buffer_snapshot();
2706 if self.mode == EditorMode::SingleLine
2707 && let [selection] = disjoint
2708 && selection.start == selection.end
2709 && selection.end.to_offset(snapshot) == snapshot.len()
2710 {
2711 key_context.add("end_of_input");
2712 }
2713
2714 if self.has_any_expanded_diff_hunks(cx) {
2715 key_context.add("diffs_expanded");
2716 }
2717
2718 key_context
2719 }
2720
2721 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2722 self.last_bounds.as_ref()
2723 }
2724
2725 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2726 if self.mouse_cursor_hidden {
2727 self.mouse_cursor_hidden = false;
2728 cx.notify();
2729 }
2730 }
2731
2732 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2733 let hide_mouse_cursor = match origin {
2734 HideMouseCursorOrigin::TypingAction => {
2735 matches!(
2736 self.hide_mouse_mode,
2737 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2738 )
2739 }
2740 HideMouseCursorOrigin::MovementAction => {
2741 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2742 }
2743 };
2744 if self.mouse_cursor_hidden != hide_mouse_cursor {
2745 self.mouse_cursor_hidden = hide_mouse_cursor;
2746 cx.notify();
2747 }
2748 }
2749
2750 pub fn edit_prediction_in_conflict(&self) -> bool {
2751 if !self.show_edit_predictions_in_menu() {
2752 return false;
2753 }
2754
2755 let showing_completions = self
2756 .context_menu
2757 .borrow()
2758 .as_ref()
2759 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2760
2761 showing_completions
2762 || self.edit_prediction_requires_modifier()
2763 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2764 // bindings to insert tab characters.
2765 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2766 }
2767
2768 pub fn accept_edit_prediction_keybind(
2769 &self,
2770 granularity: EditPredictionGranularity,
2771 window: &mut Window,
2772 cx: &mut App,
2773 ) -> AcceptEditPredictionBinding {
2774 let key_context = self.key_context_internal(true, window, cx);
2775 let in_conflict = self.edit_prediction_in_conflict();
2776
2777 let bindings =
2778 match granularity {
2779 EditPredictionGranularity::Word => window
2780 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2781 EditPredictionGranularity::Line => window
2782 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2783 EditPredictionGranularity::Full => {
2784 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2785 }
2786 };
2787
2788 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2789 !in_conflict
2790 || binding
2791 .keystrokes()
2792 .first()
2793 .is_some_and(|keystroke| keystroke.modifiers().modified())
2794 }))
2795 }
2796
2797 pub fn new_file(
2798 workspace: &mut Workspace,
2799 _: &workspace::NewFile,
2800 window: &mut Window,
2801 cx: &mut Context<Workspace>,
2802 ) {
2803 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2804 "Failed to create buffer",
2805 window,
2806 cx,
2807 |e, _, _| match e.error_code() {
2808 ErrorCode::RemoteUpgradeRequired => Some(format!(
2809 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2810 e.error_tag("required").unwrap_or("the latest version")
2811 )),
2812 _ => None,
2813 },
2814 );
2815 }
2816
2817 pub fn new_in_workspace(
2818 workspace: &mut Workspace,
2819 window: &mut Window,
2820 cx: &mut Context<Workspace>,
2821 ) -> Task<Result<Entity<Editor>>> {
2822 let project = workspace.project().clone();
2823 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2824
2825 cx.spawn_in(window, async move |workspace, cx| {
2826 let buffer = create.await?;
2827 workspace.update_in(cx, |workspace, window, cx| {
2828 let editor =
2829 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2830 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2831 editor
2832 })
2833 })
2834 }
2835
2836 fn new_file_vertical(
2837 workspace: &mut Workspace,
2838 _: &workspace::NewFileSplitVertical,
2839 window: &mut Window,
2840 cx: &mut Context<Workspace>,
2841 ) {
2842 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2843 }
2844
2845 fn new_file_horizontal(
2846 workspace: &mut Workspace,
2847 _: &workspace::NewFileSplitHorizontal,
2848 window: &mut Window,
2849 cx: &mut Context<Workspace>,
2850 ) {
2851 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2852 }
2853
2854 fn new_file_split(
2855 workspace: &mut Workspace,
2856 action: &workspace::NewFileSplit,
2857 window: &mut Window,
2858 cx: &mut Context<Workspace>,
2859 ) {
2860 Self::new_file_in_direction(workspace, action.0, window, cx)
2861 }
2862
2863 fn new_file_in_direction(
2864 workspace: &mut Workspace,
2865 direction: SplitDirection,
2866 window: &mut Window,
2867 cx: &mut Context<Workspace>,
2868 ) {
2869 let project = workspace.project().clone();
2870 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2871
2872 cx.spawn_in(window, async move |workspace, cx| {
2873 let buffer = create.await?;
2874 workspace.update_in(cx, move |workspace, window, cx| {
2875 workspace.split_item(
2876 direction,
2877 Box::new(
2878 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2879 ),
2880 window,
2881 cx,
2882 )
2883 })?;
2884 anyhow::Ok(())
2885 })
2886 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2887 match e.error_code() {
2888 ErrorCode::RemoteUpgradeRequired => Some(format!(
2889 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2890 e.error_tag("required").unwrap_or("the latest version")
2891 )),
2892 _ => None,
2893 }
2894 });
2895 }
2896
2897 pub fn leader_id(&self) -> Option<CollaboratorId> {
2898 self.leader_id
2899 }
2900
2901 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2902 &self.buffer
2903 }
2904
2905 pub fn project(&self) -> Option<&Entity<Project>> {
2906 self.project.as_ref()
2907 }
2908
2909 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2910 self.workspace.as_ref()?.0.upgrade()
2911 }
2912
2913 /// Returns the workspace serialization ID if this editor should be serialized.
2914 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2915 self.workspace
2916 .as_ref()
2917 .filter(|_| self.should_serialize_buffer())
2918 .and_then(|workspace| workspace.1)
2919 }
2920
2921 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2922 self.buffer().read(cx).title(cx)
2923 }
2924
2925 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2926 let git_blame_gutter_max_author_length = self
2927 .render_git_blame_gutter(cx)
2928 .then(|| {
2929 if let Some(blame) = self.blame.as_ref() {
2930 let max_author_length =
2931 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2932 Some(max_author_length)
2933 } else {
2934 None
2935 }
2936 })
2937 .flatten();
2938
2939 EditorSnapshot {
2940 mode: self.mode.clone(),
2941 show_gutter: self.show_gutter,
2942 offset_content: self.offset_content,
2943 show_line_numbers: self.show_line_numbers,
2944 show_git_diff_gutter: self.show_git_diff_gutter,
2945 show_code_actions: self.show_code_actions,
2946 show_runnables: self.show_runnables,
2947 show_breakpoints: self.show_breakpoints,
2948 git_blame_gutter_max_author_length,
2949 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2950 placeholder_display_snapshot: self
2951 .placeholder_display_map
2952 .as_ref()
2953 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2954 scroll_anchor: self.scroll_manager.anchor(),
2955 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2956 is_focused: self.focus_handle.is_focused(window),
2957 current_line_highlight: self
2958 .current_line_highlight
2959 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2960 gutter_hovered: self.gutter_hovered,
2961 }
2962 }
2963
2964 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2965 self.buffer.read(cx).language_at(point, cx)
2966 }
2967
2968 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2969 self.buffer.read(cx).read(cx).file_at(point).cloned()
2970 }
2971
2972 pub fn active_excerpt(
2973 &self,
2974 cx: &App,
2975 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2976 self.buffer
2977 .read(cx)
2978 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2979 }
2980
2981 pub fn mode(&self) -> &EditorMode {
2982 &self.mode
2983 }
2984
2985 pub fn set_mode(&mut self, mode: EditorMode) {
2986 self.mode = mode;
2987 }
2988
2989 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2990 self.collaboration_hub.as_deref()
2991 }
2992
2993 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2994 self.collaboration_hub = Some(hub);
2995 }
2996
2997 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2998 self.in_project_search = in_project_search;
2999 }
3000
3001 pub fn set_custom_context_menu(
3002 &mut self,
3003 f: impl 'static
3004 + Fn(
3005 &mut Self,
3006 DisplayPoint,
3007 &mut Window,
3008 &mut Context<Self>,
3009 ) -> Option<Entity<ui::ContextMenu>>,
3010 ) {
3011 self.custom_context_menu = Some(Box::new(f))
3012 }
3013
3014 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3015 self.completion_provider = provider;
3016 }
3017
3018 #[cfg(any(test, feature = "test-support"))]
3019 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3020 self.completion_provider.clone()
3021 }
3022
3023 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3024 self.semantics_provider.clone()
3025 }
3026
3027 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3028 self.semantics_provider = provider;
3029 }
3030
3031 pub fn set_edit_prediction_provider<T>(
3032 &mut self,
3033 provider: Option<Entity<T>>,
3034 window: &mut Window,
3035 cx: &mut Context<Self>,
3036 ) where
3037 T: EditPredictionDelegate,
3038 {
3039 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3040 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3041 if this.focus_handle.is_focused(window) {
3042 this.update_visible_edit_prediction(window, cx);
3043 }
3044 }),
3045 provider: Arc::new(provider),
3046 });
3047 self.update_edit_prediction_settings(cx);
3048 self.refresh_edit_prediction(false, false, window, cx);
3049 }
3050
3051 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3052 self.placeholder_display_map
3053 .as_ref()
3054 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3055 }
3056
3057 pub fn set_placeholder_text(
3058 &mut self,
3059 placeholder_text: &str,
3060 window: &mut Window,
3061 cx: &mut Context<Self>,
3062 ) {
3063 let multibuffer = cx
3064 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3065
3066 let style = window.text_style();
3067
3068 self.placeholder_display_map = Some(cx.new(|cx| {
3069 DisplayMap::new(
3070 multibuffer,
3071 style.font(),
3072 style.font_size.to_pixels(window.rem_size()),
3073 None,
3074 FILE_HEADER_HEIGHT,
3075 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3076 Default::default(),
3077 DiagnosticSeverity::Off,
3078 cx,
3079 )
3080 }));
3081 cx.notify();
3082 }
3083
3084 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3085 self.cursor_shape = cursor_shape;
3086
3087 // Disrupt blink for immediate user feedback that the cursor shape has changed
3088 self.blink_manager.update(cx, BlinkManager::show_cursor);
3089
3090 cx.notify();
3091 }
3092
3093 pub fn cursor_shape(&self) -> CursorShape {
3094 self.cursor_shape
3095 }
3096
3097 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3098 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3099 }
3100
3101 pub fn set_current_line_highlight(
3102 &mut self,
3103 current_line_highlight: Option<CurrentLineHighlight>,
3104 ) {
3105 self.current_line_highlight = current_line_highlight;
3106 }
3107
3108 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3109 self.collapse_matches = collapse_matches;
3110 }
3111
3112 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3113 if self.collapse_matches {
3114 return range.start..range.start;
3115 }
3116 range.clone()
3117 }
3118
3119 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3120 self.display_map.read(cx).clip_at_line_ends
3121 }
3122
3123 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3124 if self.display_map.read(cx).clip_at_line_ends != clip {
3125 self.display_map
3126 .update(cx, |map, _| map.clip_at_line_ends = clip);
3127 }
3128 }
3129
3130 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3131 self.input_enabled = input_enabled;
3132 }
3133
3134 pub fn set_edit_predictions_hidden_for_vim_mode(
3135 &mut self,
3136 hidden: bool,
3137 window: &mut Window,
3138 cx: &mut Context<Self>,
3139 ) {
3140 if hidden != self.edit_predictions_hidden_for_vim_mode {
3141 self.edit_predictions_hidden_for_vim_mode = hidden;
3142 if hidden {
3143 self.update_visible_edit_prediction(window, cx);
3144 } else {
3145 self.refresh_edit_prediction(true, false, window, cx);
3146 }
3147 }
3148 }
3149
3150 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3151 self.menu_edit_predictions_policy = value;
3152 }
3153
3154 pub fn set_autoindent(&mut self, autoindent: bool) {
3155 if autoindent {
3156 self.autoindent_mode = Some(AutoindentMode::EachLine);
3157 } else {
3158 self.autoindent_mode = None;
3159 }
3160 }
3161
3162 pub fn read_only(&self, cx: &App) -> bool {
3163 self.read_only || self.buffer.read(cx).read_only()
3164 }
3165
3166 pub fn set_read_only(&mut self, read_only: bool) {
3167 self.read_only = read_only;
3168 }
3169
3170 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3171 self.use_autoclose = autoclose;
3172 }
3173
3174 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3175 self.use_auto_surround = auto_surround;
3176 }
3177
3178 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3179 self.auto_replace_emoji_shortcode = auto_replace;
3180 }
3181
3182 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3183 self.buffer_serialization = should_serialize.then(|| {
3184 BufferSerialization::new(
3185 ProjectSettings::get_global(cx)
3186 .session
3187 .restore_unsaved_buffers,
3188 )
3189 })
3190 }
3191
3192 fn should_serialize_buffer(&self) -> bool {
3193 self.buffer_serialization.is_some()
3194 }
3195
3196 pub fn toggle_edit_predictions(
3197 &mut self,
3198 _: &ToggleEditPrediction,
3199 window: &mut Window,
3200 cx: &mut Context<Self>,
3201 ) {
3202 if self.show_edit_predictions_override.is_some() {
3203 self.set_show_edit_predictions(None, window, cx);
3204 } else {
3205 let show_edit_predictions = !self.edit_predictions_enabled();
3206 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3207 }
3208 }
3209
3210 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3211 self.show_completions_on_input_override = show_completions_on_input;
3212 }
3213
3214 pub fn set_show_edit_predictions(
3215 &mut self,
3216 show_edit_predictions: Option<bool>,
3217 window: &mut Window,
3218 cx: &mut Context<Self>,
3219 ) {
3220 self.show_edit_predictions_override = show_edit_predictions;
3221 self.update_edit_prediction_settings(cx);
3222
3223 if let Some(false) = show_edit_predictions {
3224 self.discard_edit_prediction(false, cx);
3225 } else {
3226 self.refresh_edit_prediction(false, true, window, cx);
3227 }
3228 }
3229
3230 fn edit_predictions_disabled_in_scope(
3231 &self,
3232 buffer: &Entity<Buffer>,
3233 buffer_position: language::Anchor,
3234 cx: &App,
3235 ) -> bool {
3236 let snapshot = buffer.read(cx).snapshot();
3237 let settings = snapshot.settings_at(buffer_position, cx);
3238
3239 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3240 return false;
3241 };
3242
3243 scope.override_name().is_some_and(|scope_name| {
3244 settings
3245 .edit_predictions_disabled_in
3246 .iter()
3247 .any(|s| s == scope_name)
3248 })
3249 }
3250
3251 pub fn set_use_modal_editing(&mut self, to: bool) {
3252 self.use_modal_editing = to;
3253 }
3254
3255 pub fn use_modal_editing(&self) -> bool {
3256 self.use_modal_editing
3257 }
3258
3259 fn selections_did_change(
3260 &mut self,
3261 local: bool,
3262 old_cursor_position: &Anchor,
3263 effects: SelectionEffects,
3264 window: &mut Window,
3265 cx: &mut Context<Self>,
3266 ) {
3267 window.invalidate_character_coordinates();
3268
3269 // Copy selections to primary selection buffer
3270 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3271 if local {
3272 let selections = self
3273 .selections
3274 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3275 let buffer_handle = self.buffer.read(cx).read(cx);
3276
3277 let mut text = String::new();
3278 for (index, selection) in selections.iter().enumerate() {
3279 let text_for_selection = buffer_handle
3280 .text_for_range(selection.start..selection.end)
3281 .collect::<String>();
3282
3283 text.push_str(&text_for_selection);
3284 if index != selections.len() - 1 {
3285 text.push('\n');
3286 }
3287 }
3288
3289 if !text.is_empty() {
3290 cx.write_to_primary(ClipboardItem::new_string(text));
3291 }
3292 }
3293
3294 let selection_anchors = self.selections.disjoint_anchors_arc();
3295
3296 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3297 self.buffer.update(cx, |buffer, cx| {
3298 buffer.set_active_selections(
3299 &selection_anchors,
3300 self.selections.line_mode(),
3301 self.cursor_shape,
3302 cx,
3303 )
3304 });
3305 }
3306 let display_map = self
3307 .display_map
3308 .update(cx, |display_map, cx| display_map.snapshot(cx));
3309 let buffer = display_map.buffer_snapshot();
3310 if self.selections.count() == 1 {
3311 self.add_selections_state = None;
3312 }
3313 self.select_next_state = None;
3314 self.select_prev_state = None;
3315 self.select_syntax_node_history.try_clear();
3316 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3317 self.snippet_stack.invalidate(&selection_anchors, buffer);
3318 self.take_rename(false, window, cx);
3319
3320 let newest_selection = self.selections.newest_anchor();
3321 let new_cursor_position = newest_selection.head();
3322 let selection_start = newest_selection.start;
3323
3324 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3325 self.push_to_nav_history(
3326 *old_cursor_position,
3327 Some(new_cursor_position.to_point(buffer)),
3328 false,
3329 effects.nav_history == Some(true),
3330 cx,
3331 );
3332 }
3333
3334 if local {
3335 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3336 self.register_buffer(buffer_id, cx);
3337 }
3338
3339 let mut context_menu = self.context_menu.borrow_mut();
3340 let completion_menu = match context_menu.as_ref() {
3341 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3342 Some(CodeContextMenu::CodeActions(_)) => {
3343 *context_menu = None;
3344 None
3345 }
3346 None => None,
3347 };
3348 let completion_position = completion_menu.map(|menu| menu.initial_position);
3349 drop(context_menu);
3350
3351 if effects.completions
3352 && let Some(completion_position) = completion_position
3353 {
3354 let start_offset = selection_start.to_offset(buffer);
3355 let position_matches = start_offset == completion_position.to_offset(buffer);
3356 let continue_showing = if position_matches {
3357 if self.snippet_stack.is_empty() {
3358 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3359 == Some(CharKind::Word)
3360 } else {
3361 // Snippet choices can be shown even when the cursor is in whitespace.
3362 // Dismissing the menu with actions like backspace is handled by
3363 // invalidation regions.
3364 true
3365 }
3366 } else {
3367 false
3368 };
3369
3370 if continue_showing {
3371 self.open_or_update_completions_menu(None, None, false, window, cx);
3372 } else {
3373 self.hide_context_menu(window, cx);
3374 }
3375 }
3376
3377 hide_hover(self, cx);
3378
3379 if old_cursor_position.to_display_point(&display_map).row()
3380 != new_cursor_position.to_display_point(&display_map).row()
3381 {
3382 self.available_code_actions.take();
3383 }
3384 self.refresh_code_actions(window, cx);
3385 self.refresh_document_highlights(cx);
3386 refresh_linked_ranges(self, window, cx);
3387
3388 self.refresh_selected_text_highlights(false, window, cx);
3389 self.refresh_matching_bracket_highlights(window, cx);
3390 self.update_visible_edit_prediction(window, cx);
3391 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3392 self.inline_blame_popover.take();
3393 if self.git_blame_inline_enabled {
3394 self.start_inline_blame_timer(window, cx);
3395 }
3396 }
3397
3398 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3399 cx.emit(EditorEvent::SelectionsChanged { local });
3400
3401 let selections = &self.selections.disjoint_anchors_arc();
3402 if selections.len() == 1 {
3403 cx.emit(SearchEvent::ActiveMatchChanged)
3404 }
3405 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3406 let inmemory_selections = selections
3407 .iter()
3408 .map(|s| {
3409 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3410 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3411 })
3412 .collect();
3413 self.update_restoration_data(cx, |data| {
3414 data.selections = inmemory_selections;
3415 });
3416
3417 if WorkspaceSettings::get(None, cx).restore_on_startup
3418 != RestoreOnStartupBehavior::EmptyTab
3419 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3420 {
3421 let snapshot = self.buffer().read(cx).snapshot(cx);
3422 let selections = selections.clone();
3423 let background_executor = cx.background_executor().clone();
3424 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3425 self.serialize_selections = cx.background_spawn(async move {
3426 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3427 let db_selections = selections
3428 .iter()
3429 .map(|selection| {
3430 (
3431 selection.start.to_offset(&snapshot).0,
3432 selection.end.to_offset(&snapshot).0,
3433 )
3434 })
3435 .collect();
3436
3437 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3438 .await
3439 .with_context(|| {
3440 format!(
3441 "persisting editor selections for editor {editor_id}, \
3442 workspace {workspace_id:?}"
3443 )
3444 })
3445 .log_err();
3446 });
3447 }
3448 }
3449
3450 cx.notify();
3451 }
3452
3453 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3454 use text::ToOffset as _;
3455 use text::ToPoint as _;
3456
3457 if self.mode.is_minimap()
3458 || WorkspaceSettings::get(None, cx).restore_on_startup
3459 == RestoreOnStartupBehavior::EmptyTab
3460 {
3461 return;
3462 }
3463
3464 if !self.buffer().read(cx).is_singleton() {
3465 return;
3466 }
3467
3468 let display_snapshot = self
3469 .display_map
3470 .update(cx, |display_map, cx| display_map.snapshot(cx));
3471 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3472 return;
3473 };
3474 let inmemory_folds = display_snapshot
3475 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3476 .map(|fold| {
3477 fold.range.start.text_anchor.to_point(&snapshot)
3478 ..fold.range.end.text_anchor.to_point(&snapshot)
3479 })
3480 .collect();
3481 self.update_restoration_data(cx, |data| {
3482 data.folds = inmemory_folds;
3483 });
3484
3485 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3486 return;
3487 };
3488 let background_executor = cx.background_executor().clone();
3489 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3490 let db_folds = display_snapshot
3491 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3492 .map(|fold| {
3493 (
3494 fold.range.start.text_anchor.to_offset(&snapshot),
3495 fold.range.end.text_anchor.to_offset(&snapshot),
3496 )
3497 })
3498 .collect();
3499 self.serialize_folds = cx.background_spawn(async move {
3500 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3501 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3502 .await
3503 .with_context(|| {
3504 format!(
3505 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3506 )
3507 })
3508 .log_err();
3509 });
3510 }
3511
3512 pub fn sync_selections(
3513 &mut self,
3514 other: Entity<Editor>,
3515 cx: &mut Context<Self>,
3516 ) -> gpui::Subscription {
3517 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3518 if !other_selections.is_empty() {
3519 self.selections
3520 .change_with(&self.display_snapshot(cx), |selections| {
3521 selections.select_anchors(other_selections);
3522 });
3523 }
3524
3525 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3526 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3527 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3528 if other_selections.is_empty() {
3529 return;
3530 }
3531 let snapshot = this.display_snapshot(cx);
3532 this.selections.change_with(&snapshot, |selections| {
3533 selections.select_anchors(other_selections);
3534 });
3535 }
3536 });
3537
3538 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3539 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3540 let these_selections = this.selections.disjoint_anchors().to_vec();
3541 if these_selections.is_empty() {
3542 return;
3543 }
3544 other.update(cx, |other_editor, cx| {
3545 let snapshot = other_editor.display_snapshot(cx);
3546 other_editor
3547 .selections
3548 .change_with(&snapshot, |selections| {
3549 selections.select_anchors(these_selections);
3550 })
3551 });
3552 }
3553 });
3554
3555 Subscription::join(other_subscription, this_subscription)
3556 }
3557
3558 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3559 if self.buffer().read(cx).is_singleton() {
3560 return;
3561 }
3562 let snapshot = self.buffer.read(cx).snapshot(cx);
3563 let buffer_ids: HashSet<BufferId> = self
3564 .selections
3565 .disjoint_anchor_ranges()
3566 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3567 .collect();
3568 for buffer_id in buffer_ids {
3569 self.unfold_buffer(buffer_id, cx);
3570 }
3571 }
3572
3573 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3574 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3575 /// effects of selection change occur at the end of the transaction.
3576 pub fn change_selections<R>(
3577 &mut self,
3578 effects: SelectionEffects,
3579 window: &mut Window,
3580 cx: &mut Context<Self>,
3581 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3582 ) -> R {
3583 let snapshot = self.display_snapshot(cx);
3584 if let Some(state) = &mut self.deferred_selection_effects_state {
3585 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3586 state.effects.completions = effects.completions;
3587 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3588 let (changed, result) = self.selections.change_with(&snapshot, change);
3589 state.changed |= changed;
3590 return result;
3591 }
3592 let mut state = DeferredSelectionEffectsState {
3593 changed: false,
3594 effects,
3595 old_cursor_position: self.selections.newest_anchor().head(),
3596 history_entry: SelectionHistoryEntry {
3597 selections: self.selections.disjoint_anchors_arc(),
3598 select_next_state: self.select_next_state.clone(),
3599 select_prev_state: self.select_prev_state.clone(),
3600 add_selections_state: self.add_selections_state.clone(),
3601 },
3602 };
3603 let (changed, result) = self.selections.change_with(&snapshot, change);
3604 state.changed = state.changed || changed;
3605 if self.defer_selection_effects {
3606 self.deferred_selection_effects_state = Some(state);
3607 } else {
3608 self.apply_selection_effects(state, window, cx);
3609 }
3610 result
3611 }
3612
3613 /// Defers the effects of selection change, so that the effects of multiple calls to
3614 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3615 /// to selection history and the state of popovers based on selection position aren't
3616 /// erroneously updated.
3617 pub fn with_selection_effects_deferred<R>(
3618 &mut self,
3619 window: &mut Window,
3620 cx: &mut Context<Self>,
3621 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3622 ) -> R {
3623 let already_deferred = self.defer_selection_effects;
3624 self.defer_selection_effects = true;
3625 let result = update(self, window, cx);
3626 if !already_deferred {
3627 self.defer_selection_effects = false;
3628 if let Some(state) = self.deferred_selection_effects_state.take() {
3629 self.apply_selection_effects(state, window, cx);
3630 }
3631 }
3632 result
3633 }
3634
3635 fn apply_selection_effects(
3636 &mut self,
3637 state: DeferredSelectionEffectsState,
3638 window: &mut Window,
3639 cx: &mut Context<Self>,
3640 ) {
3641 if state.changed {
3642 self.selection_history.push(state.history_entry);
3643
3644 if let Some(autoscroll) = state.effects.scroll {
3645 self.request_autoscroll(autoscroll, cx);
3646 }
3647
3648 let old_cursor_position = &state.old_cursor_position;
3649
3650 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3651
3652 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3653 self.show_signature_help(&ShowSignatureHelp, window, cx);
3654 }
3655 }
3656 }
3657
3658 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3659 where
3660 I: IntoIterator<Item = (Range<S>, T)>,
3661 S: ToOffset,
3662 T: Into<Arc<str>>,
3663 {
3664 if self.read_only(cx) {
3665 return;
3666 }
3667
3668 self.buffer
3669 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3670 }
3671
3672 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3673 where
3674 I: IntoIterator<Item = (Range<S>, T)>,
3675 S: ToOffset,
3676 T: Into<Arc<str>>,
3677 {
3678 if self.read_only(cx) {
3679 return;
3680 }
3681
3682 self.buffer.update(cx, |buffer, cx| {
3683 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3684 });
3685 }
3686
3687 pub fn edit_with_block_indent<I, S, T>(
3688 &mut self,
3689 edits: I,
3690 original_indent_columns: Vec<Option<u32>>,
3691 cx: &mut Context<Self>,
3692 ) where
3693 I: IntoIterator<Item = (Range<S>, T)>,
3694 S: ToOffset,
3695 T: Into<Arc<str>>,
3696 {
3697 if self.read_only(cx) {
3698 return;
3699 }
3700
3701 self.buffer.update(cx, |buffer, cx| {
3702 buffer.edit(
3703 edits,
3704 Some(AutoindentMode::Block {
3705 original_indent_columns,
3706 }),
3707 cx,
3708 )
3709 });
3710 }
3711
3712 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3713 self.hide_context_menu(window, cx);
3714
3715 match phase {
3716 SelectPhase::Begin {
3717 position,
3718 add,
3719 click_count,
3720 } => self.begin_selection(position, add, click_count, window, cx),
3721 SelectPhase::BeginColumnar {
3722 position,
3723 goal_column,
3724 reset,
3725 mode,
3726 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3727 SelectPhase::Extend {
3728 position,
3729 click_count,
3730 } => self.extend_selection(position, click_count, window, cx),
3731 SelectPhase::Update {
3732 position,
3733 goal_column,
3734 scroll_delta,
3735 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3736 SelectPhase::End => self.end_selection(window, cx),
3737 }
3738 }
3739
3740 fn extend_selection(
3741 &mut self,
3742 position: DisplayPoint,
3743 click_count: usize,
3744 window: &mut Window,
3745 cx: &mut Context<Self>,
3746 ) {
3747 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3748 let tail = self
3749 .selections
3750 .newest::<MultiBufferOffset>(&display_map)
3751 .tail();
3752 let click_count = click_count.max(match self.selections.select_mode() {
3753 SelectMode::Character => 1,
3754 SelectMode::Word(_) => 2,
3755 SelectMode::Line(_) => 3,
3756 SelectMode::All => 4,
3757 });
3758 self.begin_selection(position, false, click_count, window, cx);
3759
3760 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3761
3762 let current_selection = match self.selections.select_mode() {
3763 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3764 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3765 };
3766
3767 let mut pending_selection = self
3768 .selections
3769 .pending_anchor()
3770 .cloned()
3771 .expect("extend_selection not called with pending selection");
3772
3773 if pending_selection
3774 .start
3775 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3776 == Ordering::Greater
3777 {
3778 pending_selection.start = current_selection.start;
3779 }
3780 if pending_selection
3781 .end
3782 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3783 == Ordering::Less
3784 {
3785 pending_selection.end = current_selection.end;
3786 pending_selection.reversed = true;
3787 }
3788
3789 let mut pending_mode = self.selections.pending_mode().unwrap();
3790 match &mut pending_mode {
3791 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3792 _ => {}
3793 }
3794
3795 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3796 SelectionEffects::scroll(Autoscroll::fit())
3797 } else {
3798 SelectionEffects::no_scroll()
3799 };
3800
3801 self.change_selections(effects, window, cx, |s| {
3802 s.set_pending(pending_selection.clone(), pending_mode);
3803 s.set_is_extending(true);
3804 });
3805 }
3806
3807 fn begin_selection(
3808 &mut self,
3809 position: DisplayPoint,
3810 add: bool,
3811 click_count: usize,
3812 window: &mut Window,
3813 cx: &mut Context<Self>,
3814 ) {
3815 if !self.focus_handle.is_focused(window) {
3816 self.last_focused_descendant = None;
3817 window.focus(&self.focus_handle, cx);
3818 }
3819
3820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3821 let buffer = display_map.buffer_snapshot();
3822 let position = display_map.clip_point(position, Bias::Left);
3823
3824 let start;
3825 let end;
3826 let mode;
3827 let mut auto_scroll;
3828 match click_count {
3829 1 => {
3830 start = buffer.anchor_before(position.to_point(&display_map));
3831 end = start;
3832 mode = SelectMode::Character;
3833 auto_scroll = true;
3834 }
3835 2 => {
3836 let position = display_map
3837 .clip_point(position, Bias::Left)
3838 .to_offset(&display_map, Bias::Left);
3839 let (range, _) = buffer.surrounding_word(position, None);
3840 start = buffer.anchor_before(range.start);
3841 end = buffer.anchor_before(range.end);
3842 mode = SelectMode::Word(start..end);
3843 auto_scroll = true;
3844 }
3845 3 => {
3846 let position = display_map
3847 .clip_point(position, Bias::Left)
3848 .to_point(&display_map);
3849 let line_start = display_map.prev_line_boundary(position).0;
3850 let next_line_start = buffer.clip_point(
3851 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3852 Bias::Left,
3853 );
3854 start = buffer.anchor_before(line_start);
3855 end = buffer.anchor_before(next_line_start);
3856 mode = SelectMode::Line(start..end);
3857 auto_scroll = true;
3858 }
3859 _ => {
3860 start = buffer.anchor_before(MultiBufferOffset(0));
3861 end = buffer.anchor_before(buffer.len());
3862 mode = SelectMode::All;
3863 auto_scroll = false;
3864 }
3865 }
3866 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3867
3868 let point_to_delete: Option<usize> = {
3869 let selected_points: Vec<Selection<Point>> =
3870 self.selections.disjoint_in_range(start..end, &display_map);
3871
3872 if !add || click_count > 1 {
3873 None
3874 } else if !selected_points.is_empty() {
3875 Some(selected_points[0].id)
3876 } else {
3877 let clicked_point_already_selected =
3878 self.selections.disjoint_anchors().iter().find(|selection| {
3879 selection.start.to_point(buffer) == start.to_point(buffer)
3880 || selection.end.to_point(buffer) == end.to_point(buffer)
3881 });
3882
3883 clicked_point_already_selected.map(|selection| selection.id)
3884 }
3885 };
3886
3887 let selections_count = self.selections.count();
3888 let effects = if auto_scroll {
3889 SelectionEffects::default()
3890 } else {
3891 SelectionEffects::no_scroll()
3892 };
3893
3894 self.change_selections(effects, window, cx, |s| {
3895 if let Some(point_to_delete) = point_to_delete {
3896 s.delete(point_to_delete);
3897
3898 if selections_count == 1 {
3899 s.set_pending_anchor_range(start..end, mode);
3900 }
3901 } else {
3902 if !add {
3903 s.clear_disjoint();
3904 }
3905
3906 s.set_pending_anchor_range(start..end, mode);
3907 }
3908 });
3909 }
3910
3911 fn begin_columnar_selection(
3912 &mut self,
3913 position: DisplayPoint,
3914 goal_column: u32,
3915 reset: bool,
3916 mode: ColumnarMode,
3917 window: &mut Window,
3918 cx: &mut Context<Self>,
3919 ) {
3920 if !self.focus_handle.is_focused(window) {
3921 self.last_focused_descendant = None;
3922 window.focus(&self.focus_handle, cx);
3923 }
3924
3925 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3926
3927 if reset {
3928 let pointer_position = display_map
3929 .buffer_snapshot()
3930 .anchor_before(position.to_point(&display_map));
3931
3932 self.change_selections(
3933 SelectionEffects::scroll(Autoscroll::newest()),
3934 window,
3935 cx,
3936 |s| {
3937 s.clear_disjoint();
3938 s.set_pending_anchor_range(
3939 pointer_position..pointer_position,
3940 SelectMode::Character,
3941 );
3942 },
3943 );
3944 };
3945
3946 let tail = self.selections.newest::<Point>(&display_map).tail();
3947 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3948 self.columnar_selection_state = match mode {
3949 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3950 selection_tail: selection_anchor,
3951 display_point: if reset {
3952 if position.column() != goal_column {
3953 Some(DisplayPoint::new(position.row(), goal_column))
3954 } else {
3955 None
3956 }
3957 } else {
3958 None
3959 },
3960 }),
3961 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3962 selection_tail: selection_anchor,
3963 }),
3964 };
3965
3966 if !reset {
3967 self.select_columns(position, goal_column, &display_map, window, cx);
3968 }
3969 }
3970
3971 fn update_selection(
3972 &mut self,
3973 position: DisplayPoint,
3974 goal_column: u32,
3975 scroll_delta: gpui::Point<f32>,
3976 window: &mut Window,
3977 cx: &mut Context<Self>,
3978 ) {
3979 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3980
3981 if self.columnar_selection_state.is_some() {
3982 self.select_columns(position, goal_column, &display_map, window, cx);
3983 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3984 let buffer = display_map.buffer_snapshot();
3985 let head;
3986 let tail;
3987 let mode = self.selections.pending_mode().unwrap();
3988 match &mode {
3989 SelectMode::Character => {
3990 head = position.to_point(&display_map);
3991 tail = pending.tail().to_point(buffer);
3992 }
3993 SelectMode::Word(original_range) => {
3994 let offset = display_map
3995 .clip_point(position, Bias::Left)
3996 .to_offset(&display_map, Bias::Left);
3997 let original_range = original_range.to_offset(buffer);
3998
3999 let head_offset = if buffer.is_inside_word(offset, None)
4000 || original_range.contains(&offset)
4001 {
4002 let (word_range, _) = buffer.surrounding_word(offset, None);
4003 if word_range.start < original_range.start {
4004 word_range.start
4005 } else {
4006 word_range.end
4007 }
4008 } else {
4009 offset
4010 };
4011
4012 head = head_offset.to_point(buffer);
4013 if head_offset <= original_range.start {
4014 tail = original_range.end.to_point(buffer);
4015 } else {
4016 tail = original_range.start.to_point(buffer);
4017 }
4018 }
4019 SelectMode::Line(original_range) => {
4020 let original_range = original_range.to_point(display_map.buffer_snapshot());
4021
4022 let position = display_map
4023 .clip_point(position, Bias::Left)
4024 .to_point(&display_map);
4025 let line_start = display_map.prev_line_boundary(position).0;
4026 let next_line_start = buffer.clip_point(
4027 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4028 Bias::Left,
4029 );
4030
4031 if line_start < original_range.start {
4032 head = line_start
4033 } else {
4034 head = next_line_start
4035 }
4036
4037 if head <= original_range.start {
4038 tail = original_range.end;
4039 } else {
4040 tail = original_range.start;
4041 }
4042 }
4043 SelectMode::All => {
4044 return;
4045 }
4046 };
4047
4048 if head < tail {
4049 pending.start = buffer.anchor_before(head);
4050 pending.end = buffer.anchor_before(tail);
4051 pending.reversed = true;
4052 } else {
4053 pending.start = buffer.anchor_before(tail);
4054 pending.end = buffer.anchor_before(head);
4055 pending.reversed = false;
4056 }
4057
4058 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4059 s.set_pending(pending.clone(), mode);
4060 });
4061 } else {
4062 log::error!("update_selection dispatched with no pending selection");
4063 return;
4064 }
4065
4066 self.apply_scroll_delta(scroll_delta, window, cx);
4067 cx.notify();
4068 }
4069
4070 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4071 self.columnar_selection_state.take();
4072 if let Some(pending_mode) = self.selections.pending_mode() {
4073 let selections = self
4074 .selections
4075 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4076 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4077 s.select(selections);
4078 s.clear_pending();
4079 if s.is_extending() {
4080 s.set_is_extending(false);
4081 } else {
4082 s.set_select_mode(pending_mode);
4083 }
4084 });
4085 }
4086 }
4087
4088 fn select_columns(
4089 &mut self,
4090 head: DisplayPoint,
4091 goal_column: u32,
4092 display_map: &DisplaySnapshot,
4093 window: &mut Window,
4094 cx: &mut Context<Self>,
4095 ) {
4096 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4097 return;
4098 };
4099
4100 let tail = match columnar_state {
4101 ColumnarSelectionState::FromMouse {
4102 selection_tail,
4103 display_point,
4104 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4105 ColumnarSelectionState::FromSelection { selection_tail } => {
4106 selection_tail.to_display_point(display_map)
4107 }
4108 };
4109
4110 let start_row = cmp::min(tail.row(), head.row());
4111 let end_row = cmp::max(tail.row(), head.row());
4112 let start_column = cmp::min(tail.column(), goal_column);
4113 let end_column = cmp::max(tail.column(), goal_column);
4114 let reversed = start_column < tail.column();
4115
4116 let selection_ranges = (start_row.0..=end_row.0)
4117 .map(DisplayRow)
4118 .filter_map(|row| {
4119 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4120 || start_column <= display_map.line_len(row))
4121 && !display_map.is_block_line(row)
4122 {
4123 let start = display_map
4124 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4125 .to_point(display_map);
4126 let end = display_map
4127 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4128 .to_point(display_map);
4129 if reversed {
4130 Some(end..start)
4131 } else {
4132 Some(start..end)
4133 }
4134 } else {
4135 None
4136 }
4137 })
4138 .collect::<Vec<_>>();
4139 if selection_ranges.is_empty() {
4140 return;
4141 }
4142
4143 let ranges = match columnar_state {
4144 ColumnarSelectionState::FromMouse { .. } => {
4145 let mut non_empty_ranges = selection_ranges
4146 .iter()
4147 .filter(|selection_range| selection_range.start != selection_range.end)
4148 .peekable();
4149 if non_empty_ranges.peek().is_some() {
4150 non_empty_ranges.cloned().collect()
4151 } else {
4152 selection_ranges
4153 }
4154 }
4155 _ => selection_ranges,
4156 };
4157
4158 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4159 s.select_ranges(ranges);
4160 });
4161 cx.notify();
4162 }
4163
4164 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4165 self.selections
4166 .all_adjusted(snapshot)
4167 .iter()
4168 .any(|selection| !selection.is_empty())
4169 }
4170
4171 pub fn has_pending_nonempty_selection(&self) -> bool {
4172 let pending_nonempty_selection = match self.selections.pending_anchor() {
4173 Some(Selection { start, end, .. }) => start != end,
4174 None => false,
4175 };
4176
4177 pending_nonempty_selection
4178 || (self.columnar_selection_state.is_some()
4179 && self.selections.disjoint_anchors().len() > 1)
4180 }
4181
4182 pub fn has_pending_selection(&self) -> bool {
4183 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4184 }
4185
4186 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4187 self.selection_mark_mode = false;
4188 self.selection_drag_state = SelectionDragState::None;
4189
4190 if self.dismiss_menus_and_popups(true, window, cx) {
4191 cx.notify();
4192 return;
4193 }
4194 if self.clear_expanded_diff_hunks(cx) {
4195 cx.notify();
4196 return;
4197 }
4198 if self.show_git_blame_gutter {
4199 self.show_git_blame_gutter = false;
4200 cx.notify();
4201 return;
4202 }
4203
4204 if self.mode.is_full()
4205 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4206 {
4207 cx.notify();
4208 return;
4209 }
4210
4211 cx.propagate();
4212 }
4213
4214 pub fn dismiss_menus_and_popups(
4215 &mut self,
4216 is_user_requested: bool,
4217 window: &mut Window,
4218 cx: &mut Context<Self>,
4219 ) -> bool {
4220 let mut dismissed = false;
4221
4222 dismissed |= self.take_rename(false, window, cx).is_some();
4223 dismissed |= self.hide_blame_popover(true, cx);
4224 dismissed |= hide_hover(self, cx);
4225 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4226 dismissed |= self.hide_context_menu(window, cx).is_some();
4227 dismissed |= self.mouse_context_menu.take().is_some();
4228 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4229 dismissed |= self.snippet_stack.pop().is_some();
4230
4231 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4232 self.dismiss_diagnostics(cx);
4233 dismissed = true;
4234 }
4235
4236 dismissed
4237 }
4238
4239 fn linked_editing_ranges_for(
4240 &self,
4241 selection: Range<text::Anchor>,
4242 cx: &App,
4243 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4244 if self.linked_edit_ranges.is_empty() {
4245 return None;
4246 }
4247 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4248 selection.end.buffer_id.and_then(|end_buffer_id| {
4249 if selection.start.buffer_id != Some(end_buffer_id) {
4250 return None;
4251 }
4252 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4253 let snapshot = buffer.read(cx).snapshot();
4254 self.linked_edit_ranges
4255 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4256 .map(|ranges| (ranges, snapshot, buffer))
4257 })?;
4258 use text::ToOffset as TO;
4259 // find offset from the start of current range to current cursor position
4260 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4261
4262 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4263 let start_difference = start_offset - start_byte_offset;
4264 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4265 let end_difference = end_offset - start_byte_offset;
4266 // Current range has associated linked ranges.
4267 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4268 for range in linked_ranges.iter() {
4269 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4270 let end_offset = start_offset + end_difference;
4271 let start_offset = start_offset + start_difference;
4272 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4273 continue;
4274 }
4275 if self.selections.disjoint_anchor_ranges().any(|s| {
4276 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4277 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4278 {
4279 return false;
4280 }
4281 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4282 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4283 }) {
4284 continue;
4285 }
4286 let start = buffer_snapshot.anchor_after(start_offset);
4287 let end = buffer_snapshot.anchor_after(end_offset);
4288 linked_edits
4289 .entry(buffer.clone())
4290 .or_default()
4291 .push(start..end);
4292 }
4293 Some(linked_edits)
4294 }
4295
4296 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4297 let text: Arc<str> = text.into();
4298
4299 if self.read_only(cx) {
4300 return;
4301 }
4302
4303 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4304
4305 self.unfold_buffers_with_selections(cx);
4306
4307 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4308 let mut bracket_inserted = false;
4309 let mut edits = Vec::new();
4310 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4311 let mut new_selections = Vec::with_capacity(selections.len());
4312 let mut new_autoclose_regions = Vec::new();
4313 let snapshot = self.buffer.read(cx).read(cx);
4314 let mut clear_linked_edit_ranges = false;
4315
4316 for (selection, autoclose_region) in
4317 self.selections_with_autoclose_regions(selections, &snapshot)
4318 {
4319 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4320 // Determine if the inserted text matches the opening or closing
4321 // bracket of any of this language's bracket pairs.
4322 let mut bracket_pair = None;
4323 let mut is_bracket_pair_start = false;
4324 let mut is_bracket_pair_end = false;
4325 if !text.is_empty() {
4326 let mut bracket_pair_matching_end = None;
4327 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4328 // and they are removing the character that triggered IME popup.
4329 for (pair, enabled) in scope.brackets() {
4330 if !pair.close && !pair.surround {
4331 continue;
4332 }
4333
4334 if enabled && pair.start.ends_with(text.as_ref()) {
4335 let prefix_len = pair.start.len() - text.len();
4336 let preceding_text_matches_prefix = prefix_len == 0
4337 || (selection.start.column >= (prefix_len as u32)
4338 && snapshot.contains_str_at(
4339 Point::new(
4340 selection.start.row,
4341 selection.start.column - (prefix_len as u32),
4342 ),
4343 &pair.start[..prefix_len],
4344 ));
4345 if preceding_text_matches_prefix {
4346 bracket_pair = Some(pair.clone());
4347 is_bracket_pair_start = true;
4348 break;
4349 }
4350 }
4351 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4352 {
4353 // take first bracket pair matching end, but don't break in case a later bracket
4354 // pair matches start
4355 bracket_pair_matching_end = Some(pair.clone());
4356 }
4357 }
4358 if let Some(end) = bracket_pair_matching_end
4359 && bracket_pair.is_none()
4360 {
4361 bracket_pair = Some(end);
4362 is_bracket_pair_end = true;
4363 }
4364 }
4365
4366 if let Some(bracket_pair) = bracket_pair {
4367 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4368 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4369 let auto_surround =
4370 self.use_auto_surround && snapshot_settings.use_auto_surround;
4371 if selection.is_empty() {
4372 if is_bracket_pair_start {
4373 // If the inserted text is a suffix of an opening bracket and the
4374 // selection is preceded by the rest of the opening bracket, then
4375 // insert the closing bracket.
4376 let following_text_allows_autoclose = snapshot
4377 .chars_at(selection.start)
4378 .next()
4379 .is_none_or(|c| scope.should_autoclose_before(c));
4380
4381 let preceding_text_allows_autoclose = selection.start.column == 0
4382 || snapshot
4383 .reversed_chars_at(selection.start)
4384 .next()
4385 .is_none_or(|c| {
4386 bracket_pair.start != bracket_pair.end
4387 || !snapshot
4388 .char_classifier_at(selection.start)
4389 .is_word(c)
4390 });
4391
4392 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4393 && bracket_pair.start.len() == 1
4394 {
4395 let target = bracket_pair.start.chars().next().unwrap();
4396 let mut byte_offset = 0u32;
4397 let current_line_count = snapshot
4398 .reversed_chars_at(selection.start)
4399 .take_while(|&c| c != '\n')
4400 .filter(|c| {
4401 byte_offset += c.len_utf8() as u32;
4402 if *c != target {
4403 return false;
4404 }
4405
4406 let point = Point::new(
4407 selection.start.row,
4408 selection.start.column.saturating_sub(byte_offset),
4409 );
4410
4411 let is_enabled = snapshot
4412 .language_scope_at(point)
4413 .and_then(|scope| {
4414 scope
4415 .brackets()
4416 .find(|(pair, _)| {
4417 pair.start == bracket_pair.start
4418 })
4419 .map(|(_, enabled)| enabled)
4420 })
4421 .unwrap_or(true);
4422
4423 let is_delimiter = snapshot
4424 .language_scope_at(Point::new(
4425 point.row,
4426 point.column + 1,
4427 ))
4428 .and_then(|scope| {
4429 scope
4430 .brackets()
4431 .find(|(pair, _)| {
4432 pair.start == bracket_pair.start
4433 })
4434 .map(|(_, enabled)| !enabled)
4435 })
4436 .unwrap_or(false);
4437
4438 is_enabled && !is_delimiter
4439 })
4440 .count();
4441 current_line_count % 2 == 1
4442 } else {
4443 false
4444 };
4445
4446 if autoclose
4447 && bracket_pair.close
4448 && following_text_allows_autoclose
4449 && preceding_text_allows_autoclose
4450 && !is_closing_quote
4451 {
4452 let anchor = snapshot.anchor_before(selection.end);
4453 new_selections.push((selection.map(|_| anchor), text.len()));
4454 new_autoclose_regions.push((
4455 anchor,
4456 text.len(),
4457 selection.id,
4458 bracket_pair.clone(),
4459 ));
4460 edits.push((
4461 selection.range(),
4462 format!("{}{}", text, bracket_pair.end).into(),
4463 ));
4464 bracket_inserted = true;
4465 continue;
4466 }
4467 }
4468
4469 if let Some(region) = autoclose_region {
4470 // If the selection is followed by an auto-inserted closing bracket,
4471 // then don't insert that closing bracket again; just move the selection
4472 // past the closing bracket.
4473 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4474 && text.as_ref() == region.pair.end.as_str()
4475 && snapshot.contains_str_at(region.range.end, text.as_ref());
4476 if should_skip {
4477 let anchor = snapshot.anchor_after(selection.end);
4478 new_selections
4479 .push((selection.map(|_| anchor), region.pair.end.len()));
4480 continue;
4481 }
4482 }
4483
4484 let always_treat_brackets_as_autoclosed = snapshot
4485 .language_settings_at(selection.start, cx)
4486 .always_treat_brackets_as_autoclosed;
4487 if always_treat_brackets_as_autoclosed
4488 && is_bracket_pair_end
4489 && snapshot.contains_str_at(selection.end, text.as_ref())
4490 {
4491 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4492 // and the inserted text is a closing bracket and the selection is followed
4493 // by the closing bracket then move the selection past the closing bracket.
4494 let anchor = snapshot.anchor_after(selection.end);
4495 new_selections.push((selection.map(|_| anchor), text.len()));
4496 continue;
4497 }
4498 }
4499 // If an opening bracket is 1 character long and is typed while
4500 // text is selected, then surround that text with the bracket pair.
4501 else if auto_surround
4502 && bracket_pair.surround
4503 && is_bracket_pair_start
4504 && bracket_pair.start.chars().count() == 1
4505 {
4506 edits.push((selection.start..selection.start, text.clone()));
4507 edits.push((
4508 selection.end..selection.end,
4509 bracket_pair.end.as_str().into(),
4510 ));
4511 bracket_inserted = true;
4512 new_selections.push((
4513 Selection {
4514 id: selection.id,
4515 start: snapshot.anchor_after(selection.start),
4516 end: snapshot.anchor_before(selection.end),
4517 reversed: selection.reversed,
4518 goal: selection.goal,
4519 },
4520 0,
4521 ));
4522 continue;
4523 }
4524 }
4525 }
4526
4527 if self.auto_replace_emoji_shortcode
4528 && selection.is_empty()
4529 && text.as_ref().ends_with(':')
4530 && let Some(possible_emoji_short_code) =
4531 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4532 && !possible_emoji_short_code.is_empty()
4533 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4534 {
4535 let emoji_shortcode_start = Point::new(
4536 selection.start.row,
4537 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4538 );
4539
4540 // Remove shortcode from buffer
4541 edits.push((
4542 emoji_shortcode_start..selection.start,
4543 "".to_string().into(),
4544 ));
4545 new_selections.push((
4546 Selection {
4547 id: selection.id,
4548 start: snapshot.anchor_after(emoji_shortcode_start),
4549 end: snapshot.anchor_before(selection.start),
4550 reversed: selection.reversed,
4551 goal: selection.goal,
4552 },
4553 0,
4554 ));
4555
4556 // Insert emoji
4557 let selection_start_anchor = snapshot.anchor_after(selection.start);
4558 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4559 edits.push((selection.start..selection.end, emoji.to_string().into()));
4560
4561 continue;
4562 }
4563
4564 // If not handling any auto-close operation, then just replace the selected
4565 // text with the given input and move the selection to the end of the
4566 // newly inserted text.
4567 let anchor = snapshot.anchor_after(selection.end);
4568 if !self.linked_edit_ranges.is_empty() {
4569 let start_anchor = snapshot.anchor_before(selection.start);
4570
4571 let is_word_char = text.chars().next().is_none_or(|char| {
4572 let classifier = snapshot
4573 .char_classifier_at(start_anchor.to_offset(&snapshot))
4574 .scope_context(Some(CharScopeContext::LinkedEdit));
4575 classifier.is_word(char)
4576 });
4577
4578 if is_word_char {
4579 if let Some(ranges) = self
4580 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4581 {
4582 for (buffer, edits) in ranges {
4583 linked_edits
4584 .entry(buffer.clone())
4585 .or_default()
4586 .extend(edits.into_iter().map(|range| (range, text.clone())));
4587 }
4588 }
4589 } else {
4590 clear_linked_edit_ranges = true;
4591 }
4592 }
4593
4594 new_selections.push((selection.map(|_| anchor), 0));
4595 edits.push((selection.start..selection.end, text.clone()));
4596 }
4597
4598 drop(snapshot);
4599
4600 self.transact(window, cx, |this, window, cx| {
4601 if clear_linked_edit_ranges {
4602 this.linked_edit_ranges.clear();
4603 }
4604 let initial_buffer_versions =
4605 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4606
4607 this.buffer.update(cx, |buffer, cx| {
4608 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4609 });
4610 for (buffer, edits) in linked_edits {
4611 buffer.update(cx, |buffer, cx| {
4612 let snapshot = buffer.snapshot();
4613 let edits = edits
4614 .into_iter()
4615 .map(|(range, text)| {
4616 use text::ToPoint as TP;
4617 let end_point = TP::to_point(&range.end, &snapshot);
4618 let start_point = TP::to_point(&range.start, &snapshot);
4619 (start_point..end_point, text)
4620 })
4621 .sorted_by_key(|(range, _)| range.start);
4622 buffer.edit(edits, None, cx);
4623 })
4624 }
4625 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4626 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4627 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4628 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4629 new_anchor_selections,
4630 &map,
4631 )
4632 .zip(new_selection_deltas)
4633 .map(|(selection, delta)| Selection {
4634 id: selection.id,
4635 start: selection.start + delta,
4636 end: selection.end + delta,
4637 reversed: selection.reversed,
4638 goal: SelectionGoal::None,
4639 })
4640 .collect::<Vec<_>>();
4641
4642 let mut i = 0;
4643 for (position, delta, selection_id, pair) in new_autoclose_regions {
4644 let position = position.to_offset(map.buffer_snapshot()) + delta;
4645 let start = map.buffer_snapshot().anchor_before(position);
4646 let end = map.buffer_snapshot().anchor_after(position);
4647 while let Some(existing_state) = this.autoclose_regions.get(i) {
4648 match existing_state
4649 .range
4650 .start
4651 .cmp(&start, map.buffer_snapshot())
4652 {
4653 Ordering::Less => i += 1,
4654 Ordering::Greater => break,
4655 Ordering::Equal => {
4656 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4657 Ordering::Less => i += 1,
4658 Ordering::Equal => break,
4659 Ordering::Greater => break,
4660 }
4661 }
4662 }
4663 }
4664 this.autoclose_regions.insert(
4665 i,
4666 AutocloseRegion {
4667 selection_id,
4668 range: start..end,
4669 pair,
4670 },
4671 );
4672 }
4673
4674 let had_active_edit_prediction = this.has_active_edit_prediction();
4675 this.change_selections(
4676 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4677 window,
4678 cx,
4679 |s| s.select(new_selections),
4680 );
4681
4682 if !bracket_inserted
4683 && let Some(on_type_format_task) =
4684 this.trigger_on_type_formatting(text.to_string(), window, cx)
4685 {
4686 on_type_format_task.detach_and_log_err(cx);
4687 }
4688
4689 let editor_settings = EditorSettings::get_global(cx);
4690 if bracket_inserted
4691 && (editor_settings.auto_signature_help
4692 || editor_settings.show_signature_help_after_edits)
4693 {
4694 this.show_signature_help(&ShowSignatureHelp, window, cx);
4695 }
4696
4697 let trigger_in_words =
4698 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4699 if this.hard_wrap.is_some() {
4700 let latest: Range<Point> = this.selections.newest(&map).range();
4701 if latest.is_empty()
4702 && this
4703 .buffer()
4704 .read(cx)
4705 .snapshot(cx)
4706 .line_len(MultiBufferRow(latest.start.row))
4707 == latest.start.column
4708 {
4709 this.rewrap_impl(
4710 RewrapOptions {
4711 override_language_settings: true,
4712 preserve_existing_whitespace: true,
4713 },
4714 cx,
4715 )
4716 }
4717 }
4718 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4719 refresh_linked_ranges(this, window, cx);
4720 this.refresh_edit_prediction(true, false, window, cx);
4721 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4722 });
4723 }
4724
4725 fn find_possible_emoji_shortcode_at_position(
4726 snapshot: &MultiBufferSnapshot,
4727 position: Point,
4728 ) -> Option<String> {
4729 let mut chars = Vec::new();
4730 let mut found_colon = false;
4731 for char in snapshot.reversed_chars_at(position).take(100) {
4732 // Found a possible emoji shortcode in the middle of the buffer
4733 if found_colon {
4734 if char.is_whitespace() {
4735 chars.reverse();
4736 return Some(chars.iter().collect());
4737 }
4738 // If the previous character is not a whitespace, we are in the middle of a word
4739 // and we only want to complete the shortcode if the word is made up of other emojis
4740 let mut containing_word = String::new();
4741 for ch in snapshot
4742 .reversed_chars_at(position)
4743 .skip(chars.len() + 1)
4744 .take(100)
4745 {
4746 if ch.is_whitespace() {
4747 break;
4748 }
4749 containing_word.push(ch);
4750 }
4751 let containing_word = containing_word.chars().rev().collect::<String>();
4752 if util::word_consists_of_emojis(containing_word.as_str()) {
4753 chars.reverse();
4754 return Some(chars.iter().collect());
4755 }
4756 }
4757
4758 if char.is_whitespace() || !char.is_ascii() {
4759 return None;
4760 }
4761 if char == ':' {
4762 found_colon = true;
4763 } else {
4764 chars.push(char);
4765 }
4766 }
4767 // Found a possible emoji shortcode at the beginning of the buffer
4768 chars.reverse();
4769 Some(chars.iter().collect())
4770 }
4771
4772 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4773 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4774 self.transact(window, cx, |this, window, cx| {
4775 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4776 let selections = this
4777 .selections
4778 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4779 let multi_buffer = this.buffer.read(cx);
4780 let buffer = multi_buffer.snapshot(cx);
4781 selections
4782 .iter()
4783 .map(|selection| {
4784 let start_point = selection.start.to_point(&buffer);
4785 let mut existing_indent =
4786 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4787 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4788 let start = selection.start;
4789 let end = selection.end;
4790 let selection_is_empty = start == end;
4791 let language_scope = buffer.language_scope_at(start);
4792 let (delimiter, newline_config) = if let Some(language) = &language_scope {
4793 let needs_extra_newline = NewlineConfig::insert_extra_newline_brackets(
4794 &buffer,
4795 start..end,
4796 language,
4797 )
4798 || NewlineConfig::insert_extra_newline_tree_sitter(
4799 &buffer,
4800 start..end,
4801 );
4802
4803 let mut newline_config = NewlineConfig::Newline {
4804 additional_indent: IndentSize::spaces(0),
4805 extra_line_additional_indent: if needs_extra_newline {
4806 Some(IndentSize::spaces(0))
4807 } else {
4808 None
4809 },
4810 prevent_auto_indent: false,
4811 };
4812
4813 let comment_delimiter = maybe!({
4814 if !selection_is_empty {
4815 return None;
4816 }
4817
4818 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4819 return None;
4820 }
4821
4822 return comment_delimiter_for_newline(
4823 &start_point,
4824 &buffer,
4825 language,
4826 );
4827 });
4828
4829 let doc_delimiter = maybe!({
4830 if !selection_is_empty {
4831 return None;
4832 }
4833
4834 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4835 return None;
4836 }
4837
4838 return documentation_delimiter_for_newline(
4839 &start_point,
4840 &buffer,
4841 language,
4842 &mut newline_config,
4843 );
4844 });
4845
4846 let list_delimiter = maybe!({
4847 if !selection_is_empty {
4848 return None;
4849 }
4850
4851 if !multi_buffer.language_settings(cx).extend_list_on_newline {
4852 return None;
4853 }
4854
4855 return list_delimiter_for_newline(
4856 &start_point,
4857 &buffer,
4858 language,
4859 &mut newline_config,
4860 );
4861 });
4862
4863 (
4864 comment_delimiter.or(doc_delimiter).or(list_delimiter),
4865 newline_config,
4866 )
4867 } else {
4868 (
4869 None,
4870 NewlineConfig::Newline {
4871 additional_indent: IndentSize::spaces(0),
4872 extra_line_additional_indent: None,
4873 prevent_auto_indent: false,
4874 },
4875 )
4876 };
4877
4878 let (edit_start, new_text, prevent_auto_indent) = match &newline_config {
4879 NewlineConfig::ClearCurrentLine => {
4880 let row_start =
4881 buffer.point_to_offset(Point::new(start_point.row, 0));
4882 (row_start, String::new(), false)
4883 }
4884 NewlineConfig::UnindentCurrentLine { continuation } => {
4885 let row_start =
4886 buffer.point_to_offset(Point::new(start_point.row, 0));
4887 let tab_size = buffer.language_settings_at(start, cx).tab_size;
4888 let tab_size_indent = IndentSize::spaces(tab_size.get());
4889 let reduced_indent =
4890 existing_indent.with_delta(Ordering::Less, tab_size_indent);
4891 let mut new_text = String::new();
4892 new_text.extend(reduced_indent.chars());
4893 new_text.push_str(continuation);
4894 (row_start, new_text, true)
4895 }
4896 NewlineConfig::Newline {
4897 additional_indent,
4898 extra_line_additional_indent,
4899 prevent_auto_indent,
4900 } => {
4901 let capacity_for_delimiter =
4902 delimiter.as_deref().map(str::len).unwrap_or_default();
4903 let extra_line_len = extra_line_additional_indent
4904 .map(|i| 1 + existing_indent.len as usize + i.len as usize)
4905 .unwrap_or(0);
4906 let mut new_text = String::with_capacity(
4907 1 + capacity_for_delimiter
4908 + existing_indent.len as usize
4909 + additional_indent.len as usize
4910 + extra_line_len,
4911 );
4912 new_text.push('\n');
4913 new_text.extend(existing_indent.chars());
4914 new_text.extend(additional_indent.chars());
4915 if let Some(delimiter) = &delimiter {
4916 new_text.push_str(delimiter);
4917 }
4918 if let Some(extra_indent) = extra_line_additional_indent {
4919 new_text.push('\n');
4920 new_text.extend(existing_indent.chars());
4921 new_text.extend(extra_indent.chars());
4922 }
4923 (start, new_text, *prevent_auto_indent)
4924 }
4925 };
4926
4927 let anchor = buffer.anchor_after(end);
4928 let new_selection = selection.map(|_| anchor);
4929 (
4930 ((edit_start..end, new_text), prevent_auto_indent),
4931 (newline_config.has_extra_line(), new_selection),
4932 )
4933 })
4934 .unzip()
4935 };
4936
4937 let mut auto_indent_edits = Vec::new();
4938 let mut edits = Vec::new();
4939 for (edit, prevent_auto_indent) in edits_with_flags {
4940 if prevent_auto_indent {
4941 edits.push(edit);
4942 } else {
4943 auto_indent_edits.push(edit);
4944 }
4945 }
4946 if !edits.is_empty() {
4947 this.edit(edits, cx);
4948 }
4949 if !auto_indent_edits.is_empty() {
4950 this.edit_with_autoindent(auto_indent_edits, cx);
4951 }
4952
4953 let buffer = this.buffer.read(cx).snapshot(cx);
4954 let new_selections = selection_info
4955 .into_iter()
4956 .map(|(extra_newline_inserted, new_selection)| {
4957 let mut cursor = new_selection.end.to_point(&buffer);
4958 if extra_newline_inserted {
4959 cursor.row -= 1;
4960 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4961 }
4962 new_selection.map(|_| cursor)
4963 })
4964 .collect();
4965
4966 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4967 this.refresh_edit_prediction(true, false, window, cx);
4968 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
4969 task.detach_and_log_err(cx);
4970 }
4971 });
4972 }
4973
4974 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4975 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4976
4977 let buffer = self.buffer.read(cx);
4978 let snapshot = buffer.snapshot(cx);
4979
4980 let mut edits = Vec::new();
4981 let mut rows = Vec::new();
4982
4983 for (rows_inserted, selection) in self
4984 .selections
4985 .all_adjusted(&self.display_snapshot(cx))
4986 .into_iter()
4987 .enumerate()
4988 {
4989 let cursor = selection.head();
4990 let row = cursor.row;
4991
4992 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4993
4994 let newline = "\n".to_string();
4995 edits.push((start_of_line..start_of_line, newline));
4996
4997 rows.push(row + rows_inserted as u32);
4998 }
4999
5000 self.transact(window, cx, |editor, window, cx| {
5001 editor.edit(edits, cx);
5002
5003 editor.change_selections(Default::default(), window, cx, |s| {
5004 let mut index = 0;
5005 s.move_cursors_with(|map, _, _| {
5006 let row = rows[index];
5007 index += 1;
5008
5009 let point = Point::new(row, 0);
5010 let boundary = map.next_line_boundary(point).1;
5011 let clipped = map.clip_point(boundary, Bias::Left);
5012
5013 (clipped, SelectionGoal::None)
5014 });
5015 });
5016
5017 let mut indent_edits = Vec::new();
5018 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5019 for row in rows {
5020 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5021 for (row, indent) in indents {
5022 if indent.len == 0 {
5023 continue;
5024 }
5025
5026 let text = match indent.kind {
5027 IndentKind::Space => " ".repeat(indent.len as usize),
5028 IndentKind::Tab => "\t".repeat(indent.len as usize),
5029 };
5030 let point = Point::new(row.0, 0);
5031 indent_edits.push((point..point, text));
5032 }
5033 }
5034 editor.edit(indent_edits, cx);
5035 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5036 format.detach_and_log_err(cx);
5037 }
5038 });
5039 }
5040
5041 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5043
5044 let buffer = self.buffer.read(cx);
5045 let snapshot = buffer.snapshot(cx);
5046
5047 let mut edits = Vec::new();
5048 let mut rows = Vec::new();
5049 let mut rows_inserted = 0;
5050
5051 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5052 let cursor = selection.head();
5053 let row = cursor.row;
5054
5055 let point = Point::new(row + 1, 0);
5056 let start_of_line = snapshot.clip_point(point, Bias::Left);
5057
5058 let newline = "\n".to_string();
5059 edits.push((start_of_line..start_of_line, newline));
5060
5061 rows_inserted += 1;
5062 rows.push(row + rows_inserted);
5063 }
5064
5065 self.transact(window, cx, |editor, window, cx| {
5066 editor.edit(edits, cx);
5067
5068 editor.change_selections(Default::default(), window, cx, |s| {
5069 let mut index = 0;
5070 s.move_cursors_with(|map, _, _| {
5071 let row = rows[index];
5072 index += 1;
5073
5074 let point = Point::new(row, 0);
5075 let boundary = map.next_line_boundary(point).1;
5076 let clipped = map.clip_point(boundary, Bias::Left);
5077
5078 (clipped, SelectionGoal::None)
5079 });
5080 });
5081
5082 let mut indent_edits = Vec::new();
5083 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5084 for row in rows {
5085 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5086 for (row, indent) in indents {
5087 if indent.len == 0 {
5088 continue;
5089 }
5090
5091 let text = match indent.kind {
5092 IndentKind::Space => " ".repeat(indent.len as usize),
5093 IndentKind::Tab => "\t".repeat(indent.len as usize),
5094 };
5095 let point = Point::new(row.0, 0);
5096 indent_edits.push((point..point, text));
5097 }
5098 }
5099 editor.edit(indent_edits, cx);
5100 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5101 format.detach_and_log_err(cx);
5102 }
5103 });
5104 }
5105
5106 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5107 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5108 original_indent_columns: Vec::new(),
5109 });
5110 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5111 }
5112
5113 fn insert_with_autoindent_mode(
5114 &mut self,
5115 text: &str,
5116 autoindent_mode: Option<AutoindentMode>,
5117 window: &mut Window,
5118 cx: &mut Context<Self>,
5119 ) {
5120 if self.read_only(cx) {
5121 return;
5122 }
5123
5124 let text: Arc<str> = text.into();
5125 self.transact(window, cx, |this, window, cx| {
5126 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5127 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5128 let anchors = {
5129 let snapshot = buffer.read(cx);
5130 old_selections
5131 .iter()
5132 .map(|s| {
5133 let anchor = snapshot.anchor_after(s.head());
5134 s.map(|_| anchor)
5135 })
5136 .collect::<Vec<_>>()
5137 };
5138 buffer.edit(
5139 old_selections
5140 .iter()
5141 .map(|s| (s.start..s.end, text.clone())),
5142 autoindent_mode,
5143 cx,
5144 );
5145 anchors
5146 });
5147
5148 this.change_selections(Default::default(), window, cx, |s| {
5149 s.select_anchors(selection_anchors);
5150 });
5151
5152 cx.notify();
5153 });
5154 }
5155
5156 fn trigger_completion_on_input(
5157 &mut self,
5158 text: &str,
5159 trigger_in_words: bool,
5160 window: &mut Window,
5161 cx: &mut Context<Self>,
5162 ) {
5163 let completions_source = self
5164 .context_menu
5165 .borrow()
5166 .as_ref()
5167 .and_then(|menu| match menu {
5168 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5169 CodeContextMenu::CodeActions(_) => None,
5170 });
5171
5172 match completions_source {
5173 Some(CompletionsMenuSource::Words { .. }) => {
5174 self.open_or_update_completions_menu(
5175 Some(CompletionsMenuSource::Words {
5176 ignore_threshold: false,
5177 }),
5178 None,
5179 trigger_in_words,
5180 window,
5181 cx,
5182 );
5183 }
5184 _ => self.open_or_update_completions_menu(
5185 None,
5186 Some(text.to_owned()).filter(|x| !x.is_empty()),
5187 true,
5188 window,
5189 cx,
5190 ),
5191 }
5192 }
5193
5194 /// If any empty selections is touching the start of its innermost containing autoclose
5195 /// region, expand it to select the brackets.
5196 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5197 let selections = self
5198 .selections
5199 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5200 let buffer = self.buffer.read(cx).read(cx);
5201 let new_selections = self
5202 .selections_with_autoclose_regions(selections, &buffer)
5203 .map(|(mut selection, region)| {
5204 if !selection.is_empty() {
5205 return selection;
5206 }
5207
5208 if let Some(region) = region {
5209 let mut range = region.range.to_offset(&buffer);
5210 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5211 range.start -= region.pair.start.len();
5212 if buffer.contains_str_at(range.start, ®ion.pair.start)
5213 && buffer.contains_str_at(range.end, ®ion.pair.end)
5214 {
5215 range.end += region.pair.end.len();
5216 selection.start = range.start;
5217 selection.end = range.end;
5218
5219 return selection;
5220 }
5221 }
5222 }
5223
5224 let always_treat_brackets_as_autoclosed = buffer
5225 .language_settings_at(selection.start, cx)
5226 .always_treat_brackets_as_autoclosed;
5227
5228 if !always_treat_brackets_as_autoclosed {
5229 return selection;
5230 }
5231
5232 if let Some(scope) = buffer.language_scope_at(selection.start) {
5233 for (pair, enabled) in scope.brackets() {
5234 if !enabled || !pair.close {
5235 continue;
5236 }
5237
5238 if buffer.contains_str_at(selection.start, &pair.end) {
5239 let pair_start_len = pair.start.len();
5240 if buffer.contains_str_at(
5241 selection.start.saturating_sub_usize(pair_start_len),
5242 &pair.start,
5243 ) {
5244 selection.start -= pair_start_len;
5245 selection.end += pair.end.len();
5246
5247 return selection;
5248 }
5249 }
5250 }
5251 }
5252
5253 selection
5254 })
5255 .collect();
5256
5257 drop(buffer);
5258 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5259 selections.select(new_selections)
5260 });
5261 }
5262
5263 /// Iterate the given selections, and for each one, find the smallest surrounding
5264 /// autoclose region. This uses the ordering of the selections and the autoclose
5265 /// regions to avoid repeated comparisons.
5266 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5267 &'a self,
5268 selections: impl IntoIterator<Item = Selection<D>>,
5269 buffer: &'a MultiBufferSnapshot,
5270 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5271 let mut i = 0;
5272 let mut regions = self.autoclose_regions.as_slice();
5273 selections.into_iter().map(move |selection| {
5274 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5275
5276 let mut enclosing = None;
5277 while let Some(pair_state) = regions.get(i) {
5278 if pair_state.range.end.to_offset(buffer) < range.start {
5279 regions = ®ions[i + 1..];
5280 i = 0;
5281 } else if pair_state.range.start.to_offset(buffer) > range.end {
5282 break;
5283 } else {
5284 if pair_state.selection_id == selection.id {
5285 enclosing = Some(pair_state);
5286 }
5287 i += 1;
5288 }
5289 }
5290
5291 (selection, enclosing)
5292 })
5293 }
5294
5295 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5296 fn invalidate_autoclose_regions(
5297 &mut self,
5298 mut selections: &[Selection<Anchor>],
5299 buffer: &MultiBufferSnapshot,
5300 ) {
5301 self.autoclose_regions.retain(|state| {
5302 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5303 return false;
5304 }
5305
5306 let mut i = 0;
5307 while let Some(selection) = selections.get(i) {
5308 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5309 selections = &selections[1..];
5310 continue;
5311 }
5312 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5313 break;
5314 }
5315 if selection.id == state.selection_id {
5316 return true;
5317 } else {
5318 i += 1;
5319 }
5320 }
5321 false
5322 });
5323 }
5324
5325 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5326 let offset = position.to_offset(buffer);
5327 let (word_range, kind) =
5328 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5329 if offset > word_range.start && kind == Some(CharKind::Word) {
5330 Some(
5331 buffer
5332 .text_for_range(word_range.start..offset)
5333 .collect::<String>(),
5334 )
5335 } else {
5336 None
5337 }
5338 }
5339
5340 pub fn visible_excerpts(
5341 &self,
5342 lsp_related_only: bool,
5343 cx: &mut Context<Editor>,
5344 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5345 let project = self.project().cloned();
5346 let multi_buffer = self.buffer().read(cx);
5347 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5348 let multi_buffer_visible_start = self
5349 .scroll_manager
5350 .anchor()
5351 .anchor
5352 .to_point(&multi_buffer_snapshot);
5353 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5354 multi_buffer_visible_start
5355 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5356 Bias::Left,
5357 );
5358 multi_buffer_snapshot
5359 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5360 .into_iter()
5361 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5362 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5363 if !lsp_related_only {
5364 return Some((
5365 excerpt_id,
5366 (
5367 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5368 buffer.version().clone(),
5369 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5370 ),
5371 ));
5372 }
5373
5374 let project = project.as_ref()?.read(cx);
5375 let buffer_file = project::File::from_dyn(buffer.file())?;
5376 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5377 let worktree_entry = buffer_worktree
5378 .read(cx)
5379 .entry_for_id(buffer_file.project_entry_id()?)?;
5380 if worktree_entry.is_ignored {
5381 None
5382 } else {
5383 Some((
5384 excerpt_id,
5385 (
5386 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5387 buffer.version().clone(),
5388 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5389 ),
5390 ))
5391 }
5392 })
5393 .collect()
5394 }
5395
5396 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5397 TextLayoutDetails {
5398 text_system: window.text_system().clone(),
5399 editor_style: self.style.clone().unwrap(),
5400 rem_size: window.rem_size(),
5401 scroll_anchor: self.scroll_manager.anchor(),
5402 visible_rows: self.visible_line_count(),
5403 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5404 }
5405 }
5406
5407 fn trigger_on_type_formatting(
5408 &self,
5409 input: String,
5410 window: &mut Window,
5411 cx: &mut Context<Self>,
5412 ) -> Option<Task<Result<()>>> {
5413 if input.chars().count() != 1 {
5414 return None;
5415 }
5416
5417 let project = self.project()?;
5418 let position = self.selections.newest_anchor().head();
5419 let (buffer, buffer_position) = self
5420 .buffer
5421 .read(cx)
5422 .text_anchor_for_position(position, cx)?;
5423
5424 let settings = language_settings::language_settings(
5425 buffer
5426 .read(cx)
5427 .language_at(buffer_position)
5428 .map(|l| l.name()),
5429 buffer.read(cx).file(),
5430 cx,
5431 );
5432 if !settings.use_on_type_format {
5433 return None;
5434 }
5435
5436 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5437 // hence we do LSP request & edit on host side only — add formats to host's history.
5438 let push_to_lsp_host_history = true;
5439 // If this is not the host, append its history with new edits.
5440 let push_to_client_history = project.read(cx).is_via_collab();
5441
5442 let on_type_formatting = project.update(cx, |project, cx| {
5443 project.on_type_format(
5444 buffer.clone(),
5445 buffer_position,
5446 input,
5447 push_to_lsp_host_history,
5448 cx,
5449 )
5450 });
5451 Some(cx.spawn_in(window, async move |editor, cx| {
5452 if let Some(transaction) = on_type_formatting.await? {
5453 if push_to_client_history {
5454 buffer
5455 .update(cx, |buffer, _| {
5456 buffer.push_transaction(transaction, Instant::now());
5457 buffer.finalize_last_transaction();
5458 })
5459 .ok();
5460 }
5461 editor.update(cx, |editor, cx| {
5462 editor.refresh_document_highlights(cx);
5463 })?;
5464 }
5465 Ok(())
5466 }))
5467 }
5468
5469 pub fn show_word_completions(
5470 &mut self,
5471 _: &ShowWordCompletions,
5472 window: &mut Window,
5473 cx: &mut Context<Self>,
5474 ) {
5475 self.open_or_update_completions_menu(
5476 Some(CompletionsMenuSource::Words {
5477 ignore_threshold: true,
5478 }),
5479 None,
5480 false,
5481 window,
5482 cx,
5483 );
5484 }
5485
5486 pub fn show_completions(
5487 &mut self,
5488 _: &ShowCompletions,
5489 window: &mut Window,
5490 cx: &mut Context<Self>,
5491 ) {
5492 self.open_or_update_completions_menu(None, None, false, window, cx);
5493 }
5494
5495 fn open_or_update_completions_menu(
5496 &mut self,
5497 requested_source: Option<CompletionsMenuSource>,
5498 trigger: Option<String>,
5499 trigger_in_words: bool,
5500 window: &mut Window,
5501 cx: &mut Context<Self>,
5502 ) {
5503 if self.pending_rename.is_some() {
5504 return;
5505 }
5506
5507 let completions_source = self
5508 .context_menu
5509 .borrow()
5510 .as_ref()
5511 .and_then(|menu| match menu {
5512 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5513 CodeContextMenu::CodeActions(_) => None,
5514 });
5515
5516 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5517
5518 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5519 // inserted and selected. To handle that case, the start of the selection is used so that
5520 // the menu starts with all choices.
5521 let position = self
5522 .selections
5523 .newest_anchor()
5524 .start
5525 .bias_right(&multibuffer_snapshot);
5526 if position.diff_base_anchor.is_some() {
5527 return;
5528 }
5529 let buffer_position = multibuffer_snapshot.anchor_before(position);
5530 let Some(buffer) = buffer_position
5531 .text_anchor
5532 .buffer_id
5533 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5534 else {
5535 return;
5536 };
5537 let buffer_snapshot = buffer.read(cx).snapshot();
5538
5539 let menu_is_open = matches!(
5540 self.context_menu.borrow().as_ref(),
5541 Some(CodeContextMenu::Completions(_))
5542 );
5543
5544 let language = buffer_snapshot
5545 .language_at(buffer_position.text_anchor)
5546 .map(|language| language.name());
5547
5548 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5549 let completion_settings = language_settings.completions.clone();
5550
5551 let show_completions_on_input = self
5552 .show_completions_on_input_override
5553 .unwrap_or(language_settings.show_completions_on_input);
5554 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5555 return;
5556 }
5557
5558 let query: Option<Arc<String>> =
5559 Self::completion_query(&multibuffer_snapshot, buffer_position)
5560 .map(|query| query.into());
5561
5562 drop(multibuffer_snapshot);
5563
5564 // Hide the current completions menu when query is empty. Without this, cached
5565 // completions from before the trigger char may be reused (#32774).
5566 if query.is_none() && menu_is_open {
5567 self.hide_context_menu(window, cx);
5568 }
5569
5570 let mut ignore_word_threshold = false;
5571 let provider = match requested_source {
5572 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5573 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5574 ignore_word_threshold = ignore_threshold;
5575 None
5576 }
5577 Some(CompletionsMenuSource::SnippetChoices)
5578 | Some(CompletionsMenuSource::SnippetsOnly) => {
5579 log::error!("bug: SnippetChoices requested_source is not handled");
5580 None
5581 }
5582 };
5583
5584 let sort_completions = provider
5585 .as_ref()
5586 .is_some_and(|provider| provider.sort_completions());
5587
5588 let filter_completions = provider
5589 .as_ref()
5590 .is_none_or(|provider| provider.filter_completions());
5591
5592 let was_snippets_only = matches!(
5593 completions_source,
5594 Some(CompletionsMenuSource::SnippetsOnly)
5595 );
5596
5597 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5598 if filter_completions {
5599 menu.filter(
5600 query.clone().unwrap_or_default(),
5601 buffer_position.text_anchor,
5602 &buffer,
5603 provider.clone(),
5604 window,
5605 cx,
5606 );
5607 }
5608 // When `is_incomplete` is false, no need to re-query completions when the current query
5609 // is a suffix of the initial query.
5610 let was_complete = !menu.is_incomplete;
5611 if was_complete && !was_snippets_only {
5612 // If the new query is a suffix of the old query (typing more characters) and
5613 // the previous result was complete, the existing completions can be filtered.
5614 //
5615 // Note that snippet completions are always complete.
5616 let query_matches = match (&menu.initial_query, &query) {
5617 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5618 (None, _) => true,
5619 _ => false,
5620 };
5621 if query_matches {
5622 let position_matches = if menu.initial_position == position {
5623 true
5624 } else {
5625 let snapshot = self.buffer.read(cx).read(cx);
5626 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5627 };
5628 if position_matches {
5629 return;
5630 }
5631 }
5632 }
5633 };
5634
5635 let Anchor {
5636 excerpt_id: buffer_excerpt_id,
5637 text_anchor: buffer_position,
5638 ..
5639 } = buffer_position;
5640
5641 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5642 buffer_snapshot.surrounding_word(buffer_position, None)
5643 {
5644 let word_to_exclude = buffer_snapshot
5645 .text_for_range(word_range.clone())
5646 .collect::<String>();
5647 (
5648 buffer_snapshot.anchor_before(word_range.start)
5649 ..buffer_snapshot.anchor_after(buffer_position),
5650 Some(word_to_exclude),
5651 )
5652 } else {
5653 (buffer_position..buffer_position, None)
5654 };
5655
5656 let show_completion_documentation = buffer_snapshot
5657 .settings_at(buffer_position, cx)
5658 .show_completion_documentation;
5659
5660 // The document can be large, so stay in reasonable bounds when searching for words,
5661 // otherwise completion pop-up might be slow to appear.
5662 const WORD_LOOKUP_ROWS: u32 = 5_000;
5663 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5664 let min_word_search = buffer_snapshot.clip_point(
5665 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5666 Bias::Left,
5667 );
5668 let max_word_search = buffer_snapshot.clip_point(
5669 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5670 Bias::Right,
5671 );
5672 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5673 ..buffer_snapshot.point_to_offset(max_word_search);
5674
5675 let skip_digits = query
5676 .as_ref()
5677 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5678
5679 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5680 trigger.as_ref().is_none_or(|trigger| {
5681 provider.is_completion_trigger(
5682 &buffer,
5683 position.text_anchor,
5684 trigger,
5685 trigger_in_words,
5686 cx,
5687 )
5688 })
5689 });
5690
5691 let provider_responses = if let Some(provider) = &provider
5692 && load_provider_completions
5693 {
5694 let trigger_character =
5695 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5696 let completion_context = CompletionContext {
5697 trigger_kind: match &trigger_character {
5698 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5699 None => CompletionTriggerKind::INVOKED,
5700 },
5701 trigger_character,
5702 };
5703
5704 provider.completions(
5705 buffer_excerpt_id,
5706 &buffer,
5707 buffer_position,
5708 completion_context,
5709 window,
5710 cx,
5711 )
5712 } else {
5713 Task::ready(Ok(Vec::new()))
5714 };
5715
5716 let load_word_completions = if !self.word_completions_enabled {
5717 false
5718 } else if requested_source
5719 == Some(CompletionsMenuSource::Words {
5720 ignore_threshold: true,
5721 })
5722 {
5723 true
5724 } else {
5725 load_provider_completions
5726 && completion_settings.words != WordsCompletionMode::Disabled
5727 && (ignore_word_threshold || {
5728 let words_min_length = completion_settings.words_min_length;
5729 // check whether word has at least `words_min_length` characters
5730 let query_chars = query.iter().flat_map(|q| q.chars());
5731 query_chars.take(words_min_length).count() == words_min_length
5732 })
5733 };
5734
5735 let mut words = if load_word_completions {
5736 cx.background_spawn({
5737 let buffer_snapshot = buffer_snapshot.clone();
5738 async move {
5739 buffer_snapshot.words_in_range(WordsQuery {
5740 fuzzy_contents: None,
5741 range: word_search_range,
5742 skip_digits,
5743 })
5744 }
5745 })
5746 } else {
5747 Task::ready(BTreeMap::default())
5748 };
5749
5750 let snippets = if let Some(provider) = &provider
5751 && provider.show_snippets()
5752 && let Some(project) = self.project()
5753 {
5754 let char_classifier = buffer_snapshot
5755 .char_classifier_at(buffer_position)
5756 .scope_context(Some(CharScopeContext::Completion));
5757 project.update(cx, |project, cx| {
5758 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5759 })
5760 } else {
5761 Task::ready(Ok(CompletionResponse {
5762 completions: Vec::new(),
5763 display_options: Default::default(),
5764 is_incomplete: false,
5765 }))
5766 };
5767
5768 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5769
5770 let id = post_inc(&mut self.next_completion_id);
5771 let task = cx.spawn_in(window, async move |editor, cx| {
5772 let Ok(()) = editor.update(cx, |this, _| {
5773 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5774 }) else {
5775 return;
5776 };
5777
5778 // TODO: Ideally completions from different sources would be selectively re-queried, so
5779 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5780 let mut completions = Vec::new();
5781 let mut is_incomplete = false;
5782 let mut display_options: Option<CompletionDisplayOptions> = None;
5783 if let Some(provider_responses) = provider_responses.await.log_err()
5784 && !provider_responses.is_empty()
5785 {
5786 for response in provider_responses {
5787 completions.extend(response.completions);
5788 is_incomplete = is_incomplete || response.is_incomplete;
5789 match display_options.as_mut() {
5790 None => {
5791 display_options = Some(response.display_options);
5792 }
5793 Some(options) => options.merge(&response.display_options),
5794 }
5795 }
5796 if completion_settings.words == WordsCompletionMode::Fallback {
5797 words = Task::ready(BTreeMap::default());
5798 }
5799 }
5800 let display_options = display_options.unwrap_or_default();
5801
5802 let mut words = words.await;
5803 if let Some(word_to_exclude) = &word_to_exclude {
5804 words.remove(word_to_exclude);
5805 }
5806 for lsp_completion in &completions {
5807 words.remove(&lsp_completion.new_text);
5808 }
5809 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5810 replace_range: word_replace_range.clone(),
5811 new_text: word.clone(),
5812 label: CodeLabel::plain(word, None),
5813 match_start: None,
5814 snippet_deduplication_key: None,
5815 icon_path: None,
5816 documentation: None,
5817 source: CompletionSource::BufferWord {
5818 word_range,
5819 resolved: false,
5820 },
5821 insert_text_mode: Some(InsertTextMode::AS_IS),
5822 confirm: None,
5823 }));
5824
5825 completions.extend(
5826 snippets
5827 .await
5828 .into_iter()
5829 .flat_map(|response| response.completions),
5830 );
5831
5832 let menu = if completions.is_empty() {
5833 None
5834 } else {
5835 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5836 let languages = editor
5837 .workspace
5838 .as_ref()
5839 .and_then(|(workspace, _)| workspace.upgrade())
5840 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5841 let menu = CompletionsMenu::new(
5842 id,
5843 requested_source.unwrap_or(if load_provider_completions {
5844 CompletionsMenuSource::Normal
5845 } else {
5846 CompletionsMenuSource::SnippetsOnly
5847 }),
5848 sort_completions,
5849 show_completion_documentation,
5850 position,
5851 query.clone(),
5852 is_incomplete,
5853 buffer.clone(),
5854 completions.into(),
5855 editor
5856 .context_menu()
5857 .borrow_mut()
5858 .as_ref()
5859 .map(|menu| menu.primary_scroll_handle()),
5860 display_options,
5861 snippet_sort_order,
5862 languages,
5863 language,
5864 cx,
5865 );
5866
5867 let query = if filter_completions { query } else { None };
5868 let matches_task = menu.do_async_filtering(
5869 query.unwrap_or_default(),
5870 buffer_position,
5871 &buffer,
5872 cx,
5873 );
5874 (menu, matches_task)
5875 }) else {
5876 return;
5877 };
5878
5879 let matches = matches_task.await;
5880
5881 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5882 // Newer menu already set, so exit.
5883 if let Some(CodeContextMenu::Completions(prev_menu)) =
5884 editor.context_menu.borrow().as_ref()
5885 && prev_menu.id > id
5886 {
5887 return;
5888 };
5889
5890 // Only valid to take prev_menu because either the new menu is immediately set
5891 // below, or the menu is hidden.
5892 if let Some(CodeContextMenu::Completions(prev_menu)) =
5893 editor.context_menu.borrow_mut().take()
5894 {
5895 let position_matches =
5896 if prev_menu.initial_position == menu.initial_position {
5897 true
5898 } else {
5899 let snapshot = editor.buffer.read(cx).read(cx);
5900 prev_menu.initial_position.to_offset(&snapshot)
5901 == menu.initial_position.to_offset(&snapshot)
5902 };
5903 if position_matches {
5904 // Preserve markdown cache before `set_filter_results` because it will
5905 // try to populate the documentation cache.
5906 menu.preserve_markdown_cache(prev_menu);
5907 }
5908 };
5909
5910 menu.set_filter_results(matches, provider, window, cx);
5911 }) else {
5912 return;
5913 };
5914
5915 menu.visible().then_some(menu)
5916 };
5917
5918 editor
5919 .update_in(cx, |editor, window, cx| {
5920 if editor.focus_handle.is_focused(window)
5921 && let Some(menu) = menu
5922 {
5923 *editor.context_menu.borrow_mut() =
5924 Some(CodeContextMenu::Completions(menu));
5925
5926 crate::hover_popover::hide_hover(editor, cx);
5927 if editor.show_edit_predictions_in_menu() {
5928 editor.update_visible_edit_prediction(window, cx);
5929 } else {
5930 editor.discard_edit_prediction(false, cx);
5931 }
5932
5933 cx.notify();
5934 return;
5935 }
5936
5937 if editor.completion_tasks.len() <= 1 {
5938 // If there are no more completion tasks and the last menu was empty, we should hide it.
5939 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5940 // If it was already hidden and we don't show edit predictions in the menu,
5941 // we should also show the edit prediction when available.
5942 if was_hidden && editor.show_edit_predictions_in_menu() {
5943 editor.update_visible_edit_prediction(window, cx);
5944 }
5945 }
5946 })
5947 .ok();
5948 });
5949
5950 self.completion_tasks.push((id, task));
5951 }
5952
5953 #[cfg(feature = "test-support")]
5954 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5955 let menu = self.context_menu.borrow();
5956 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5957 let completions = menu.completions.borrow();
5958 Some(completions.to_vec())
5959 } else {
5960 None
5961 }
5962 }
5963
5964 pub fn with_completions_menu_matching_id<R>(
5965 &self,
5966 id: CompletionId,
5967 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5968 ) -> R {
5969 let mut context_menu = self.context_menu.borrow_mut();
5970 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5971 return f(None);
5972 };
5973 if completions_menu.id != id {
5974 return f(None);
5975 }
5976 f(Some(completions_menu))
5977 }
5978
5979 pub fn confirm_completion(
5980 &mut self,
5981 action: &ConfirmCompletion,
5982 window: &mut Window,
5983 cx: &mut Context<Self>,
5984 ) -> Option<Task<Result<()>>> {
5985 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5986 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5987 }
5988
5989 pub fn confirm_completion_insert(
5990 &mut self,
5991 _: &ConfirmCompletionInsert,
5992 window: &mut Window,
5993 cx: &mut Context<Self>,
5994 ) -> Option<Task<Result<()>>> {
5995 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5996 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5997 }
5998
5999 pub fn confirm_completion_replace(
6000 &mut self,
6001 _: &ConfirmCompletionReplace,
6002 window: &mut Window,
6003 cx: &mut Context<Self>,
6004 ) -> Option<Task<Result<()>>> {
6005 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6006 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6007 }
6008
6009 pub fn compose_completion(
6010 &mut self,
6011 action: &ComposeCompletion,
6012 window: &mut Window,
6013 cx: &mut Context<Self>,
6014 ) -> Option<Task<Result<()>>> {
6015 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6016 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6017 }
6018
6019 fn do_completion(
6020 &mut self,
6021 item_ix: Option<usize>,
6022 intent: CompletionIntent,
6023 window: &mut Window,
6024 cx: &mut Context<Editor>,
6025 ) -> Option<Task<Result<()>>> {
6026 use language::ToOffset as _;
6027
6028 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6029 else {
6030 return None;
6031 };
6032
6033 let candidate_id = {
6034 let entries = completions_menu.entries.borrow();
6035 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6036 if self.show_edit_predictions_in_menu() {
6037 self.discard_edit_prediction(true, cx);
6038 }
6039 mat.candidate_id
6040 };
6041
6042 let completion = completions_menu
6043 .completions
6044 .borrow()
6045 .get(candidate_id)?
6046 .clone();
6047 cx.stop_propagation();
6048
6049 let buffer_handle = completions_menu.buffer.clone();
6050
6051 let CompletionEdit {
6052 new_text,
6053 snippet,
6054 replace_range,
6055 } = process_completion_for_edit(
6056 &completion,
6057 intent,
6058 &buffer_handle,
6059 &completions_menu.initial_position.text_anchor,
6060 cx,
6061 );
6062
6063 let buffer = buffer_handle.read(cx);
6064 let snapshot = self.buffer.read(cx).snapshot(cx);
6065 let newest_anchor = self.selections.newest_anchor();
6066 let replace_range_multibuffer = {
6067 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6068 excerpt.map_range_from_buffer(replace_range.clone())
6069 };
6070 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6071 return None;
6072 }
6073
6074 let old_text = buffer
6075 .text_for_range(replace_range.clone())
6076 .collect::<String>();
6077 let lookbehind = newest_anchor
6078 .start
6079 .text_anchor
6080 .to_offset(buffer)
6081 .saturating_sub(replace_range.start.0);
6082 let lookahead = replace_range
6083 .end
6084 .0
6085 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6086 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6087 let suffix = &old_text[lookbehind.min(old_text.len())..];
6088
6089 let selections = self
6090 .selections
6091 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6092 let mut ranges = Vec::new();
6093 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6094
6095 for selection in &selections {
6096 let range = if selection.id == newest_anchor.id {
6097 replace_range_multibuffer.clone()
6098 } else {
6099 let mut range = selection.range();
6100
6101 // if prefix is present, don't duplicate it
6102 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6103 range.start = range.start.saturating_sub_usize(lookbehind);
6104
6105 // if suffix is also present, mimic the newest cursor and replace it
6106 if selection.id != newest_anchor.id
6107 && snapshot.contains_str_at(range.end, suffix)
6108 {
6109 range.end += lookahead;
6110 }
6111 }
6112 range
6113 };
6114
6115 ranges.push(range.clone());
6116
6117 if !self.linked_edit_ranges.is_empty() {
6118 let start_anchor = snapshot.anchor_before(range.start);
6119 let end_anchor = snapshot.anchor_after(range.end);
6120 if let Some(ranges) = self
6121 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6122 {
6123 for (buffer, edits) in ranges {
6124 linked_edits
6125 .entry(buffer.clone())
6126 .or_default()
6127 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6128 }
6129 }
6130 }
6131 }
6132
6133 let common_prefix_len = old_text
6134 .chars()
6135 .zip(new_text.chars())
6136 .take_while(|(a, b)| a == b)
6137 .map(|(a, _)| a.len_utf8())
6138 .sum::<usize>();
6139
6140 cx.emit(EditorEvent::InputHandled {
6141 utf16_range_to_replace: None,
6142 text: new_text[common_prefix_len..].into(),
6143 });
6144
6145 self.transact(window, cx, |editor, window, cx| {
6146 if let Some(mut snippet) = snippet {
6147 snippet.text = new_text.to_string();
6148 editor
6149 .insert_snippet(&ranges, snippet, window, cx)
6150 .log_err();
6151 } else {
6152 editor.buffer.update(cx, |multi_buffer, cx| {
6153 let auto_indent = match completion.insert_text_mode {
6154 Some(InsertTextMode::AS_IS) => None,
6155 _ => editor.autoindent_mode.clone(),
6156 };
6157 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6158 multi_buffer.edit(edits, auto_indent, cx);
6159 });
6160 }
6161 for (buffer, edits) in linked_edits {
6162 buffer.update(cx, |buffer, cx| {
6163 let snapshot = buffer.snapshot();
6164 let edits = edits
6165 .into_iter()
6166 .map(|(range, text)| {
6167 use text::ToPoint as TP;
6168 let end_point = TP::to_point(&range.end, &snapshot);
6169 let start_point = TP::to_point(&range.start, &snapshot);
6170 (start_point..end_point, text)
6171 })
6172 .sorted_by_key(|(range, _)| range.start);
6173 buffer.edit(edits, None, cx);
6174 })
6175 }
6176
6177 editor.refresh_edit_prediction(true, false, window, cx);
6178 });
6179 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6180
6181 let show_new_completions_on_confirm = completion
6182 .confirm
6183 .as_ref()
6184 .is_some_and(|confirm| confirm(intent, window, cx));
6185 if show_new_completions_on_confirm {
6186 self.open_or_update_completions_menu(None, None, false, window, cx);
6187 }
6188
6189 let provider = self.completion_provider.as_ref()?;
6190
6191 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6192 let command = lsp_store.as_ref().and_then(|lsp_store| {
6193 let CompletionSource::Lsp {
6194 lsp_completion,
6195 server_id,
6196 ..
6197 } = &completion.source
6198 else {
6199 return None;
6200 };
6201 let lsp_command = lsp_completion.command.as_ref()?;
6202 let available_commands = lsp_store
6203 .read(cx)
6204 .lsp_server_capabilities
6205 .get(server_id)
6206 .and_then(|server_capabilities| {
6207 server_capabilities
6208 .execute_command_provider
6209 .as_ref()
6210 .map(|options| options.commands.as_slice())
6211 })?;
6212 if available_commands.contains(&lsp_command.command) {
6213 Some(CodeAction {
6214 server_id: *server_id,
6215 range: language::Anchor::MIN..language::Anchor::MIN,
6216 lsp_action: LspAction::Command(lsp_command.clone()),
6217 resolved: false,
6218 })
6219 } else {
6220 None
6221 }
6222 });
6223
6224 drop(completion);
6225 let apply_edits = provider.apply_additional_edits_for_completion(
6226 buffer_handle.clone(),
6227 completions_menu.completions.clone(),
6228 candidate_id,
6229 true,
6230 cx,
6231 );
6232
6233 let editor_settings = EditorSettings::get_global(cx);
6234 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6235 // After the code completion is finished, users often want to know what signatures are needed.
6236 // so we should automatically call signature_help
6237 self.show_signature_help(&ShowSignatureHelp, window, cx);
6238 }
6239
6240 Some(cx.spawn_in(window, async move |editor, cx| {
6241 apply_edits.await?;
6242
6243 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6244 let title = command.lsp_action.title().to_owned();
6245 let project_transaction = lsp_store
6246 .update(cx, |lsp_store, cx| {
6247 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6248 })?
6249 .await
6250 .context("applying post-completion command")?;
6251 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6252 Self::open_project_transaction(
6253 &editor,
6254 workspace.downgrade(),
6255 project_transaction,
6256 title,
6257 cx,
6258 )
6259 .await?;
6260 }
6261 }
6262
6263 Ok(())
6264 }))
6265 }
6266
6267 pub fn toggle_code_actions(
6268 &mut self,
6269 action: &ToggleCodeActions,
6270 window: &mut Window,
6271 cx: &mut Context<Self>,
6272 ) {
6273 let quick_launch = action.quick_launch;
6274 let mut context_menu = self.context_menu.borrow_mut();
6275 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6276 if code_actions.deployed_from == action.deployed_from {
6277 // Toggle if we're selecting the same one
6278 *context_menu = None;
6279 cx.notify();
6280 return;
6281 } else {
6282 // Otherwise, clear it and start a new one
6283 *context_menu = None;
6284 cx.notify();
6285 }
6286 }
6287 drop(context_menu);
6288 let snapshot = self.snapshot(window, cx);
6289 let deployed_from = action.deployed_from.clone();
6290 let action = action.clone();
6291 self.completion_tasks.clear();
6292 self.discard_edit_prediction(false, cx);
6293
6294 let multibuffer_point = match &action.deployed_from {
6295 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6296 DisplayPoint::new(*row, 0).to_point(&snapshot)
6297 }
6298 _ => self
6299 .selections
6300 .newest::<Point>(&snapshot.display_snapshot)
6301 .head(),
6302 };
6303 let Some((buffer, buffer_row)) = snapshot
6304 .buffer_snapshot()
6305 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6306 .and_then(|(buffer_snapshot, range)| {
6307 self.buffer()
6308 .read(cx)
6309 .buffer(buffer_snapshot.remote_id())
6310 .map(|buffer| (buffer, range.start.row))
6311 })
6312 else {
6313 return;
6314 };
6315 let buffer_id = buffer.read(cx).remote_id();
6316 let tasks = self
6317 .tasks
6318 .get(&(buffer_id, buffer_row))
6319 .map(|t| Arc::new(t.to_owned()));
6320
6321 if !self.focus_handle.is_focused(window) {
6322 return;
6323 }
6324 let project = self.project.clone();
6325
6326 let code_actions_task = match deployed_from {
6327 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6328 _ => self.code_actions(buffer_row, window, cx),
6329 };
6330
6331 let runnable_task = match deployed_from {
6332 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6333 _ => {
6334 let mut task_context_task = Task::ready(None);
6335 if let Some(tasks) = &tasks
6336 && let Some(project) = project
6337 {
6338 task_context_task =
6339 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6340 }
6341
6342 cx.spawn_in(window, {
6343 let buffer = buffer.clone();
6344 async move |editor, cx| {
6345 let task_context = task_context_task.await;
6346
6347 let resolved_tasks =
6348 tasks
6349 .zip(task_context.clone())
6350 .map(|(tasks, task_context)| ResolvedTasks {
6351 templates: tasks.resolve(&task_context).collect(),
6352 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6353 multibuffer_point.row,
6354 tasks.column,
6355 )),
6356 });
6357 let debug_scenarios = editor
6358 .update(cx, |editor, cx| {
6359 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6360 })?
6361 .await;
6362 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6363 }
6364 })
6365 }
6366 };
6367
6368 cx.spawn_in(window, async move |editor, cx| {
6369 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6370 let code_actions = code_actions_task.await;
6371 let spawn_straight_away = quick_launch
6372 && resolved_tasks
6373 .as_ref()
6374 .is_some_and(|tasks| tasks.templates.len() == 1)
6375 && code_actions
6376 .as_ref()
6377 .is_none_or(|actions| actions.is_empty())
6378 && debug_scenarios.is_empty();
6379
6380 editor.update_in(cx, |editor, window, cx| {
6381 crate::hover_popover::hide_hover(editor, cx);
6382 let actions = CodeActionContents::new(
6383 resolved_tasks,
6384 code_actions,
6385 debug_scenarios,
6386 task_context.unwrap_or_default(),
6387 );
6388
6389 // Don't show the menu if there are no actions available
6390 if actions.is_empty() {
6391 cx.notify();
6392 return Task::ready(Ok(()));
6393 }
6394
6395 *editor.context_menu.borrow_mut() =
6396 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6397 buffer,
6398 actions,
6399 selected_item: Default::default(),
6400 scroll_handle: UniformListScrollHandle::default(),
6401 deployed_from,
6402 }));
6403 cx.notify();
6404 if spawn_straight_away
6405 && let Some(task) = editor.confirm_code_action(
6406 &ConfirmCodeAction { item_ix: Some(0) },
6407 window,
6408 cx,
6409 )
6410 {
6411 return task;
6412 }
6413
6414 Task::ready(Ok(()))
6415 })
6416 })
6417 .detach_and_log_err(cx);
6418 }
6419
6420 fn debug_scenarios(
6421 &mut self,
6422 resolved_tasks: &Option<ResolvedTasks>,
6423 buffer: &Entity<Buffer>,
6424 cx: &mut App,
6425 ) -> Task<Vec<task::DebugScenario>> {
6426 maybe!({
6427 let project = self.project()?;
6428 let dap_store = project.read(cx).dap_store();
6429 let mut scenarios = vec![];
6430 let resolved_tasks = resolved_tasks.as_ref()?;
6431 let buffer = buffer.read(cx);
6432 let language = buffer.language()?;
6433 let file = buffer.file();
6434 let debug_adapter = language_settings(language.name().into(), file, cx)
6435 .debuggers
6436 .first()
6437 .map(SharedString::from)
6438 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6439
6440 dap_store.update(cx, |dap_store, cx| {
6441 for (_, task) in &resolved_tasks.templates {
6442 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6443 task.original_task().clone(),
6444 debug_adapter.clone().into(),
6445 task.display_label().to_owned().into(),
6446 cx,
6447 );
6448 scenarios.push(maybe_scenario);
6449 }
6450 });
6451 Some(cx.background_spawn(async move {
6452 futures::future::join_all(scenarios)
6453 .await
6454 .into_iter()
6455 .flatten()
6456 .collect::<Vec<_>>()
6457 }))
6458 })
6459 .unwrap_or_else(|| Task::ready(vec![]))
6460 }
6461
6462 fn code_actions(
6463 &mut self,
6464 buffer_row: u32,
6465 window: &mut Window,
6466 cx: &mut Context<Self>,
6467 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6468 let mut task = self.code_actions_task.take();
6469 cx.spawn_in(window, async move |editor, cx| {
6470 while let Some(prev_task) = task {
6471 prev_task.await.log_err();
6472 task = editor
6473 .update(cx, |this, _| this.code_actions_task.take())
6474 .ok()?;
6475 }
6476
6477 editor
6478 .update(cx, |editor, cx| {
6479 editor
6480 .available_code_actions
6481 .clone()
6482 .and_then(|(location, code_actions)| {
6483 let snapshot = location.buffer.read(cx).snapshot();
6484 let point_range = location.range.to_point(&snapshot);
6485 let point_range = point_range.start.row..=point_range.end.row;
6486 if point_range.contains(&buffer_row) {
6487 Some(code_actions)
6488 } else {
6489 None
6490 }
6491 })
6492 })
6493 .ok()
6494 .flatten()
6495 })
6496 }
6497
6498 pub fn confirm_code_action(
6499 &mut self,
6500 action: &ConfirmCodeAction,
6501 window: &mut Window,
6502 cx: &mut Context<Self>,
6503 ) -> Option<Task<Result<()>>> {
6504 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6505
6506 let actions_menu =
6507 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6508 menu
6509 } else {
6510 return None;
6511 };
6512
6513 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6514 let action = actions_menu.actions.get(action_ix)?;
6515 let title = action.label();
6516 let buffer = actions_menu.buffer;
6517 let workspace = self.workspace()?;
6518
6519 match action {
6520 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6521 workspace.update(cx, |workspace, cx| {
6522 workspace.schedule_resolved_task(
6523 task_source_kind,
6524 resolved_task,
6525 false,
6526 window,
6527 cx,
6528 );
6529
6530 Some(Task::ready(Ok(())))
6531 })
6532 }
6533 CodeActionsItem::CodeAction {
6534 excerpt_id,
6535 action,
6536 provider,
6537 } => {
6538 let apply_code_action =
6539 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6540 let workspace = workspace.downgrade();
6541 Some(cx.spawn_in(window, async move |editor, cx| {
6542 let project_transaction = apply_code_action.await?;
6543 Self::open_project_transaction(
6544 &editor,
6545 workspace,
6546 project_transaction,
6547 title,
6548 cx,
6549 )
6550 .await
6551 }))
6552 }
6553 CodeActionsItem::DebugScenario(scenario) => {
6554 let context = actions_menu.actions.context;
6555
6556 workspace.update(cx, |workspace, cx| {
6557 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6558 workspace.start_debug_session(
6559 scenario,
6560 context,
6561 Some(buffer),
6562 None,
6563 window,
6564 cx,
6565 );
6566 });
6567 Some(Task::ready(Ok(())))
6568 }
6569 }
6570 }
6571
6572 fn open_transaction_for_hidden_buffers(
6573 workspace: Entity<Workspace>,
6574 transaction: ProjectTransaction,
6575 title: String,
6576 window: &mut Window,
6577 cx: &mut Context<Self>,
6578 ) {
6579 if transaction.0.is_empty() {
6580 return;
6581 }
6582
6583 let edited_buffers_already_open = {
6584 let other_editors: Vec<Entity<Editor>> = workspace
6585 .read(cx)
6586 .panes()
6587 .iter()
6588 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6589 .filter(|editor| editor.entity_id() != cx.entity_id())
6590 .collect();
6591
6592 transaction.0.keys().all(|buffer| {
6593 other_editors.iter().any(|editor| {
6594 let multi_buffer = editor.read(cx).buffer();
6595 multi_buffer.read(cx).is_singleton()
6596 && multi_buffer
6597 .read(cx)
6598 .as_singleton()
6599 .map_or(false, |singleton| {
6600 singleton.entity_id() == buffer.entity_id()
6601 })
6602 })
6603 })
6604 };
6605 if !edited_buffers_already_open {
6606 let workspace = workspace.downgrade();
6607 cx.defer_in(window, move |_, window, cx| {
6608 cx.spawn_in(window, async move |editor, cx| {
6609 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6610 .await
6611 .ok()
6612 })
6613 .detach();
6614 });
6615 }
6616 }
6617
6618 pub async fn open_project_transaction(
6619 editor: &WeakEntity<Editor>,
6620 workspace: WeakEntity<Workspace>,
6621 transaction: ProjectTransaction,
6622 title: String,
6623 cx: &mut AsyncWindowContext,
6624 ) -> Result<()> {
6625 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6626 cx.update(|_, cx| {
6627 entries.sort_unstable_by_key(|(buffer, _)| {
6628 buffer.read(cx).file().map(|f| f.path().clone())
6629 });
6630 })?;
6631 if entries.is_empty() {
6632 return Ok(());
6633 }
6634
6635 // If the project transaction's edits are all contained within this editor, then
6636 // avoid opening a new editor to display them.
6637
6638 if let [(buffer, transaction)] = &*entries {
6639 let excerpt = editor.update(cx, |editor, cx| {
6640 editor
6641 .buffer()
6642 .read(cx)
6643 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6644 })?;
6645 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6646 && excerpted_buffer == *buffer
6647 {
6648 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6649 let excerpt_range = excerpt_range.to_offset(buffer);
6650 buffer
6651 .edited_ranges_for_transaction::<usize>(transaction)
6652 .all(|range| {
6653 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6654 })
6655 })?;
6656
6657 if all_edits_within_excerpt {
6658 return Ok(());
6659 }
6660 }
6661 }
6662
6663 let mut ranges_to_highlight = Vec::new();
6664 let excerpt_buffer = cx.new(|cx| {
6665 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6666 for (buffer_handle, transaction) in &entries {
6667 let edited_ranges = buffer_handle
6668 .read(cx)
6669 .edited_ranges_for_transaction::<Point>(transaction)
6670 .collect::<Vec<_>>();
6671 let (ranges, _) = multibuffer.set_excerpts_for_path(
6672 PathKey::for_buffer(buffer_handle, cx),
6673 buffer_handle.clone(),
6674 edited_ranges,
6675 multibuffer_context_lines(cx),
6676 cx,
6677 );
6678
6679 ranges_to_highlight.extend(ranges);
6680 }
6681 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6682 multibuffer
6683 })?;
6684
6685 workspace.update_in(cx, |workspace, window, cx| {
6686 let project = workspace.project().clone();
6687 let editor =
6688 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6689 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6690 editor.update(cx, |editor, cx| {
6691 editor.highlight_background::<Self>(
6692 &ranges_to_highlight,
6693 |_, theme| theme.colors().editor_highlighted_line_background,
6694 cx,
6695 );
6696 });
6697 })?;
6698
6699 Ok(())
6700 }
6701
6702 pub fn clear_code_action_providers(&mut self) {
6703 self.code_action_providers.clear();
6704 self.available_code_actions.take();
6705 }
6706
6707 pub fn add_code_action_provider(
6708 &mut self,
6709 provider: Rc<dyn CodeActionProvider>,
6710 window: &mut Window,
6711 cx: &mut Context<Self>,
6712 ) {
6713 if self
6714 .code_action_providers
6715 .iter()
6716 .any(|existing_provider| existing_provider.id() == provider.id())
6717 {
6718 return;
6719 }
6720
6721 self.code_action_providers.push(provider);
6722 self.refresh_code_actions(window, cx);
6723 }
6724
6725 pub fn remove_code_action_provider(
6726 &mut self,
6727 id: Arc<str>,
6728 window: &mut Window,
6729 cx: &mut Context<Self>,
6730 ) {
6731 self.code_action_providers
6732 .retain(|provider| provider.id() != id);
6733 self.refresh_code_actions(window, cx);
6734 }
6735
6736 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6737 !self.code_action_providers.is_empty()
6738 && EditorSettings::get_global(cx).toolbar.code_actions
6739 }
6740
6741 pub fn has_available_code_actions(&self) -> bool {
6742 self.available_code_actions
6743 .as_ref()
6744 .is_some_and(|(_, actions)| !actions.is_empty())
6745 }
6746
6747 fn render_inline_code_actions(
6748 &self,
6749 icon_size: ui::IconSize,
6750 display_row: DisplayRow,
6751 is_active: bool,
6752 cx: &mut Context<Self>,
6753 ) -> AnyElement {
6754 let show_tooltip = !self.context_menu_visible();
6755 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6756 .icon_size(icon_size)
6757 .shape(ui::IconButtonShape::Square)
6758 .icon_color(ui::Color::Hidden)
6759 .toggle_state(is_active)
6760 .when(show_tooltip, |this| {
6761 this.tooltip({
6762 let focus_handle = self.focus_handle.clone();
6763 move |_window, cx| {
6764 Tooltip::for_action_in(
6765 "Toggle Code Actions",
6766 &ToggleCodeActions {
6767 deployed_from: None,
6768 quick_launch: false,
6769 },
6770 &focus_handle,
6771 cx,
6772 )
6773 }
6774 })
6775 })
6776 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6777 window.focus(&editor.focus_handle(cx), cx);
6778 editor.toggle_code_actions(
6779 &crate::actions::ToggleCodeActions {
6780 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6781 display_row,
6782 )),
6783 quick_launch: false,
6784 },
6785 window,
6786 cx,
6787 );
6788 }))
6789 .into_any_element()
6790 }
6791
6792 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6793 &self.context_menu
6794 }
6795
6796 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6797 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6798 cx.background_executor()
6799 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6800 .await;
6801
6802 let (start_buffer, start, _, end, newest_selection) = this
6803 .update(cx, |this, cx| {
6804 let newest_selection = this.selections.newest_anchor().clone();
6805 if newest_selection.head().diff_base_anchor.is_some() {
6806 return None;
6807 }
6808 let display_snapshot = this.display_snapshot(cx);
6809 let newest_selection_adjusted =
6810 this.selections.newest_adjusted(&display_snapshot);
6811 let buffer = this.buffer.read(cx);
6812
6813 let (start_buffer, start) =
6814 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6815 let (end_buffer, end) =
6816 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6817
6818 Some((start_buffer, start, end_buffer, end, newest_selection))
6819 })?
6820 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6821 .context(
6822 "Expected selection to lie in a single buffer when refreshing code actions",
6823 )?;
6824 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6825 let providers = this.code_action_providers.clone();
6826 let tasks = this
6827 .code_action_providers
6828 .iter()
6829 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6830 .collect::<Vec<_>>();
6831 (providers, tasks)
6832 })?;
6833
6834 let mut actions = Vec::new();
6835 for (provider, provider_actions) in
6836 providers.into_iter().zip(future::join_all(tasks).await)
6837 {
6838 if let Some(provider_actions) = provider_actions.log_err() {
6839 actions.extend(provider_actions.into_iter().map(|action| {
6840 AvailableCodeAction {
6841 excerpt_id: newest_selection.start.excerpt_id,
6842 action,
6843 provider: provider.clone(),
6844 }
6845 }));
6846 }
6847 }
6848
6849 this.update(cx, |this, cx| {
6850 this.available_code_actions = if actions.is_empty() {
6851 None
6852 } else {
6853 Some((
6854 Location {
6855 buffer: start_buffer,
6856 range: start..end,
6857 },
6858 actions.into(),
6859 ))
6860 };
6861 cx.notify();
6862 })
6863 }));
6864 }
6865
6866 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6867 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6868 self.show_git_blame_inline = false;
6869
6870 self.show_git_blame_inline_delay_task =
6871 Some(cx.spawn_in(window, async move |this, cx| {
6872 cx.background_executor().timer(delay).await;
6873
6874 this.update(cx, |this, cx| {
6875 this.show_git_blame_inline = true;
6876 cx.notify();
6877 })
6878 .log_err();
6879 }));
6880 }
6881 }
6882
6883 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6884 let snapshot = self.snapshot(window, cx);
6885 let cursor = self
6886 .selections
6887 .newest::<Point>(&snapshot.display_snapshot)
6888 .head();
6889 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6890 else {
6891 return;
6892 };
6893
6894 if self.blame.is_none() {
6895 self.start_git_blame(true, window, cx);
6896 }
6897 let Some(blame) = self.blame.as_ref() else {
6898 return;
6899 };
6900
6901 let row_info = RowInfo {
6902 buffer_id: Some(buffer.remote_id()),
6903 buffer_row: Some(point.row),
6904 ..Default::default()
6905 };
6906 let Some((buffer, blame_entry)) = blame
6907 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6908 .flatten()
6909 else {
6910 return;
6911 };
6912
6913 let anchor = self.selections.newest_anchor().head();
6914 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6915 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6916 self.show_blame_popover(
6917 buffer,
6918 &blame_entry,
6919 position + last_bounds.origin,
6920 true,
6921 cx,
6922 );
6923 };
6924 }
6925
6926 fn show_blame_popover(
6927 &mut self,
6928 buffer: BufferId,
6929 blame_entry: &BlameEntry,
6930 position: gpui::Point<Pixels>,
6931 ignore_timeout: bool,
6932 cx: &mut Context<Self>,
6933 ) {
6934 if let Some(state) = &mut self.inline_blame_popover {
6935 state.hide_task.take();
6936 } else {
6937 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6938 let blame_entry = blame_entry.clone();
6939 let show_task = cx.spawn(async move |editor, cx| {
6940 if !ignore_timeout {
6941 cx.background_executor()
6942 .timer(std::time::Duration::from_millis(blame_popover_delay))
6943 .await;
6944 }
6945 editor
6946 .update(cx, |editor, cx| {
6947 editor.inline_blame_popover_show_task.take();
6948 let Some(blame) = editor.blame.as_ref() else {
6949 return;
6950 };
6951 let blame = blame.read(cx);
6952 let details = blame.details_for_entry(buffer, &blame_entry);
6953 let markdown = cx.new(|cx| {
6954 Markdown::new(
6955 details
6956 .as_ref()
6957 .map(|message| message.message.clone())
6958 .unwrap_or_default(),
6959 None,
6960 None,
6961 cx,
6962 )
6963 });
6964 editor.inline_blame_popover = Some(InlineBlamePopover {
6965 position,
6966 hide_task: None,
6967 popover_bounds: None,
6968 popover_state: InlineBlamePopoverState {
6969 scroll_handle: ScrollHandle::new(),
6970 commit_message: details,
6971 markdown,
6972 },
6973 keyboard_grace: ignore_timeout,
6974 });
6975 cx.notify();
6976 })
6977 .ok();
6978 });
6979 self.inline_blame_popover_show_task = Some(show_task);
6980 }
6981 }
6982
6983 pub fn has_mouse_context_menu(&self) -> bool {
6984 self.mouse_context_menu.is_some()
6985 }
6986
6987 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6988 self.inline_blame_popover_show_task.take();
6989 if let Some(state) = &mut self.inline_blame_popover {
6990 let hide_task = cx.spawn(async move |editor, cx| {
6991 if !ignore_timeout {
6992 cx.background_executor()
6993 .timer(std::time::Duration::from_millis(100))
6994 .await;
6995 }
6996 editor
6997 .update(cx, |editor, cx| {
6998 editor.inline_blame_popover.take();
6999 cx.notify();
7000 })
7001 .ok();
7002 });
7003 state.hide_task = Some(hide_task);
7004 true
7005 } else {
7006 false
7007 }
7008 }
7009
7010 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7011 if self.pending_rename.is_some() {
7012 return None;
7013 }
7014
7015 let provider = self.semantics_provider.clone()?;
7016 let buffer = self.buffer.read(cx);
7017 let newest_selection = self.selections.newest_anchor().clone();
7018 let cursor_position = newest_selection.head();
7019 let (cursor_buffer, cursor_buffer_position) =
7020 buffer.text_anchor_for_position(cursor_position, cx)?;
7021 let (tail_buffer, tail_buffer_position) =
7022 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7023 if cursor_buffer != tail_buffer {
7024 return None;
7025 }
7026
7027 let snapshot = cursor_buffer.read(cx).snapshot();
7028 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7029 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7030 if start_word_range != end_word_range {
7031 self.document_highlights_task.take();
7032 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7033 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7034 return None;
7035 }
7036
7037 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7038 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7039 cx.background_executor()
7040 .timer(Duration::from_millis(debounce))
7041 .await;
7042
7043 let highlights = if let Some(highlights) = cx
7044 .update(|cx| {
7045 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7046 })
7047 .ok()
7048 .flatten()
7049 {
7050 highlights.await.log_err()
7051 } else {
7052 None
7053 };
7054
7055 if let Some(highlights) = highlights {
7056 this.update(cx, |this, cx| {
7057 if this.pending_rename.is_some() {
7058 return;
7059 }
7060
7061 let buffer = this.buffer.read(cx);
7062 if buffer
7063 .text_anchor_for_position(cursor_position, cx)
7064 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7065 {
7066 return;
7067 }
7068
7069 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7070 let mut write_ranges = Vec::new();
7071 let mut read_ranges = Vec::new();
7072 for highlight in highlights {
7073 let buffer_id = cursor_buffer.read(cx).remote_id();
7074 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7075 {
7076 let start = highlight
7077 .range
7078 .start
7079 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7080 let end = highlight
7081 .range
7082 .end
7083 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7084 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7085 continue;
7086 }
7087
7088 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7089 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7090 write_ranges.push(range);
7091 } else {
7092 read_ranges.push(range);
7093 }
7094 }
7095 }
7096
7097 this.highlight_background::<DocumentHighlightRead>(
7098 &read_ranges,
7099 |_, theme| theme.colors().editor_document_highlight_read_background,
7100 cx,
7101 );
7102 this.highlight_background::<DocumentHighlightWrite>(
7103 &write_ranges,
7104 |_, theme| theme.colors().editor_document_highlight_write_background,
7105 cx,
7106 );
7107 cx.notify();
7108 })
7109 .log_err();
7110 }
7111 }));
7112 None
7113 }
7114
7115 fn prepare_highlight_query_from_selection(
7116 &mut self,
7117 window: &Window,
7118 cx: &mut Context<Editor>,
7119 ) -> Option<(String, Range<Anchor>)> {
7120 if matches!(self.mode, EditorMode::SingleLine) {
7121 return None;
7122 }
7123 if !EditorSettings::get_global(cx).selection_highlight {
7124 return None;
7125 }
7126 if self.selections.count() != 1 || self.selections.line_mode() {
7127 return None;
7128 }
7129 let snapshot = self.snapshot(window, cx);
7130 let selection = self.selections.newest::<Point>(&snapshot);
7131 // If the selection spans multiple rows OR it is empty
7132 if selection.start.row != selection.end.row
7133 || selection.start.column == selection.end.column
7134 {
7135 return None;
7136 }
7137 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7138 let query = snapshot
7139 .buffer_snapshot()
7140 .text_for_range(selection_anchor_range.clone())
7141 .collect::<String>();
7142 if query.trim().is_empty() {
7143 return None;
7144 }
7145 Some((query, selection_anchor_range))
7146 }
7147
7148 #[ztracing::instrument(skip_all)]
7149 fn update_selection_occurrence_highlights(
7150 &mut self,
7151 query_text: String,
7152 query_range: Range<Anchor>,
7153 multi_buffer_range_to_query: Range<Point>,
7154 use_debounce: bool,
7155 window: &mut Window,
7156 cx: &mut Context<Editor>,
7157 ) -> Task<()> {
7158 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7159 cx.spawn_in(window, async move |editor, cx| {
7160 if use_debounce {
7161 cx.background_executor()
7162 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7163 .await;
7164 }
7165 let match_task = cx.background_spawn(async move {
7166 let buffer_ranges = multi_buffer_snapshot
7167 .range_to_buffer_ranges(multi_buffer_range_to_query)
7168 .into_iter()
7169 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7170 let mut match_ranges = Vec::new();
7171 let Ok(regex) = project::search::SearchQuery::text(
7172 query_text.clone(),
7173 false,
7174 false,
7175 false,
7176 Default::default(),
7177 Default::default(),
7178 false,
7179 None,
7180 ) else {
7181 return Vec::default();
7182 };
7183 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7184 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7185 match_ranges.extend(
7186 regex
7187 .search(
7188 buffer_snapshot,
7189 Some(search_range.start.0..search_range.end.0),
7190 )
7191 .await
7192 .into_iter()
7193 .filter_map(|match_range| {
7194 let match_start = buffer_snapshot
7195 .anchor_after(search_range.start + match_range.start);
7196 let match_end = buffer_snapshot
7197 .anchor_before(search_range.start + match_range.end);
7198 let match_anchor_range =
7199 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7200 (match_anchor_range != query_range).then_some(match_anchor_range)
7201 }),
7202 );
7203 }
7204 match_ranges
7205 });
7206 let match_ranges = match_task.await;
7207 editor
7208 .update_in(cx, |editor, _, cx| {
7209 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7210 if !match_ranges.is_empty() {
7211 editor.highlight_background::<SelectedTextHighlight>(
7212 &match_ranges,
7213 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7214 cx,
7215 )
7216 }
7217 })
7218 .log_err();
7219 })
7220 }
7221
7222 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7223 struct NewlineFold;
7224 let type_id = std::any::TypeId::of::<NewlineFold>();
7225 if !self.mode.is_single_line() {
7226 return;
7227 }
7228 let snapshot = self.snapshot(window, cx);
7229 if snapshot.buffer_snapshot().max_point().row == 0 {
7230 return;
7231 }
7232 let task = cx.background_spawn(async move {
7233 let new_newlines = snapshot
7234 .buffer_chars_at(MultiBufferOffset(0))
7235 .filter_map(|(c, i)| {
7236 if c == '\n' {
7237 Some(
7238 snapshot.buffer_snapshot().anchor_after(i)
7239 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7240 )
7241 } else {
7242 None
7243 }
7244 })
7245 .collect::<Vec<_>>();
7246 let existing_newlines = snapshot
7247 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7248 .filter_map(|fold| {
7249 if fold.placeholder.type_tag == Some(type_id) {
7250 Some(fold.range.start..fold.range.end)
7251 } else {
7252 None
7253 }
7254 })
7255 .collect::<Vec<_>>();
7256
7257 (new_newlines, existing_newlines)
7258 });
7259 self.folding_newlines = cx.spawn(async move |this, cx| {
7260 let (new_newlines, existing_newlines) = task.await;
7261 if new_newlines == existing_newlines {
7262 return;
7263 }
7264 let placeholder = FoldPlaceholder {
7265 render: Arc::new(move |_, _, cx| {
7266 div()
7267 .bg(cx.theme().status().hint_background)
7268 .border_b_1()
7269 .size_full()
7270 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7271 .border_color(cx.theme().status().hint)
7272 .child("\\n")
7273 .into_any()
7274 }),
7275 constrain_width: false,
7276 merge_adjacent: false,
7277 type_tag: Some(type_id),
7278 };
7279 let creases = new_newlines
7280 .into_iter()
7281 .map(|range| Crease::simple(range, placeholder.clone()))
7282 .collect();
7283 this.update(cx, |this, cx| {
7284 this.display_map.update(cx, |display_map, cx| {
7285 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7286 display_map.fold(creases, cx);
7287 });
7288 })
7289 .ok();
7290 });
7291 }
7292
7293 #[ztracing::instrument(skip_all)]
7294 fn refresh_selected_text_highlights(
7295 &mut self,
7296 on_buffer_edit: bool,
7297 window: &mut Window,
7298 cx: &mut Context<Editor>,
7299 ) {
7300 let Some((query_text, query_range)) =
7301 self.prepare_highlight_query_from_selection(window, cx)
7302 else {
7303 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7304 self.quick_selection_highlight_task.take();
7305 self.debounced_selection_highlight_task.take();
7306 return;
7307 };
7308 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7309 if on_buffer_edit
7310 || self
7311 .quick_selection_highlight_task
7312 .as_ref()
7313 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7314 {
7315 let multi_buffer_visible_start = self
7316 .scroll_manager
7317 .anchor()
7318 .anchor
7319 .to_point(&multi_buffer_snapshot);
7320 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7321 multi_buffer_visible_start
7322 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7323 Bias::Left,
7324 );
7325 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7326 self.quick_selection_highlight_task = Some((
7327 query_range.clone(),
7328 self.update_selection_occurrence_highlights(
7329 query_text.clone(),
7330 query_range.clone(),
7331 multi_buffer_visible_range,
7332 false,
7333 window,
7334 cx,
7335 ),
7336 ));
7337 }
7338 if on_buffer_edit
7339 || self
7340 .debounced_selection_highlight_task
7341 .as_ref()
7342 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7343 {
7344 let multi_buffer_start = multi_buffer_snapshot
7345 .anchor_before(MultiBufferOffset(0))
7346 .to_point(&multi_buffer_snapshot);
7347 let multi_buffer_end = multi_buffer_snapshot
7348 .anchor_after(multi_buffer_snapshot.len())
7349 .to_point(&multi_buffer_snapshot);
7350 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7351 self.debounced_selection_highlight_task = Some((
7352 query_range.clone(),
7353 self.update_selection_occurrence_highlights(
7354 query_text,
7355 query_range,
7356 multi_buffer_full_range,
7357 true,
7358 window,
7359 cx,
7360 ),
7361 ));
7362 }
7363 }
7364
7365 pub fn refresh_edit_prediction(
7366 &mut self,
7367 debounce: bool,
7368 user_requested: bool,
7369 window: &mut Window,
7370 cx: &mut Context<Self>,
7371 ) -> Option<()> {
7372 if DisableAiSettings::get_global(cx).disable_ai {
7373 return None;
7374 }
7375
7376 let provider = self.edit_prediction_provider()?;
7377 let cursor = self.selections.newest_anchor().head();
7378 let (buffer, cursor_buffer_position) =
7379 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7380
7381 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7382 self.discard_edit_prediction(false, cx);
7383 return None;
7384 }
7385
7386 self.update_visible_edit_prediction(window, cx);
7387
7388 if !user_requested
7389 && (!self.should_show_edit_predictions()
7390 || !self.is_focused(window)
7391 || buffer.read(cx).is_empty())
7392 {
7393 self.discard_edit_prediction(false, cx);
7394 return None;
7395 }
7396
7397 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7398 Some(())
7399 }
7400
7401 fn show_edit_predictions_in_menu(&self) -> bool {
7402 match self.edit_prediction_settings {
7403 EditPredictionSettings::Disabled => false,
7404 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7405 }
7406 }
7407
7408 pub fn edit_predictions_enabled(&self) -> bool {
7409 match self.edit_prediction_settings {
7410 EditPredictionSettings::Disabled => false,
7411 EditPredictionSettings::Enabled { .. } => true,
7412 }
7413 }
7414
7415 fn edit_prediction_requires_modifier(&self) -> bool {
7416 match self.edit_prediction_settings {
7417 EditPredictionSettings::Disabled => false,
7418 EditPredictionSettings::Enabled {
7419 preview_requires_modifier,
7420 ..
7421 } => preview_requires_modifier,
7422 }
7423 }
7424
7425 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7426 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7427 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7428 self.discard_edit_prediction(false, cx);
7429 } else {
7430 let selection = self.selections.newest_anchor();
7431 let cursor = selection.head();
7432
7433 if let Some((buffer, cursor_buffer_position)) =
7434 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7435 {
7436 self.edit_prediction_settings =
7437 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7438 }
7439 }
7440 }
7441
7442 fn edit_prediction_settings_at_position(
7443 &self,
7444 buffer: &Entity<Buffer>,
7445 buffer_position: language::Anchor,
7446 cx: &App,
7447 ) -> EditPredictionSettings {
7448 if !self.mode.is_full()
7449 || !self.show_edit_predictions_override.unwrap_or(true)
7450 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7451 {
7452 return EditPredictionSettings::Disabled;
7453 }
7454
7455 let buffer = buffer.read(cx);
7456
7457 let file = buffer.file();
7458
7459 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7460 return EditPredictionSettings::Disabled;
7461 };
7462
7463 let by_provider = matches!(
7464 self.menu_edit_predictions_policy,
7465 MenuEditPredictionsPolicy::ByProvider
7466 );
7467
7468 let show_in_menu = by_provider
7469 && self
7470 .edit_prediction_provider
7471 .as_ref()
7472 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7473
7474 let preview_requires_modifier =
7475 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7476
7477 EditPredictionSettings::Enabled {
7478 show_in_menu,
7479 preview_requires_modifier,
7480 }
7481 }
7482
7483 fn should_show_edit_predictions(&self) -> bool {
7484 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7485 }
7486
7487 pub fn edit_prediction_preview_is_active(&self) -> bool {
7488 matches!(
7489 self.edit_prediction_preview,
7490 EditPredictionPreview::Active { .. }
7491 )
7492 }
7493
7494 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7495 let cursor = self.selections.newest_anchor().head();
7496 if let Some((buffer, cursor_position)) =
7497 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7498 {
7499 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7500 } else {
7501 false
7502 }
7503 }
7504
7505 pub fn supports_minimap(&self, cx: &App) -> bool {
7506 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7507 }
7508
7509 fn edit_predictions_enabled_in_buffer(
7510 &self,
7511 buffer: &Entity<Buffer>,
7512 buffer_position: language::Anchor,
7513 cx: &App,
7514 ) -> bool {
7515 maybe!({
7516 if self.read_only(cx) {
7517 return Some(false);
7518 }
7519 let provider = self.edit_prediction_provider()?;
7520 if !provider.is_enabled(buffer, buffer_position, cx) {
7521 return Some(false);
7522 }
7523 let buffer = buffer.read(cx);
7524 let Some(file) = buffer.file() else {
7525 return Some(true);
7526 };
7527 let settings = all_language_settings(Some(file), cx);
7528 Some(settings.edit_predictions_enabled_for_file(file, cx))
7529 })
7530 .unwrap_or(false)
7531 }
7532
7533 pub fn show_edit_prediction(
7534 &mut self,
7535 _: &ShowEditPrediction,
7536 window: &mut Window,
7537 cx: &mut Context<Self>,
7538 ) {
7539 if !self.has_active_edit_prediction() {
7540 self.refresh_edit_prediction(false, true, window, cx);
7541 return;
7542 }
7543
7544 self.update_visible_edit_prediction(window, cx);
7545 }
7546
7547 pub fn display_cursor_names(
7548 &mut self,
7549 _: &DisplayCursorNames,
7550 window: &mut Window,
7551 cx: &mut Context<Self>,
7552 ) {
7553 self.show_cursor_names(window, cx);
7554 }
7555
7556 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7557 self.show_cursor_names = true;
7558 cx.notify();
7559 cx.spawn_in(window, async move |this, cx| {
7560 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7561 this.update(cx, |this, cx| {
7562 this.show_cursor_names = false;
7563 cx.notify()
7564 })
7565 .ok()
7566 })
7567 .detach();
7568 }
7569
7570 pub fn accept_partial_edit_prediction(
7571 &mut self,
7572 granularity: EditPredictionGranularity,
7573 window: &mut Window,
7574 cx: &mut Context<Self>,
7575 ) {
7576 if self.show_edit_predictions_in_menu() {
7577 self.hide_context_menu(window, cx);
7578 }
7579
7580 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7581 return;
7582 };
7583
7584 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7585 return;
7586 }
7587
7588 match &active_edit_prediction.completion {
7589 EditPrediction::MoveWithin { target, .. } => {
7590 let target = *target;
7591
7592 if matches!(granularity, EditPredictionGranularity::Full) {
7593 if let Some(position_map) = &self.last_position_map {
7594 let target_row = target.to_display_point(&position_map.snapshot).row();
7595 let is_visible = position_map.visible_row_range.contains(&target_row);
7596
7597 if is_visible || !self.edit_prediction_requires_modifier() {
7598 self.unfold_ranges(&[target..target], true, false, cx);
7599 self.change_selections(
7600 SelectionEffects::scroll(Autoscroll::newest()),
7601 window,
7602 cx,
7603 |selections| {
7604 selections.select_anchor_ranges([target..target]);
7605 },
7606 );
7607 self.clear_row_highlights::<EditPredictionPreview>();
7608 self.edit_prediction_preview
7609 .set_previous_scroll_position(None);
7610 } else {
7611 // Highlight and request scroll
7612 self.edit_prediction_preview
7613 .set_previous_scroll_position(Some(
7614 position_map.snapshot.scroll_anchor,
7615 ));
7616 self.highlight_rows::<EditPredictionPreview>(
7617 target..target,
7618 cx.theme().colors().editor_highlighted_line_background,
7619 RowHighlightOptions {
7620 autoscroll: true,
7621 ..Default::default()
7622 },
7623 cx,
7624 );
7625 self.request_autoscroll(Autoscroll::fit(), cx);
7626 }
7627 }
7628 } else {
7629 self.change_selections(
7630 SelectionEffects::scroll(Autoscroll::newest()),
7631 window,
7632 cx,
7633 |selections| {
7634 selections.select_anchor_ranges([target..target]);
7635 },
7636 );
7637 }
7638 }
7639 EditPrediction::MoveOutside { snapshot, target } => {
7640 if let Some(workspace) = self.workspace() {
7641 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7642 .detach_and_log_err(cx);
7643 }
7644 }
7645 EditPrediction::Edit { edits, .. } => {
7646 self.report_edit_prediction_event(
7647 active_edit_prediction.completion_id.clone(),
7648 true,
7649 cx,
7650 );
7651
7652 match granularity {
7653 EditPredictionGranularity::Full => {
7654 if let Some(provider) = self.edit_prediction_provider() {
7655 provider.accept(cx);
7656 }
7657
7658 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7659 let snapshot = self.buffer.read(cx).snapshot(cx);
7660 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7661
7662 self.buffer.update(cx, |buffer, cx| {
7663 buffer.edit(edits.iter().cloned(), None, cx)
7664 });
7665
7666 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7667 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7668 });
7669
7670 let selections = self.selections.disjoint_anchors_arc();
7671 if let Some(transaction_id_now) =
7672 self.buffer.read(cx).last_transaction_id(cx)
7673 {
7674 if transaction_id_prev != Some(transaction_id_now) {
7675 self.selection_history
7676 .insert_transaction(transaction_id_now, selections);
7677 }
7678 }
7679
7680 self.update_visible_edit_prediction(window, cx);
7681 if self.active_edit_prediction.is_none() {
7682 self.refresh_edit_prediction(true, true, window, cx);
7683 }
7684 cx.notify();
7685 }
7686 _ => {
7687 let snapshot = self.buffer.read(cx).snapshot(cx);
7688 let cursor_offset = self
7689 .selections
7690 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7691 .head();
7692
7693 let insertion = edits.iter().find_map(|(range, text)| {
7694 let range = range.to_offset(&snapshot);
7695 if range.is_empty() && range.start == cursor_offset {
7696 Some(text)
7697 } else {
7698 None
7699 }
7700 });
7701
7702 if let Some(text) = insertion {
7703 let text_to_insert = match granularity {
7704 EditPredictionGranularity::Word => {
7705 let mut partial = text
7706 .chars()
7707 .by_ref()
7708 .take_while(|c| c.is_alphabetic())
7709 .collect::<String>();
7710 if partial.is_empty() {
7711 partial = text
7712 .chars()
7713 .by_ref()
7714 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7715 .collect::<String>();
7716 }
7717 partial
7718 }
7719 EditPredictionGranularity::Line => {
7720 if let Some(line) = text.split_inclusive('\n').next() {
7721 line.to_string()
7722 } else {
7723 text.to_string()
7724 }
7725 }
7726 EditPredictionGranularity::Full => unreachable!(),
7727 };
7728
7729 cx.emit(EditorEvent::InputHandled {
7730 utf16_range_to_replace: None,
7731 text: text_to_insert.clone().into(),
7732 });
7733
7734 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7735 self.refresh_edit_prediction(true, true, window, cx);
7736 cx.notify();
7737 } else {
7738 self.accept_partial_edit_prediction(
7739 EditPredictionGranularity::Full,
7740 window,
7741 cx,
7742 );
7743 }
7744 }
7745 }
7746 }
7747 }
7748
7749 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7750 }
7751
7752 pub fn accept_next_word_edit_prediction(
7753 &mut self,
7754 _: &AcceptNextWordEditPrediction,
7755 window: &mut Window,
7756 cx: &mut Context<Self>,
7757 ) {
7758 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7759 }
7760
7761 pub fn accept_next_line_edit_prediction(
7762 &mut self,
7763 _: &AcceptNextLineEditPrediction,
7764 window: &mut Window,
7765 cx: &mut Context<Self>,
7766 ) {
7767 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7768 }
7769
7770 pub fn accept_edit_prediction(
7771 &mut self,
7772 _: &AcceptEditPrediction,
7773 window: &mut Window,
7774 cx: &mut Context<Self>,
7775 ) {
7776 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7777 }
7778
7779 fn discard_edit_prediction(
7780 &mut self,
7781 should_report_edit_prediction_event: bool,
7782 cx: &mut Context<Self>,
7783 ) -> bool {
7784 if should_report_edit_prediction_event {
7785 let completion_id = self
7786 .active_edit_prediction
7787 .as_ref()
7788 .and_then(|active_completion| active_completion.completion_id.clone());
7789
7790 self.report_edit_prediction_event(completion_id, false, cx);
7791 }
7792
7793 if let Some(provider) = self.edit_prediction_provider() {
7794 provider.discard(cx);
7795 }
7796
7797 self.take_active_edit_prediction(cx)
7798 }
7799
7800 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7801 let Some(provider) = self.edit_prediction_provider() else {
7802 return;
7803 };
7804
7805 let Some((_, buffer, _)) = self
7806 .buffer
7807 .read(cx)
7808 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7809 else {
7810 return;
7811 };
7812
7813 let extension = buffer
7814 .read(cx)
7815 .file()
7816 .and_then(|file| Some(file.path().extension()?.to_string()));
7817
7818 let event_type = match accepted {
7819 true => "Edit Prediction Accepted",
7820 false => "Edit Prediction Discarded",
7821 };
7822 telemetry::event!(
7823 event_type,
7824 provider = provider.name(),
7825 prediction_id = id,
7826 suggestion_accepted = accepted,
7827 file_extension = extension,
7828 );
7829 }
7830
7831 fn open_editor_at_anchor(
7832 snapshot: &language::BufferSnapshot,
7833 target: language::Anchor,
7834 workspace: &Entity<Workspace>,
7835 window: &mut Window,
7836 cx: &mut App,
7837 ) -> Task<Result<()>> {
7838 workspace.update(cx, |workspace, cx| {
7839 let path = snapshot.file().map(|file| file.full_path(cx));
7840 let Some(path) =
7841 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7842 else {
7843 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7844 };
7845 let target = text::ToPoint::to_point(&target, snapshot);
7846 let item = workspace.open_path(path, None, true, window, cx);
7847 window.spawn(cx, async move |cx| {
7848 let Some(editor) = item.await?.downcast::<Editor>() else {
7849 return Ok(());
7850 };
7851 editor
7852 .update_in(cx, |editor, window, cx| {
7853 editor.go_to_singleton_buffer_point(target, window, cx);
7854 })
7855 .ok();
7856 anyhow::Ok(())
7857 })
7858 })
7859 }
7860
7861 pub fn has_active_edit_prediction(&self) -> bool {
7862 self.active_edit_prediction.is_some()
7863 }
7864
7865 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7866 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7867 return false;
7868 };
7869
7870 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7871 self.clear_highlights::<EditPredictionHighlight>(cx);
7872 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7873 true
7874 }
7875
7876 /// Returns true when we're displaying the edit prediction popover below the cursor
7877 /// like we are not previewing and the LSP autocomplete menu is visible
7878 /// or we are in `when_holding_modifier` mode.
7879 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7880 if self.edit_prediction_preview_is_active()
7881 || !self.show_edit_predictions_in_menu()
7882 || !self.edit_predictions_enabled()
7883 {
7884 return false;
7885 }
7886
7887 if self.has_visible_completions_menu() {
7888 return true;
7889 }
7890
7891 has_completion && self.edit_prediction_requires_modifier()
7892 }
7893
7894 fn handle_modifiers_changed(
7895 &mut self,
7896 modifiers: Modifiers,
7897 position_map: &PositionMap,
7898 window: &mut Window,
7899 cx: &mut Context<Self>,
7900 ) {
7901 // Ensure that the edit prediction preview is updated, even when not
7902 // enabled, if there's an active edit prediction preview.
7903 if self.show_edit_predictions_in_menu()
7904 || matches!(
7905 self.edit_prediction_preview,
7906 EditPredictionPreview::Active { .. }
7907 )
7908 {
7909 self.update_edit_prediction_preview(&modifiers, window, cx);
7910 }
7911
7912 self.update_selection_mode(&modifiers, position_map, window, cx);
7913
7914 let mouse_position = window.mouse_position();
7915 if !position_map.text_hitbox.is_hovered(window) {
7916 return;
7917 }
7918
7919 self.update_hovered_link(
7920 position_map.point_for_position(mouse_position),
7921 &position_map.snapshot,
7922 modifiers,
7923 window,
7924 cx,
7925 )
7926 }
7927
7928 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7929 match EditorSettings::get_global(cx).multi_cursor_modifier {
7930 MultiCursorModifier::Alt => modifiers.secondary(),
7931 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7932 }
7933 }
7934
7935 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7936 match EditorSettings::get_global(cx).multi_cursor_modifier {
7937 MultiCursorModifier::Alt => modifiers.alt,
7938 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7939 }
7940 }
7941
7942 fn columnar_selection_mode(
7943 modifiers: &Modifiers,
7944 cx: &mut Context<Self>,
7945 ) -> Option<ColumnarMode> {
7946 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7947 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7948 Some(ColumnarMode::FromMouse)
7949 } else if Self::is_alt_pressed(modifiers, cx) {
7950 Some(ColumnarMode::FromSelection)
7951 } else {
7952 None
7953 }
7954 } else {
7955 None
7956 }
7957 }
7958
7959 fn update_selection_mode(
7960 &mut self,
7961 modifiers: &Modifiers,
7962 position_map: &PositionMap,
7963 window: &mut Window,
7964 cx: &mut Context<Self>,
7965 ) {
7966 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7967 return;
7968 };
7969 if self.selections.pending_anchor().is_none() {
7970 return;
7971 }
7972
7973 let mouse_position = window.mouse_position();
7974 let point_for_position = position_map.point_for_position(mouse_position);
7975 let position = point_for_position.previous_valid;
7976
7977 self.select(
7978 SelectPhase::BeginColumnar {
7979 position,
7980 reset: false,
7981 mode,
7982 goal_column: point_for_position.exact_unclipped.column(),
7983 },
7984 window,
7985 cx,
7986 );
7987 }
7988
7989 fn update_edit_prediction_preview(
7990 &mut self,
7991 modifiers: &Modifiers,
7992 window: &mut Window,
7993 cx: &mut Context<Self>,
7994 ) {
7995 let mut modifiers_held = false;
7996
7997 // Check bindings for all granularities.
7998 // If the user holds the key for Word, Line, or Full, we want to show the preview.
7999 let granularities = [
8000 EditPredictionGranularity::Full,
8001 EditPredictionGranularity::Line,
8002 EditPredictionGranularity::Word,
8003 ];
8004
8005 for granularity in granularities {
8006 if let Some(keystroke) = self
8007 .accept_edit_prediction_keybind(granularity, window, cx)
8008 .keystroke()
8009 {
8010 modifiers_held = modifiers_held
8011 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8012 }
8013 }
8014
8015 if modifiers_held {
8016 if matches!(
8017 self.edit_prediction_preview,
8018 EditPredictionPreview::Inactive { .. }
8019 ) {
8020 if let Some(provider) = self.edit_prediction_provider.as_ref() {
8021 provider.provider.did_show(cx)
8022 }
8023
8024 self.edit_prediction_preview = EditPredictionPreview::Active {
8025 previous_scroll_position: None,
8026 since: Instant::now(),
8027 };
8028
8029 self.update_visible_edit_prediction(window, cx);
8030 cx.notify();
8031 }
8032 } else if let EditPredictionPreview::Active {
8033 previous_scroll_position,
8034 since,
8035 } = self.edit_prediction_preview
8036 {
8037 if let (Some(previous_scroll_position), Some(position_map)) =
8038 (previous_scroll_position, self.last_position_map.as_ref())
8039 {
8040 self.set_scroll_position(
8041 previous_scroll_position
8042 .scroll_position(&position_map.snapshot.display_snapshot),
8043 window,
8044 cx,
8045 );
8046 }
8047
8048 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8049 released_too_fast: since.elapsed() < Duration::from_millis(200),
8050 };
8051 self.clear_row_highlights::<EditPredictionPreview>();
8052 self.update_visible_edit_prediction(window, cx);
8053 cx.notify();
8054 }
8055 }
8056
8057 fn update_visible_edit_prediction(
8058 &mut self,
8059 _window: &mut Window,
8060 cx: &mut Context<Self>,
8061 ) -> Option<()> {
8062 if DisableAiSettings::get_global(cx).disable_ai {
8063 return None;
8064 }
8065
8066 if self.ime_transaction.is_some() {
8067 self.discard_edit_prediction(false, cx);
8068 return None;
8069 }
8070
8071 let selection = self.selections.newest_anchor();
8072 let cursor = selection.head();
8073 let multibuffer = self.buffer.read(cx).snapshot(cx);
8074 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8075 let excerpt_id = cursor.excerpt_id;
8076
8077 let show_in_menu = self.show_edit_predictions_in_menu();
8078 let completions_menu_has_precedence = !show_in_menu
8079 && (self.context_menu.borrow().is_some()
8080 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8081
8082 if completions_menu_has_precedence
8083 || !offset_selection.is_empty()
8084 || self
8085 .active_edit_prediction
8086 .as_ref()
8087 .is_some_and(|completion| {
8088 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8089 return false;
8090 };
8091 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8092 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8093 !invalidation_range.contains(&offset_selection.head())
8094 })
8095 {
8096 self.discard_edit_prediction(false, cx);
8097 return None;
8098 }
8099
8100 self.take_active_edit_prediction(cx);
8101 let Some(provider) = self.edit_prediction_provider() else {
8102 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8103 return None;
8104 };
8105
8106 let (buffer, cursor_buffer_position) =
8107 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8108
8109 self.edit_prediction_settings =
8110 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8111
8112 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8113
8114 if self.edit_prediction_indent_conflict {
8115 let cursor_point = cursor.to_point(&multibuffer);
8116 let mut suggested_indent = None;
8117 multibuffer.suggested_indents_callback(
8118 cursor_point.row..cursor_point.row + 1,
8119 |_, indent| {
8120 suggested_indent = Some(indent);
8121 ControlFlow::Break(())
8122 },
8123 cx,
8124 );
8125
8126 if let Some(indent) = suggested_indent
8127 && indent.len == cursor_point.column
8128 {
8129 self.edit_prediction_indent_conflict = false;
8130 }
8131 }
8132
8133 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8134
8135 let (completion_id, edits, edit_preview) = match edit_prediction {
8136 edit_prediction_types::EditPrediction::Local {
8137 id,
8138 edits,
8139 edit_preview,
8140 } => (id, edits, edit_preview),
8141 edit_prediction_types::EditPrediction::Jump {
8142 id,
8143 snapshot,
8144 target,
8145 } => {
8146 self.stale_edit_prediction_in_menu = None;
8147 self.active_edit_prediction = Some(EditPredictionState {
8148 inlay_ids: vec![],
8149 completion: EditPrediction::MoveOutside { snapshot, target },
8150 completion_id: id,
8151 invalidation_range: None,
8152 });
8153 cx.notify();
8154 return Some(());
8155 }
8156 };
8157
8158 let edits = edits
8159 .into_iter()
8160 .flat_map(|(range, new_text)| {
8161 Some((
8162 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8163 new_text,
8164 ))
8165 })
8166 .collect::<Vec<_>>();
8167 if edits.is_empty() {
8168 return None;
8169 }
8170
8171 let first_edit_start = edits.first().unwrap().0.start;
8172 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8173 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8174
8175 let last_edit_end = edits.last().unwrap().0.end;
8176 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8177 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8178
8179 let cursor_row = cursor.to_point(&multibuffer).row;
8180
8181 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8182
8183 let mut inlay_ids = Vec::new();
8184 let invalidation_row_range;
8185 let move_invalidation_row_range = if cursor_row < edit_start_row {
8186 Some(cursor_row..edit_end_row)
8187 } else if cursor_row > edit_end_row {
8188 Some(edit_start_row..cursor_row)
8189 } else {
8190 None
8191 };
8192 let supports_jump = self
8193 .edit_prediction_provider
8194 .as_ref()
8195 .map(|provider| provider.provider.supports_jump_to_edit())
8196 .unwrap_or(true);
8197
8198 let is_move = supports_jump
8199 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8200 let completion = if is_move {
8201 invalidation_row_range =
8202 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8203 let target = first_edit_start;
8204 EditPrediction::MoveWithin { target, snapshot }
8205 } else {
8206 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8207 && !self.edit_predictions_hidden_for_vim_mode;
8208
8209 if show_completions_in_buffer {
8210 if let Some(provider) = &self.edit_prediction_provider {
8211 provider.provider.did_show(cx);
8212 }
8213 if edits
8214 .iter()
8215 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8216 {
8217 let mut inlays = Vec::new();
8218 for (range, new_text) in &edits {
8219 let inlay = Inlay::edit_prediction(
8220 post_inc(&mut self.next_inlay_id),
8221 range.start,
8222 new_text.as_ref(),
8223 );
8224 inlay_ids.push(inlay.id);
8225 inlays.push(inlay);
8226 }
8227
8228 self.splice_inlays(&[], inlays, cx);
8229 } else {
8230 let background_color = cx.theme().status().deleted_background;
8231 self.highlight_text::<EditPredictionHighlight>(
8232 edits.iter().map(|(range, _)| range.clone()).collect(),
8233 HighlightStyle {
8234 background_color: Some(background_color),
8235 ..Default::default()
8236 },
8237 cx,
8238 );
8239 }
8240 }
8241
8242 invalidation_row_range = edit_start_row..edit_end_row;
8243
8244 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8245 if provider.show_tab_accept_marker() {
8246 EditDisplayMode::TabAccept
8247 } else {
8248 EditDisplayMode::Inline
8249 }
8250 } else {
8251 EditDisplayMode::DiffPopover
8252 };
8253
8254 EditPrediction::Edit {
8255 edits,
8256 edit_preview,
8257 display_mode,
8258 snapshot,
8259 }
8260 };
8261
8262 let invalidation_range = multibuffer
8263 .anchor_before(Point::new(invalidation_row_range.start, 0))
8264 ..multibuffer.anchor_after(Point::new(
8265 invalidation_row_range.end,
8266 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8267 ));
8268
8269 self.stale_edit_prediction_in_menu = None;
8270 self.active_edit_prediction = Some(EditPredictionState {
8271 inlay_ids,
8272 completion,
8273 completion_id,
8274 invalidation_range: Some(invalidation_range),
8275 });
8276
8277 cx.notify();
8278
8279 Some(())
8280 }
8281
8282 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8283 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8284 }
8285
8286 fn clear_tasks(&mut self) {
8287 self.tasks.clear()
8288 }
8289
8290 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8291 if self.tasks.insert(key, value).is_some() {
8292 // This case should hopefully be rare, but just in case...
8293 log::error!(
8294 "multiple different run targets found on a single line, only the last target will be rendered"
8295 )
8296 }
8297 }
8298
8299 /// Get all display points of breakpoints that will be rendered within editor
8300 ///
8301 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8302 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8303 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8304 fn active_breakpoints(
8305 &self,
8306 range: Range<DisplayRow>,
8307 window: &mut Window,
8308 cx: &mut Context<Self>,
8309 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8310 let mut breakpoint_display_points = HashMap::default();
8311
8312 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8313 return breakpoint_display_points;
8314 };
8315
8316 let snapshot = self.snapshot(window, cx);
8317
8318 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8319 let Some(project) = self.project() else {
8320 return breakpoint_display_points;
8321 };
8322
8323 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8324 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8325
8326 for (buffer_snapshot, range, excerpt_id) in
8327 multi_buffer_snapshot.range_to_buffer_ranges(range)
8328 {
8329 let Some(buffer) = project
8330 .read(cx)
8331 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8332 else {
8333 continue;
8334 };
8335 let breakpoints = breakpoint_store.read(cx).breakpoints(
8336 &buffer,
8337 Some(
8338 buffer_snapshot.anchor_before(range.start)
8339 ..buffer_snapshot.anchor_after(range.end),
8340 ),
8341 buffer_snapshot,
8342 cx,
8343 );
8344 for (breakpoint, state) in breakpoints {
8345 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8346 let position = multi_buffer_anchor
8347 .to_point(&multi_buffer_snapshot)
8348 .to_display_point(&snapshot);
8349
8350 breakpoint_display_points.insert(
8351 position.row(),
8352 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8353 );
8354 }
8355 }
8356
8357 breakpoint_display_points
8358 }
8359
8360 fn breakpoint_context_menu(
8361 &self,
8362 anchor: Anchor,
8363 window: &mut Window,
8364 cx: &mut Context<Self>,
8365 ) -> Entity<ui::ContextMenu> {
8366 let weak_editor = cx.weak_entity();
8367 let focus_handle = self.focus_handle(cx);
8368
8369 let row = self
8370 .buffer
8371 .read(cx)
8372 .snapshot(cx)
8373 .summary_for_anchor::<Point>(&anchor)
8374 .row;
8375
8376 let breakpoint = self
8377 .breakpoint_at_row(row, window, cx)
8378 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8379
8380 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8381 "Edit Log Breakpoint"
8382 } else {
8383 "Set Log Breakpoint"
8384 };
8385
8386 let condition_breakpoint_msg = if breakpoint
8387 .as_ref()
8388 .is_some_and(|bp| bp.1.condition.is_some())
8389 {
8390 "Edit Condition Breakpoint"
8391 } else {
8392 "Set Condition Breakpoint"
8393 };
8394
8395 let hit_condition_breakpoint_msg = if breakpoint
8396 .as_ref()
8397 .is_some_and(|bp| bp.1.hit_condition.is_some())
8398 {
8399 "Edit Hit Condition Breakpoint"
8400 } else {
8401 "Set Hit Condition Breakpoint"
8402 };
8403
8404 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8405 "Unset Breakpoint"
8406 } else {
8407 "Set Breakpoint"
8408 };
8409
8410 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8411
8412 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8413 BreakpointState::Enabled => Some("Disable"),
8414 BreakpointState::Disabled => Some("Enable"),
8415 });
8416
8417 let (anchor, breakpoint) =
8418 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8419
8420 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8421 menu.on_blur_subscription(Subscription::new(|| {}))
8422 .context(focus_handle)
8423 .when(run_to_cursor, |this| {
8424 let weak_editor = weak_editor.clone();
8425 this.entry("Run to cursor", None, move |window, cx| {
8426 weak_editor
8427 .update(cx, |editor, cx| {
8428 editor.change_selections(
8429 SelectionEffects::no_scroll(),
8430 window,
8431 cx,
8432 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8433 );
8434 })
8435 .ok();
8436
8437 window.dispatch_action(Box::new(RunToCursor), cx);
8438 })
8439 .separator()
8440 })
8441 .when_some(toggle_state_msg, |this, msg| {
8442 this.entry(msg, None, {
8443 let weak_editor = weak_editor.clone();
8444 let breakpoint = breakpoint.clone();
8445 move |_window, cx| {
8446 weak_editor
8447 .update(cx, |this, cx| {
8448 this.edit_breakpoint_at_anchor(
8449 anchor,
8450 breakpoint.as_ref().clone(),
8451 BreakpointEditAction::InvertState,
8452 cx,
8453 );
8454 })
8455 .log_err();
8456 }
8457 })
8458 })
8459 .entry(set_breakpoint_msg, None, {
8460 let weak_editor = weak_editor.clone();
8461 let breakpoint = breakpoint.clone();
8462 move |_window, cx| {
8463 weak_editor
8464 .update(cx, |this, cx| {
8465 this.edit_breakpoint_at_anchor(
8466 anchor,
8467 breakpoint.as_ref().clone(),
8468 BreakpointEditAction::Toggle,
8469 cx,
8470 );
8471 })
8472 .log_err();
8473 }
8474 })
8475 .entry(log_breakpoint_msg, None, {
8476 let breakpoint = breakpoint.clone();
8477 let weak_editor = weak_editor.clone();
8478 move |window, cx| {
8479 weak_editor
8480 .update(cx, |this, cx| {
8481 this.add_edit_breakpoint_block(
8482 anchor,
8483 breakpoint.as_ref(),
8484 BreakpointPromptEditAction::Log,
8485 window,
8486 cx,
8487 );
8488 })
8489 .log_err();
8490 }
8491 })
8492 .entry(condition_breakpoint_msg, None, {
8493 let breakpoint = breakpoint.clone();
8494 let weak_editor = weak_editor.clone();
8495 move |window, cx| {
8496 weak_editor
8497 .update(cx, |this, cx| {
8498 this.add_edit_breakpoint_block(
8499 anchor,
8500 breakpoint.as_ref(),
8501 BreakpointPromptEditAction::Condition,
8502 window,
8503 cx,
8504 );
8505 })
8506 .log_err();
8507 }
8508 })
8509 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8510 weak_editor
8511 .update(cx, |this, cx| {
8512 this.add_edit_breakpoint_block(
8513 anchor,
8514 breakpoint.as_ref(),
8515 BreakpointPromptEditAction::HitCondition,
8516 window,
8517 cx,
8518 );
8519 })
8520 .log_err();
8521 })
8522 })
8523 }
8524
8525 fn render_breakpoint(
8526 &self,
8527 position: Anchor,
8528 row: DisplayRow,
8529 breakpoint: &Breakpoint,
8530 state: Option<BreakpointSessionState>,
8531 cx: &mut Context<Self>,
8532 ) -> IconButton {
8533 let is_rejected = state.is_some_and(|s| !s.verified);
8534 // Is it a breakpoint that shows up when hovering over gutter?
8535 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8536 (false, false),
8537 |PhantomBreakpointIndicator {
8538 is_active,
8539 display_row,
8540 collides_with_existing_breakpoint,
8541 }| {
8542 (
8543 is_active && display_row == row,
8544 collides_with_existing_breakpoint,
8545 )
8546 },
8547 );
8548
8549 let (color, icon) = {
8550 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8551 (false, false) => ui::IconName::DebugBreakpoint,
8552 (true, false) => ui::IconName::DebugLogBreakpoint,
8553 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8554 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8555 };
8556
8557 let color = cx.theme().colors();
8558
8559 let color = if is_phantom {
8560 if collides_with_existing {
8561 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8562 } else {
8563 Color::Hint
8564 }
8565 } else if is_rejected {
8566 Color::Disabled
8567 } else {
8568 Color::Debugger
8569 };
8570
8571 (color, icon)
8572 };
8573
8574 let breakpoint = Arc::from(breakpoint.clone());
8575
8576 let alt_as_text = gpui::Keystroke {
8577 modifiers: Modifiers::secondary_key(),
8578 ..Default::default()
8579 };
8580 let primary_action_text = if breakpoint.is_disabled() {
8581 "Enable breakpoint"
8582 } else if is_phantom && !collides_with_existing {
8583 "Set breakpoint"
8584 } else {
8585 "Unset breakpoint"
8586 };
8587 let focus_handle = self.focus_handle.clone();
8588
8589 let meta = if is_rejected {
8590 SharedString::from("No executable code is associated with this line.")
8591 } else if collides_with_existing && !breakpoint.is_disabled() {
8592 SharedString::from(format!(
8593 "{alt_as_text}-click to disable,\nright-click for more options."
8594 ))
8595 } else {
8596 SharedString::from("Right-click for more options.")
8597 };
8598 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8599 .icon_size(IconSize::XSmall)
8600 .size(ui::ButtonSize::None)
8601 .when(is_rejected, |this| {
8602 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8603 })
8604 .icon_color(color)
8605 .style(ButtonStyle::Transparent)
8606 .on_click(cx.listener({
8607 move |editor, event: &ClickEvent, window, cx| {
8608 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8609 BreakpointEditAction::InvertState
8610 } else {
8611 BreakpointEditAction::Toggle
8612 };
8613
8614 window.focus(&editor.focus_handle(cx), cx);
8615 editor.edit_breakpoint_at_anchor(
8616 position,
8617 breakpoint.as_ref().clone(),
8618 edit_action,
8619 cx,
8620 );
8621 }
8622 }))
8623 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8624 editor.set_breakpoint_context_menu(
8625 row,
8626 Some(position),
8627 event.position(),
8628 window,
8629 cx,
8630 );
8631 }))
8632 .tooltip(move |_window, cx| {
8633 Tooltip::with_meta_in(
8634 primary_action_text,
8635 Some(&ToggleBreakpoint),
8636 meta.clone(),
8637 &focus_handle,
8638 cx,
8639 )
8640 })
8641 }
8642
8643 fn build_tasks_context(
8644 project: &Entity<Project>,
8645 buffer: &Entity<Buffer>,
8646 buffer_row: u32,
8647 tasks: &Arc<RunnableTasks>,
8648 cx: &mut Context<Self>,
8649 ) -> Task<Option<task::TaskContext>> {
8650 let position = Point::new(buffer_row, tasks.column);
8651 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8652 let location = Location {
8653 buffer: buffer.clone(),
8654 range: range_start..range_start,
8655 };
8656 // Fill in the environmental variables from the tree-sitter captures
8657 let mut captured_task_variables = TaskVariables::default();
8658 for (capture_name, value) in tasks.extra_variables.clone() {
8659 captured_task_variables.insert(
8660 task::VariableName::Custom(capture_name.into()),
8661 value.clone(),
8662 );
8663 }
8664 project.update(cx, |project, cx| {
8665 project.task_store().update(cx, |task_store, cx| {
8666 task_store.task_context_for_location(captured_task_variables, location, cx)
8667 })
8668 })
8669 }
8670
8671 pub fn spawn_nearest_task(
8672 &mut self,
8673 action: &SpawnNearestTask,
8674 window: &mut Window,
8675 cx: &mut Context<Self>,
8676 ) {
8677 let Some((workspace, _)) = self.workspace.clone() else {
8678 return;
8679 };
8680 let Some(project) = self.project.clone() else {
8681 return;
8682 };
8683
8684 // Try to find a closest, enclosing node using tree-sitter that has a task
8685 let Some((buffer, buffer_row, tasks)) = self
8686 .find_enclosing_node_task(cx)
8687 // Or find the task that's closest in row-distance.
8688 .or_else(|| self.find_closest_task(cx))
8689 else {
8690 return;
8691 };
8692
8693 let reveal_strategy = action.reveal;
8694 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8695 cx.spawn_in(window, async move |_, cx| {
8696 let context = task_context.await?;
8697 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8698
8699 let resolved = &mut resolved_task.resolved;
8700 resolved.reveal = reveal_strategy;
8701
8702 workspace
8703 .update_in(cx, |workspace, window, cx| {
8704 workspace.schedule_resolved_task(
8705 task_source_kind,
8706 resolved_task,
8707 false,
8708 window,
8709 cx,
8710 );
8711 })
8712 .ok()
8713 })
8714 .detach();
8715 }
8716
8717 fn find_closest_task(
8718 &mut self,
8719 cx: &mut Context<Self>,
8720 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8721 let cursor_row = self
8722 .selections
8723 .newest_adjusted(&self.display_snapshot(cx))
8724 .head()
8725 .row;
8726
8727 let ((buffer_id, row), tasks) = self
8728 .tasks
8729 .iter()
8730 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8731
8732 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8733 let tasks = Arc::new(tasks.to_owned());
8734 Some((buffer, *row, tasks))
8735 }
8736
8737 fn find_enclosing_node_task(
8738 &mut self,
8739 cx: &mut Context<Self>,
8740 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8741 let snapshot = self.buffer.read(cx).snapshot(cx);
8742 let offset = self
8743 .selections
8744 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8745 .head();
8746 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8747 let offset = excerpt.map_offset_to_buffer(offset);
8748 let buffer_id = excerpt.buffer().remote_id();
8749
8750 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8751 let mut cursor = layer.node().walk();
8752
8753 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8754 if cursor.node().end_byte() == offset.0 {
8755 cursor.goto_next_sibling();
8756 }
8757 }
8758
8759 // Ascend to the smallest ancestor that contains the range and has a task.
8760 loop {
8761 let node = cursor.node();
8762 let node_range = node.byte_range();
8763 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8764
8765 // Check if this node contains our offset
8766 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8767 // If it contains offset, check for task
8768 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8769 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8770 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8771 }
8772 }
8773
8774 if !cursor.goto_parent() {
8775 break;
8776 }
8777 }
8778 None
8779 }
8780
8781 fn render_run_indicator(
8782 &self,
8783 _style: &EditorStyle,
8784 is_active: bool,
8785 row: DisplayRow,
8786 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8787 cx: &mut Context<Self>,
8788 ) -> IconButton {
8789 let color = Color::Muted;
8790 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8791
8792 IconButton::new(
8793 ("run_indicator", row.0 as usize),
8794 ui::IconName::PlayOutlined,
8795 )
8796 .shape(ui::IconButtonShape::Square)
8797 .icon_size(IconSize::XSmall)
8798 .icon_color(color)
8799 .toggle_state(is_active)
8800 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8801 let quick_launch = match e {
8802 ClickEvent::Keyboard(_) => true,
8803 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8804 };
8805
8806 window.focus(&editor.focus_handle(cx), cx);
8807 editor.toggle_code_actions(
8808 &ToggleCodeActions {
8809 deployed_from: Some(CodeActionSource::RunMenu(row)),
8810 quick_launch,
8811 },
8812 window,
8813 cx,
8814 );
8815 }))
8816 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8817 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8818 }))
8819 }
8820
8821 pub fn context_menu_visible(&self) -> bool {
8822 !self.edit_prediction_preview_is_active()
8823 && self
8824 .context_menu
8825 .borrow()
8826 .as_ref()
8827 .is_some_and(|menu| menu.visible())
8828 }
8829
8830 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8831 self.context_menu
8832 .borrow()
8833 .as_ref()
8834 .map(|menu| menu.origin())
8835 }
8836
8837 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8838 self.context_menu_options = Some(options);
8839 }
8840
8841 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8842 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8843
8844 fn render_edit_prediction_popover(
8845 &mut self,
8846 text_bounds: &Bounds<Pixels>,
8847 content_origin: gpui::Point<Pixels>,
8848 right_margin: Pixels,
8849 editor_snapshot: &EditorSnapshot,
8850 visible_row_range: Range<DisplayRow>,
8851 scroll_top: ScrollOffset,
8852 scroll_bottom: ScrollOffset,
8853 line_layouts: &[LineWithInvisibles],
8854 line_height: Pixels,
8855 scroll_position: gpui::Point<ScrollOffset>,
8856 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8857 newest_selection_head: Option<DisplayPoint>,
8858 editor_width: Pixels,
8859 style: &EditorStyle,
8860 window: &mut Window,
8861 cx: &mut App,
8862 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8863 if self.mode().is_minimap() {
8864 return None;
8865 }
8866 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8867
8868 if self.edit_prediction_visible_in_cursor_popover(true) {
8869 return None;
8870 }
8871
8872 match &active_edit_prediction.completion {
8873 EditPrediction::MoveWithin { target, .. } => {
8874 let target_display_point = target.to_display_point(editor_snapshot);
8875
8876 if self.edit_prediction_requires_modifier() {
8877 if !self.edit_prediction_preview_is_active() {
8878 return None;
8879 }
8880
8881 self.render_edit_prediction_modifier_jump_popover(
8882 text_bounds,
8883 content_origin,
8884 visible_row_range,
8885 line_layouts,
8886 line_height,
8887 scroll_pixel_position,
8888 newest_selection_head,
8889 target_display_point,
8890 window,
8891 cx,
8892 )
8893 } else {
8894 self.render_edit_prediction_eager_jump_popover(
8895 text_bounds,
8896 content_origin,
8897 editor_snapshot,
8898 visible_row_range,
8899 scroll_top,
8900 scroll_bottom,
8901 line_height,
8902 scroll_pixel_position,
8903 target_display_point,
8904 editor_width,
8905 window,
8906 cx,
8907 )
8908 }
8909 }
8910 EditPrediction::Edit {
8911 display_mode: EditDisplayMode::Inline,
8912 ..
8913 } => None,
8914 EditPrediction::Edit {
8915 display_mode: EditDisplayMode::TabAccept,
8916 edits,
8917 ..
8918 } => {
8919 let range = &edits.first()?.0;
8920 let target_display_point = range.end.to_display_point(editor_snapshot);
8921
8922 self.render_edit_prediction_end_of_line_popover(
8923 "Accept",
8924 editor_snapshot,
8925 visible_row_range,
8926 target_display_point,
8927 line_height,
8928 scroll_pixel_position,
8929 content_origin,
8930 editor_width,
8931 window,
8932 cx,
8933 )
8934 }
8935 EditPrediction::Edit {
8936 edits,
8937 edit_preview,
8938 display_mode: EditDisplayMode::DiffPopover,
8939 snapshot,
8940 } => self.render_edit_prediction_diff_popover(
8941 text_bounds,
8942 content_origin,
8943 right_margin,
8944 editor_snapshot,
8945 visible_row_range,
8946 line_layouts,
8947 line_height,
8948 scroll_position,
8949 scroll_pixel_position,
8950 newest_selection_head,
8951 editor_width,
8952 style,
8953 edits,
8954 edit_preview,
8955 snapshot,
8956 window,
8957 cx,
8958 ),
8959 EditPrediction::MoveOutside { snapshot, .. } => {
8960 let mut element = self
8961 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8962 .into_any();
8963
8964 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8965 let origin_x = text_bounds.size.width - size.width - px(30.);
8966 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8967 element.prepaint_at(origin, window, cx);
8968
8969 Some((element, origin))
8970 }
8971 }
8972 }
8973
8974 fn render_edit_prediction_modifier_jump_popover(
8975 &mut self,
8976 text_bounds: &Bounds<Pixels>,
8977 content_origin: gpui::Point<Pixels>,
8978 visible_row_range: Range<DisplayRow>,
8979 line_layouts: &[LineWithInvisibles],
8980 line_height: Pixels,
8981 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8982 newest_selection_head: Option<DisplayPoint>,
8983 target_display_point: DisplayPoint,
8984 window: &mut Window,
8985 cx: &mut App,
8986 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8987 let scrolled_content_origin =
8988 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8989
8990 const SCROLL_PADDING_Y: Pixels = px(12.);
8991
8992 if target_display_point.row() < visible_row_range.start {
8993 return self.render_edit_prediction_scroll_popover(
8994 |_| SCROLL_PADDING_Y,
8995 IconName::ArrowUp,
8996 visible_row_range,
8997 line_layouts,
8998 newest_selection_head,
8999 scrolled_content_origin,
9000 window,
9001 cx,
9002 );
9003 } else if target_display_point.row() >= visible_row_range.end {
9004 return self.render_edit_prediction_scroll_popover(
9005 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9006 IconName::ArrowDown,
9007 visible_row_range,
9008 line_layouts,
9009 newest_selection_head,
9010 scrolled_content_origin,
9011 window,
9012 cx,
9013 );
9014 }
9015
9016 const POLE_WIDTH: Pixels = px(2.);
9017
9018 let line_layout =
9019 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9020 let target_column = target_display_point.column() as usize;
9021
9022 let target_x = line_layout.x_for_index(target_column);
9023 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9024 - scroll_pixel_position.y;
9025
9026 let flag_on_right = target_x < text_bounds.size.width / 2.;
9027
9028 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9029 border_color.l += 0.001;
9030
9031 let mut element = v_flex()
9032 .items_end()
9033 .when(flag_on_right, |el| el.items_start())
9034 .child(if flag_on_right {
9035 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9036 .rounded_bl(px(0.))
9037 .rounded_tl(px(0.))
9038 .border_l_2()
9039 .border_color(border_color)
9040 } else {
9041 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9042 .rounded_br(px(0.))
9043 .rounded_tr(px(0.))
9044 .border_r_2()
9045 .border_color(border_color)
9046 })
9047 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9048 .into_any();
9049
9050 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9051
9052 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9053 - point(
9054 if flag_on_right {
9055 POLE_WIDTH
9056 } else {
9057 size.width - POLE_WIDTH
9058 },
9059 size.height - line_height,
9060 );
9061
9062 origin.x = origin.x.max(content_origin.x);
9063
9064 element.prepaint_at(origin, window, cx);
9065
9066 Some((element, origin))
9067 }
9068
9069 fn render_edit_prediction_scroll_popover(
9070 &mut self,
9071 to_y: impl Fn(Size<Pixels>) -> Pixels,
9072 scroll_icon: IconName,
9073 visible_row_range: Range<DisplayRow>,
9074 line_layouts: &[LineWithInvisibles],
9075 newest_selection_head: Option<DisplayPoint>,
9076 scrolled_content_origin: gpui::Point<Pixels>,
9077 window: &mut Window,
9078 cx: &mut App,
9079 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9080 let mut element = self
9081 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9082 .into_any();
9083
9084 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9085
9086 let cursor = newest_selection_head?;
9087 let cursor_row_layout =
9088 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9089 let cursor_column = cursor.column() as usize;
9090
9091 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9092
9093 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9094
9095 element.prepaint_at(origin, window, cx);
9096 Some((element, origin))
9097 }
9098
9099 fn render_edit_prediction_eager_jump_popover(
9100 &mut self,
9101 text_bounds: &Bounds<Pixels>,
9102 content_origin: gpui::Point<Pixels>,
9103 editor_snapshot: &EditorSnapshot,
9104 visible_row_range: Range<DisplayRow>,
9105 scroll_top: ScrollOffset,
9106 scroll_bottom: ScrollOffset,
9107 line_height: Pixels,
9108 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9109 target_display_point: DisplayPoint,
9110 editor_width: Pixels,
9111 window: &mut Window,
9112 cx: &mut App,
9113 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9114 if target_display_point.row().as_f64() < scroll_top {
9115 let mut element = self
9116 .render_edit_prediction_line_popover(
9117 "Jump to Edit",
9118 Some(IconName::ArrowUp),
9119 window,
9120 cx,
9121 )
9122 .into_any();
9123
9124 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9125 let offset = point(
9126 (text_bounds.size.width - size.width) / 2.,
9127 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9128 );
9129
9130 let origin = text_bounds.origin + offset;
9131 element.prepaint_at(origin, window, cx);
9132 Some((element, origin))
9133 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9134 let mut element = self
9135 .render_edit_prediction_line_popover(
9136 "Jump to Edit",
9137 Some(IconName::ArrowDown),
9138 window,
9139 cx,
9140 )
9141 .into_any();
9142
9143 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9144 let offset = point(
9145 (text_bounds.size.width - size.width) / 2.,
9146 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9147 );
9148
9149 let origin = text_bounds.origin + offset;
9150 element.prepaint_at(origin, window, cx);
9151 Some((element, origin))
9152 } else {
9153 self.render_edit_prediction_end_of_line_popover(
9154 "Jump to Edit",
9155 editor_snapshot,
9156 visible_row_range,
9157 target_display_point,
9158 line_height,
9159 scroll_pixel_position,
9160 content_origin,
9161 editor_width,
9162 window,
9163 cx,
9164 )
9165 }
9166 }
9167
9168 fn render_edit_prediction_end_of_line_popover(
9169 self: &mut Editor,
9170 label: &'static str,
9171 editor_snapshot: &EditorSnapshot,
9172 visible_row_range: Range<DisplayRow>,
9173 target_display_point: DisplayPoint,
9174 line_height: Pixels,
9175 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9176 content_origin: gpui::Point<Pixels>,
9177 editor_width: Pixels,
9178 window: &mut Window,
9179 cx: &mut App,
9180 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9181 let target_line_end = DisplayPoint::new(
9182 target_display_point.row(),
9183 editor_snapshot.line_len(target_display_point.row()),
9184 );
9185
9186 let mut element = self
9187 .render_edit_prediction_line_popover(label, None, window, cx)
9188 .into_any();
9189
9190 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9191
9192 let line_origin =
9193 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9194
9195 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9196 let mut origin = start_point
9197 + line_origin
9198 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9199 origin.x = origin.x.max(content_origin.x);
9200
9201 let max_x = content_origin.x + editor_width - size.width;
9202
9203 if origin.x > max_x {
9204 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9205
9206 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9207 origin.y += offset;
9208 IconName::ArrowUp
9209 } else {
9210 origin.y -= offset;
9211 IconName::ArrowDown
9212 };
9213
9214 element = self
9215 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9216 .into_any();
9217
9218 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9219
9220 origin.x = content_origin.x + editor_width - size.width - px(2.);
9221 }
9222
9223 element.prepaint_at(origin, window, cx);
9224 Some((element, origin))
9225 }
9226
9227 fn render_edit_prediction_diff_popover(
9228 self: &Editor,
9229 text_bounds: &Bounds<Pixels>,
9230 content_origin: gpui::Point<Pixels>,
9231 right_margin: Pixels,
9232 editor_snapshot: &EditorSnapshot,
9233 visible_row_range: Range<DisplayRow>,
9234 line_layouts: &[LineWithInvisibles],
9235 line_height: Pixels,
9236 scroll_position: gpui::Point<ScrollOffset>,
9237 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9238 newest_selection_head: Option<DisplayPoint>,
9239 editor_width: Pixels,
9240 style: &EditorStyle,
9241 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9242 edit_preview: &Option<language::EditPreview>,
9243 snapshot: &language::BufferSnapshot,
9244 window: &mut Window,
9245 cx: &mut App,
9246 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9247 let edit_start = edits
9248 .first()
9249 .unwrap()
9250 .0
9251 .start
9252 .to_display_point(editor_snapshot);
9253 let edit_end = edits
9254 .last()
9255 .unwrap()
9256 .0
9257 .end
9258 .to_display_point(editor_snapshot);
9259
9260 let is_visible = visible_row_range.contains(&edit_start.row())
9261 || visible_row_range.contains(&edit_end.row());
9262 if !is_visible {
9263 return None;
9264 }
9265
9266 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9267 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9268 } else {
9269 // Fallback for providers without edit_preview
9270 crate::edit_prediction_fallback_text(edits, cx)
9271 };
9272
9273 let styled_text = highlighted_edits.to_styled_text(&style.text);
9274 let line_count = highlighted_edits.text.lines().count();
9275
9276 const BORDER_WIDTH: Pixels = px(1.);
9277
9278 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9279 let has_keybind = keybind.is_some();
9280
9281 let mut element = h_flex()
9282 .items_start()
9283 .child(
9284 h_flex()
9285 .bg(cx.theme().colors().editor_background)
9286 .border(BORDER_WIDTH)
9287 .shadow_xs()
9288 .border_color(cx.theme().colors().border)
9289 .rounded_l_lg()
9290 .when(line_count > 1, |el| el.rounded_br_lg())
9291 .pr_1()
9292 .child(styled_text),
9293 )
9294 .child(
9295 h_flex()
9296 .h(line_height + BORDER_WIDTH * 2.)
9297 .px_1p5()
9298 .gap_1()
9299 // Workaround: For some reason, there's a gap if we don't do this
9300 .ml(-BORDER_WIDTH)
9301 .shadow(vec![gpui::BoxShadow {
9302 color: gpui::black().opacity(0.05),
9303 offset: point(px(1.), px(1.)),
9304 blur_radius: px(2.),
9305 spread_radius: px(0.),
9306 }])
9307 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9308 .border(BORDER_WIDTH)
9309 .border_color(cx.theme().colors().border)
9310 .rounded_r_lg()
9311 .id("edit_prediction_diff_popover_keybind")
9312 .when(!has_keybind, |el| {
9313 let status_colors = cx.theme().status();
9314
9315 el.bg(status_colors.error_background)
9316 .border_color(status_colors.error.opacity(0.6))
9317 .child(Icon::new(IconName::Info).color(Color::Error))
9318 .cursor_default()
9319 .hoverable_tooltip(move |_window, cx| {
9320 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9321 })
9322 })
9323 .children(keybind),
9324 )
9325 .into_any();
9326
9327 let longest_row =
9328 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9329 let longest_line_width = if visible_row_range.contains(&longest_row) {
9330 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9331 } else {
9332 layout_line(
9333 longest_row,
9334 editor_snapshot,
9335 style,
9336 editor_width,
9337 |_| false,
9338 window,
9339 cx,
9340 )
9341 .width
9342 };
9343
9344 let viewport_bounds =
9345 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9346 right: -right_margin,
9347 ..Default::default()
9348 });
9349
9350 let x_after_longest = Pixels::from(
9351 ScrollPixelOffset::from(
9352 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9353 ) - scroll_pixel_position.x,
9354 );
9355
9356 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9357
9358 // Fully visible if it can be displayed within the window (allow overlapping other
9359 // panes). However, this is only allowed if the popover starts within text_bounds.
9360 let can_position_to_the_right = x_after_longest < text_bounds.right()
9361 && x_after_longest + element_bounds.width < viewport_bounds.right();
9362
9363 let mut origin = if can_position_to_the_right {
9364 point(
9365 x_after_longest,
9366 text_bounds.origin.y
9367 + Pixels::from(
9368 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9369 - scroll_pixel_position.y,
9370 ),
9371 )
9372 } else {
9373 let cursor_row = newest_selection_head.map(|head| head.row());
9374 let above_edit = edit_start
9375 .row()
9376 .0
9377 .checked_sub(line_count as u32)
9378 .map(DisplayRow);
9379 let below_edit = Some(edit_end.row() + 1);
9380 let above_cursor =
9381 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9382 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9383
9384 // Place the edit popover adjacent to the edit if there is a location
9385 // available that is onscreen and does not obscure the cursor. Otherwise,
9386 // place it adjacent to the cursor.
9387 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9388 .into_iter()
9389 .flatten()
9390 .find(|&start_row| {
9391 let end_row = start_row + line_count as u32;
9392 visible_row_range.contains(&start_row)
9393 && visible_row_range.contains(&end_row)
9394 && cursor_row
9395 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9396 })?;
9397
9398 content_origin
9399 + point(
9400 Pixels::from(-scroll_pixel_position.x),
9401 Pixels::from(
9402 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9403 ),
9404 )
9405 };
9406
9407 origin.x -= BORDER_WIDTH;
9408
9409 window.defer_draw(element, origin, 1);
9410
9411 // Do not return an element, since it will already be drawn due to defer_draw.
9412 None
9413 }
9414
9415 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9416 px(30.)
9417 }
9418
9419 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9420 if self.read_only(cx) {
9421 cx.theme().players().read_only()
9422 } else {
9423 self.style.as_ref().unwrap().local_player
9424 }
9425 }
9426
9427 fn render_edit_prediction_accept_keybind(
9428 &self,
9429 window: &mut Window,
9430 cx: &mut App,
9431 ) -> Option<AnyElement> {
9432 let accept_binding =
9433 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9434 let accept_keystroke = accept_binding.keystroke()?;
9435
9436 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9437
9438 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9439 Color::Accent
9440 } else {
9441 Color::Muted
9442 };
9443
9444 h_flex()
9445 .px_0p5()
9446 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9447 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9448 .text_size(TextSize::XSmall.rems(cx))
9449 .child(h_flex().children(ui::render_modifiers(
9450 accept_keystroke.modifiers(),
9451 PlatformStyle::platform(),
9452 Some(modifiers_color),
9453 Some(IconSize::XSmall.rems().into()),
9454 true,
9455 )))
9456 .when(is_platform_style_mac, |parent| {
9457 parent.child(accept_keystroke.key().to_string())
9458 })
9459 .when(!is_platform_style_mac, |parent| {
9460 parent.child(
9461 Key::new(
9462 util::capitalize(accept_keystroke.key()),
9463 Some(Color::Default),
9464 )
9465 .size(Some(IconSize::XSmall.rems().into())),
9466 )
9467 })
9468 .into_any()
9469 .into()
9470 }
9471
9472 fn render_edit_prediction_line_popover(
9473 &self,
9474 label: impl Into<SharedString>,
9475 icon: Option<IconName>,
9476 window: &mut Window,
9477 cx: &mut App,
9478 ) -> Stateful<Div> {
9479 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9480
9481 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9482 let has_keybind = keybind.is_some();
9483
9484 h_flex()
9485 .id("ep-line-popover")
9486 .py_0p5()
9487 .pl_1()
9488 .pr(padding_right)
9489 .gap_1()
9490 .rounded_md()
9491 .border_1()
9492 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9493 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9494 .shadow_xs()
9495 .when(!has_keybind, |el| {
9496 let status_colors = cx.theme().status();
9497
9498 el.bg(status_colors.error_background)
9499 .border_color(status_colors.error.opacity(0.6))
9500 .pl_2()
9501 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9502 .cursor_default()
9503 .hoverable_tooltip(move |_window, cx| {
9504 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9505 })
9506 })
9507 .children(keybind)
9508 .child(
9509 Label::new(label)
9510 .size(LabelSize::Small)
9511 .when(!has_keybind, |el| {
9512 el.color(cx.theme().status().error.into()).strikethrough()
9513 }),
9514 )
9515 .when(!has_keybind, |el| {
9516 el.child(
9517 h_flex().ml_1().child(
9518 Icon::new(IconName::Info)
9519 .size(IconSize::Small)
9520 .color(cx.theme().status().error.into()),
9521 ),
9522 )
9523 })
9524 .when_some(icon, |element, icon| {
9525 element.child(
9526 div()
9527 .mt(px(1.5))
9528 .child(Icon::new(icon).size(IconSize::Small)),
9529 )
9530 })
9531 }
9532
9533 fn render_edit_prediction_jump_outside_popover(
9534 &self,
9535 snapshot: &BufferSnapshot,
9536 window: &mut Window,
9537 cx: &mut App,
9538 ) -> Stateful<Div> {
9539 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9540 let has_keybind = keybind.is_some();
9541
9542 let file_name = snapshot
9543 .file()
9544 .map(|file| SharedString::new(file.file_name(cx)))
9545 .unwrap_or(SharedString::new_static("untitled"));
9546
9547 h_flex()
9548 .id("ep-jump-outside-popover")
9549 .py_1()
9550 .px_2()
9551 .gap_1()
9552 .rounded_md()
9553 .border_1()
9554 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9555 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9556 .shadow_xs()
9557 .when(!has_keybind, |el| {
9558 let status_colors = cx.theme().status();
9559
9560 el.bg(status_colors.error_background)
9561 .border_color(status_colors.error.opacity(0.6))
9562 .pl_2()
9563 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9564 .cursor_default()
9565 .hoverable_tooltip(move |_window, cx| {
9566 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9567 })
9568 })
9569 .children(keybind)
9570 .child(
9571 Label::new(file_name)
9572 .size(LabelSize::Small)
9573 .buffer_font(cx)
9574 .when(!has_keybind, |el| {
9575 el.color(cx.theme().status().error.into()).strikethrough()
9576 }),
9577 )
9578 .when(!has_keybind, |el| {
9579 el.child(
9580 h_flex().ml_1().child(
9581 Icon::new(IconName::Info)
9582 .size(IconSize::Small)
9583 .color(cx.theme().status().error.into()),
9584 ),
9585 )
9586 })
9587 .child(
9588 div()
9589 .mt(px(1.5))
9590 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9591 )
9592 }
9593
9594 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9595 let accent_color = cx.theme().colors().text_accent;
9596 let editor_bg_color = cx.theme().colors().editor_background;
9597 editor_bg_color.blend(accent_color.opacity(0.1))
9598 }
9599
9600 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9601 let accent_color = cx.theme().colors().text_accent;
9602 let editor_bg_color = cx.theme().colors().editor_background;
9603 editor_bg_color.blend(accent_color.opacity(0.6))
9604 }
9605 fn get_prediction_provider_icon_name(
9606 provider: &Option<RegisteredEditPredictionDelegate>,
9607 ) -> IconName {
9608 match provider {
9609 Some(provider) => match provider.provider.name() {
9610 "copilot" => IconName::Copilot,
9611 "supermaven" => IconName::Supermaven,
9612 _ => IconName::ZedPredict,
9613 },
9614 None => IconName::ZedPredict,
9615 }
9616 }
9617
9618 fn render_edit_prediction_cursor_popover(
9619 &self,
9620 min_width: Pixels,
9621 max_width: Pixels,
9622 cursor_point: Point,
9623 style: &EditorStyle,
9624 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9625 _window: &Window,
9626 cx: &mut Context<Editor>,
9627 ) -> Option<AnyElement> {
9628 let provider = self.edit_prediction_provider.as_ref()?;
9629 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9630
9631 let is_refreshing = provider.provider.is_refreshing(cx);
9632
9633 fn pending_completion_container(icon: IconName) -> Div {
9634 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9635 }
9636
9637 let completion = match &self.active_edit_prediction {
9638 Some(prediction) => {
9639 if !self.has_visible_completions_menu() {
9640 const RADIUS: Pixels = px(6.);
9641 const BORDER_WIDTH: Pixels = px(1.);
9642
9643 return Some(
9644 h_flex()
9645 .elevation_2(cx)
9646 .border(BORDER_WIDTH)
9647 .border_color(cx.theme().colors().border)
9648 .when(accept_keystroke.is_none(), |el| {
9649 el.border_color(cx.theme().status().error)
9650 })
9651 .rounded(RADIUS)
9652 .rounded_tl(px(0.))
9653 .overflow_hidden()
9654 .child(div().px_1p5().child(match &prediction.completion {
9655 EditPrediction::MoveWithin { target, snapshot } => {
9656 use text::ToPoint as _;
9657 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9658 {
9659 Icon::new(IconName::ZedPredictDown)
9660 } else {
9661 Icon::new(IconName::ZedPredictUp)
9662 }
9663 }
9664 EditPrediction::MoveOutside { .. } => {
9665 // TODO [zeta2] custom icon for external jump?
9666 Icon::new(provider_icon)
9667 }
9668 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9669 }))
9670 .child(
9671 h_flex()
9672 .gap_1()
9673 .py_1()
9674 .px_2()
9675 .rounded_r(RADIUS - BORDER_WIDTH)
9676 .border_l_1()
9677 .border_color(cx.theme().colors().border)
9678 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9679 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9680 el.child(
9681 Label::new("Hold")
9682 .size(LabelSize::Small)
9683 .when(accept_keystroke.is_none(), |el| {
9684 el.strikethrough()
9685 })
9686 .line_height_style(LineHeightStyle::UiLabel),
9687 )
9688 })
9689 .id("edit_prediction_cursor_popover_keybind")
9690 .when(accept_keystroke.is_none(), |el| {
9691 let status_colors = cx.theme().status();
9692
9693 el.bg(status_colors.error_background)
9694 .border_color(status_colors.error.opacity(0.6))
9695 .child(Icon::new(IconName::Info).color(Color::Error))
9696 .cursor_default()
9697 .hoverable_tooltip(move |_window, cx| {
9698 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9699 .into()
9700 })
9701 })
9702 .when_some(
9703 accept_keystroke.as_ref(),
9704 |el, accept_keystroke| {
9705 el.child(h_flex().children(ui::render_modifiers(
9706 accept_keystroke.modifiers(),
9707 PlatformStyle::platform(),
9708 Some(Color::Default),
9709 Some(IconSize::XSmall.rems().into()),
9710 false,
9711 )))
9712 },
9713 ),
9714 )
9715 .into_any(),
9716 );
9717 }
9718
9719 self.render_edit_prediction_cursor_popover_preview(
9720 prediction,
9721 cursor_point,
9722 style,
9723 cx,
9724 )?
9725 }
9726
9727 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9728 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9729 stale_completion,
9730 cursor_point,
9731 style,
9732 cx,
9733 )?,
9734
9735 None => pending_completion_container(provider_icon)
9736 .child(Label::new("...").size(LabelSize::Small)),
9737 },
9738
9739 None => pending_completion_container(provider_icon)
9740 .child(Label::new("...").size(LabelSize::Small)),
9741 };
9742
9743 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9744 completion
9745 .with_animation(
9746 "loading-completion",
9747 Animation::new(Duration::from_secs(2))
9748 .repeat()
9749 .with_easing(pulsating_between(0.4, 0.8)),
9750 |label, delta| label.opacity(delta),
9751 )
9752 .into_any_element()
9753 } else {
9754 completion.into_any_element()
9755 };
9756
9757 let has_completion = self.active_edit_prediction.is_some();
9758
9759 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9760 Some(
9761 h_flex()
9762 .min_w(min_width)
9763 .max_w(max_width)
9764 .flex_1()
9765 .elevation_2(cx)
9766 .border_color(cx.theme().colors().border)
9767 .child(
9768 div()
9769 .flex_1()
9770 .py_1()
9771 .px_2()
9772 .overflow_hidden()
9773 .child(completion),
9774 )
9775 .when_some(accept_keystroke, |el, accept_keystroke| {
9776 if !accept_keystroke.modifiers().modified() {
9777 return el;
9778 }
9779
9780 el.child(
9781 h_flex()
9782 .h_full()
9783 .border_l_1()
9784 .rounded_r_lg()
9785 .border_color(cx.theme().colors().border)
9786 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9787 .gap_1()
9788 .py_1()
9789 .px_2()
9790 .child(
9791 h_flex()
9792 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9793 .when(is_platform_style_mac, |parent| parent.gap_1())
9794 .child(h_flex().children(ui::render_modifiers(
9795 accept_keystroke.modifiers(),
9796 PlatformStyle::platform(),
9797 Some(if !has_completion {
9798 Color::Muted
9799 } else {
9800 Color::Default
9801 }),
9802 None,
9803 false,
9804 ))),
9805 )
9806 .child(Label::new("Preview").into_any_element())
9807 .opacity(if has_completion { 1.0 } else { 0.4 }),
9808 )
9809 })
9810 .into_any(),
9811 )
9812 }
9813
9814 fn render_edit_prediction_cursor_popover_preview(
9815 &self,
9816 completion: &EditPredictionState,
9817 cursor_point: Point,
9818 style: &EditorStyle,
9819 cx: &mut Context<Editor>,
9820 ) -> Option<Div> {
9821 use text::ToPoint as _;
9822
9823 fn render_relative_row_jump(
9824 prefix: impl Into<String>,
9825 current_row: u32,
9826 target_row: u32,
9827 ) -> Div {
9828 let (row_diff, arrow) = if target_row < current_row {
9829 (current_row - target_row, IconName::ArrowUp)
9830 } else {
9831 (target_row - current_row, IconName::ArrowDown)
9832 };
9833
9834 h_flex()
9835 .child(
9836 Label::new(format!("{}{}", prefix.into(), row_diff))
9837 .color(Color::Muted)
9838 .size(LabelSize::Small),
9839 )
9840 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9841 }
9842
9843 let supports_jump = self
9844 .edit_prediction_provider
9845 .as_ref()
9846 .map(|provider| provider.provider.supports_jump_to_edit())
9847 .unwrap_or(true);
9848
9849 match &completion.completion {
9850 EditPrediction::MoveWithin {
9851 target, snapshot, ..
9852 } => {
9853 if !supports_jump {
9854 return None;
9855 }
9856
9857 Some(
9858 h_flex()
9859 .px_2()
9860 .gap_2()
9861 .flex_1()
9862 .child(
9863 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9864 Icon::new(IconName::ZedPredictDown)
9865 } else {
9866 Icon::new(IconName::ZedPredictUp)
9867 },
9868 )
9869 .child(Label::new("Jump to Edit")),
9870 )
9871 }
9872 EditPrediction::MoveOutside { snapshot, .. } => {
9873 let file_name = snapshot
9874 .file()
9875 .map(|file| file.file_name(cx))
9876 .unwrap_or("untitled");
9877 Some(
9878 h_flex()
9879 .px_2()
9880 .gap_2()
9881 .flex_1()
9882 .child(Icon::new(IconName::ZedPredict))
9883 .child(Label::new(format!("Jump to {file_name}"))),
9884 )
9885 }
9886 EditPrediction::Edit {
9887 edits,
9888 edit_preview,
9889 snapshot,
9890 display_mode: _,
9891 } => {
9892 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9893
9894 let (highlighted_edits, has_more_lines) =
9895 if let Some(edit_preview) = edit_preview.as_ref() {
9896 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9897 .first_line_preview()
9898 } else {
9899 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9900 };
9901
9902 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9903 .with_default_highlights(&style.text, highlighted_edits.highlights);
9904
9905 let preview = h_flex()
9906 .gap_1()
9907 .min_w_16()
9908 .child(styled_text)
9909 .when(has_more_lines, |parent| parent.child("…"));
9910
9911 let left = if supports_jump && first_edit_row != cursor_point.row {
9912 render_relative_row_jump("", cursor_point.row, first_edit_row)
9913 .into_any_element()
9914 } else {
9915 let icon_name =
9916 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9917 Icon::new(icon_name).into_any_element()
9918 };
9919
9920 Some(
9921 h_flex()
9922 .h_full()
9923 .flex_1()
9924 .gap_2()
9925 .pr_1()
9926 .overflow_x_hidden()
9927 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9928 .child(left)
9929 .child(preview),
9930 )
9931 }
9932 }
9933 }
9934
9935 pub fn render_context_menu(
9936 &mut self,
9937 max_height_in_lines: u32,
9938 window: &mut Window,
9939 cx: &mut Context<Editor>,
9940 ) -> Option<AnyElement> {
9941 let menu = self.context_menu.borrow();
9942 let menu = menu.as_ref()?;
9943 if !menu.visible() {
9944 return None;
9945 };
9946 self.style
9947 .as_ref()
9948 .map(|style| menu.render(style, max_height_in_lines, window, cx))
9949 }
9950
9951 fn render_context_menu_aside(
9952 &mut self,
9953 max_size: Size<Pixels>,
9954 window: &mut Window,
9955 cx: &mut Context<Editor>,
9956 ) -> Option<AnyElement> {
9957 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9958 if menu.visible() {
9959 menu.render_aside(max_size, window, cx)
9960 } else {
9961 None
9962 }
9963 })
9964 }
9965
9966 fn hide_context_menu(
9967 &mut self,
9968 window: &mut Window,
9969 cx: &mut Context<Self>,
9970 ) -> Option<CodeContextMenu> {
9971 cx.notify();
9972 self.completion_tasks.clear();
9973 let context_menu = self.context_menu.borrow_mut().take();
9974 self.stale_edit_prediction_in_menu.take();
9975 self.update_visible_edit_prediction(window, cx);
9976 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9977 && let Some(completion_provider) = &self.completion_provider
9978 {
9979 completion_provider.selection_changed(None, window, cx);
9980 }
9981 context_menu
9982 }
9983
9984 fn show_snippet_choices(
9985 &mut self,
9986 choices: &Vec<String>,
9987 selection: Range<Anchor>,
9988 cx: &mut Context<Self>,
9989 ) {
9990 let Some((_, buffer, _)) = self
9991 .buffer()
9992 .read(cx)
9993 .excerpt_containing(selection.start, cx)
9994 else {
9995 return;
9996 };
9997 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9998 else {
9999 return;
10000 };
10001 if buffer != end_buffer {
10002 log::error!("expected anchor range to have matching buffer IDs");
10003 return;
10004 }
10005
10006 let id = post_inc(&mut self.next_completion_id);
10007 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10008 let mut context_menu = self.context_menu.borrow_mut();
10009 let old_menu = context_menu.take();
10010 *context_menu = Some(CodeContextMenu::Completions(
10011 CompletionsMenu::new_snippet_choices(
10012 id,
10013 true,
10014 choices,
10015 selection,
10016 buffer,
10017 old_menu.map(|menu| menu.primary_scroll_handle()),
10018 snippet_sort_order,
10019 ),
10020 ));
10021 }
10022
10023 pub fn insert_snippet(
10024 &mut self,
10025 insertion_ranges: &[Range<MultiBufferOffset>],
10026 snippet: Snippet,
10027 window: &mut Window,
10028 cx: &mut Context<Self>,
10029 ) -> Result<()> {
10030 struct Tabstop<T> {
10031 is_end_tabstop: bool,
10032 ranges: Vec<Range<T>>,
10033 choices: Option<Vec<String>>,
10034 }
10035
10036 let tabstops = self.buffer.update(cx, |buffer, cx| {
10037 let snippet_text: Arc<str> = snippet.text.clone().into();
10038 let edits = insertion_ranges
10039 .iter()
10040 .cloned()
10041 .map(|range| (range, snippet_text.clone()));
10042 let autoindent_mode = AutoindentMode::Block {
10043 original_indent_columns: Vec::new(),
10044 };
10045 buffer.edit(edits, Some(autoindent_mode), cx);
10046
10047 let snapshot = &*buffer.read(cx);
10048 let snippet = &snippet;
10049 snippet
10050 .tabstops
10051 .iter()
10052 .map(|tabstop| {
10053 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10054 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10055 });
10056 let mut tabstop_ranges = tabstop
10057 .ranges
10058 .iter()
10059 .flat_map(|tabstop_range| {
10060 let mut delta = 0_isize;
10061 insertion_ranges.iter().map(move |insertion_range| {
10062 let insertion_start = insertion_range.start + delta;
10063 delta += snippet.text.len() as isize
10064 - (insertion_range.end - insertion_range.start) as isize;
10065
10066 let start =
10067 (insertion_start + tabstop_range.start).min(snapshot.len());
10068 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10069 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10070 })
10071 })
10072 .collect::<Vec<_>>();
10073 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10074
10075 Tabstop {
10076 is_end_tabstop,
10077 ranges: tabstop_ranges,
10078 choices: tabstop.choices.clone(),
10079 }
10080 })
10081 .collect::<Vec<_>>()
10082 });
10083 if let Some(tabstop) = tabstops.first() {
10084 self.change_selections(Default::default(), window, cx, |s| {
10085 // Reverse order so that the first range is the newest created selection.
10086 // Completions will use it and autoscroll will prioritize it.
10087 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10088 });
10089
10090 if let Some(choices) = &tabstop.choices
10091 && let Some(selection) = tabstop.ranges.first()
10092 {
10093 self.show_snippet_choices(choices, selection.clone(), cx)
10094 }
10095
10096 // If we're already at the last tabstop and it's at the end of the snippet,
10097 // we're done, we don't need to keep the state around.
10098 if !tabstop.is_end_tabstop {
10099 let choices = tabstops
10100 .iter()
10101 .map(|tabstop| tabstop.choices.clone())
10102 .collect();
10103
10104 let ranges = tabstops
10105 .into_iter()
10106 .map(|tabstop| tabstop.ranges)
10107 .collect::<Vec<_>>();
10108
10109 self.snippet_stack.push(SnippetState {
10110 active_index: 0,
10111 ranges,
10112 choices,
10113 });
10114 }
10115
10116 // Check whether the just-entered snippet ends with an auto-closable bracket.
10117 if self.autoclose_regions.is_empty() {
10118 let snapshot = self.buffer.read(cx).snapshot(cx);
10119 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10120 let selection_head = selection.head();
10121 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10122 continue;
10123 };
10124
10125 let mut bracket_pair = None;
10126 let max_lookup_length = scope
10127 .brackets()
10128 .map(|(pair, _)| {
10129 pair.start
10130 .as_str()
10131 .chars()
10132 .count()
10133 .max(pair.end.as_str().chars().count())
10134 })
10135 .max();
10136 if let Some(max_lookup_length) = max_lookup_length {
10137 let next_text = snapshot
10138 .chars_at(selection_head)
10139 .take(max_lookup_length)
10140 .collect::<String>();
10141 let prev_text = snapshot
10142 .reversed_chars_at(selection_head)
10143 .take(max_lookup_length)
10144 .collect::<String>();
10145
10146 for (pair, enabled) in scope.brackets() {
10147 if enabled
10148 && pair.close
10149 && prev_text.starts_with(pair.start.as_str())
10150 && next_text.starts_with(pair.end.as_str())
10151 {
10152 bracket_pair = Some(pair.clone());
10153 break;
10154 }
10155 }
10156 }
10157
10158 if let Some(pair) = bracket_pair {
10159 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10160 let autoclose_enabled =
10161 self.use_autoclose && snapshot_settings.use_autoclose;
10162 if autoclose_enabled {
10163 let start = snapshot.anchor_after(selection_head);
10164 let end = snapshot.anchor_after(selection_head);
10165 self.autoclose_regions.push(AutocloseRegion {
10166 selection_id: selection.id,
10167 range: start..end,
10168 pair,
10169 });
10170 }
10171 }
10172 }
10173 }
10174 }
10175 Ok(())
10176 }
10177
10178 pub fn move_to_next_snippet_tabstop(
10179 &mut self,
10180 window: &mut Window,
10181 cx: &mut Context<Self>,
10182 ) -> bool {
10183 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10184 }
10185
10186 pub fn move_to_prev_snippet_tabstop(
10187 &mut self,
10188 window: &mut Window,
10189 cx: &mut Context<Self>,
10190 ) -> bool {
10191 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10192 }
10193
10194 pub fn move_to_snippet_tabstop(
10195 &mut self,
10196 bias: Bias,
10197 window: &mut Window,
10198 cx: &mut Context<Self>,
10199 ) -> bool {
10200 if let Some(mut snippet) = self.snippet_stack.pop() {
10201 match bias {
10202 Bias::Left => {
10203 if snippet.active_index > 0 {
10204 snippet.active_index -= 1;
10205 } else {
10206 self.snippet_stack.push(snippet);
10207 return false;
10208 }
10209 }
10210 Bias::Right => {
10211 if snippet.active_index + 1 < snippet.ranges.len() {
10212 snippet.active_index += 1;
10213 } else {
10214 self.snippet_stack.push(snippet);
10215 return false;
10216 }
10217 }
10218 }
10219 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10220 self.change_selections(Default::default(), window, cx, |s| {
10221 // Reverse order so that the first range is the newest created selection.
10222 // Completions will use it and autoscroll will prioritize it.
10223 s.select_ranges(current_ranges.iter().rev().cloned())
10224 });
10225
10226 if let Some(choices) = &snippet.choices[snippet.active_index]
10227 && let Some(selection) = current_ranges.first()
10228 {
10229 self.show_snippet_choices(choices, selection.clone(), cx);
10230 }
10231
10232 // If snippet state is not at the last tabstop, push it back on the stack
10233 if snippet.active_index + 1 < snippet.ranges.len() {
10234 self.snippet_stack.push(snippet);
10235 }
10236 return true;
10237 }
10238 }
10239
10240 false
10241 }
10242
10243 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10244 self.transact(window, cx, |this, window, cx| {
10245 this.select_all(&SelectAll, window, cx);
10246 this.insert("", window, cx);
10247 });
10248 }
10249
10250 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10251 if self.read_only(cx) {
10252 return;
10253 }
10254 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10255 self.transact(window, cx, |this, window, cx| {
10256 this.select_autoclose_pair(window, cx);
10257
10258 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10259
10260 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10261 if !this.linked_edit_ranges.is_empty() {
10262 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10263 let snapshot = this.buffer.read(cx).snapshot(cx);
10264
10265 for selection in selections.iter() {
10266 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10267 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10268 if selection_start.buffer_id != selection_end.buffer_id {
10269 continue;
10270 }
10271 if let Some(ranges) =
10272 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10273 {
10274 for (buffer, entries) in ranges {
10275 linked_ranges.entry(buffer).or_default().extend(entries);
10276 }
10277 }
10278 }
10279 }
10280
10281 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10282 for selection in &mut selections {
10283 if selection.is_empty() {
10284 let old_head = selection.head();
10285 let mut new_head =
10286 movement::left(&display_map, old_head.to_display_point(&display_map))
10287 .to_point(&display_map);
10288 if let Some((buffer, line_buffer_range)) = display_map
10289 .buffer_snapshot()
10290 .buffer_line_for_row(MultiBufferRow(old_head.row))
10291 {
10292 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10293 let indent_len = match indent_size.kind {
10294 IndentKind::Space => {
10295 buffer.settings_at(line_buffer_range.start, cx).tab_size
10296 }
10297 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10298 };
10299 if old_head.column <= indent_size.len && old_head.column > 0 {
10300 let indent_len = indent_len.get();
10301 new_head = cmp::min(
10302 new_head,
10303 MultiBufferPoint::new(
10304 old_head.row,
10305 ((old_head.column - 1) / indent_len) * indent_len,
10306 ),
10307 );
10308 }
10309 }
10310
10311 selection.set_head(new_head, SelectionGoal::None);
10312 }
10313 }
10314
10315 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10316 this.insert("", window, cx);
10317 let empty_str: Arc<str> = Arc::from("");
10318 for (buffer, edits) in linked_ranges {
10319 let snapshot = buffer.read(cx).snapshot();
10320 use text::ToPoint as TP;
10321
10322 let edits = edits
10323 .into_iter()
10324 .map(|range| {
10325 let end_point = TP::to_point(&range.end, &snapshot);
10326 let mut start_point = TP::to_point(&range.start, &snapshot);
10327
10328 if end_point == start_point {
10329 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10330 .saturating_sub(1);
10331 start_point =
10332 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10333 };
10334
10335 (start_point..end_point, empty_str.clone())
10336 })
10337 .sorted_by_key(|(range, _)| range.start)
10338 .collect::<Vec<_>>();
10339 buffer.update(cx, |this, cx| {
10340 this.edit(edits, None, cx);
10341 })
10342 }
10343 this.refresh_edit_prediction(true, false, window, cx);
10344 refresh_linked_ranges(this, window, cx);
10345 });
10346 }
10347
10348 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10349 if self.read_only(cx) {
10350 return;
10351 }
10352 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10353 self.transact(window, cx, |this, window, cx| {
10354 this.change_selections(Default::default(), window, cx, |s| {
10355 s.move_with(|map, selection| {
10356 if selection.is_empty() {
10357 let cursor = movement::right(map, selection.head());
10358 selection.end = cursor;
10359 selection.reversed = true;
10360 selection.goal = SelectionGoal::None;
10361 }
10362 })
10363 });
10364 this.insert("", window, cx);
10365 this.refresh_edit_prediction(true, false, window, cx);
10366 });
10367 }
10368
10369 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10370 if self.mode.is_single_line() {
10371 cx.propagate();
10372 return;
10373 }
10374
10375 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10376 if self.move_to_prev_snippet_tabstop(window, cx) {
10377 return;
10378 }
10379 self.outdent(&Outdent, window, cx);
10380 }
10381
10382 pub fn next_snippet_tabstop(
10383 &mut self,
10384 _: &NextSnippetTabstop,
10385 window: &mut Window,
10386 cx: &mut Context<Self>,
10387 ) {
10388 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10389 cx.propagate();
10390 return;
10391 }
10392
10393 if self.move_to_next_snippet_tabstop(window, cx) {
10394 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10395 return;
10396 }
10397 cx.propagate();
10398 }
10399
10400 pub fn previous_snippet_tabstop(
10401 &mut self,
10402 _: &PreviousSnippetTabstop,
10403 window: &mut Window,
10404 cx: &mut Context<Self>,
10405 ) {
10406 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10407 cx.propagate();
10408 return;
10409 }
10410
10411 if self.move_to_prev_snippet_tabstop(window, cx) {
10412 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10413 return;
10414 }
10415 cx.propagate();
10416 }
10417
10418 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10419 if self.mode.is_single_line() {
10420 cx.propagate();
10421 return;
10422 }
10423
10424 if self.move_to_next_snippet_tabstop(window, cx) {
10425 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10426 return;
10427 }
10428 if self.read_only(cx) {
10429 return;
10430 }
10431 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10432 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10433 let buffer = self.buffer.read(cx);
10434 let snapshot = buffer.snapshot(cx);
10435 let rows_iter = selections.iter().map(|s| s.head().row);
10436 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10437
10438 let has_some_cursor_in_whitespace = selections
10439 .iter()
10440 .filter(|selection| selection.is_empty())
10441 .any(|selection| {
10442 let cursor = selection.head();
10443 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10444 cursor.column < current_indent.len
10445 });
10446
10447 let mut edits = Vec::new();
10448 let mut prev_edited_row = 0;
10449 let mut row_delta = 0;
10450 for selection in &mut selections {
10451 if selection.start.row != prev_edited_row {
10452 row_delta = 0;
10453 }
10454 prev_edited_row = selection.end.row;
10455
10456 // If cursor is after a list prefix, make selection non-empty to trigger line indent
10457 if selection.is_empty() {
10458 let cursor = selection.head();
10459 let settings = buffer.language_settings_at(cursor, cx);
10460 if settings.indent_list_on_tab {
10461 if let Some(language) = snapshot.language_scope_at(Point::new(cursor.row, 0)) {
10462 if is_list_prefix_row(MultiBufferRow(cursor.row), &snapshot, &language) {
10463 row_delta = Self::indent_selection(
10464 buffer, &snapshot, selection, &mut edits, row_delta, cx,
10465 );
10466 continue;
10467 }
10468 }
10469 }
10470 }
10471
10472 // If the selection is non-empty, then increase the indentation of the selected lines.
10473 if !selection.is_empty() {
10474 row_delta =
10475 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10476 continue;
10477 }
10478
10479 let cursor = selection.head();
10480 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10481 if let Some(suggested_indent) =
10482 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10483 {
10484 // Don't do anything if already at suggested indent
10485 // and there is any other cursor which is not
10486 if has_some_cursor_in_whitespace
10487 && cursor.column == current_indent.len
10488 && current_indent.len == suggested_indent.len
10489 {
10490 continue;
10491 }
10492
10493 // Adjust line and move cursor to suggested indent
10494 // if cursor is not at suggested indent
10495 if cursor.column < suggested_indent.len
10496 && cursor.column <= current_indent.len
10497 && current_indent.len <= suggested_indent.len
10498 {
10499 selection.start = Point::new(cursor.row, suggested_indent.len);
10500 selection.end = selection.start;
10501 if row_delta == 0 {
10502 edits.extend(Buffer::edit_for_indent_size_adjustment(
10503 cursor.row,
10504 current_indent,
10505 suggested_indent,
10506 ));
10507 row_delta = suggested_indent.len - current_indent.len;
10508 }
10509 continue;
10510 }
10511
10512 // If current indent is more than suggested indent
10513 // only move cursor to current indent and skip indent
10514 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10515 selection.start = Point::new(cursor.row, current_indent.len);
10516 selection.end = selection.start;
10517 continue;
10518 }
10519 }
10520
10521 // Otherwise, insert a hard or soft tab.
10522 let settings = buffer.language_settings_at(cursor, cx);
10523 let tab_size = if settings.hard_tabs {
10524 IndentSize::tab()
10525 } else {
10526 let tab_size = settings.tab_size.get();
10527 let indent_remainder = snapshot
10528 .text_for_range(Point::new(cursor.row, 0)..cursor)
10529 .flat_map(str::chars)
10530 .fold(row_delta % tab_size, |counter: u32, c| {
10531 if c == '\t' {
10532 0
10533 } else {
10534 (counter + 1) % tab_size
10535 }
10536 });
10537
10538 let chars_to_next_tab_stop = tab_size - indent_remainder;
10539 IndentSize::spaces(chars_to_next_tab_stop)
10540 };
10541 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10542 selection.end = selection.start;
10543 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10544 row_delta += tab_size.len;
10545 }
10546
10547 self.transact(window, cx, |this, window, cx| {
10548 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10549 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10550 this.refresh_edit_prediction(true, false, window, cx);
10551 });
10552 }
10553
10554 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10555 if self.read_only(cx) {
10556 return;
10557 }
10558 if self.mode.is_single_line() {
10559 cx.propagate();
10560 return;
10561 }
10562
10563 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10564 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10565 let mut prev_edited_row = 0;
10566 let mut row_delta = 0;
10567 let mut edits = Vec::new();
10568 let buffer = self.buffer.read(cx);
10569 let snapshot = buffer.snapshot(cx);
10570 for selection in &mut selections {
10571 if selection.start.row != prev_edited_row {
10572 row_delta = 0;
10573 }
10574 prev_edited_row = selection.end.row;
10575
10576 row_delta =
10577 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10578 }
10579
10580 self.transact(window, cx, |this, window, cx| {
10581 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10582 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10583 });
10584 }
10585
10586 fn indent_selection(
10587 buffer: &MultiBuffer,
10588 snapshot: &MultiBufferSnapshot,
10589 selection: &mut Selection<Point>,
10590 edits: &mut Vec<(Range<Point>, String)>,
10591 delta_for_start_row: u32,
10592 cx: &App,
10593 ) -> u32 {
10594 let settings = buffer.language_settings_at(selection.start, cx);
10595 let tab_size = settings.tab_size.get();
10596 let indent_kind = if settings.hard_tabs {
10597 IndentKind::Tab
10598 } else {
10599 IndentKind::Space
10600 };
10601 let mut start_row = selection.start.row;
10602 let mut end_row = selection.end.row + 1;
10603
10604 // If a selection ends at the beginning of a line, don't indent
10605 // that last line.
10606 if selection.end.column == 0 && selection.end.row > selection.start.row {
10607 end_row -= 1;
10608 }
10609
10610 // Avoid re-indenting a row that has already been indented by a
10611 // previous selection, but still update this selection's column
10612 // to reflect that indentation.
10613 if delta_for_start_row > 0 {
10614 start_row += 1;
10615 selection.start.column += delta_for_start_row;
10616 if selection.end.row == selection.start.row {
10617 selection.end.column += delta_for_start_row;
10618 }
10619 }
10620
10621 let mut delta_for_end_row = 0;
10622 let has_multiple_rows = start_row + 1 != end_row;
10623 for row in start_row..end_row {
10624 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10625 let indent_delta = match (current_indent.kind, indent_kind) {
10626 (IndentKind::Space, IndentKind::Space) => {
10627 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10628 IndentSize::spaces(columns_to_next_tab_stop)
10629 }
10630 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10631 (_, IndentKind::Tab) => IndentSize::tab(),
10632 };
10633
10634 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10635 0
10636 } else {
10637 selection.start.column
10638 };
10639 let row_start = Point::new(row, start);
10640 edits.push((
10641 row_start..row_start,
10642 indent_delta.chars().collect::<String>(),
10643 ));
10644
10645 // Update this selection's endpoints to reflect the indentation.
10646 if row == selection.start.row {
10647 selection.start.column += indent_delta.len;
10648 }
10649 if row == selection.end.row {
10650 selection.end.column += indent_delta.len;
10651 delta_for_end_row = indent_delta.len;
10652 }
10653 }
10654
10655 if selection.start.row == selection.end.row {
10656 delta_for_start_row + delta_for_end_row
10657 } else {
10658 delta_for_end_row
10659 }
10660 }
10661
10662 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10663 if self.read_only(cx) {
10664 return;
10665 }
10666 if self.mode.is_single_line() {
10667 cx.propagate();
10668 return;
10669 }
10670
10671 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10672 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10673 let selections = self.selections.all::<Point>(&display_map);
10674 let mut deletion_ranges = Vec::new();
10675 let mut last_outdent = None;
10676 {
10677 let buffer = self.buffer.read(cx);
10678 let snapshot = buffer.snapshot(cx);
10679 for selection in &selections {
10680 let settings = buffer.language_settings_at(selection.start, cx);
10681 let tab_size = settings.tab_size.get();
10682 let mut rows = selection.spanned_rows(false, &display_map);
10683
10684 // Avoid re-outdenting a row that has already been outdented by a
10685 // previous selection.
10686 if let Some(last_row) = last_outdent
10687 && last_row == rows.start
10688 {
10689 rows.start = rows.start.next_row();
10690 }
10691 let has_multiple_rows = rows.len() > 1;
10692 for row in rows.iter_rows() {
10693 let indent_size = snapshot.indent_size_for_line(row);
10694 if indent_size.len > 0 {
10695 let deletion_len = match indent_size.kind {
10696 IndentKind::Space => {
10697 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10698 if columns_to_prev_tab_stop == 0 {
10699 tab_size
10700 } else {
10701 columns_to_prev_tab_stop
10702 }
10703 }
10704 IndentKind::Tab => 1,
10705 };
10706 let start = if has_multiple_rows
10707 || deletion_len > selection.start.column
10708 || indent_size.len < selection.start.column
10709 {
10710 0
10711 } else {
10712 selection.start.column - deletion_len
10713 };
10714 deletion_ranges.push(
10715 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10716 );
10717 last_outdent = Some(row);
10718 }
10719 }
10720 }
10721 }
10722
10723 self.transact(window, cx, |this, window, cx| {
10724 this.buffer.update(cx, |buffer, cx| {
10725 let empty_str: Arc<str> = Arc::default();
10726 buffer.edit(
10727 deletion_ranges
10728 .into_iter()
10729 .map(|range| (range, empty_str.clone())),
10730 None,
10731 cx,
10732 );
10733 });
10734 let selections = this
10735 .selections
10736 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10737 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10738 });
10739 }
10740
10741 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10742 if self.read_only(cx) {
10743 return;
10744 }
10745 if self.mode.is_single_line() {
10746 cx.propagate();
10747 return;
10748 }
10749
10750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10751 let selections = self
10752 .selections
10753 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10754 .into_iter()
10755 .map(|s| s.range());
10756
10757 self.transact(window, cx, |this, window, cx| {
10758 this.buffer.update(cx, |buffer, cx| {
10759 buffer.autoindent_ranges(selections, cx);
10760 });
10761 let selections = this
10762 .selections
10763 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10764 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10765 });
10766 }
10767
10768 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10769 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10770 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10771 let selections = self.selections.all::<Point>(&display_map);
10772
10773 let mut new_cursors = Vec::new();
10774 let mut edit_ranges = Vec::new();
10775 let mut selections = selections.iter().peekable();
10776 while let Some(selection) = selections.next() {
10777 let mut rows = selection.spanned_rows(false, &display_map);
10778
10779 // Accumulate contiguous regions of rows that we want to delete.
10780 while let Some(next_selection) = selections.peek() {
10781 let next_rows = next_selection.spanned_rows(false, &display_map);
10782 if next_rows.start <= rows.end {
10783 rows.end = next_rows.end;
10784 selections.next().unwrap();
10785 } else {
10786 break;
10787 }
10788 }
10789
10790 let buffer = display_map.buffer_snapshot();
10791 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10792 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10793 // If there's a line after the range, delete the \n from the end of the row range
10794 (
10795 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10796 rows.end,
10797 )
10798 } else {
10799 // If there isn't a line after the range, delete the \n from the line before the
10800 // start of the row range
10801 edit_start = edit_start.saturating_sub_usize(1);
10802 (buffer.len(), rows.start.previous_row())
10803 };
10804
10805 let text_layout_details = self.text_layout_details(window);
10806 let x = display_map.x_for_display_point(
10807 selection.head().to_display_point(&display_map),
10808 &text_layout_details,
10809 );
10810 let row = Point::new(target_row.0, 0)
10811 .to_display_point(&display_map)
10812 .row();
10813 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10814
10815 new_cursors.push((
10816 selection.id,
10817 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10818 SelectionGoal::None,
10819 ));
10820 edit_ranges.push(edit_start..edit_end);
10821 }
10822
10823 self.transact(window, cx, |this, window, cx| {
10824 let buffer = this.buffer.update(cx, |buffer, cx| {
10825 let empty_str: Arc<str> = Arc::default();
10826 buffer.edit(
10827 edit_ranges
10828 .into_iter()
10829 .map(|range| (range, empty_str.clone())),
10830 None,
10831 cx,
10832 );
10833 buffer.snapshot(cx)
10834 });
10835 let new_selections = new_cursors
10836 .into_iter()
10837 .map(|(id, cursor, goal)| {
10838 let cursor = cursor.to_point(&buffer);
10839 Selection {
10840 id,
10841 start: cursor,
10842 end: cursor,
10843 reversed: false,
10844 goal,
10845 }
10846 })
10847 .collect();
10848
10849 this.change_selections(Default::default(), window, cx, |s| {
10850 s.select(new_selections);
10851 });
10852 });
10853 }
10854
10855 pub fn join_lines_impl(
10856 &mut self,
10857 insert_whitespace: bool,
10858 window: &mut Window,
10859 cx: &mut Context<Self>,
10860 ) {
10861 if self.read_only(cx) {
10862 return;
10863 }
10864 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10865 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10866 let start = MultiBufferRow(selection.start.row);
10867 // Treat single line selections as if they include the next line. Otherwise this action
10868 // would do nothing for single line selections individual cursors.
10869 let end = if selection.start.row == selection.end.row {
10870 MultiBufferRow(selection.start.row + 1)
10871 } else {
10872 MultiBufferRow(selection.end.row)
10873 };
10874
10875 if let Some(last_row_range) = row_ranges.last_mut()
10876 && start <= last_row_range.end
10877 {
10878 last_row_range.end = end;
10879 continue;
10880 }
10881 row_ranges.push(start..end);
10882 }
10883
10884 let snapshot = self.buffer.read(cx).snapshot(cx);
10885 let mut cursor_positions = Vec::new();
10886 for row_range in &row_ranges {
10887 let anchor = snapshot.anchor_before(Point::new(
10888 row_range.end.previous_row().0,
10889 snapshot.line_len(row_range.end.previous_row()),
10890 ));
10891 cursor_positions.push(anchor..anchor);
10892 }
10893
10894 self.transact(window, cx, |this, window, cx| {
10895 for row_range in row_ranges.into_iter().rev() {
10896 for row in row_range.iter_rows().rev() {
10897 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10898 let next_line_row = row.next_row();
10899 let indent = snapshot.indent_size_for_line(next_line_row);
10900 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10901
10902 let replace =
10903 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10904 " "
10905 } else {
10906 ""
10907 };
10908
10909 this.buffer.update(cx, |buffer, cx| {
10910 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10911 });
10912 }
10913 }
10914
10915 this.change_selections(Default::default(), window, cx, |s| {
10916 s.select_anchor_ranges(cursor_positions)
10917 });
10918 });
10919 }
10920
10921 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10922 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10923 self.join_lines_impl(true, window, cx);
10924 }
10925
10926 pub fn sort_lines_case_sensitive(
10927 &mut self,
10928 _: &SortLinesCaseSensitive,
10929 window: &mut Window,
10930 cx: &mut Context<Self>,
10931 ) {
10932 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10933 }
10934
10935 pub fn sort_lines_by_length(
10936 &mut self,
10937 _: &SortLinesByLength,
10938 window: &mut Window,
10939 cx: &mut Context<Self>,
10940 ) {
10941 self.manipulate_immutable_lines(window, cx, |lines| {
10942 lines.sort_by_key(|&line| line.chars().count())
10943 })
10944 }
10945
10946 pub fn sort_lines_case_insensitive(
10947 &mut self,
10948 _: &SortLinesCaseInsensitive,
10949 window: &mut Window,
10950 cx: &mut Context<Self>,
10951 ) {
10952 self.manipulate_immutable_lines(window, cx, |lines| {
10953 lines.sort_by_key(|line| line.to_lowercase())
10954 })
10955 }
10956
10957 pub fn unique_lines_case_insensitive(
10958 &mut self,
10959 _: &UniqueLinesCaseInsensitive,
10960 window: &mut Window,
10961 cx: &mut Context<Self>,
10962 ) {
10963 self.manipulate_immutable_lines(window, cx, |lines| {
10964 let mut seen = HashSet::default();
10965 lines.retain(|line| seen.insert(line.to_lowercase()));
10966 })
10967 }
10968
10969 pub fn unique_lines_case_sensitive(
10970 &mut self,
10971 _: &UniqueLinesCaseSensitive,
10972 window: &mut Window,
10973 cx: &mut Context<Self>,
10974 ) {
10975 self.manipulate_immutable_lines(window, cx, |lines| {
10976 let mut seen = HashSet::default();
10977 lines.retain(|line| seen.insert(*line));
10978 })
10979 }
10980
10981 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10982 let snapshot = self.buffer.read(cx).snapshot(cx);
10983 for selection in self.selections.disjoint_anchors_arc().iter() {
10984 if snapshot
10985 .language_at(selection.start)
10986 .and_then(|lang| lang.config().wrap_characters.as_ref())
10987 .is_some()
10988 {
10989 return true;
10990 }
10991 }
10992 false
10993 }
10994
10995 fn wrap_selections_in_tag(
10996 &mut self,
10997 _: &WrapSelectionsInTag,
10998 window: &mut Window,
10999 cx: &mut Context<Self>,
11000 ) {
11001 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11002
11003 let snapshot = self.buffer.read(cx).snapshot(cx);
11004
11005 let mut edits = Vec::new();
11006 let mut boundaries = Vec::new();
11007
11008 for selection in self
11009 .selections
11010 .all_adjusted(&self.display_snapshot(cx))
11011 .iter()
11012 {
11013 let Some(wrap_config) = snapshot
11014 .language_at(selection.start)
11015 .and_then(|lang| lang.config().wrap_characters.clone())
11016 else {
11017 continue;
11018 };
11019
11020 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11021 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11022
11023 let start_before = snapshot.anchor_before(selection.start);
11024 let end_after = snapshot.anchor_after(selection.end);
11025
11026 edits.push((start_before..start_before, open_tag));
11027 edits.push((end_after..end_after, close_tag));
11028
11029 boundaries.push((
11030 start_before,
11031 end_after,
11032 wrap_config.start_prefix.len(),
11033 wrap_config.end_suffix.len(),
11034 ));
11035 }
11036
11037 if edits.is_empty() {
11038 return;
11039 }
11040
11041 self.transact(window, cx, |this, window, cx| {
11042 let buffer = this.buffer.update(cx, |buffer, cx| {
11043 buffer.edit(edits, None, cx);
11044 buffer.snapshot(cx)
11045 });
11046
11047 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11048 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11049 boundaries.into_iter()
11050 {
11051 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11052 let close_offset = end_after
11053 .to_offset(&buffer)
11054 .saturating_sub_usize(end_suffix_len);
11055 new_selections.push(open_offset..open_offset);
11056 new_selections.push(close_offset..close_offset);
11057 }
11058
11059 this.change_selections(Default::default(), window, cx, |s| {
11060 s.select_ranges(new_selections);
11061 });
11062
11063 this.request_autoscroll(Autoscroll::fit(), cx);
11064 });
11065 }
11066
11067 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11068 let Some(project) = self.project.clone() else {
11069 return;
11070 };
11071 self.reload(project, window, cx)
11072 .detach_and_notify_err(window, cx);
11073 }
11074
11075 pub fn restore_file(
11076 &mut self,
11077 _: &::git::RestoreFile,
11078 window: &mut Window,
11079 cx: &mut Context<Self>,
11080 ) {
11081 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11082 let mut buffer_ids = HashSet::default();
11083 let snapshot = self.buffer().read(cx).snapshot(cx);
11084 for selection in self
11085 .selections
11086 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11087 {
11088 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11089 }
11090
11091 let buffer = self.buffer().read(cx);
11092 let ranges = buffer_ids
11093 .into_iter()
11094 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11095 .collect::<Vec<_>>();
11096
11097 self.restore_hunks_in_ranges(ranges, window, cx);
11098 }
11099
11100 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11101 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11102 let selections = self
11103 .selections
11104 .all(&self.display_snapshot(cx))
11105 .into_iter()
11106 .map(|s| s.range())
11107 .collect();
11108 self.restore_hunks_in_ranges(selections, window, cx);
11109 }
11110
11111 pub fn restore_hunks_in_ranges(
11112 &mut self,
11113 ranges: Vec<Range<Point>>,
11114 window: &mut Window,
11115 cx: &mut Context<Editor>,
11116 ) {
11117 let mut revert_changes = HashMap::default();
11118 let chunk_by = self
11119 .snapshot(window, cx)
11120 .hunks_for_ranges(ranges)
11121 .into_iter()
11122 .chunk_by(|hunk| hunk.buffer_id);
11123 for (buffer_id, hunks) in &chunk_by {
11124 let hunks = hunks.collect::<Vec<_>>();
11125 for hunk in &hunks {
11126 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11127 }
11128 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11129 }
11130 drop(chunk_by);
11131 if !revert_changes.is_empty() {
11132 self.transact(window, cx, |editor, window, cx| {
11133 editor.restore(revert_changes, window, cx);
11134 });
11135 }
11136 }
11137
11138 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11139 if let Some(status) = self
11140 .addons
11141 .iter()
11142 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11143 {
11144 return Some(status);
11145 }
11146 self.project
11147 .as_ref()?
11148 .read(cx)
11149 .status_for_buffer_id(buffer_id, cx)
11150 }
11151
11152 pub fn open_active_item_in_terminal(
11153 &mut self,
11154 _: &OpenInTerminal,
11155 window: &mut Window,
11156 cx: &mut Context<Self>,
11157 ) {
11158 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11159 let project_path = buffer.read(cx).project_path(cx)?;
11160 let project = self.project()?.read(cx);
11161 let entry = project.entry_for_path(&project_path, cx)?;
11162 let parent = match &entry.canonical_path {
11163 Some(canonical_path) => canonical_path.to_path_buf(),
11164 None => project.absolute_path(&project_path, cx)?,
11165 }
11166 .parent()?
11167 .to_path_buf();
11168 Some(parent)
11169 }) {
11170 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11171 }
11172 }
11173
11174 fn set_breakpoint_context_menu(
11175 &mut self,
11176 display_row: DisplayRow,
11177 position: Option<Anchor>,
11178 clicked_point: gpui::Point<Pixels>,
11179 window: &mut Window,
11180 cx: &mut Context<Self>,
11181 ) {
11182 let source = self
11183 .buffer
11184 .read(cx)
11185 .snapshot(cx)
11186 .anchor_before(Point::new(display_row.0, 0u32));
11187
11188 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11189
11190 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11191 self,
11192 source,
11193 clicked_point,
11194 context_menu,
11195 window,
11196 cx,
11197 );
11198 }
11199
11200 fn add_edit_breakpoint_block(
11201 &mut self,
11202 anchor: Anchor,
11203 breakpoint: &Breakpoint,
11204 edit_action: BreakpointPromptEditAction,
11205 window: &mut Window,
11206 cx: &mut Context<Self>,
11207 ) {
11208 let weak_editor = cx.weak_entity();
11209 let bp_prompt = cx.new(|cx| {
11210 BreakpointPromptEditor::new(
11211 weak_editor,
11212 anchor,
11213 breakpoint.clone(),
11214 edit_action,
11215 window,
11216 cx,
11217 )
11218 });
11219
11220 let height = bp_prompt.update(cx, |this, cx| {
11221 this.prompt
11222 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11223 });
11224 let cloned_prompt = bp_prompt.clone();
11225 let blocks = vec![BlockProperties {
11226 style: BlockStyle::Sticky,
11227 placement: BlockPlacement::Above(anchor),
11228 height: Some(height),
11229 render: Arc::new(move |cx| {
11230 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11231 cloned_prompt.clone().into_any_element()
11232 }),
11233 priority: 0,
11234 }];
11235
11236 let focus_handle = bp_prompt.focus_handle(cx);
11237 window.focus(&focus_handle, cx);
11238
11239 let block_ids = self.insert_blocks(blocks, None, cx);
11240 bp_prompt.update(cx, |prompt, _| {
11241 prompt.add_block_ids(block_ids);
11242 });
11243 }
11244
11245 pub(crate) fn breakpoint_at_row(
11246 &self,
11247 row: u32,
11248 window: &mut Window,
11249 cx: &mut Context<Self>,
11250 ) -> Option<(Anchor, Breakpoint)> {
11251 let snapshot = self.snapshot(window, cx);
11252 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11253
11254 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11255 }
11256
11257 pub(crate) fn breakpoint_at_anchor(
11258 &self,
11259 breakpoint_position: Anchor,
11260 snapshot: &EditorSnapshot,
11261 cx: &mut Context<Self>,
11262 ) -> Option<(Anchor, Breakpoint)> {
11263 let buffer = self
11264 .buffer
11265 .read(cx)
11266 .buffer_for_anchor(breakpoint_position, cx)?;
11267
11268 let enclosing_excerpt = breakpoint_position.excerpt_id;
11269 let buffer_snapshot = buffer.read(cx).snapshot();
11270
11271 let row = buffer_snapshot
11272 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11273 .row;
11274
11275 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11276 let anchor_end = snapshot
11277 .buffer_snapshot()
11278 .anchor_after(Point::new(row, line_len));
11279
11280 self.breakpoint_store
11281 .as_ref()?
11282 .read_with(cx, |breakpoint_store, cx| {
11283 breakpoint_store
11284 .breakpoints(
11285 &buffer,
11286 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11287 &buffer_snapshot,
11288 cx,
11289 )
11290 .next()
11291 .and_then(|(bp, _)| {
11292 let breakpoint_row = buffer_snapshot
11293 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11294 .row;
11295
11296 if breakpoint_row == row {
11297 snapshot
11298 .buffer_snapshot()
11299 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11300 .map(|position| (position, bp.bp.clone()))
11301 } else {
11302 None
11303 }
11304 })
11305 })
11306 }
11307
11308 pub fn edit_log_breakpoint(
11309 &mut self,
11310 _: &EditLogBreakpoint,
11311 window: &mut Window,
11312 cx: &mut Context<Self>,
11313 ) {
11314 if self.breakpoint_store.is_none() {
11315 return;
11316 }
11317
11318 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11319 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11320 message: None,
11321 state: BreakpointState::Enabled,
11322 condition: None,
11323 hit_condition: None,
11324 });
11325
11326 self.add_edit_breakpoint_block(
11327 anchor,
11328 &breakpoint,
11329 BreakpointPromptEditAction::Log,
11330 window,
11331 cx,
11332 );
11333 }
11334 }
11335
11336 fn breakpoints_at_cursors(
11337 &self,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11341 let snapshot = self.snapshot(window, cx);
11342 let cursors = self
11343 .selections
11344 .disjoint_anchors_arc()
11345 .iter()
11346 .map(|selection| {
11347 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11348
11349 let breakpoint_position = self
11350 .breakpoint_at_row(cursor_position.row, window, cx)
11351 .map(|bp| bp.0)
11352 .unwrap_or_else(|| {
11353 snapshot
11354 .display_snapshot
11355 .buffer_snapshot()
11356 .anchor_after(Point::new(cursor_position.row, 0))
11357 });
11358
11359 let breakpoint = self
11360 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11361 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11362
11363 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11364 })
11365 // 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.
11366 .collect::<HashMap<Anchor, _>>();
11367
11368 cursors.into_iter().collect()
11369 }
11370
11371 pub fn enable_breakpoint(
11372 &mut self,
11373 _: &crate::actions::EnableBreakpoint,
11374 window: &mut Window,
11375 cx: &mut Context<Self>,
11376 ) {
11377 if self.breakpoint_store.is_none() {
11378 return;
11379 }
11380
11381 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11382 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11383 continue;
11384 };
11385 self.edit_breakpoint_at_anchor(
11386 anchor,
11387 breakpoint,
11388 BreakpointEditAction::InvertState,
11389 cx,
11390 );
11391 }
11392 }
11393
11394 pub fn disable_breakpoint(
11395 &mut self,
11396 _: &crate::actions::DisableBreakpoint,
11397 window: &mut Window,
11398 cx: &mut Context<Self>,
11399 ) {
11400 if self.breakpoint_store.is_none() {
11401 return;
11402 }
11403
11404 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11405 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11406 continue;
11407 };
11408 self.edit_breakpoint_at_anchor(
11409 anchor,
11410 breakpoint,
11411 BreakpointEditAction::InvertState,
11412 cx,
11413 );
11414 }
11415 }
11416
11417 pub fn toggle_breakpoint(
11418 &mut self,
11419 _: &crate::actions::ToggleBreakpoint,
11420 window: &mut Window,
11421 cx: &mut Context<Self>,
11422 ) {
11423 if self.breakpoint_store.is_none() {
11424 return;
11425 }
11426
11427 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11428 if let Some(breakpoint) = breakpoint {
11429 self.edit_breakpoint_at_anchor(
11430 anchor,
11431 breakpoint,
11432 BreakpointEditAction::Toggle,
11433 cx,
11434 );
11435 } else {
11436 self.edit_breakpoint_at_anchor(
11437 anchor,
11438 Breakpoint::new_standard(),
11439 BreakpointEditAction::Toggle,
11440 cx,
11441 );
11442 }
11443 }
11444 }
11445
11446 pub fn edit_breakpoint_at_anchor(
11447 &mut self,
11448 breakpoint_position: Anchor,
11449 breakpoint: Breakpoint,
11450 edit_action: BreakpointEditAction,
11451 cx: &mut Context<Self>,
11452 ) {
11453 let Some(breakpoint_store) = &self.breakpoint_store else {
11454 return;
11455 };
11456
11457 let Some(buffer) = self
11458 .buffer
11459 .read(cx)
11460 .buffer_for_anchor(breakpoint_position, cx)
11461 else {
11462 return;
11463 };
11464
11465 breakpoint_store.update(cx, |breakpoint_store, cx| {
11466 breakpoint_store.toggle_breakpoint(
11467 buffer,
11468 BreakpointWithPosition {
11469 position: breakpoint_position.text_anchor,
11470 bp: breakpoint,
11471 },
11472 edit_action,
11473 cx,
11474 );
11475 });
11476
11477 cx.notify();
11478 }
11479
11480 #[cfg(any(test, feature = "test-support"))]
11481 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11482 self.breakpoint_store.clone()
11483 }
11484
11485 pub fn prepare_restore_change(
11486 &self,
11487 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11488 hunk: &MultiBufferDiffHunk,
11489 cx: &mut App,
11490 ) -> Option<()> {
11491 if hunk.is_created_file() {
11492 return None;
11493 }
11494 let buffer = self.buffer.read(cx);
11495 let diff = buffer.diff_for(hunk.buffer_id)?;
11496 let buffer = buffer.buffer(hunk.buffer_id)?;
11497 let buffer = buffer.read(cx);
11498 let original_text = diff
11499 .read(cx)
11500 .base_text()
11501 .as_rope()
11502 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11503 let buffer_snapshot = buffer.snapshot();
11504 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11505 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11506 probe
11507 .0
11508 .start
11509 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11510 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11511 }) {
11512 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11513 Some(())
11514 } else {
11515 None
11516 }
11517 }
11518
11519 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11520 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11521 }
11522
11523 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11524 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11525 }
11526
11527 pub fn rotate_selections_forward(
11528 &mut self,
11529 _: &RotateSelectionsForward,
11530 window: &mut Window,
11531 cx: &mut Context<Self>,
11532 ) {
11533 self.rotate_selections(window, cx, false)
11534 }
11535
11536 pub fn rotate_selections_backward(
11537 &mut self,
11538 _: &RotateSelectionsBackward,
11539 window: &mut Window,
11540 cx: &mut Context<Self>,
11541 ) {
11542 self.rotate_selections(window, cx, true)
11543 }
11544
11545 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11546 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11547 let display_snapshot = self.display_snapshot(cx);
11548 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11549
11550 if selections.len() < 2 {
11551 return;
11552 }
11553
11554 let (edits, new_selections) = {
11555 let buffer = self.buffer.read(cx).read(cx);
11556 let has_selections = selections.iter().any(|s| !s.is_empty());
11557 if has_selections {
11558 let mut selected_texts: Vec<String> = selections
11559 .iter()
11560 .map(|selection| {
11561 buffer
11562 .text_for_range(selection.start..selection.end)
11563 .collect()
11564 })
11565 .collect();
11566
11567 if reverse {
11568 selected_texts.rotate_left(1);
11569 } else {
11570 selected_texts.rotate_right(1);
11571 }
11572
11573 let mut offset_delta: i64 = 0;
11574 let mut new_selections = Vec::new();
11575 let edits: Vec<_> = selections
11576 .iter()
11577 .zip(selected_texts.iter())
11578 .map(|(selection, new_text)| {
11579 let old_len = (selection.end.0 - selection.start.0) as i64;
11580 let new_len = new_text.len() as i64;
11581 let adjusted_start =
11582 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11583 let adjusted_end =
11584 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11585
11586 new_selections.push(Selection {
11587 id: selection.id,
11588 start: adjusted_start,
11589 end: adjusted_end,
11590 reversed: selection.reversed,
11591 goal: selection.goal,
11592 });
11593
11594 offset_delta += new_len - old_len;
11595 (selection.start..selection.end, new_text.clone())
11596 })
11597 .collect();
11598 (edits, new_selections)
11599 } else {
11600 let mut all_rows: Vec<u32> = selections
11601 .iter()
11602 .map(|selection| buffer.offset_to_point(selection.start).row)
11603 .collect();
11604 all_rows.sort_unstable();
11605 all_rows.dedup();
11606
11607 if all_rows.len() < 2 {
11608 return;
11609 }
11610
11611 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11612 .iter()
11613 .map(|&row| {
11614 let start = Point::new(row, 0);
11615 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11616 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11617 })
11618 .collect();
11619
11620 let mut line_texts: Vec<String> = line_ranges
11621 .iter()
11622 .map(|range| buffer.text_for_range(range.clone()).collect())
11623 .collect();
11624
11625 if reverse {
11626 line_texts.rotate_left(1);
11627 } else {
11628 line_texts.rotate_right(1);
11629 }
11630
11631 let edits = line_ranges
11632 .iter()
11633 .zip(line_texts.iter())
11634 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11635 .collect();
11636
11637 let num_rows = all_rows.len();
11638 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11639 .iter()
11640 .enumerate()
11641 .map(|(i, &row)| (row, i))
11642 .collect();
11643
11644 // Compute new line start offsets after rotation (handles CRLF)
11645 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11646 let first_line_start = line_ranges[0].start.0;
11647 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11648 for text in line_texts.iter().take(num_rows - 1) {
11649 let prev_start = *new_line_starts.last().unwrap();
11650 new_line_starts.push(prev_start + text.len() + newline_len);
11651 }
11652
11653 let new_selections = selections
11654 .iter()
11655 .map(|selection| {
11656 let point = buffer.offset_to_point(selection.start);
11657 let old_index = row_to_index[&point.row];
11658 let new_index = if reverse {
11659 (old_index + num_rows - 1) % num_rows
11660 } else {
11661 (old_index + 1) % num_rows
11662 };
11663 let new_offset =
11664 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11665 Selection {
11666 id: selection.id,
11667 start: new_offset,
11668 end: new_offset,
11669 reversed: selection.reversed,
11670 goal: selection.goal,
11671 }
11672 })
11673 .collect();
11674
11675 (edits, new_selections)
11676 }
11677 };
11678
11679 self.transact(window, cx, |this, window, cx| {
11680 this.buffer.update(cx, |buffer, cx| {
11681 buffer.edit(edits, None, cx);
11682 });
11683 this.change_selections(Default::default(), window, cx, |s| {
11684 s.select(new_selections);
11685 });
11686 });
11687 }
11688
11689 fn manipulate_lines<M>(
11690 &mut self,
11691 window: &mut Window,
11692 cx: &mut Context<Self>,
11693 mut manipulate: M,
11694 ) where
11695 M: FnMut(&str) -> LineManipulationResult,
11696 {
11697 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11698
11699 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11700 let buffer = self.buffer.read(cx).snapshot(cx);
11701
11702 let mut edits = Vec::new();
11703
11704 let selections = self.selections.all::<Point>(&display_map);
11705 let mut selections = selections.iter().peekable();
11706 let mut contiguous_row_selections = Vec::new();
11707 let mut new_selections = Vec::new();
11708 let mut added_lines = 0;
11709 let mut removed_lines = 0;
11710
11711 while let Some(selection) = selections.next() {
11712 let (start_row, end_row) = consume_contiguous_rows(
11713 &mut contiguous_row_selections,
11714 selection,
11715 &display_map,
11716 &mut selections,
11717 );
11718
11719 let start_point = Point::new(start_row.0, 0);
11720 let end_point = Point::new(
11721 end_row.previous_row().0,
11722 buffer.line_len(end_row.previous_row()),
11723 );
11724 let text = buffer
11725 .text_for_range(start_point..end_point)
11726 .collect::<String>();
11727
11728 let LineManipulationResult {
11729 new_text,
11730 line_count_before,
11731 line_count_after,
11732 } = manipulate(&text);
11733
11734 edits.push((start_point..end_point, new_text));
11735
11736 // Selections must change based on added and removed line count
11737 let start_row =
11738 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11739 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11740 new_selections.push(Selection {
11741 id: selection.id,
11742 start: start_row,
11743 end: end_row,
11744 goal: SelectionGoal::None,
11745 reversed: selection.reversed,
11746 });
11747
11748 if line_count_after > line_count_before {
11749 added_lines += line_count_after - line_count_before;
11750 } else if line_count_before > line_count_after {
11751 removed_lines += line_count_before - line_count_after;
11752 }
11753 }
11754
11755 self.transact(window, cx, |this, window, cx| {
11756 let buffer = this.buffer.update(cx, |buffer, cx| {
11757 buffer.edit(edits, None, cx);
11758 buffer.snapshot(cx)
11759 });
11760
11761 // Recalculate offsets on newly edited buffer
11762 let new_selections = new_selections
11763 .iter()
11764 .map(|s| {
11765 let start_point = Point::new(s.start.0, 0);
11766 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11767 Selection {
11768 id: s.id,
11769 start: buffer.point_to_offset(start_point),
11770 end: buffer.point_to_offset(end_point),
11771 goal: s.goal,
11772 reversed: s.reversed,
11773 }
11774 })
11775 .collect();
11776
11777 this.change_selections(Default::default(), window, cx, |s| {
11778 s.select(new_selections);
11779 });
11780
11781 this.request_autoscroll(Autoscroll::fit(), cx);
11782 });
11783 }
11784
11785 fn manipulate_immutable_lines<Fn>(
11786 &mut self,
11787 window: &mut Window,
11788 cx: &mut Context<Self>,
11789 mut callback: Fn,
11790 ) where
11791 Fn: FnMut(&mut Vec<&str>),
11792 {
11793 self.manipulate_lines(window, cx, |text| {
11794 let mut lines: Vec<&str> = text.split('\n').collect();
11795 let line_count_before = lines.len();
11796
11797 callback(&mut lines);
11798
11799 LineManipulationResult {
11800 new_text: lines.join("\n"),
11801 line_count_before,
11802 line_count_after: lines.len(),
11803 }
11804 });
11805 }
11806
11807 fn manipulate_mutable_lines<Fn>(
11808 &mut self,
11809 window: &mut Window,
11810 cx: &mut Context<Self>,
11811 mut callback: Fn,
11812 ) where
11813 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11814 {
11815 self.manipulate_lines(window, cx, |text| {
11816 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11817 let line_count_before = lines.len();
11818
11819 callback(&mut lines);
11820
11821 LineManipulationResult {
11822 new_text: lines.join("\n"),
11823 line_count_before,
11824 line_count_after: lines.len(),
11825 }
11826 });
11827 }
11828
11829 pub fn convert_indentation_to_spaces(
11830 &mut self,
11831 _: &ConvertIndentationToSpaces,
11832 window: &mut Window,
11833 cx: &mut Context<Self>,
11834 ) {
11835 let settings = self.buffer.read(cx).language_settings(cx);
11836 let tab_size = settings.tab_size.get() as usize;
11837
11838 self.manipulate_mutable_lines(window, cx, |lines| {
11839 // Allocates a reasonably sized scratch buffer once for the whole loop
11840 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11841 // Avoids recomputing spaces that could be inserted many times
11842 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11843 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11844 .collect();
11845
11846 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11847 let mut chars = line.as_ref().chars();
11848 let mut col = 0;
11849 let mut changed = false;
11850
11851 for ch in chars.by_ref() {
11852 match ch {
11853 ' ' => {
11854 reindented_line.push(' ');
11855 col += 1;
11856 }
11857 '\t' => {
11858 // \t are converted to spaces depending on the current column
11859 let spaces_len = tab_size - (col % tab_size);
11860 reindented_line.extend(&space_cache[spaces_len - 1]);
11861 col += spaces_len;
11862 changed = true;
11863 }
11864 _ => {
11865 // If we dont append before break, the character is consumed
11866 reindented_line.push(ch);
11867 break;
11868 }
11869 }
11870 }
11871
11872 if !changed {
11873 reindented_line.clear();
11874 continue;
11875 }
11876 // Append the rest of the line and replace old reference with new one
11877 reindented_line.extend(chars);
11878 *line = Cow::Owned(reindented_line.clone());
11879 reindented_line.clear();
11880 }
11881 });
11882 }
11883
11884 pub fn convert_indentation_to_tabs(
11885 &mut self,
11886 _: &ConvertIndentationToTabs,
11887 window: &mut Window,
11888 cx: &mut Context<Self>,
11889 ) {
11890 let settings = self.buffer.read(cx).language_settings(cx);
11891 let tab_size = settings.tab_size.get() as usize;
11892
11893 self.manipulate_mutable_lines(window, cx, |lines| {
11894 // Allocates a reasonably sized buffer once for the whole loop
11895 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11896 // Avoids recomputing spaces that could be inserted many times
11897 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11898 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11899 .collect();
11900
11901 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11902 let mut chars = line.chars();
11903 let mut spaces_count = 0;
11904 let mut first_non_indent_char = None;
11905 let mut changed = false;
11906
11907 for ch in chars.by_ref() {
11908 match ch {
11909 ' ' => {
11910 // Keep track of spaces. Append \t when we reach tab_size
11911 spaces_count += 1;
11912 changed = true;
11913 if spaces_count == tab_size {
11914 reindented_line.push('\t');
11915 spaces_count = 0;
11916 }
11917 }
11918 '\t' => {
11919 reindented_line.push('\t');
11920 spaces_count = 0;
11921 }
11922 _ => {
11923 // Dont append it yet, we might have remaining spaces
11924 first_non_indent_char = Some(ch);
11925 break;
11926 }
11927 }
11928 }
11929
11930 if !changed {
11931 reindented_line.clear();
11932 continue;
11933 }
11934 // Remaining spaces that didn't make a full tab stop
11935 if spaces_count > 0 {
11936 reindented_line.extend(&space_cache[spaces_count - 1]);
11937 }
11938 // If we consume an extra character that was not indentation, add it back
11939 if let Some(extra_char) = first_non_indent_char {
11940 reindented_line.push(extra_char);
11941 }
11942 // Append the rest of the line and replace old reference with new one
11943 reindented_line.extend(chars);
11944 *line = Cow::Owned(reindented_line.clone());
11945 reindented_line.clear();
11946 }
11947 });
11948 }
11949
11950 pub fn convert_to_upper_case(
11951 &mut self,
11952 _: &ConvertToUpperCase,
11953 window: &mut Window,
11954 cx: &mut Context<Self>,
11955 ) {
11956 self.manipulate_text(window, cx, |text| text.to_uppercase())
11957 }
11958
11959 pub fn convert_to_lower_case(
11960 &mut self,
11961 _: &ConvertToLowerCase,
11962 window: &mut Window,
11963 cx: &mut Context<Self>,
11964 ) {
11965 self.manipulate_text(window, cx, |text| text.to_lowercase())
11966 }
11967
11968 pub fn convert_to_title_case(
11969 &mut self,
11970 _: &ConvertToTitleCase,
11971 window: &mut Window,
11972 cx: &mut Context<Self>,
11973 ) {
11974 self.manipulate_text(window, cx, |text| {
11975 text.split('\n')
11976 .map(|line| line.to_case(Case::Title))
11977 .join("\n")
11978 })
11979 }
11980
11981 pub fn convert_to_snake_case(
11982 &mut self,
11983 _: &ConvertToSnakeCase,
11984 window: &mut Window,
11985 cx: &mut Context<Self>,
11986 ) {
11987 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11988 }
11989
11990 pub fn convert_to_kebab_case(
11991 &mut self,
11992 _: &ConvertToKebabCase,
11993 window: &mut Window,
11994 cx: &mut Context<Self>,
11995 ) {
11996 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11997 }
11998
11999 pub fn convert_to_upper_camel_case(
12000 &mut self,
12001 _: &ConvertToUpperCamelCase,
12002 window: &mut Window,
12003 cx: &mut Context<Self>,
12004 ) {
12005 self.manipulate_text(window, cx, |text| {
12006 text.split('\n')
12007 .map(|line| line.to_case(Case::UpperCamel))
12008 .join("\n")
12009 })
12010 }
12011
12012 pub fn convert_to_lower_camel_case(
12013 &mut self,
12014 _: &ConvertToLowerCamelCase,
12015 window: &mut Window,
12016 cx: &mut Context<Self>,
12017 ) {
12018 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12019 }
12020
12021 pub fn convert_to_opposite_case(
12022 &mut self,
12023 _: &ConvertToOppositeCase,
12024 window: &mut Window,
12025 cx: &mut Context<Self>,
12026 ) {
12027 self.manipulate_text(window, cx, |text| {
12028 text.chars()
12029 .fold(String::with_capacity(text.len()), |mut t, c| {
12030 if c.is_uppercase() {
12031 t.extend(c.to_lowercase());
12032 } else {
12033 t.extend(c.to_uppercase());
12034 }
12035 t
12036 })
12037 })
12038 }
12039
12040 pub fn convert_to_sentence_case(
12041 &mut self,
12042 _: &ConvertToSentenceCase,
12043 window: &mut Window,
12044 cx: &mut Context<Self>,
12045 ) {
12046 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12047 }
12048
12049 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12050 self.manipulate_text(window, cx, |text| {
12051 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12052 if has_upper_case_characters {
12053 text.to_lowercase()
12054 } else {
12055 text.to_uppercase()
12056 }
12057 })
12058 }
12059
12060 pub fn convert_to_rot13(
12061 &mut self,
12062 _: &ConvertToRot13,
12063 window: &mut Window,
12064 cx: &mut Context<Self>,
12065 ) {
12066 self.manipulate_text(window, cx, |text| {
12067 text.chars()
12068 .map(|c| match c {
12069 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12070 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12071 _ => c,
12072 })
12073 .collect()
12074 })
12075 }
12076
12077 pub fn convert_to_rot47(
12078 &mut self,
12079 _: &ConvertToRot47,
12080 window: &mut Window,
12081 cx: &mut Context<Self>,
12082 ) {
12083 self.manipulate_text(window, cx, |text| {
12084 text.chars()
12085 .map(|c| {
12086 let code_point = c as u32;
12087 if code_point >= 33 && code_point <= 126 {
12088 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12089 }
12090 c
12091 })
12092 .collect()
12093 })
12094 }
12095
12096 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12097 where
12098 Fn: FnMut(&str) -> String,
12099 {
12100 let buffer = self.buffer.read(cx).snapshot(cx);
12101
12102 let mut new_selections = Vec::new();
12103 let mut edits = Vec::new();
12104 let mut selection_adjustment = 0isize;
12105
12106 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12107 let selection_is_empty = selection.is_empty();
12108
12109 let (start, end) = if selection_is_empty {
12110 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12111 (word_range.start, word_range.end)
12112 } else {
12113 (
12114 buffer.point_to_offset(selection.start),
12115 buffer.point_to_offset(selection.end),
12116 )
12117 };
12118
12119 let text = buffer.text_for_range(start..end).collect::<String>();
12120 let old_length = text.len() as isize;
12121 let text = callback(&text);
12122
12123 new_selections.push(Selection {
12124 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12125 end: MultiBufferOffset(
12126 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12127 ),
12128 goal: SelectionGoal::None,
12129 id: selection.id,
12130 reversed: selection.reversed,
12131 });
12132
12133 selection_adjustment += old_length - text.len() as isize;
12134
12135 edits.push((start..end, text));
12136 }
12137
12138 self.transact(window, cx, |this, window, cx| {
12139 this.buffer.update(cx, |buffer, cx| {
12140 buffer.edit(edits, None, cx);
12141 });
12142
12143 this.change_selections(Default::default(), window, cx, |s| {
12144 s.select(new_selections);
12145 });
12146
12147 this.request_autoscroll(Autoscroll::fit(), cx);
12148 });
12149 }
12150
12151 pub fn move_selection_on_drop(
12152 &mut self,
12153 selection: &Selection<Anchor>,
12154 target: DisplayPoint,
12155 is_cut: bool,
12156 window: &mut Window,
12157 cx: &mut Context<Self>,
12158 ) {
12159 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12160 let buffer = display_map.buffer_snapshot();
12161 let mut edits = Vec::new();
12162 let insert_point = display_map
12163 .clip_point(target, Bias::Left)
12164 .to_point(&display_map);
12165 let text = buffer
12166 .text_for_range(selection.start..selection.end)
12167 .collect::<String>();
12168 if is_cut {
12169 edits.push(((selection.start..selection.end), String::new()));
12170 }
12171 let insert_anchor = buffer.anchor_before(insert_point);
12172 edits.push(((insert_anchor..insert_anchor), text));
12173 let last_edit_start = insert_anchor.bias_left(buffer);
12174 let last_edit_end = insert_anchor.bias_right(buffer);
12175 self.transact(window, cx, |this, window, cx| {
12176 this.buffer.update(cx, |buffer, cx| {
12177 buffer.edit(edits, None, cx);
12178 });
12179 this.change_selections(Default::default(), window, cx, |s| {
12180 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12181 });
12182 });
12183 }
12184
12185 pub fn clear_selection_drag_state(&mut self) {
12186 self.selection_drag_state = SelectionDragState::None;
12187 }
12188
12189 pub fn duplicate(
12190 &mut self,
12191 upwards: bool,
12192 whole_lines: bool,
12193 window: &mut Window,
12194 cx: &mut Context<Self>,
12195 ) {
12196 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12197
12198 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12199 let buffer = display_map.buffer_snapshot();
12200 let selections = self.selections.all::<Point>(&display_map);
12201
12202 let mut edits = Vec::new();
12203 let mut selections_iter = selections.iter().peekable();
12204 while let Some(selection) = selections_iter.next() {
12205 let mut rows = selection.spanned_rows(false, &display_map);
12206 // duplicate line-wise
12207 if whole_lines || selection.start == selection.end {
12208 // Avoid duplicating the same lines twice.
12209 while let Some(next_selection) = selections_iter.peek() {
12210 let next_rows = next_selection.spanned_rows(false, &display_map);
12211 if next_rows.start < rows.end {
12212 rows.end = next_rows.end;
12213 selections_iter.next().unwrap();
12214 } else {
12215 break;
12216 }
12217 }
12218
12219 // Copy the text from the selected row region and splice it either at the start
12220 // or end of the region.
12221 let start = Point::new(rows.start.0, 0);
12222 let end = Point::new(
12223 rows.end.previous_row().0,
12224 buffer.line_len(rows.end.previous_row()),
12225 );
12226
12227 let mut text = buffer.text_for_range(start..end).collect::<String>();
12228
12229 let insert_location = if upwards {
12230 // When duplicating upward, we need to insert before the current line.
12231 // If we're on the last line and it doesn't end with a newline,
12232 // we need to add a newline before the duplicated content.
12233 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12234 && buffer.max_point().column > 0
12235 && !text.ends_with('\n');
12236
12237 if needs_leading_newline {
12238 text.insert(0, '\n');
12239 end
12240 } else {
12241 text.push('\n');
12242 Point::new(rows.start.0, 0)
12243 }
12244 } else {
12245 text.push('\n');
12246 start
12247 };
12248 edits.push((insert_location..insert_location, text));
12249 } else {
12250 // duplicate character-wise
12251 let start = selection.start;
12252 let end = selection.end;
12253 let text = buffer.text_for_range(start..end).collect::<String>();
12254 edits.push((selection.end..selection.end, text));
12255 }
12256 }
12257
12258 self.transact(window, cx, |this, window, cx| {
12259 this.buffer.update(cx, |buffer, cx| {
12260 buffer.edit(edits, None, cx);
12261 });
12262
12263 // When duplicating upward with whole lines, move the cursor to the duplicated line
12264 if upwards && whole_lines {
12265 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12266
12267 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12268 let mut new_ranges = Vec::new();
12269 let selections = s.all::<Point>(&display_map);
12270 let mut selections_iter = selections.iter().peekable();
12271
12272 while let Some(first_selection) = selections_iter.next() {
12273 // Group contiguous selections together to find the total row span
12274 let mut group_selections = vec![first_selection];
12275 let mut rows = first_selection.spanned_rows(false, &display_map);
12276
12277 while let Some(next_selection) = selections_iter.peek() {
12278 let next_rows = next_selection.spanned_rows(false, &display_map);
12279 if next_rows.start < rows.end {
12280 rows.end = next_rows.end;
12281 group_selections.push(selections_iter.next().unwrap());
12282 } else {
12283 break;
12284 }
12285 }
12286
12287 let row_count = rows.end.0 - rows.start.0;
12288
12289 // Move all selections in this group up by the total number of duplicated rows
12290 for selection in group_selections {
12291 let new_start = Point::new(
12292 selection.start.row.saturating_sub(row_count),
12293 selection.start.column,
12294 );
12295
12296 let new_end = Point::new(
12297 selection.end.row.saturating_sub(row_count),
12298 selection.end.column,
12299 );
12300
12301 new_ranges.push(new_start..new_end);
12302 }
12303 }
12304
12305 s.select_ranges(new_ranges);
12306 });
12307 }
12308
12309 this.request_autoscroll(Autoscroll::fit(), cx);
12310 });
12311 }
12312
12313 pub fn duplicate_line_up(
12314 &mut self,
12315 _: &DuplicateLineUp,
12316 window: &mut Window,
12317 cx: &mut Context<Self>,
12318 ) {
12319 self.duplicate(true, true, window, cx);
12320 }
12321
12322 pub fn duplicate_line_down(
12323 &mut self,
12324 _: &DuplicateLineDown,
12325 window: &mut Window,
12326 cx: &mut Context<Self>,
12327 ) {
12328 self.duplicate(false, true, window, cx);
12329 }
12330
12331 pub fn duplicate_selection(
12332 &mut self,
12333 _: &DuplicateSelection,
12334 window: &mut Window,
12335 cx: &mut Context<Self>,
12336 ) {
12337 self.duplicate(false, false, window, cx);
12338 }
12339
12340 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12341 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12342 if self.mode.is_single_line() {
12343 cx.propagate();
12344 return;
12345 }
12346
12347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12348 let buffer = self.buffer.read(cx).snapshot(cx);
12349
12350 let mut edits = Vec::new();
12351 let mut unfold_ranges = Vec::new();
12352 let mut refold_creases = Vec::new();
12353
12354 let selections = self.selections.all::<Point>(&display_map);
12355 let mut selections = selections.iter().peekable();
12356 let mut contiguous_row_selections = Vec::new();
12357 let mut new_selections = Vec::new();
12358
12359 while let Some(selection) = selections.next() {
12360 // Find all the selections that span a contiguous row range
12361 let (start_row, end_row) = consume_contiguous_rows(
12362 &mut contiguous_row_selections,
12363 selection,
12364 &display_map,
12365 &mut selections,
12366 );
12367
12368 // Move the text spanned by the row range to be before the line preceding the row range
12369 if start_row.0 > 0 {
12370 let range_to_move = Point::new(
12371 start_row.previous_row().0,
12372 buffer.line_len(start_row.previous_row()),
12373 )
12374 ..Point::new(
12375 end_row.previous_row().0,
12376 buffer.line_len(end_row.previous_row()),
12377 );
12378 let insertion_point = display_map
12379 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12380 .0;
12381
12382 // Don't move lines across excerpts
12383 if buffer
12384 .excerpt_containing(insertion_point..range_to_move.end)
12385 .is_some()
12386 {
12387 let text = buffer
12388 .text_for_range(range_to_move.clone())
12389 .flat_map(|s| s.chars())
12390 .skip(1)
12391 .chain(['\n'])
12392 .collect::<String>();
12393
12394 edits.push((
12395 buffer.anchor_after(range_to_move.start)
12396 ..buffer.anchor_before(range_to_move.end),
12397 String::new(),
12398 ));
12399 let insertion_anchor = buffer.anchor_after(insertion_point);
12400 edits.push((insertion_anchor..insertion_anchor, text));
12401
12402 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12403
12404 // Move selections up
12405 new_selections.extend(contiguous_row_selections.drain(..).map(
12406 |mut selection| {
12407 selection.start.row -= row_delta;
12408 selection.end.row -= row_delta;
12409 selection
12410 },
12411 ));
12412
12413 // Move folds up
12414 unfold_ranges.push(range_to_move.clone());
12415 for fold in display_map.folds_in_range(
12416 buffer.anchor_before(range_to_move.start)
12417 ..buffer.anchor_after(range_to_move.end),
12418 ) {
12419 let mut start = fold.range.start.to_point(&buffer);
12420 let mut end = fold.range.end.to_point(&buffer);
12421 start.row -= row_delta;
12422 end.row -= row_delta;
12423 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12424 }
12425 }
12426 }
12427
12428 // If we didn't move line(s), preserve the existing selections
12429 new_selections.append(&mut contiguous_row_selections);
12430 }
12431
12432 self.transact(window, cx, |this, window, cx| {
12433 this.unfold_ranges(&unfold_ranges, true, true, cx);
12434 this.buffer.update(cx, |buffer, cx| {
12435 for (range, text) in edits {
12436 buffer.edit([(range, text)], None, cx);
12437 }
12438 });
12439 this.fold_creases(refold_creases, true, window, cx);
12440 this.change_selections(Default::default(), window, cx, |s| {
12441 s.select(new_selections);
12442 })
12443 });
12444 }
12445
12446 pub fn move_line_down(
12447 &mut self,
12448 _: &MoveLineDown,
12449 window: &mut Window,
12450 cx: &mut Context<Self>,
12451 ) {
12452 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12453 if self.mode.is_single_line() {
12454 cx.propagate();
12455 return;
12456 }
12457
12458 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12459 let buffer = self.buffer.read(cx).snapshot(cx);
12460
12461 let mut edits = Vec::new();
12462 let mut unfold_ranges = Vec::new();
12463 let mut refold_creases = Vec::new();
12464
12465 let selections = self.selections.all::<Point>(&display_map);
12466 let mut selections = selections.iter().peekable();
12467 let mut contiguous_row_selections = Vec::new();
12468 let mut new_selections = Vec::new();
12469
12470 while let Some(selection) = selections.next() {
12471 // Find all the selections that span a contiguous row range
12472 let (start_row, end_row) = consume_contiguous_rows(
12473 &mut contiguous_row_selections,
12474 selection,
12475 &display_map,
12476 &mut selections,
12477 );
12478
12479 // Move the text spanned by the row range to be after the last line of the row range
12480 if end_row.0 <= buffer.max_point().row {
12481 let range_to_move =
12482 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12483 let insertion_point = display_map
12484 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12485 .0;
12486
12487 // Don't move lines across excerpt boundaries
12488 if buffer
12489 .excerpt_containing(range_to_move.start..insertion_point)
12490 .is_some()
12491 {
12492 let mut text = String::from("\n");
12493 text.extend(buffer.text_for_range(range_to_move.clone()));
12494 text.pop(); // Drop trailing newline
12495 edits.push((
12496 buffer.anchor_after(range_to_move.start)
12497 ..buffer.anchor_before(range_to_move.end),
12498 String::new(),
12499 ));
12500 let insertion_anchor = buffer.anchor_after(insertion_point);
12501 edits.push((insertion_anchor..insertion_anchor, text));
12502
12503 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12504
12505 // Move selections down
12506 new_selections.extend(contiguous_row_selections.drain(..).map(
12507 |mut selection| {
12508 selection.start.row += row_delta;
12509 selection.end.row += row_delta;
12510 selection
12511 },
12512 ));
12513
12514 // Move folds down
12515 unfold_ranges.push(range_to_move.clone());
12516 for fold in display_map.folds_in_range(
12517 buffer.anchor_before(range_to_move.start)
12518 ..buffer.anchor_after(range_to_move.end),
12519 ) {
12520 let mut start = fold.range.start.to_point(&buffer);
12521 let mut end = fold.range.end.to_point(&buffer);
12522 start.row += row_delta;
12523 end.row += row_delta;
12524 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12525 }
12526 }
12527 }
12528
12529 // If we didn't move line(s), preserve the existing selections
12530 new_selections.append(&mut contiguous_row_selections);
12531 }
12532
12533 self.transact(window, cx, |this, window, cx| {
12534 this.unfold_ranges(&unfold_ranges, true, true, cx);
12535 this.buffer.update(cx, |buffer, cx| {
12536 for (range, text) in edits {
12537 buffer.edit([(range, text)], None, cx);
12538 }
12539 });
12540 this.fold_creases(refold_creases, true, window, cx);
12541 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12542 });
12543 }
12544
12545 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12546 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12547 let text_layout_details = &self.text_layout_details(window);
12548 self.transact(window, cx, |this, window, cx| {
12549 let edits = this.change_selections(Default::default(), window, cx, |s| {
12550 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12551 s.move_with(|display_map, selection| {
12552 if !selection.is_empty() {
12553 return;
12554 }
12555
12556 let mut head = selection.head();
12557 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12558 if head.column() == display_map.line_len(head.row()) {
12559 transpose_offset = display_map
12560 .buffer_snapshot()
12561 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12562 }
12563
12564 if transpose_offset == MultiBufferOffset(0) {
12565 return;
12566 }
12567
12568 *head.column_mut() += 1;
12569 head = display_map.clip_point(head, Bias::Right);
12570 let goal = SelectionGoal::HorizontalPosition(
12571 display_map
12572 .x_for_display_point(head, text_layout_details)
12573 .into(),
12574 );
12575 selection.collapse_to(head, goal);
12576
12577 let transpose_start = display_map
12578 .buffer_snapshot()
12579 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12580 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12581 let transpose_end = display_map
12582 .buffer_snapshot()
12583 .clip_offset(transpose_offset + 1usize, Bias::Right);
12584 if let Some(ch) = display_map
12585 .buffer_snapshot()
12586 .chars_at(transpose_start)
12587 .next()
12588 {
12589 edits.push((transpose_start..transpose_offset, String::new()));
12590 edits.push((transpose_end..transpose_end, ch.to_string()));
12591 }
12592 }
12593 });
12594 edits
12595 });
12596 this.buffer
12597 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12598 let selections = this
12599 .selections
12600 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12601 this.change_selections(Default::default(), window, cx, |s| {
12602 s.select(selections);
12603 });
12604 });
12605 }
12606
12607 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12609 if self.mode.is_single_line() {
12610 cx.propagate();
12611 return;
12612 }
12613
12614 self.rewrap_impl(RewrapOptions::default(), cx)
12615 }
12616
12617 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12618 let buffer = self.buffer.read(cx).snapshot(cx);
12619 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12620
12621 #[derive(Clone, Debug, PartialEq)]
12622 enum CommentFormat {
12623 /// single line comment, with prefix for line
12624 Line(String),
12625 /// single line within a block comment, with prefix for line
12626 BlockLine(String),
12627 /// a single line of a block comment that includes the initial delimiter
12628 BlockCommentWithStart(BlockCommentConfig),
12629 /// a single line of a block comment that includes the ending delimiter
12630 BlockCommentWithEnd(BlockCommentConfig),
12631 }
12632
12633 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12634 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12635 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12636 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12637 .peekable();
12638
12639 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12640 row
12641 } else {
12642 return Vec::new();
12643 };
12644
12645 let language_settings = buffer.language_settings_at(selection.head(), cx);
12646 let language_scope = buffer.language_scope_at(selection.head());
12647
12648 let indent_and_prefix_for_row =
12649 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12650 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12651 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12652 &language_scope
12653 {
12654 let indent_end = Point::new(row, indent.len);
12655 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12656 let line_text_after_indent = buffer
12657 .text_for_range(indent_end..line_end)
12658 .collect::<String>();
12659
12660 let is_within_comment_override = buffer
12661 .language_scope_at(indent_end)
12662 .is_some_and(|scope| scope.override_name() == Some("comment"));
12663 let comment_delimiters = if is_within_comment_override {
12664 // we are within a comment syntax node, but we don't
12665 // yet know what kind of comment: block, doc or line
12666 match (
12667 language_scope.documentation_comment(),
12668 language_scope.block_comment(),
12669 ) {
12670 (Some(config), _) | (_, Some(config))
12671 if buffer.contains_str_at(indent_end, &config.start) =>
12672 {
12673 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12674 }
12675 (Some(config), _) | (_, Some(config))
12676 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12677 {
12678 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12679 }
12680 (Some(config), _) | (_, Some(config))
12681 if buffer.contains_str_at(indent_end, &config.prefix) =>
12682 {
12683 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12684 }
12685 (_, _) => language_scope
12686 .line_comment_prefixes()
12687 .iter()
12688 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12689 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12690 }
12691 } else {
12692 // we not in an overridden comment node, but we may
12693 // be within a non-overridden line comment node
12694 language_scope
12695 .line_comment_prefixes()
12696 .iter()
12697 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12698 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12699 };
12700
12701 let rewrap_prefix = language_scope
12702 .rewrap_prefixes()
12703 .iter()
12704 .find_map(|prefix_regex| {
12705 prefix_regex.find(&line_text_after_indent).map(|mat| {
12706 if mat.start() == 0 {
12707 Some(mat.as_str().to_string())
12708 } else {
12709 None
12710 }
12711 })
12712 })
12713 .flatten();
12714 (comment_delimiters, rewrap_prefix)
12715 } else {
12716 (None, None)
12717 };
12718 (indent, comment_prefix, rewrap_prefix)
12719 };
12720
12721 let mut ranges = Vec::new();
12722 let from_empty_selection = selection.is_empty();
12723
12724 let mut current_range_start = first_row;
12725 let mut prev_row = first_row;
12726 let (
12727 mut current_range_indent,
12728 mut current_range_comment_delimiters,
12729 mut current_range_rewrap_prefix,
12730 ) = indent_and_prefix_for_row(first_row);
12731
12732 for row in non_blank_rows_iter.skip(1) {
12733 let has_paragraph_break = row > prev_row + 1;
12734
12735 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12736 indent_and_prefix_for_row(row);
12737
12738 let has_indent_change = row_indent != current_range_indent;
12739 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12740
12741 let has_boundary_change = has_comment_change
12742 || row_rewrap_prefix.is_some()
12743 || (has_indent_change && current_range_comment_delimiters.is_some());
12744
12745 if has_paragraph_break || has_boundary_change {
12746 ranges.push((
12747 language_settings.clone(),
12748 Point::new(current_range_start, 0)
12749 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12750 current_range_indent,
12751 current_range_comment_delimiters.clone(),
12752 current_range_rewrap_prefix.clone(),
12753 from_empty_selection,
12754 ));
12755 current_range_start = row;
12756 current_range_indent = row_indent;
12757 current_range_comment_delimiters = row_comment_delimiters;
12758 current_range_rewrap_prefix = row_rewrap_prefix;
12759 }
12760 prev_row = row;
12761 }
12762
12763 ranges.push((
12764 language_settings.clone(),
12765 Point::new(current_range_start, 0)
12766 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12767 current_range_indent,
12768 current_range_comment_delimiters,
12769 current_range_rewrap_prefix,
12770 from_empty_selection,
12771 ));
12772
12773 ranges
12774 });
12775
12776 let mut edits = Vec::new();
12777 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12778
12779 for (
12780 language_settings,
12781 wrap_range,
12782 mut indent_size,
12783 comment_prefix,
12784 rewrap_prefix,
12785 from_empty_selection,
12786 ) in wrap_ranges
12787 {
12788 let mut start_row = wrap_range.start.row;
12789 let mut end_row = wrap_range.end.row;
12790
12791 // Skip selections that overlap with a range that has already been rewrapped.
12792 let selection_range = start_row..end_row;
12793 if rewrapped_row_ranges
12794 .iter()
12795 .any(|range| range.overlaps(&selection_range))
12796 {
12797 continue;
12798 }
12799
12800 let tab_size = language_settings.tab_size;
12801
12802 let (line_prefix, inside_comment) = match &comment_prefix {
12803 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12804 (Some(prefix.as_str()), true)
12805 }
12806 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12807 (Some(prefix.as_ref()), true)
12808 }
12809 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12810 start: _,
12811 end: _,
12812 prefix,
12813 tab_size,
12814 })) => {
12815 indent_size.len += tab_size;
12816 (Some(prefix.as_ref()), true)
12817 }
12818 None => (None, false),
12819 };
12820 let indent_prefix = indent_size.chars().collect::<String>();
12821 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12822
12823 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12824 RewrapBehavior::InComments => inside_comment,
12825 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12826 RewrapBehavior::Anywhere => true,
12827 };
12828
12829 let should_rewrap = options.override_language_settings
12830 || allow_rewrap_based_on_language
12831 || self.hard_wrap.is_some();
12832 if !should_rewrap {
12833 continue;
12834 }
12835
12836 if from_empty_selection {
12837 'expand_upwards: while start_row > 0 {
12838 let prev_row = start_row - 1;
12839 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12840 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12841 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12842 {
12843 start_row = prev_row;
12844 } else {
12845 break 'expand_upwards;
12846 }
12847 }
12848
12849 'expand_downwards: while end_row < buffer.max_point().row {
12850 let next_row = end_row + 1;
12851 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12852 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12853 && !buffer.is_line_blank(MultiBufferRow(next_row))
12854 {
12855 end_row = next_row;
12856 } else {
12857 break 'expand_downwards;
12858 }
12859 }
12860 }
12861
12862 let start = Point::new(start_row, 0);
12863 let start_offset = ToOffset::to_offset(&start, &buffer);
12864 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12865 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12866 let mut first_line_delimiter = None;
12867 let mut last_line_delimiter = None;
12868 let Some(lines_without_prefixes) = selection_text
12869 .lines()
12870 .enumerate()
12871 .map(|(ix, line)| {
12872 let line_trimmed = line.trim_start();
12873 if rewrap_prefix.is_some() && ix > 0 {
12874 Ok(line_trimmed)
12875 } else if let Some(
12876 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12877 start,
12878 prefix,
12879 end,
12880 tab_size,
12881 })
12882 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12883 start,
12884 prefix,
12885 end,
12886 tab_size,
12887 }),
12888 ) = &comment_prefix
12889 {
12890 let line_trimmed = line_trimmed
12891 .strip_prefix(start.as_ref())
12892 .map(|s| {
12893 let mut indent_size = indent_size;
12894 indent_size.len -= tab_size;
12895 let indent_prefix: String = indent_size.chars().collect();
12896 first_line_delimiter = Some((indent_prefix, start));
12897 s.trim_start()
12898 })
12899 .unwrap_or(line_trimmed);
12900 let line_trimmed = line_trimmed
12901 .strip_suffix(end.as_ref())
12902 .map(|s| {
12903 last_line_delimiter = Some(end);
12904 s.trim_end()
12905 })
12906 .unwrap_or(line_trimmed);
12907 let line_trimmed = line_trimmed
12908 .strip_prefix(prefix.as_ref())
12909 .unwrap_or(line_trimmed);
12910 Ok(line_trimmed)
12911 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12912 line_trimmed.strip_prefix(prefix).with_context(|| {
12913 format!("line did not start with prefix {prefix:?}: {line:?}")
12914 })
12915 } else {
12916 line_trimmed
12917 .strip_prefix(&line_prefix.trim_start())
12918 .with_context(|| {
12919 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12920 })
12921 }
12922 })
12923 .collect::<Result<Vec<_>, _>>()
12924 .log_err()
12925 else {
12926 continue;
12927 };
12928
12929 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12930 buffer
12931 .language_settings_at(Point::new(start_row, 0), cx)
12932 .preferred_line_length as usize
12933 });
12934
12935 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12936 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12937 } else {
12938 line_prefix.clone()
12939 };
12940
12941 let wrapped_text = {
12942 let mut wrapped_text = wrap_with_prefix(
12943 line_prefix,
12944 subsequent_lines_prefix,
12945 lines_without_prefixes.join("\n"),
12946 wrap_column,
12947 tab_size,
12948 options.preserve_existing_whitespace,
12949 );
12950
12951 if let Some((indent, delimiter)) = first_line_delimiter {
12952 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12953 }
12954 if let Some(last_line) = last_line_delimiter {
12955 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12956 }
12957
12958 wrapped_text
12959 };
12960
12961 // TODO: should always use char-based diff while still supporting cursor behavior that
12962 // matches vim.
12963 let mut diff_options = DiffOptions::default();
12964 if options.override_language_settings {
12965 diff_options.max_word_diff_len = 0;
12966 diff_options.max_word_diff_line_count = 0;
12967 } else {
12968 diff_options.max_word_diff_len = usize::MAX;
12969 diff_options.max_word_diff_line_count = usize::MAX;
12970 }
12971
12972 for (old_range, new_text) in
12973 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12974 {
12975 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12976 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12977 edits.push((edit_start..edit_end, new_text));
12978 }
12979
12980 rewrapped_row_ranges.push(start_row..=end_row);
12981 }
12982
12983 self.buffer
12984 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12985 }
12986
12987 pub fn cut_common(
12988 &mut self,
12989 cut_no_selection_line: bool,
12990 window: &mut Window,
12991 cx: &mut Context<Self>,
12992 ) -> ClipboardItem {
12993 let mut text = String::new();
12994 let buffer = self.buffer.read(cx).snapshot(cx);
12995 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12996 let mut clipboard_selections = Vec::with_capacity(selections.len());
12997 {
12998 let max_point = buffer.max_point();
12999 let mut is_first = true;
13000 let mut prev_selection_was_entire_line = false;
13001 for selection in &mut selections {
13002 let is_entire_line =
13003 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13004 if is_entire_line {
13005 selection.start = Point::new(selection.start.row, 0);
13006 if !selection.is_empty() && selection.end.column == 0 {
13007 selection.end = cmp::min(max_point, selection.end);
13008 } else {
13009 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13010 }
13011 selection.goal = SelectionGoal::None;
13012 }
13013 if is_first {
13014 is_first = false;
13015 } else if !prev_selection_was_entire_line {
13016 text += "\n";
13017 }
13018 prev_selection_was_entire_line = is_entire_line;
13019 let mut len = 0;
13020 for chunk in buffer.text_for_range(selection.start..selection.end) {
13021 text.push_str(chunk);
13022 len += chunk.len();
13023 }
13024
13025 clipboard_selections.push(ClipboardSelection::for_buffer(
13026 len,
13027 is_entire_line,
13028 selection.range(),
13029 &buffer,
13030 self.project.as_ref(),
13031 cx,
13032 ));
13033 }
13034 }
13035
13036 self.transact(window, cx, |this, window, cx| {
13037 this.change_selections(Default::default(), window, cx, |s| {
13038 s.select(selections);
13039 });
13040 this.insert("", window, cx);
13041 });
13042 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13043 }
13044
13045 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13046 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13047 let item = self.cut_common(true, window, cx);
13048 cx.write_to_clipboard(item);
13049 }
13050
13051 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13052 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13053 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13054 s.move_with(|snapshot, sel| {
13055 if sel.is_empty() {
13056 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13057 }
13058 if sel.is_empty() {
13059 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13060 }
13061 });
13062 });
13063 let item = self.cut_common(false, window, cx);
13064 cx.set_global(KillRing(item))
13065 }
13066
13067 pub fn kill_ring_yank(
13068 &mut self,
13069 _: &KillRingYank,
13070 window: &mut Window,
13071 cx: &mut Context<Self>,
13072 ) {
13073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13074 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13075 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13076 (kill_ring.text().to_string(), kill_ring.metadata_json())
13077 } else {
13078 return;
13079 }
13080 } else {
13081 return;
13082 };
13083 self.do_paste(&text, metadata, false, window, cx);
13084 }
13085
13086 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13087 self.do_copy(true, cx);
13088 }
13089
13090 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13091 self.do_copy(false, cx);
13092 }
13093
13094 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13095 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13096 let buffer = self.buffer.read(cx).read(cx);
13097 let mut text = String::new();
13098
13099 let mut clipboard_selections = Vec::with_capacity(selections.len());
13100 {
13101 let max_point = buffer.max_point();
13102 let mut is_first = true;
13103 let mut prev_selection_was_entire_line = false;
13104 for selection in &selections {
13105 let mut start = selection.start;
13106 let mut end = selection.end;
13107 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13108 let mut add_trailing_newline = false;
13109 if is_entire_line {
13110 start = Point::new(start.row, 0);
13111 let next_line_start = Point::new(end.row + 1, 0);
13112 if next_line_start <= max_point {
13113 end = next_line_start;
13114 } else {
13115 // We're on the last line without a trailing newline.
13116 // Copy to the end of the line and add a newline afterwards.
13117 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13118 add_trailing_newline = true;
13119 }
13120 }
13121
13122 let mut trimmed_selections = Vec::new();
13123 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13124 let row = MultiBufferRow(start.row);
13125 let first_indent = buffer.indent_size_for_line(row);
13126 if first_indent.len == 0 || start.column > first_indent.len {
13127 trimmed_selections.push(start..end);
13128 } else {
13129 trimmed_selections.push(
13130 Point::new(row.0, first_indent.len)
13131 ..Point::new(row.0, buffer.line_len(row)),
13132 );
13133 for row in start.row + 1..=end.row {
13134 let mut line_len = buffer.line_len(MultiBufferRow(row));
13135 if row == end.row {
13136 line_len = end.column;
13137 }
13138 if line_len == 0 {
13139 trimmed_selections
13140 .push(Point::new(row, 0)..Point::new(row, line_len));
13141 continue;
13142 }
13143 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13144 if row_indent_size.len >= first_indent.len {
13145 trimmed_selections.push(
13146 Point::new(row, first_indent.len)..Point::new(row, line_len),
13147 );
13148 } else {
13149 trimmed_selections.clear();
13150 trimmed_selections.push(start..end);
13151 break;
13152 }
13153 }
13154 }
13155 } else {
13156 trimmed_selections.push(start..end);
13157 }
13158
13159 for trimmed_range in trimmed_selections {
13160 if is_first {
13161 is_first = false;
13162 } else if !prev_selection_was_entire_line {
13163 text += "\n";
13164 }
13165 prev_selection_was_entire_line = is_entire_line;
13166 let mut len = 0;
13167 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13168 text.push_str(chunk);
13169 len += chunk.len();
13170 }
13171 if add_trailing_newline {
13172 text.push('\n');
13173 len += 1;
13174 }
13175 clipboard_selections.push(ClipboardSelection::for_buffer(
13176 len,
13177 is_entire_line,
13178 trimmed_range,
13179 &buffer,
13180 self.project.as_ref(),
13181 cx,
13182 ));
13183 }
13184 }
13185 }
13186
13187 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13188 text,
13189 clipboard_selections,
13190 ));
13191 }
13192
13193 pub fn do_paste(
13194 &mut self,
13195 text: &String,
13196 clipboard_selections: Option<Vec<ClipboardSelection>>,
13197 handle_entire_lines: bool,
13198 window: &mut Window,
13199 cx: &mut Context<Self>,
13200 ) {
13201 if self.read_only(cx) {
13202 return;
13203 }
13204
13205 let clipboard_text = Cow::Borrowed(text.as_str());
13206
13207 self.transact(window, cx, |this, window, cx| {
13208 let had_active_edit_prediction = this.has_active_edit_prediction();
13209 let display_map = this.display_snapshot(cx);
13210 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13211 let cursor_offset = this
13212 .selections
13213 .last::<MultiBufferOffset>(&display_map)
13214 .head();
13215
13216 if let Some(mut clipboard_selections) = clipboard_selections {
13217 let all_selections_were_entire_line =
13218 clipboard_selections.iter().all(|s| s.is_entire_line);
13219 let first_selection_indent_column =
13220 clipboard_selections.first().map(|s| s.first_line_indent);
13221 if clipboard_selections.len() != old_selections.len() {
13222 clipboard_selections.drain(..);
13223 }
13224 let mut auto_indent_on_paste = true;
13225
13226 this.buffer.update(cx, |buffer, cx| {
13227 let snapshot = buffer.read(cx);
13228 auto_indent_on_paste = snapshot
13229 .language_settings_at(cursor_offset, cx)
13230 .auto_indent_on_paste;
13231
13232 let mut start_offset = 0;
13233 let mut edits = Vec::new();
13234 let mut original_indent_columns = Vec::new();
13235 for (ix, selection) in old_selections.iter().enumerate() {
13236 let to_insert;
13237 let entire_line;
13238 let original_indent_column;
13239 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13240 let end_offset = start_offset + clipboard_selection.len;
13241 to_insert = &clipboard_text[start_offset..end_offset];
13242 entire_line = clipboard_selection.is_entire_line;
13243 start_offset = if entire_line {
13244 end_offset
13245 } else {
13246 end_offset + 1
13247 };
13248 original_indent_column = Some(clipboard_selection.first_line_indent);
13249 } else {
13250 to_insert = &*clipboard_text;
13251 entire_line = all_selections_were_entire_line;
13252 original_indent_column = first_selection_indent_column
13253 }
13254
13255 let (range, to_insert) =
13256 if selection.is_empty() && handle_entire_lines && entire_line {
13257 // If the corresponding selection was empty when this slice of the
13258 // clipboard text was written, then the entire line containing the
13259 // selection was copied. If this selection is also currently empty,
13260 // then paste the line before the current line of the buffer.
13261 let column = selection.start.to_point(&snapshot).column as usize;
13262 let line_start = selection.start - column;
13263 (line_start..line_start, Cow::Borrowed(to_insert))
13264 } else {
13265 let language = snapshot.language_at(selection.head());
13266 let range = selection.range();
13267 if let Some(language) = language
13268 && language.name() == "Markdown".into()
13269 {
13270 edit_for_markdown_paste(
13271 &snapshot,
13272 range,
13273 to_insert,
13274 url::Url::parse(to_insert).ok(),
13275 )
13276 } else {
13277 (range, Cow::Borrowed(to_insert))
13278 }
13279 };
13280
13281 edits.push((range, to_insert));
13282 original_indent_columns.push(original_indent_column);
13283 }
13284 drop(snapshot);
13285
13286 buffer.edit(
13287 edits,
13288 if auto_indent_on_paste {
13289 Some(AutoindentMode::Block {
13290 original_indent_columns,
13291 })
13292 } else {
13293 None
13294 },
13295 cx,
13296 );
13297 });
13298
13299 let selections = this
13300 .selections
13301 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13302 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13303 } else {
13304 let url = url::Url::parse(&clipboard_text).ok();
13305
13306 let auto_indent_mode = if !clipboard_text.is_empty() {
13307 Some(AutoindentMode::Block {
13308 original_indent_columns: Vec::new(),
13309 })
13310 } else {
13311 None
13312 };
13313
13314 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13315 let snapshot = buffer.snapshot(cx);
13316
13317 let anchors = old_selections
13318 .iter()
13319 .map(|s| {
13320 let anchor = snapshot.anchor_after(s.head());
13321 s.map(|_| anchor)
13322 })
13323 .collect::<Vec<_>>();
13324
13325 let mut edits = Vec::new();
13326
13327 for selection in old_selections.iter() {
13328 let language = snapshot.language_at(selection.head());
13329 let range = selection.range();
13330
13331 let (edit_range, edit_text) = if let Some(language) = language
13332 && language.name() == "Markdown".into()
13333 {
13334 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13335 } else {
13336 (range, clipboard_text.clone())
13337 };
13338
13339 edits.push((edit_range, edit_text));
13340 }
13341
13342 drop(snapshot);
13343 buffer.edit(edits, auto_indent_mode, cx);
13344
13345 anchors
13346 });
13347
13348 this.change_selections(Default::default(), window, cx, |s| {
13349 s.select_anchors(selection_anchors);
13350 });
13351 }
13352
13353 // 🤔 | .. | show_in_menu |
13354 // | .. | true true
13355 // | had_edit_prediction | false true
13356
13357 let trigger_in_words =
13358 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13359
13360 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13361 });
13362 }
13363
13364 pub fn diff_clipboard_with_selection(
13365 &mut self,
13366 _: &DiffClipboardWithSelection,
13367 window: &mut Window,
13368 cx: &mut Context<Self>,
13369 ) {
13370 let selections = self
13371 .selections
13372 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13373
13374 if selections.is_empty() {
13375 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13376 return;
13377 };
13378
13379 let clipboard_text = match cx.read_from_clipboard() {
13380 Some(item) => match item.entries().first() {
13381 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13382 _ => None,
13383 },
13384 None => None,
13385 };
13386
13387 let Some(clipboard_text) = clipboard_text else {
13388 log::warn!("Clipboard doesn't contain text.");
13389 return;
13390 };
13391
13392 window.dispatch_action(
13393 Box::new(DiffClipboardWithSelectionData {
13394 clipboard_text,
13395 editor: cx.entity(),
13396 }),
13397 cx,
13398 );
13399 }
13400
13401 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13402 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13403 if let Some(item) = cx.read_from_clipboard() {
13404 let entries = item.entries();
13405
13406 match entries.first() {
13407 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13408 // of all the pasted entries.
13409 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13410 .do_paste(
13411 clipboard_string.text(),
13412 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13413 true,
13414 window,
13415 cx,
13416 ),
13417 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13418 }
13419 }
13420 }
13421
13422 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13423 if self.read_only(cx) {
13424 return;
13425 }
13426
13427 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13428
13429 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13430 if let Some((selections, _)) =
13431 self.selection_history.transaction(transaction_id).cloned()
13432 {
13433 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13434 s.select_anchors(selections.to_vec());
13435 });
13436 } else {
13437 log::error!(
13438 "No entry in selection_history found for undo. \
13439 This may correspond to a bug where undo does not update the selection. \
13440 If this is occurring, please add details to \
13441 https://github.com/zed-industries/zed/issues/22692"
13442 );
13443 }
13444 self.request_autoscroll(Autoscroll::fit(), cx);
13445 self.unmark_text(window, cx);
13446 self.refresh_edit_prediction(true, false, window, cx);
13447 cx.emit(EditorEvent::Edited { transaction_id });
13448 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13449 }
13450 }
13451
13452 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13453 if self.read_only(cx) {
13454 return;
13455 }
13456
13457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13458
13459 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13460 if let Some((_, Some(selections))) =
13461 self.selection_history.transaction(transaction_id).cloned()
13462 {
13463 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13464 s.select_anchors(selections.to_vec());
13465 });
13466 } else {
13467 log::error!(
13468 "No entry in selection_history found for redo. \
13469 This may correspond to a bug where undo does not update the selection. \
13470 If this is occurring, please add details to \
13471 https://github.com/zed-industries/zed/issues/22692"
13472 );
13473 }
13474 self.request_autoscroll(Autoscroll::fit(), cx);
13475 self.unmark_text(window, cx);
13476 self.refresh_edit_prediction(true, false, window, cx);
13477 cx.emit(EditorEvent::Edited { transaction_id });
13478 }
13479 }
13480
13481 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13482 self.buffer
13483 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13484 }
13485
13486 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13487 self.buffer
13488 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13489 }
13490
13491 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13492 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13493 self.change_selections(Default::default(), window, cx, |s| {
13494 s.move_with(|map, selection| {
13495 let cursor = if selection.is_empty() {
13496 movement::left(map, selection.start)
13497 } else {
13498 selection.start
13499 };
13500 selection.collapse_to(cursor, SelectionGoal::None);
13501 });
13502 })
13503 }
13504
13505 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13507 self.change_selections(Default::default(), window, cx, |s| {
13508 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13509 })
13510 }
13511
13512 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13513 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13514 self.change_selections(Default::default(), window, cx, |s| {
13515 s.move_with(|map, selection| {
13516 let cursor = if selection.is_empty() {
13517 movement::right(map, selection.end)
13518 } else {
13519 selection.end
13520 };
13521 selection.collapse_to(cursor, SelectionGoal::None)
13522 });
13523 })
13524 }
13525
13526 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13527 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13528 self.change_selections(Default::default(), window, cx, |s| {
13529 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13530 });
13531 }
13532
13533 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13534 if self.take_rename(true, window, cx).is_some() {
13535 return;
13536 }
13537
13538 if self.mode.is_single_line() {
13539 cx.propagate();
13540 return;
13541 }
13542
13543 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13544
13545 let text_layout_details = &self.text_layout_details(window);
13546 let selection_count = self.selections.count();
13547 let first_selection = self.selections.first_anchor();
13548
13549 self.change_selections(Default::default(), window, cx, |s| {
13550 s.move_with(|map, selection| {
13551 if !selection.is_empty() {
13552 selection.goal = SelectionGoal::None;
13553 }
13554 let (cursor, goal) = movement::up(
13555 map,
13556 selection.start,
13557 selection.goal,
13558 false,
13559 text_layout_details,
13560 );
13561 selection.collapse_to(cursor, goal);
13562 });
13563 });
13564
13565 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13566 {
13567 cx.propagate();
13568 }
13569 }
13570
13571 pub fn move_up_by_lines(
13572 &mut self,
13573 action: &MoveUpByLines,
13574 window: &mut Window,
13575 cx: &mut Context<Self>,
13576 ) {
13577 if self.take_rename(true, window, cx).is_some() {
13578 return;
13579 }
13580
13581 if self.mode.is_single_line() {
13582 cx.propagate();
13583 return;
13584 }
13585
13586 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13587
13588 let text_layout_details = &self.text_layout_details(window);
13589
13590 self.change_selections(Default::default(), window, cx, |s| {
13591 s.move_with(|map, selection| {
13592 if !selection.is_empty() {
13593 selection.goal = SelectionGoal::None;
13594 }
13595 let (cursor, goal) = movement::up_by_rows(
13596 map,
13597 selection.start,
13598 action.lines,
13599 selection.goal,
13600 false,
13601 text_layout_details,
13602 );
13603 selection.collapse_to(cursor, goal);
13604 });
13605 })
13606 }
13607
13608 pub fn move_down_by_lines(
13609 &mut self,
13610 action: &MoveDownByLines,
13611 window: &mut Window,
13612 cx: &mut Context<Self>,
13613 ) {
13614 if self.take_rename(true, window, cx).is_some() {
13615 return;
13616 }
13617
13618 if self.mode.is_single_line() {
13619 cx.propagate();
13620 return;
13621 }
13622
13623 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13624
13625 let text_layout_details = &self.text_layout_details(window);
13626
13627 self.change_selections(Default::default(), window, cx, |s| {
13628 s.move_with(|map, selection| {
13629 if !selection.is_empty() {
13630 selection.goal = SelectionGoal::None;
13631 }
13632 let (cursor, goal) = movement::down_by_rows(
13633 map,
13634 selection.start,
13635 action.lines,
13636 selection.goal,
13637 false,
13638 text_layout_details,
13639 );
13640 selection.collapse_to(cursor, goal);
13641 });
13642 })
13643 }
13644
13645 pub fn select_down_by_lines(
13646 &mut self,
13647 action: &SelectDownByLines,
13648 window: &mut Window,
13649 cx: &mut Context<Self>,
13650 ) {
13651 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13652 let text_layout_details = &self.text_layout_details(window);
13653 self.change_selections(Default::default(), window, cx, |s| {
13654 s.move_heads_with(|map, head, goal| {
13655 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13656 })
13657 })
13658 }
13659
13660 pub fn select_up_by_lines(
13661 &mut self,
13662 action: &SelectUpByLines,
13663 window: &mut Window,
13664 cx: &mut Context<Self>,
13665 ) {
13666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13667 let text_layout_details = &self.text_layout_details(window);
13668 self.change_selections(Default::default(), window, cx, |s| {
13669 s.move_heads_with(|map, head, goal| {
13670 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13671 })
13672 })
13673 }
13674
13675 pub fn select_page_up(
13676 &mut self,
13677 _: &SelectPageUp,
13678 window: &mut Window,
13679 cx: &mut Context<Self>,
13680 ) {
13681 let Some(row_count) = self.visible_row_count() else {
13682 return;
13683 };
13684
13685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13686
13687 let text_layout_details = &self.text_layout_details(window);
13688
13689 self.change_selections(Default::default(), window, cx, |s| {
13690 s.move_heads_with(|map, head, goal| {
13691 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13692 })
13693 })
13694 }
13695
13696 pub fn move_page_up(
13697 &mut self,
13698 action: &MovePageUp,
13699 window: &mut Window,
13700 cx: &mut Context<Self>,
13701 ) {
13702 if self.take_rename(true, window, cx).is_some() {
13703 return;
13704 }
13705
13706 if self
13707 .context_menu
13708 .borrow_mut()
13709 .as_mut()
13710 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13711 .unwrap_or(false)
13712 {
13713 return;
13714 }
13715
13716 if matches!(self.mode, EditorMode::SingleLine) {
13717 cx.propagate();
13718 return;
13719 }
13720
13721 let Some(row_count) = self.visible_row_count() else {
13722 return;
13723 };
13724
13725 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13726
13727 let effects = if action.center_cursor {
13728 SelectionEffects::scroll(Autoscroll::center())
13729 } else {
13730 SelectionEffects::default()
13731 };
13732
13733 let text_layout_details = &self.text_layout_details(window);
13734
13735 self.change_selections(effects, window, cx, |s| {
13736 s.move_with(|map, selection| {
13737 if !selection.is_empty() {
13738 selection.goal = SelectionGoal::None;
13739 }
13740 let (cursor, goal) = movement::up_by_rows(
13741 map,
13742 selection.end,
13743 row_count,
13744 selection.goal,
13745 false,
13746 text_layout_details,
13747 );
13748 selection.collapse_to(cursor, goal);
13749 });
13750 });
13751 }
13752
13753 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13754 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13755 let text_layout_details = &self.text_layout_details(window);
13756 self.change_selections(Default::default(), window, cx, |s| {
13757 s.move_heads_with(|map, head, goal| {
13758 movement::up(map, head, goal, false, text_layout_details)
13759 })
13760 })
13761 }
13762
13763 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13764 self.take_rename(true, window, cx);
13765
13766 if self.mode.is_single_line() {
13767 cx.propagate();
13768 return;
13769 }
13770
13771 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13772
13773 let text_layout_details = &self.text_layout_details(window);
13774 let selection_count = self.selections.count();
13775 let first_selection = self.selections.first_anchor();
13776
13777 self.change_selections(Default::default(), window, cx, |s| {
13778 s.move_with(|map, selection| {
13779 if !selection.is_empty() {
13780 selection.goal = SelectionGoal::None;
13781 }
13782 let (cursor, goal) = movement::down(
13783 map,
13784 selection.end,
13785 selection.goal,
13786 false,
13787 text_layout_details,
13788 );
13789 selection.collapse_to(cursor, goal);
13790 });
13791 });
13792
13793 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13794 {
13795 cx.propagate();
13796 }
13797 }
13798
13799 pub fn select_page_down(
13800 &mut self,
13801 _: &SelectPageDown,
13802 window: &mut Window,
13803 cx: &mut Context<Self>,
13804 ) {
13805 let Some(row_count) = self.visible_row_count() else {
13806 return;
13807 };
13808
13809 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13810
13811 let text_layout_details = &self.text_layout_details(window);
13812
13813 self.change_selections(Default::default(), window, cx, |s| {
13814 s.move_heads_with(|map, head, goal| {
13815 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13816 })
13817 })
13818 }
13819
13820 pub fn move_page_down(
13821 &mut self,
13822 action: &MovePageDown,
13823 window: &mut Window,
13824 cx: &mut Context<Self>,
13825 ) {
13826 if self.take_rename(true, window, cx).is_some() {
13827 return;
13828 }
13829
13830 if self
13831 .context_menu
13832 .borrow_mut()
13833 .as_mut()
13834 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13835 .unwrap_or(false)
13836 {
13837 return;
13838 }
13839
13840 if matches!(self.mode, EditorMode::SingleLine) {
13841 cx.propagate();
13842 return;
13843 }
13844
13845 let Some(row_count) = self.visible_row_count() else {
13846 return;
13847 };
13848
13849 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13850
13851 let effects = if action.center_cursor {
13852 SelectionEffects::scroll(Autoscroll::center())
13853 } else {
13854 SelectionEffects::default()
13855 };
13856
13857 let text_layout_details = &self.text_layout_details(window);
13858 self.change_selections(effects, window, cx, |s| {
13859 s.move_with(|map, selection| {
13860 if !selection.is_empty() {
13861 selection.goal = SelectionGoal::None;
13862 }
13863 let (cursor, goal) = movement::down_by_rows(
13864 map,
13865 selection.end,
13866 row_count,
13867 selection.goal,
13868 false,
13869 text_layout_details,
13870 );
13871 selection.collapse_to(cursor, goal);
13872 });
13873 });
13874 }
13875
13876 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13877 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13878 let text_layout_details = &self.text_layout_details(window);
13879 self.change_selections(Default::default(), window, cx, |s| {
13880 s.move_heads_with(|map, head, goal| {
13881 movement::down(map, head, goal, false, text_layout_details)
13882 })
13883 });
13884 }
13885
13886 pub fn context_menu_first(
13887 &mut self,
13888 _: &ContextMenuFirst,
13889 window: &mut Window,
13890 cx: &mut Context<Self>,
13891 ) {
13892 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13893 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13894 }
13895 }
13896
13897 pub fn context_menu_prev(
13898 &mut self,
13899 _: &ContextMenuPrevious,
13900 window: &mut Window,
13901 cx: &mut Context<Self>,
13902 ) {
13903 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13904 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13905 }
13906 }
13907
13908 pub fn context_menu_next(
13909 &mut self,
13910 _: &ContextMenuNext,
13911 window: &mut Window,
13912 cx: &mut Context<Self>,
13913 ) {
13914 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13915 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13916 }
13917 }
13918
13919 pub fn context_menu_last(
13920 &mut self,
13921 _: &ContextMenuLast,
13922 window: &mut Window,
13923 cx: &mut Context<Self>,
13924 ) {
13925 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13926 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13927 }
13928 }
13929
13930 pub fn signature_help_prev(
13931 &mut self,
13932 _: &SignatureHelpPrevious,
13933 _: &mut Window,
13934 cx: &mut Context<Self>,
13935 ) {
13936 if let Some(popover) = self.signature_help_state.popover_mut() {
13937 if popover.current_signature == 0 {
13938 popover.current_signature = popover.signatures.len() - 1;
13939 } else {
13940 popover.current_signature -= 1;
13941 }
13942 cx.notify();
13943 }
13944 }
13945
13946 pub fn signature_help_next(
13947 &mut self,
13948 _: &SignatureHelpNext,
13949 _: &mut Window,
13950 cx: &mut Context<Self>,
13951 ) {
13952 if let Some(popover) = self.signature_help_state.popover_mut() {
13953 if popover.current_signature + 1 == popover.signatures.len() {
13954 popover.current_signature = 0;
13955 } else {
13956 popover.current_signature += 1;
13957 }
13958 cx.notify();
13959 }
13960 }
13961
13962 pub fn move_to_previous_word_start(
13963 &mut self,
13964 _: &MoveToPreviousWordStart,
13965 window: &mut Window,
13966 cx: &mut Context<Self>,
13967 ) {
13968 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13969 self.change_selections(Default::default(), window, cx, |s| {
13970 s.move_cursors_with(|map, head, _| {
13971 (
13972 movement::previous_word_start(map, head),
13973 SelectionGoal::None,
13974 )
13975 });
13976 })
13977 }
13978
13979 pub fn move_to_previous_subword_start(
13980 &mut self,
13981 _: &MoveToPreviousSubwordStart,
13982 window: &mut Window,
13983 cx: &mut Context<Self>,
13984 ) {
13985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13986 self.change_selections(Default::default(), window, cx, |s| {
13987 s.move_cursors_with(|map, head, _| {
13988 (
13989 movement::previous_subword_start(map, head),
13990 SelectionGoal::None,
13991 )
13992 });
13993 })
13994 }
13995
13996 pub fn select_to_previous_word_start(
13997 &mut self,
13998 _: &SelectToPreviousWordStart,
13999 window: &mut Window,
14000 cx: &mut Context<Self>,
14001 ) {
14002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14003 self.change_selections(Default::default(), window, cx, |s| {
14004 s.move_heads_with(|map, head, _| {
14005 (
14006 movement::previous_word_start(map, head),
14007 SelectionGoal::None,
14008 )
14009 });
14010 })
14011 }
14012
14013 pub fn select_to_previous_subword_start(
14014 &mut self,
14015 _: &SelectToPreviousSubwordStart,
14016 window: &mut Window,
14017 cx: &mut Context<Self>,
14018 ) {
14019 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14020 self.change_selections(Default::default(), window, cx, |s| {
14021 s.move_heads_with(|map, head, _| {
14022 (
14023 movement::previous_subword_start(map, head),
14024 SelectionGoal::None,
14025 )
14026 });
14027 })
14028 }
14029
14030 pub fn delete_to_previous_word_start(
14031 &mut self,
14032 action: &DeleteToPreviousWordStart,
14033 window: &mut Window,
14034 cx: &mut Context<Self>,
14035 ) {
14036 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14037 self.transact(window, cx, |this, window, cx| {
14038 this.select_autoclose_pair(window, cx);
14039 this.change_selections(Default::default(), window, cx, |s| {
14040 s.move_with(|map, selection| {
14041 if selection.is_empty() {
14042 let mut cursor = if action.ignore_newlines {
14043 movement::previous_word_start(map, selection.head())
14044 } else {
14045 movement::previous_word_start_or_newline(map, selection.head())
14046 };
14047 cursor = movement::adjust_greedy_deletion(
14048 map,
14049 selection.head(),
14050 cursor,
14051 action.ignore_brackets,
14052 );
14053 selection.set_head(cursor, SelectionGoal::None);
14054 }
14055 });
14056 });
14057 this.insert("", window, cx);
14058 });
14059 }
14060
14061 pub fn delete_to_previous_subword_start(
14062 &mut self,
14063 _: &DeleteToPreviousSubwordStart,
14064 window: &mut Window,
14065 cx: &mut Context<Self>,
14066 ) {
14067 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14068 self.transact(window, cx, |this, window, cx| {
14069 this.select_autoclose_pair(window, cx);
14070 this.change_selections(Default::default(), window, cx, |s| {
14071 s.move_with(|map, selection| {
14072 if selection.is_empty() {
14073 let mut cursor = movement::previous_subword_start(map, selection.head());
14074 cursor =
14075 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14076 selection.set_head(cursor, SelectionGoal::None);
14077 }
14078 });
14079 });
14080 this.insert("", window, cx);
14081 });
14082 }
14083
14084 pub fn move_to_next_word_end(
14085 &mut self,
14086 _: &MoveToNextWordEnd,
14087 window: &mut Window,
14088 cx: &mut Context<Self>,
14089 ) {
14090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14091 self.change_selections(Default::default(), window, cx, |s| {
14092 s.move_cursors_with(|map, head, _| {
14093 (movement::next_word_end(map, head), SelectionGoal::None)
14094 });
14095 })
14096 }
14097
14098 pub fn move_to_next_subword_end(
14099 &mut self,
14100 _: &MoveToNextSubwordEnd,
14101 window: &mut Window,
14102 cx: &mut Context<Self>,
14103 ) {
14104 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14105 self.change_selections(Default::default(), window, cx, |s| {
14106 s.move_cursors_with(|map, head, _| {
14107 (movement::next_subword_end(map, head), SelectionGoal::None)
14108 });
14109 })
14110 }
14111
14112 pub fn select_to_next_word_end(
14113 &mut self,
14114 _: &SelectToNextWordEnd,
14115 window: &mut Window,
14116 cx: &mut Context<Self>,
14117 ) {
14118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14119 self.change_selections(Default::default(), window, cx, |s| {
14120 s.move_heads_with(|map, head, _| {
14121 (movement::next_word_end(map, head), SelectionGoal::None)
14122 });
14123 })
14124 }
14125
14126 pub fn select_to_next_subword_end(
14127 &mut self,
14128 _: &SelectToNextSubwordEnd,
14129 window: &mut Window,
14130 cx: &mut Context<Self>,
14131 ) {
14132 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14133 self.change_selections(Default::default(), window, cx, |s| {
14134 s.move_heads_with(|map, head, _| {
14135 (movement::next_subword_end(map, head), SelectionGoal::None)
14136 });
14137 })
14138 }
14139
14140 pub fn delete_to_next_word_end(
14141 &mut self,
14142 action: &DeleteToNextWordEnd,
14143 window: &mut Window,
14144 cx: &mut Context<Self>,
14145 ) {
14146 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14147 self.transact(window, cx, |this, window, cx| {
14148 this.change_selections(Default::default(), window, cx, |s| {
14149 s.move_with(|map, selection| {
14150 if selection.is_empty() {
14151 let mut cursor = if action.ignore_newlines {
14152 movement::next_word_end(map, selection.head())
14153 } else {
14154 movement::next_word_end_or_newline(map, selection.head())
14155 };
14156 cursor = movement::adjust_greedy_deletion(
14157 map,
14158 selection.head(),
14159 cursor,
14160 action.ignore_brackets,
14161 );
14162 selection.set_head(cursor, SelectionGoal::None);
14163 }
14164 });
14165 });
14166 this.insert("", window, cx);
14167 });
14168 }
14169
14170 pub fn delete_to_next_subword_end(
14171 &mut self,
14172 _: &DeleteToNextSubwordEnd,
14173 window: &mut Window,
14174 cx: &mut Context<Self>,
14175 ) {
14176 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14177 self.transact(window, cx, |this, window, cx| {
14178 this.change_selections(Default::default(), window, cx, |s| {
14179 s.move_with(|map, selection| {
14180 if selection.is_empty() {
14181 let mut cursor = movement::next_subword_end(map, selection.head());
14182 cursor =
14183 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14184 selection.set_head(cursor, SelectionGoal::None);
14185 }
14186 });
14187 });
14188 this.insert("", window, cx);
14189 });
14190 }
14191
14192 pub fn move_to_beginning_of_line(
14193 &mut self,
14194 action: &MoveToBeginningOfLine,
14195 window: &mut Window,
14196 cx: &mut Context<Self>,
14197 ) {
14198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14199 self.change_selections(Default::default(), window, cx, |s| {
14200 s.move_cursors_with(|map, head, _| {
14201 (
14202 movement::indented_line_beginning(
14203 map,
14204 head,
14205 action.stop_at_soft_wraps,
14206 action.stop_at_indent,
14207 ),
14208 SelectionGoal::None,
14209 )
14210 });
14211 })
14212 }
14213
14214 pub fn select_to_beginning_of_line(
14215 &mut self,
14216 action: &SelectToBeginningOfLine,
14217 window: &mut Window,
14218 cx: &mut Context<Self>,
14219 ) {
14220 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14221 self.change_selections(Default::default(), window, cx, |s| {
14222 s.move_heads_with(|map, head, _| {
14223 (
14224 movement::indented_line_beginning(
14225 map,
14226 head,
14227 action.stop_at_soft_wraps,
14228 action.stop_at_indent,
14229 ),
14230 SelectionGoal::None,
14231 )
14232 });
14233 });
14234 }
14235
14236 pub fn delete_to_beginning_of_line(
14237 &mut self,
14238 action: &DeleteToBeginningOfLine,
14239 window: &mut Window,
14240 cx: &mut Context<Self>,
14241 ) {
14242 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14243 self.transact(window, cx, |this, window, cx| {
14244 this.change_selections(Default::default(), window, cx, |s| {
14245 s.move_with(|_, selection| {
14246 selection.reversed = true;
14247 });
14248 });
14249
14250 this.select_to_beginning_of_line(
14251 &SelectToBeginningOfLine {
14252 stop_at_soft_wraps: false,
14253 stop_at_indent: action.stop_at_indent,
14254 },
14255 window,
14256 cx,
14257 );
14258 this.backspace(&Backspace, window, cx);
14259 });
14260 }
14261
14262 pub fn move_to_end_of_line(
14263 &mut self,
14264 action: &MoveToEndOfLine,
14265 window: &mut Window,
14266 cx: &mut Context<Self>,
14267 ) {
14268 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14269 self.change_selections(Default::default(), window, cx, |s| {
14270 s.move_cursors_with(|map, head, _| {
14271 (
14272 movement::line_end(map, head, action.stop_at_soft_wraps),
14273 SelectionGoal::None,
14274 )
14275 });
14276 })
14277 }
14278
14279 pub fn select_to_end_of_line(
14280 &mut self,
14281 action: &SelectToEndOfLine,
14282 window: &mut Window,
14283 cx: &mut Context<Self>,
14284 ) {
14285 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14286 self.change_selections(Default::default(), window, cx, |s| {
14287 s.move_heads_with(|map, head, _| {
14288 (
14289 movement::line_end(map, head, action.stop_at_soft_wraps),
14290 SelectionGoal::None,
14291 )
14292 });
14293 })
14294 }
14295
14296 pub fn delete_to_end_of_line(
14297 &mut self,
14298 _: &DeleteToEndOfLine,
14299 window: &mut Window,
14300 cx: &mut Context<Self>,
14301 ) {
14302 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14303 self.transact(window, cx, |this, window, cx| {
14304 this.select_to_end_of_line(
14305 &SelectToEndOfLine {
14306 stop_at_soft_wraps: false,
14307 },
14308 window,
14309 cx,
14310 );
14311 this.delete(&Delete, window, cx);
14312 });
14313 }
14314
14315 pub fn cut_to_end_of_line(
14316 &mut self,
14317 action: &CutToEndOfLine,
14318 window: &mut Window,
14319 cx: &mut Context<Self>,
14320 ) {
14321 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14322 self.transact(window, cx, |this, window, cx| {
14323 this.select_to_end_of_line(
14324 &SelectToEndOfLine {
14325 stop_at_soft_wraps: false,
14326 },
14327 window,
14328 cx,
14329 );
14330 if !action.stop_at_newlines {
14331 this.change_selections(Default::default(), window, cx, |s| {
14332 s.move_with(|_, sel| {
14333 if sel.is_empty() {
14334 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14335 }
14336 });
14337 });
14338 }
14339 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14340 let item = this.cut_common(false, window, cx);
14341 cx.write_to_clipboard(item);
14342 });
14343 }
14344
14345 pub fn move_to_start_of_paragraph(
14346 &mut self,
14347 _: &MoveToStartOfParagraph,
14348 window: &mut Window,
14349 cx: &mut Context<Self>,
14350 ) {
14351 if matches!(self.mode, EditorMode::SingleLine) {
14352 cx.propagate();
14353 return;
14354 }
14355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14356 self.change_selections(Default::default(), window, cx, |s| {
14357 s.move_with(|map, selection| {
14358 selection.collapse_to(
14359 movement::start_of_paragraph(map, selection.head(), 1),
14360 SelectionGoal::None,
14361 )
14362 });
14363 })
14364 }
14365
14366 pub fn move_to_end_of_paragraph(
14367 &mut self,
14368 _: &MoveToEndOfParagraph,
14369 window: &mut Window,
14370 cx: &mut Context<Self>,
14371 ) {
14372 if matches!(self.mode, EditorMode::SingleLine) {
14373 cx.propagate();
14374 return;
14375 }
14376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14377 self.change_selections(Default::default(), window, cx, |s| {
14378 s.move_with(|map, selection| {
14379 selection.collapse_to(
14380 movement::end_of_paragraph(map, selection.head(), 1),
14381 SelectionGoal::None,
14382 )
14383 });
14384 })
14385 }
14386
14387 pub fn select_to_start_of_paragraph(
14388 &mut self,
14389 _: &SelectToStartOfParagraph,
14390 window: &mut Window,
14391 cx: &mut Context<Self>,
14392 ) {
14393 if matches!(self.mode, EditorMode::SingleLine) {
14394 cx.propagate();
14395 return;
14396 }
14397 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14398 self.change_selections(Default::default(), window, cx, |s| {
14399 s.move_heads_with(|map, head, _| {
14400 (
14401 movement::start_of_paragraph(map, head, 1),
14402 SelectionGoal::None,
14403 )
14404 });
14405 })
14406 }
14407
14408 pub fn select_to_end_of_paragraph(
14409 &mut self,
14410 _: &SelectToEndOfParagraph,
14411 window: &mut Window,
14412 cx: &mut Context<Self>,
14413 ) {
14414 if matches!(self.mode, EditorMode::SingleLine) {
14415 cx.propagate();
14416 return;
14417 }
14418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14419 self.change_selections(Default::default(), window, cx, |s| {
14420 s.move_heads_with(|map, head, _| {
14421 (
14422 movement::end_of_paragraph(map, head, 1),
14423 SelectionGoal::None,
14424 )
14425 });
14426 })
14427 }
14428
14429 pub fn move_to_start_of_excerpt(
14430 &mut self,
14431 _: &MoveToStartOfExcerpt,
14432 window: &mut Window,
14433 cx: &mut Context<Self>,
14434 ) {
14435 if matches!(self.mode, EditorMode::SingleLine) {
14436 cx.propagate();
14437 return;
14438 }
14439 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14440 self.change_selections(Default::default(), window, cx, |s| {
14441 s.move_with(|map, selection| {
14442 selection.collapse_to(
14443 movement::start_of_excerpt(
14444 map,
14445 selection.head(),
14446 workspace::searchable::Direction::Prev,
14447 ),
14448 SelectionGoal::None,
14449 )
14450 });
14451 })
14452 }
14453
14454 pub fn move_to_start_of_next_excerpt(
14455 &mut self,
14456 _: &MoveToStartOfNextExcerpt,
14457 window: &mut Window,
14458 cx: &mut Context<Self>,
14459 ) {
14460 if matches!(self.mode, EditorMode::SingleLine) {
14461 cx.propagate();
14462 return;
14463 }
14464
14465 self.change_selections(Default::default(), window, cx, |s| {
14466 s.move_with(|map, selection| {
14467 selection.collapse_to(
14468 movement::start_of_excerpt(
14469 map,
14470 selection.head(),
14471 workspace::searchable::Direction::Next,
14472 ),
14473 SelectionGoal::None,
14474 )
14475 });
14476 })
14477 }
14478
14479 pub fn move_to_end_of_excerpt(
14480 &mut self,
14481 _: &MoveToEndOfExcerpt,
14482 window: &mut Window,
14483 cx: &mut Context<Self>,
14484 ) {
14485 if matches!(self.mode, EditorMode::SingleLine) {
14486 cx.propagate();
14487 return;
14488 }
14489 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14490 self.change_selections(Default::default(), window, cx, |s| {
14491 s.move_with(|map, selection| {
14492 selection.collapse_to(
14493 movement::end_of_excerpt(
14494 map,
14495 selection.head(),
14496 workspace::searchable::Direction::Next,
14497 ),
14498 SelectionGoal::None,
14499 )
14500 });
14501 })
14502 }
14503
14504 pub fn move_to_end_of_previous_excerpt(
14505 &mut self,
14506 _: &MoveToEndOfPreviousExcerpt,
14507 window: &mut Window,
14508 cx: &mut Context<Self>,
14509 ) {
14510 if matches!(self.mode, EditorMode::SingleLine) {
14511 cx.propagate();
14512 return;
14513 }
14514 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14515 self.change_selections(Default::default(), window, cx, |s| {
14516 s.move_with(|map, selection| {
14517 selection.collapse_to(
14518 movement::end_of_excerpt(
14519 map,
14520 selection.head(),
14521 workspace::searchable::Direction::Prev,
14522 ),
14523 SelectionGoal::None,
14524 )
14525 });
14526 })
14527 }
14528
14529 pub fn select_to_start_of_excerpt(
14530 &mut self,
14531 _: &SelectToStartOfExcerpt,
14532 window: &mut Window,
14533 cx: &mut Context<Self>,
14534 ) {
14535 if matches!(self.mode, EditorMode::SingleLine) {
14536 cx.propagate();
14537 return;
14538 }
14539 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14540 self.change_selections(Default::default(), window, cx, |s| {
14541 s.move_heads_with(|map, head, _| {
14542 (
14543 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14544 SelectionGoal::None,
14545 )
14546 });
14547 })
14548 }
14549
14550 pub fn select_to_start_of_next_excerpt(
14551 &mut self,
14552 _: &SelectToStartOfNextExcerpt,
14553 window: &mut Window,
14554 cx: &mut Context<Self>,
14555 ) {
14556 if matches!(self.mode, EditorMode::SingleLine) {
14557 cx.propagate();
14558 return;
14559 }
14560 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14561 self.change_selections(Default::default(), window, cx, |s| {
14562 s.move_heads_with(|map, head, _| {
14563 (
14564 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14565 SelectionGoal::None,
14566 )
14567 });
14568 })
14569 }
14570
14571 pub fn select_to_end_of_excerpt(
14572 &mut self,
14573 _: &SelectToEndOfExcerpt,
14574 window: &mut Window,
14575 cx: &mut Context<Self>,
14576 ) {
14577 if matches!(self.mode, EditorMode::SingleLine) {
14578 cx.propagate();
14579 return;
14580 }
14581 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14582 self.change_selections(Default::default(), window, cx, |s| {
14583 s.move_heads_with(|map, head, _| {
14584 (
14585 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14586 SelectionGoal::None,
14587 )
14588 });
14589 })
14590 }
14591
14592 pub fn select_to_end_of_previous_excerpt(
14593 &mut self,
14594 _: &SelectToEndOfPreviousExcerpt,
14595 window: &mut Window,
14596 cx: &mut Context<Self>,
14597 ) {
14598 if matches!(self.mode, EditorMode::SingleLine) {
14599 cx.propagate();
14600 return;
14601 }
14602 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14603 self.change_selections(Default::default(), window, cx, |s| {
14604 s.move_heads_with(|map, head, _| {
14605 (
14606 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14607 SelectionGoal::None,
14608 )
14609 });
14610 })
14611 }
14612
14613 pub fn move_to_beginning(
14614 &mut self,
14615 _: &MoveToBeginning,
14616 window: &mut Window,
14617 cx: &mut Context<Self>,
14618 ) {
14619 if matches!(self.mode, EditorMode::SingleLine) {
14620 cx.propagate();
14621 return;
14622 }
14623 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14624 self.change_selections(Default::default(), window, cx, |s| {
14625 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14626 });
14627 }
14628
14629 pub fn select_to_beginning(
14630 &mut self,
14631 _: &SelectToBeginning,
14632 window: &mut Window,
14633 cx: &mut Context<Self>,
14634 ) {
14635 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14636 selection.set_head(Point::zero(), SelectionGoal::None);
14637 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14638 self.change_selections(Default::default(), window, cx, |s| {
14639 s.select(vec![selection]);
14640 });
14641 }
14642
14643 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14644 if matches!(self.mode, EditorMode::SingleLine) {
14645 cx.propagate();
14646 return;
14647 }
14648 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14649 let cursor = self.buffer.read(cx).read(cx).len();
14650 self.change_selections(Default::default(), window, cx, |s| {
14651 s.select_ranges(vec![cursor..cursor])
14652 });
14653 }
14654
14655 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14656 self.nav_history = nav_history;
14657 }
14658
14659 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14660 self.nav_history.as_ref()
14661 }
14662
14663 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14664 self.push_to_nav_history(
14665 self.selections.newest_anchor().head(),
14666 None,
14667 false,
14668 true,
14669 cx,
14670 );
14671 }
14672
14673 fn push_to_nav_history(
14674 &mut self,
14675 cursor_anchor: Anchor,
14676 new_position: Option<Point>,
14677 is_deactivate: bool,
14678 always: bool,
14679 cx: &mut Context<Self>,
14680 ) {
14681 if let Some(nav_history) = self.nav_history.as_mut() {
14682 let buffer = self.buffer.read(cx).read(cx);
14683 let cursor_position = cursor_anchor.to_point(&buffer);
14684 let scroll_state = self.scroll_manager.anchor();
14685 let scroll_top_row = scroll_state.top_row(&buffer);
14686 drop(buffer);
14687
14688 if let Some(new_position) = new_position {
14689 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14690 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14691 return;
14692 }
14693 }
14694
14695 nav_history.push(
14696 Some(NavigationData {
14697 cursor_anchor,
14698 cursor_position,
14699 scroll_anchor: scroll_state,
14700 scroll_top_row,
14701 }),
14702 cx,
14703 );
14704 cx.emit(EditorEvent::PushedToNavHistory {
14705 anchor: cursor_anchor,
14706 is_deactivate,
14707 })
14708 }
14709 }
14710
14711 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14712 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14713 let buffer = self.buffer.read(cx).snapshot(cx);
14714 let mut selection = self
14715 .selections
14716 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14717 selection.set_head(buffer.len(), SelectionGoal::None);
14718 self.change_selections(Default::default(), window, cx, |s| {
14719 s.select(vec![selection]);
14720 });
14721 }
14722
14723 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14724 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14725 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14726 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14727 });
14728 }
14729
14730 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14731 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14732 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14733 let mut selections = self.selections.all::<Point>(&display_map);
14734 let max_point = display_map.buffer_snapshot().max_point();
14735 for selection in &mut selections {
14736 let rows = selection.spanned_rows(true, &display_map);
14737 selection.start = Point::new(rows.start.0, 0);
14738 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14739 selection.reversed = false;
14740 }
14741 self.change_selections(Default::default(), window, cx, |s| {
14742 s.select(selections);
14743 });
14744 }
14745
14746 pub fn split_selection_into_lines(
14747 &mut self,
14748 action: &SplitSelectionIntoLines,
14749 window: &mut Window,
14750 cx: &mut Context<Self>,
14751 ) {
14752 let selections = self
14753 .selections
14754 .all::<Point>(&self.display_snapshot(cx))
14755 .into_iter()
14756 .map(|selection| selection.start..selection.end)
14757 .collect::<Vec<_>>();
14758 self.unfold_ranges(&selections, true, true, cx);
14759
14760 let mut new_selection_ranges = Vec::new();
14761 {
14762 let buffer = self.buffer.read(cx).read(cx);
14763 for selection in selections {
14764 for row in selection.start.row..selection.end.row {
14765 let line_start = Point::new(row, 0);
14766 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14767
14768 if action.keep_selections {
14769 // Keep the selection range for each line
14770 let selection_start = if row == selection.start.row {
14771 selection.start
14772 } else {
14773 line_start
14774 };
14775 new_selection_ranges.push(selection_start..line_end);
14776 } else {
14777 // Collapse to cursor at end of line
14778 new_selection_ranges.push(line_end..line_end);
14779 }
14780 }
14781
14782 let is_multiline_selection = selection.start.row != selection.end.row;
14783 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14784 // so this action feels more ergonomic when paired with other selection operations
14785 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14786 if !should_skip_last {
14787 if action.keep_selections {
14788 if is_multiline_selection {
14789 let line_start = Point::new(selection.end.row, 0);
14790 new_selection_ranges.push(line_start..selection.end);
14791 } else {
14792 new_selection_ranges.push(selection.start..selection.end);
14793 }
14794 } else {
14795 new_selection_ranges.push(selection.end..selection.end);
14796 }
14797 }
14798 }
14799 }
14800 self.change_selections(Default::default(), window, cx, |s| {
14801 s.select_ranges(new_selection_ranges);
14802 });
14803 }
14804
14805 pub fn add_selection_above(
14806 &mut self,
14807 action: &AddSelectionAbove,
14808 window: &mut Window,
14809 cx: &mut Context<Self>,
14810 ) {
14811 self.add_selection(true, action.skip_soft_wrap, window, cx);
14812 }
14813
14814 pub fn add_selection_below(
14815 &mut self,
14816 action: &AddSelectionBelow,
14817 window: &mut Window,
14818 cx: &mut Context<Self>,
14819 ) {
14820 self.add_selection(false, action.skip_soft_wrap, window, cx);
14821 }
14822
14823 fn add_selection(
14824 &mut self,
14825 above: bool,
14826 skip_soft_wrap: bool,
14827 window: &mut Window,
14828 cx: &mut Context<Self>,
14829 ) {
14830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14831
14832 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14833 let all_selections = self.selections.all::<Point>(&display_map);
14834 let text_layout_details = self.text_layout_details(window);
14835
14836 let (mut columnar_selections, new_selections_to_columnarize) = {
14837 if let Some(state) = self.add_selections_state.as_ref() {
14838 let columnar_selection_ids: HashSet<_> = state
14839 .groups
14840 .iter()
14841 .flat_map(|group| group.stack.iter())
14842 .copied()
14843 .collect();
14844
14845 all_selections
14846 .into_iter()
14847 .partition(|s| columnar_selection_ids.contains(&s.id))
14848 } else {
14849 (Vec::new(), all_selections)
14850 }
14851 };
14852
14853 let mut state = self
14854 .add_selections_state
14855 .take()
14856 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14857
14858 for selection in new_selections_to_columnarize {
14859 let range = selection.display_range(&display_map).sorted();
14860 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14861 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14862 let positions = start_x.min(end_x)..start_x.max(end_x);
14863 let mut stack = Vec::new();
14864 for row in range.start.row().0..=range.end.row().0 {
14865 if let Some(selection) = self.selections.build_columnar_selection(
14866 &display_map,
14867 DisplayRow(row),
14868 &positions,
14869 selection.reversed,
14870 &text_layout_details,
14871 ) {
14872 stack.push(selection.id);
14873 columnar_selections.push(selection);
14874 }
14875 }
14876 if !stack.is_empty() {
14877 if above {
14878 stack.reverse();
14879 }
14880 state.groups.push(AddSelectionsGroup { above, stack });
14881 }
14882 }
14883
14884 let mut final_selections = Vec::new();
14885 let end_row = if above {
14886 DisplayRow(0)
14887 } else {
14888 display_map.max_point().row()
14889 };
14890
14891 let mut last_added_item_per_group = HashMap::default();
14892 for group in state.groups.iter_mut() {
14893 if let Some(last_id) = group.stack.last() {
14894 last_added_item_per_group.insert(*last_id, group);
14895 }
14896 }
14897
14898 for selection in columnar_selections {
14899 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14900 if above == group.above {
14901 let range = selection.display_range(&display_map).sorted();
14902 debug_assert_eq!(range.start.row(), range.end.row());
14903 let mut row = range.start.row();
14904 let positions =
14905 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14906 Pixels::from(start)..Pixels::from(end)
14907 } else {
14908 let start_x =
14909 display_map.x_for_display_point(range.start, &text_layout_details);
14910 let end_x =
14911 display_map.x_for_display_point(range.end, &text_layout_details);
14912 start_x.min(end_x)..start_x.max(end_x)
14913 };
14914
14915 let mut maybe_new_selection = None;
14916 let direction = if above { -1 } else { 1 };
14917
14918 while row != end_row {
14919 if skip_soft_wrap {
14920 row = display_map
14921 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14922 .row();
14923 } else if above {
14924 row.0 -= 1;
14925 } else {
14926 row.0 += 1;
14927 }
14928
14929 if let Some(new_selection) = self.selections.build_columnar_selection(
14930 &display_map,
14931 row,
14932 &positions,
14933 selection.reversed,
14934 &text_layout_details,
14935 ) {
14936 maybe_new_selection = Some(new_selection);
14937 break;
14938 }
14939 }
14940
14941 if let Some(new_selection) = maybe_new_selection {
14942 group.stack.push(new_selection.id);
14943 if above {
14944 final_selections.push(new_selection);
14945 final_selections.push(selection);
14946 } else {
14947 final_selections.push(selection);
14948 final_selections.push(new_selection);
14949 }
14950 } else {
14951 final_selections.push(selection);
14952 }
14953 } else {
14954 group.stack.pop();
14955 }
14956 } else {
14957 final_selections.push(selection);
14958 }
14959 }
14960
14961 self.change_selections(Default::default(), window, cx, |s| {
14962 s.select(final_selections);
14963 });
14964
14965 let final_selection_ids: HashSet<_> = self
14966 .selections
14967 .all::<Point>(&display_map)
14968 .iter()
14969 .map(|s| s.id)
14970 .collect();
14971 state.groups.retain_mut(|group| {
14972 // selections might get merged above so we remove invalid items from stacks
14973 group.stack.retain(|id| final_selection_ids.contains(id));
14974
14975 // single selection in stack can be treated as initial state
14976 group.stack.len() > 1
14977 });
14978
14979 if !state.groups.is_empty() {
14980 self.add_selections_state = Some(state);
14981 }
14982 }
14983
14984 pub fn insert_snippet_at_selections(
14985 &mut self,
14986 action: &InsertSnippet,
14987 window: &mut Window,
14988 cx: &mut Context<Self>,
14989 ) {
14990 self.try_insert_snippet_at_selections(action, window, cx)
14991 .log_err();
14992 }
14993
14994 fn try_insert_snippet_at_selections(
14995 &mut self,
14996 action: &InsertSnippet,
14997 window: &mut Window,
14998 cx: &mut Context<Self>,
14999 ) -> Result<()> {
15000 let insertion_ranges = self
15001 .selections
15002 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15003 .into_iter()
15004 .map(|selection| selection.range())
15005 .collect_vec();
15006
15007 let snippet = if let Some(snippet_body) = &action.snippet {
15008 if action.language.is_none() && action.name.is_none() {
15009 Snippet::parse(snippet_body)?
15010 } else {
15011 bail!("`snippet` is mutually exclusive with `language` and `name`")
15012 }
15013 } else if let Some(name) = &action.name {
15014 let project = self.project().context("no project")?;
15015 let snippet_store = project.read(cx).snippets().read(cx);
15016 let snippet = snippet_store
15017 .snippets_for(action.language.clone(), cx)
15018 .into_iter()
15019 .find(|snippet| snippet.name == *name)
15020 .context("snippet not found")?;
15021 Snippet::parse(&snippet.body)?
15022 } else {
15023 // todo(andrew): open modal to select snippet
15024 bail!("`name` or `snippet` is required")
15025 };
15026
15027 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15028 }
15029
15030 fn select_match_ranges(
15031 &mut self,
15032 range: Range<MultiBufferOffset>,
15033 reversed: bool,
15034 replace_newest: bool,
15035 auto_scroll: Option<Autoscroll>,
15036 window: &mut Window,
15037 cx: &mut Context<Editor>,
15038 ) {
15039 self.unfold_ranges(
15040 std::slice::from_ref(&range),
15041 false,
15042 auto_scroll.is_some(),
15043 cx,
15044 );
15045 let effects = if let Some(scroll) = auto_scroll {
15046 SelectionEffects::scroll(scroll)
15047 } else {
15048 SelectionEffects::no_scroll()
15049 };
15050 self.change_selections(effects, window, cx, |s| {
15051 if replace_newest {
15052 s.delete(s.newest_anchor().id);
15053 }
15054 if reversed {
15055 s.insert_range(range.end..range.start);
15056 } else {
15057 s.insert_range(range);
15058 }
15059 });
15060 }
15061
15062 pub fn select_next_match_internal(
15063 &mut self,
15064 display_map: &DisplaySnapshot,
15065 replace_newest: bool,
15066 autoscroll: Option<Autoscroll>,
15067 window: &mut Window,
15068 cx: &mut Context<Self>,
15069 ) -> Result<()> {
15070 let buffer = display_map.buffer_snapshot();
15071 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15072 if let Some(mut select_next_state) = self.select_next_state.take() {
15073 let query = &select_next_state.query;
15074 if !select_next_state.done {
15075 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15076 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15077 let mut next_selected_range = None;
15078
15079 let bytes_after_last_selection =
15080 buffer.bytes_in_range(last_selection.end..buffer.len());
15081 let bytes_before_first_selection =
15082 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15083 let query_matches = query
15084 .stream_find_iter(bytes_after_last_selection)
15085 .map(|result| (last_selection.end, result))
15086 .chain(
15087 query
15088 .stream_find_iter(bytes_before_first_selection)
15089 .map(|result| (MultiBufferOffset(0), result)),
15090 );
15091
15092 for (start_offset, query_match) in query_matches {
15093 let query_match = query_match.unwrap(); // can only fail due to I/O
15094 let offset_range =
15095 start_offset + query_match.start()..start_offset + query_match.end();
15096
15097 if !select_next_state.wordwise
15098 || (!buffer.is_inside_word(offset_range.start, None)
15099 && !buffer.is_inside_word(offset_range.end, None))
15100 {
15101 let idx = selections
15102 .partition_point(|selection| selection.end <= offset_range.start);
15103 let overlaps = selections
15104 .get(idx)
15105 .map_or(false, |selection| selection.start < offset_range.end);
15106
15107 if !overlaps {
15108 next_selected_range = Some(offset_range);
15109 break;
15110 }
15111 }
15112 }
15113
15114 if let Some(next_selected_range) = next_selected_range {
15115 self.select_match_ranges(
15116 next_selected_range,
15117 last_selection.reversed,
15118 replace_newest,
15119 autoscroll,
15120 window,
15121 cx,
15122 );
15123 } else {
15124 select_next_state.done = true;
15125 }
15126 }
15127
15128 self.select_next_state = Some(select_next_state);
15129 } else {
15130 let mut only_carets = true;
15131 let mut same_text_selected = true;
15132 let mut selected_text = None;
15133
15134 let mut selections_iter = selections.iter().peekable();
15135 while let Some(selection) = selections_iter.next() {
15136 if selection.start != selection.end {
15137 only_carets = false;
15138 }
15139
15140 if same_text_selected {
15141 if selected_text.is_none() {
15142 selected_text =
15143 Some(buffer.text_for_range(selection.range()).collect::<String>());
15144 }
15145
15146 if let Some(next_selection) = selections_iter.peek() {
15147 if next_selection.len() == selection.len() {
15148 let next_selected_text = buffer
15149 .text_for_range(next_selection.range())
15150 .collect::<String>();
15151 if Some(next_selected_text) != selected_text {
15152 same_text_selected = false;
15153 selected_text = None;
15154 }
15155 } else {
15156 same_text_selected = false;
15157 selected_text = None;
15158 }
15159 }
15160 }
15161 }
15162
15163 if only_carets {
15164 for selection in &mut selections {
15165 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15166 selection.start = word_range.start;
15167 selection.end = word_range.end;
15168 selection.goal = SelectionGoal::None;
15169 selection.reversed = false;
15170 self.select_match_ranges(
15171 selection.start..selection.end,
15172 selection.reversed,
15173 replace_newest,
15174 autoscroll,
15175 window,
15176 cx,
15177 );
15178 }
15179
15180 if selections.len() == 1 {
15181 let selection = selections
15182 .last()
15183 .expect("ensured that there's only one selection");
15184 let query = buffer
15185 .text_for_range(selection.start..selection.end)
15186 .collect::<String>();
15187 let is_empty = query.is_empty();
15188 let select_state = SelectNextState {
15189 query: self.build_query(&[query], cx)?,
15190 wordwise: true,
15191 done: is_empty,
15192 };
15193 self.select_next_state = Some(select_state);
15194 } else {
15195 self.select_next_state = None;
15196 }
15197 } else if let Some(selected_text) = selected_text {
15198 self.select_next_state = Some(SelectNextState {
15199 query: self.build_query(&[selected_text], cx)?,
15200 wordwise: false,
15201 done: false,
15202 });
15203 self.select_next_match_internal(
15204 display_map,
15205 replace_newest,
15206 autoscroll,
15207 window,
15208 cx,
15209 )?;
15210 }
15211 }
15212 Ok(())
15213 }
15214
15215 pub fn select_all_matches(
15216 &mut self,
15217 _action: &SelectAllMatches,
15218 window: &mut Window,
15219 cx: &mut Context<Self>,
15220 ) -> Result<()> {
15221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15222
15223 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15224
15225 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15226 let Some(select_next_state) = self.select_next_state.as_mut() else {
15227 return Ok(());
15228 };
15229 if select_next_state.done {
15230 return Ok(());
15231 }
15232
15233 let mut new_selections = Vec::new();
15234
15235 let reversed = self
15236 .selections
15237 .oldest::<MultiBufferOffset>(&display_map)
15238 .reversed;
15239 let buffer = display_map.buffer_snapshot();
15240 let query_matches = select_next_state
15241 .query
15242 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15243
15244 for query_match in query_matches.into_iter() {
15245 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15246 let offset_range = if reversed {
15247 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15248 } else {
15249 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15250 };
15251
15252 if !select_next_state.wordwise
15253 || (!buffer.is_inside_word(offset_range.start, None)
15254 && !buffer.is_inside_word(offset_range.end, None))
15255 {
15256 new_selections.push(offset_range.start..offset_range.end);
15257 }
15258 }
15259
15260 select_next_state.done = true;
15261
15262 if new_selections.is_empty() {
15263 log::error!("bug: new_selections is empty in select_all_matches");
15264 return Ok(());
15265 }
15266
15267 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15268 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15269 selections.select_ranges(new_selections)
15270 });
15271
15272 Ok(())
15273 }
15274
15275 pub fn select_next(
15276 &mut self,
15277 action: &SelectNext,
15278 window: &mut Window,
15279 cx: &mut Context<Self>,
15280 ) -> Result<()> {
15281 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15282 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15283 self.select_next_match_internal(
15284 &display_map,
15285 action.replace_newest,
15286 Some(Autoscroll::newest()),
15287 window,
15288 cx,
15289 )?;
15290 Ok(())
15291 }
15292
15293 pub fn select_previous(
15294 &mut self,
15295 action: &SelectPrevious,
15296 window: &mut Window,
15297 cx: &mut Context<Self>,
15298 ) -> Result<()> {
15299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15300 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15301 let buffer = display_map.buffer_snapshot();
15302 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15303 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15304 let query = &select_prev_state.query;
15305 if !select_prev_state.done {
15306 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15307 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15308 let mut next_selected_range = None;
15309 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15310 let bytes_before_last_selection =
15311 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15312 let bytes_after_first_selection =
15313 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15314 let query_matches = query
15315 .stream_find_iter(bytes_before_last_selection)
15316 .map(|result| (last_selection.start, result))
15317 .chain(
15318 query
15319 .stream_find_iter(bytes_after_first_selection)
15320 .map(|result| (buffer.len(), result)),
15321 );
15322 for (end_offset, query_match) in query_matches {
15323 let query_match = query_match.unwrap(); // can only fail due to I/O
15324 let offset_range =
15325 end_offset - query_match.end()..end_offset - query_match.start();
15326
15327 if !select_prev_state.wordwise
15328 || (!buffer.is_inside_word(offset_range.start, None)
15329 && !buffer.is_inside_word(offset_range.end, None))
15330 {
15331 next_selected_range = Some(offset_range);
15332 break;
15333 }
15334 }
15335
15336 if let Some(next_selected_range) = next_selected_range {
15337 self.select_match_ranges(
15338 next_selected_range,
15339 last_selection.reversed,
15340 action.replace_newest,
15341 Some(Autoscroll::newest()),
15342 window,
15343 cx,
15344 );
15345 } else {
15346 select_prev_state.done = true;
15347 }
15348 }
15349
15350 self.select_prev_state = Some(select_prev_state);
15351 } else {
15352 let mut only_carets = true;
15353 let mut same_text_selected = true;
15354 let mut selected_text = None;
15355
15356 let mut selections_iter = selections.iter().peekable();
15357 while let Some(selection) = selections_iter.next() {
15358 if selection.start != selection.end {
15359 only_carets = false;
15360 }
15361
15362 if same_text_selected {
15363 if selected_text.is_none() {
15364 selected_text =
15365 Some(buffer.text_for_range(selection.range()).collect::<String>());
15366 }
15367
15368 if let Some(next_selection) = selections_iter.peek() {
15369 if next_selection.len() == selection.len() {
15370 let next_selected_text = buffer
15371 .text_for_range(next_selection.range())
15372 .collect::<String>();
15373 if Some(next_selected_text) != selected_text {
15374 same_text_selected = false;
15375 selected_text = None;
15376 }
15377 } else {
15378 same_text_selected = false;
15379 selected_text = None;
15380 }
15381 }
15382 }
15383 }
15384
15385 if only_carets {
15386 for selection in &mut selections {
15387 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15388 selection.start = word_range.start;
15389 selection.end = word_range.end;
15390 selection.goal = SelectionGoal::None;
15391 selection.reversed = false;
15392 self.select_match_ranges(
15393 selection.start..selection.end,
15394 selection.reversed,
15395 action.replace_newest,
15396 Some(Autoscroll::newest()),
15397 window,
15398 cx,
15399 );
15400 }
15401 if selections.len() == 1 {
15402 let selection = selections
15403 .last()
15404 .expect("ensured that there's only one selection");
15405 let query = buffer
15406 .text_for_range(selection.start..selection.end)
15407 .collect::<String>();
15408 let is_empty = query.is_empty();
15409 let select_state = SelectNextState {
15410 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15411 wordwise: true,
15412 done: is_empty,
15413 };
15414 self.select_prev_state = Some(select_state);
15415 } else {
15416 self.select_prev_state = None;
15417 }
15418 } else if let Some(selected_text) = selected_text {
15419 self.select_prev_state = Some(SelectNextState {
15420 query: self
15421 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15422 wordwise: false,
15423 done: false,
15424 });
15425 self.select_previous(action, window, cx)?;
15426 }
15427 }
15428 Ok(())
15429 }
15430
15431 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15432 /// setting the case sensitivity based on the global
15433 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15434 /// editor's settings.
15435 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15436 where
15437 I: IntoIterator<Item = P>,
15438 P: AsRef<[u8]>,
15439 {
15440 let case_sensitive = self
15441 .select_next_is_case_sensitive
15442 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15443
15444 let mut builder = AhoCorasickBuilder::new();
15445 builder.ascii_case_insensitive(!case_sensitive);
15446 builder.build(patterns)
15447 }
15448
15449 pub fn find_next_match(
15450 &mut self,
15451 _: &FindNextMatch,
15452 window: &mut Window,
15453 cx: &mut Context<Self>,
15454 ) -> Result<()> {
15455 let selections = self.selections.disjoint_anchors_arc();
15456 match selections.first() {
15457 Some(first) if selections.len() >= 2 => {
15458 self.change_selections(Default::default(), window, cx, |s| {
15459 s.select_ranges([first.range()]);
15460 });
15461 }
15462 _ => self.select_next(
15463 &SelectNext {
15464 replace_newest: true,
15465 },
15466 window,
15467 cx,
15468 )?,
15469 }
15470 Ok(())
15471 }
15472
15473 pub fn find_previous_match(
15474 &mut self,
15475 _: &FindPreviousMatch,
15476 window: &mut Window,
15477 cx: &mut Context<Self>,
15478 ) -> Result<()> {
15479 let selections = self.selections.disjoint_anchors_arc();
15480 match selections.last() {
15481 Some(last) if selections.len() >= 2 => {
15482 self.change_selections(Default::default(), window, cx, |s| {
15483 s.select_ranges([last.range()]);
15484 });
15485 }
15486 _ => self.select_previous(
15487 &SelectPrevious {
15488 replace_newest: true,
15489 },
15490 window,
15491 cx,
15492 )?,
15493 }
15494 Ok(())
15495 }
15496
15497 pub fn toggle_comments(
15498 &mut self,
15499 action: &ToggleComments,
15500 window: &mut Window,
15501 cx: &mut Context<Self>,
15502 ) {
15503 if self.read_only(cx) {
15504 return;
15505 }
15506 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15507 let text_layout_details = &self.text_layout_details(window);
15508 self.transact(window, cx, |this, window, cx| {
15509 let mut selections = this
15510 .selections
15511 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15512 let mut edits = Vec::new();
15513 let mut selection_edit_ranges = Vec::new();
15514 let mut last_toggled_row = None;
15515 let snapshot = this.buffer.read(cx).read(cx);
15516 let empty_str: Arc<str> = Arc::default();
15517 let mut suffixes_inserted = Vec::new();
15518 let ignore_indent = action.ignore_indent;
15519
15520 fn comment_prefix_range(
15521 snapshot: &MultiBufferSnapshot,
15522 row: MultiBufferRow,
15523 comment_prefix: &str,
15524 comment_prefix_whitespace: &str,
15525 ignore_indent: bool,
15526 ) -> Range<Point> {
15527 let indent_size = if ignore_indent {
15528 0
15529 } else {
15530 snapshot.indent_size_for_line(row).len
15531 };
15532
15533 let start = Point::new(row.0, indent_size);
15534
15535 let mut line_bytes = snapshot
15536 .bytes_in_range(start..snapshot.max_point())
15537 .flatten()
15538 .copied();
15539
15540 // If this line currently begins with the line comment prefix, then record
15541 // the range containing the prefix.
15542 if line_bytes
15543 .by_ref()
15544 .take(comment_prefix.len())
15545 .eq(comment_prefix.bytes())
15546 {
15547 // Include any whitespace that matches the comment prefix.
15548 let matching_whitespace_len = line_bytes
15549 .zip(comment_prefix_whitespace.bytes())
15550 .take_while(|(a, b)| a == b)
15551 .count() as u32;
15552 let end = Point::new(
15553 start.row,
15554 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15555 );
15556 start..end
15557 } else {
15558 start..start
15559 }
15560 }
15561
15562 fn comment_suffix_range(
15563 snapshot: &MultiBufferSnapshot,
15564 row: MultiBufferRow,
15565 comment_suffix: &str,
15566 comment_suffix_has_leading_space: bool,
15567 ) -> Range<Point> {
15568 let end = Point::new(row.0, snapshot.line_len(row));
15569 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15570
15571 let mut line_end_bytes = snapshot
15572 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15573 .flatten()
15574 .copied();
15575
15576 let leading_space_len = if suffix_start_column > 0
15577 && line_end_bytes.next() == Some(b' ')
15578 && comment_suffix_has_leading_space
15579 {
15580 1
15581 } else {
15582 0
15583 };
15584
15585 // If this line currently begins with the line comment prefix, then record
15586 // the range containing the prefix.
15587 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15588 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15589 start..end
15590 } else {
15591 end..end
15592 }
15593 }
15594
15595 // TODO: Handle selections that cross excerpts
15596 for selection in &mut selections {
15597 let start_column = snapshot
15598 .indent_size_for_line(MultiBufferRow(selection.start.row))
15599 .len;
15600 let language = if let Some(language) =
15601 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15602 {
15603 language
15604 } else {
15605 continue;
15606 };
15607
15608 selection_edit_ranges.clear();
15609
15610 // If multiple selections contain a given row, avoid processing that
15611 // row more than once.
15612 let mut start_row = MultiBufferRow(selection.start.row);
15613 if last_toggled_row == Some(start_row) {
15614 start_row = start_row.next_row();
15615 }
15616 let end_row =
15617 if selection.end.row > selection.start.row && selection.end.column == 0 {
15618 MultiBufferRow(selection.end.row - 1)
15619 } else {
15620 MultiBufferRow(selection.end.row)
15621 };
15622 last_toggled_row = Some(end_row);
15623
15624 if start_row > end_row {
15625 continue;
15626 }
15627
15628 // If the language has line comments, toggle those.
15629 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15630
15631 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15632 if ignore_indent {
15633 full_comment_prefixes = full_comment_prefixes
15634 .into_iter()
15635 .map(|s| Arc::from(s.trim_end()))
15636 .collect();
15637 }
15638
15639 if !full_comment_prefixes.is_empty() {
15640 let first_prefix = full_comment_prefixes
15641 .first()
15642 .expect("prefixes is non-empty");
15643 let prefix_trimmed_lengths = full_comment_prefixes
15644 .iter()
15645 .map(|p| p.trim_end_matches(' ').len())
15646 .collect::<SmallVec<[usize; 4]>>();
15647
15648 let mut all_selection_lines_are_comments = true;
15649
15650 for row in start_row.0..=end_row.0 {
15651 let row = MultiBufferRow(row);
15652 if start_row < end_row && snapshot.is_line_blank(row) {
15653 continue;
15654 }
15655
15656 let prefix_range = full_comment_prefixes
15657 .iter()
15658 .zip(prefix_trimmed_lengths.iter().copied())
15659 .map(|(prefix, trimmed_prefix_len)| {
15660 comment_prefix_range(
15661 snapshot.deref(),
15662 row,
15663 &prefix[..trimmed_prefix_len],
15664 &prefix[trimmed_prefix_len..],
15665 ignore_indent,
15666 )
15667 })
15668 .max_by_key(|range| range.end.column - range.start.column)
15669 .expect("prefixes is non-empty");
15670
15671 if prefix_range.is_empty() {
15672 all_selection_lines_are_comments = false;
15673 }
15674
15675 selection_edit_ranges.push(prefix_range);
15676 }
15677
15678 if all_selection_lines_are_comments {
15679 edits.extend(
15680 selection_edit_ranges
15681 .iter()
15682 .cloned()
15683 .map(|range| (range, empty_str.clone())),
15684 );
15685 } else {
15686 let min_column = selection_edit_ranges
15687 .iter()
15688 .map(|range| range.start.column)
15689 .min()
15690 .unwrap_or(0);
15691 edits.extend(selection_edit_ranges.iter().map(|range| {
15692 let position = Point::new(range.start.row, min_column);
15693 (position..position, first_prefix.clone())
15694 }));
15695 }
15696 } else if let Some(BlockCommentConfig {
15697 start: full_comment_prefix,
15698 end: comment_suffix,
15699 ..
15700 }) = language.block_comment()
15701 {
15702 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15703 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15704 let prefix_range = comment_prefix_range(
15705 snapshot.deref(),
15706 start_row,
15707 comment_prefix,
15708 comment_prefix_whitespace,
15709 ignore_indent,
15710 );
15711 let suffix_range = comment_suffix_range(
15712 snapshot.deref(),
15713 end_row,
15714 comment_suffix.trim_start_matches(' '),
15715 comment_suffix.starts_with(' '),
15716 );
15717
15718 if prefix_range.is_empty() || suffix_range.is_empty() {
15719 edits.push((
15720 prefix_range.start..prefix_range.start,
15721 full_comment_prefix.clone(),
15722 ));
15723 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15724 suffixes_inserted.push((end_row, comment_suffix.len()));
15725 } else {
15726 edits.push((prefix_range, empty_str.clone()));
15727 edits.push((suffix_range, empty_str.clone()));
15728 }
15729 } else {
15730 continue;
15731 }
15732 }
15733
15734 drop(snapshot);
15735 this.buffer.update(cx, |buffer, cx| {
15736 buffer.edit(edits, None, cx);
15737 });
15738
15739 // Adjust selections so that they end before any comment suffixes that
15740 // were inserted.
15741 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15742 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15743 let snapshot = this.buffer.read(cx).read(cx);
15744 for selection in &mut selections {
15745 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15746 match row.cmp(&MultiBufferRow(selection.end.row)) {
15747 Ordering::Less => {
15748 suffixes_inserted.next();
15749 continue;
15750 }
15751 Ordering::Greater => break,
15752 Ordering::Equal => {
15753 if selection.end.column == snapshot.line_len(row) {
15754 if selection.is_empty() {
15755 selection.start.column -= suffix_len as u32;
15756 }
15757 selection.end.column -= suffix_len as u32;
15758 }
15759 break;
15760 }
15761 }
15762 }
15763 }
15764
15765 drop(snapshot);
15766 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15767
15768 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15769 let selections_on_single_row = selections.windows(2).all(|selections| {
15770 selections[0].start.row == selections[1].start.row
15771 && selections[0].end.row == selections[1].end.row
15772 && selections[0].start.row == selections[0].end.row
15773 });
15774 let selections_selecting = selections
15775 .iter()
15776 .any(|selection| selection.start != selection.end);
15777 let advance_downwards = action.advance_downwards
15778 && selections_on_single_row
15779 && !selections_selecting
15780 && !matches!(this.mode, EditorMode::SingleLine);
15781
15782 if advance_downwards {
15783 let snapshot = this.buffer.read(cx).snapshot(cx);
15784
15785 this.change_selections(Default::default(), window, cx, |s| {
15786 s.move_cursors_with(|display_snapshot, display_point, _| {
15787 let mut point = display_point.to_point(display_snapshot);
15788 point.row += 1;
15789 point = snapshot.clip_point(point, Bias::Left);
15790 let display_point = point.to_display_point(display_snapshot);
15791 let goal = SelectionGoal::HorizontalPosition(
15792 display_snapshot
15793 .x_for_display_point(display_point, text_layout_details)
15794 .into(),
15795 );
15796 (display_point, goal)
15797 })
15798 });
15799 }
15800 });
15801 }
15802
15803 pub fn select_enclosing_symbol(
15804 &mut self,
15805 _: &SelectEnclosingSymbol,
15806 window: &mut Window,
15807 cx: &mut Context<Self>,
15808 ) {
15809 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15810
15811 let buffer = self.buffer.read(cx).snapshot(cx);
15812 let old_selections = self
15813 .selections
15814 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15815 .into_boxed_slice();
15816
15817 fn update_selection(
15818 selection: &Selection<MultiBufferOffset>,
15819 buffer_snap: &MultiBufferSnapshot,
15820 ) -> Option<Selection<MultiBufferOffset>> {
15821 let cursor = selection.head();
15822 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15823 for symbol in symbols.iter().rev() {
15824 let start = symbol.range.start.to_offset(buffer_snap);
15825 let end = symbol.range.end.to_offset(buffer_snap);
15826 let new_range = start..end;
15827 if start < selection.start || end > selection.end {
15828 return Some(Selection {
15829 id: selection.id,
15830 start: new_range.start,
15831 end: new_range.end,
15832 goal: SelectionGoal::None,
15833 reversed: selection.reversed,
15834 });
15835 }
15836 }
15837 None
15838 }
15839
15840 let mut selected_larger_symbol = false;
15841 let new_selections = old_selections
15842 .iter()
15843 .map(|selection| match update_selection(selection, &buffer) {
15844 Some(new_selection) => {
15845 if new_selection.range() != selection.range() {
15846 selected_larger_symbol = true;
15847 }
15848 new_selection
15849 }
15850 None => selection.clone(),
15851 })
15852 .collect::<Vec<_>>();
15853
15854 if selected_larger_symbol {
15855 self.change_selections(Default::default(), window, cx, |s| {
15856 s.select(new_selections);
15857 });
15858 }
15859 }
15860
15861 pub fn select_larger_syntax_node(
15862 &mut self,
15863 _: &SelectLargerSyntaxNode,
15864 window: &mut Window,
15865 cx: &mut Context<Self>,
15866 ) {
15867 let Some(visible_row_count) = self.visible_row_count() else {
15868 return;
15869 };
15870 let old_selections: Box<[_]> = self
15871 .selections
15872 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15873 .into();
15874 if old_selections.is_empty() {
15875 return;
15876 }
15877
15878 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15879
15880 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15881 let buffer = self.buffer.read(cx).snapshot(cx);
15882
15883 let mut selected_larger_node = false;
15884 let mut new_selections = old_selections
15885 .iter()
15886 .map(|selection| {
15887 let old_range = selection.start..selection.end;
15888
15889 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15890 // manually select word at selection
15891 if ["string_content", "inline"].contains(&node.kind()) {
15892 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15893 // ignore if word is already selected
15894 if !word_range.is_empty() && old_range != word_range {
15895 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15896 // only select word if start and end point belongs to same word
15897 if word_range == last_word_range {
15898 selected_larger_node = true;
15899 return Selection {
15900 id: selection.id,
15901 start: word_range.start,
15902 end: word_range.end,
15903 goal: SelectionGoal::None,
15904 reversed: selection.reversed,
15905 };
15906 }
15907 }
15908 }
15909 }
15910
15911 let mut new_range = old_range.clone();
15912 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15913 new_range = range;
15914 if !node.is_named() {
15915 continue;
15916 }
15917 if !display_map.intersects_fold(new_range.start)
15918 && !display_map.intersects_fold(new_range.end)
15919 {
15920 break;
15921 }
15922 }
15923
15924 selected_larger_node |= new_range != old_range;
15925 Selection {
15926 id: selection.id,
15927 start: new_range.start,
15928 end: new_range.end,
15929 goal: SelectionGoal::None,
15930 reversed: selection.reversed,
15931 }
15932 })
15933 .collect::<Vec<_>>();
15934
15935 if !selected_larger_node {
15936 return; // don't put this call in the history
15937 }
15938
15939 // scroll based on transformation done to the last selection created by the user
15940 let (last_old, last_new) = old_selections
15941 .last()
15942 .zip(new_selections.last().cloned())
15943 .expect("old_selections isn't empty");
15944
15945 // revert selection
15946 let is_selection_reversed = {
15947 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15948 new_selections.last_mut().expect("checked above").reversed =
15949 should_newest_selection_be_reversed;
15950 should_newest_selection_be_reversed
15951 };
15952
15953 if selected_larger_node {
15954 self.select_syntax_node_history.disable_clearing = true;
15955 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15956 s.select(new_selections.clone());
15957 });
15958 self.select_syntax_node_history.disable_clearing = false;
15959 }
15960
15961 let start_row = last_new.start.to_display_point(&display_map).row().0;
15962 let end_row = last_new.end.to_display_point(&display_map).row().0;
15963 let selection_height = end_row - start_row + 1;
15964 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15965
15966 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15967 let scroll_behavior = if fits_on_the_screen {
15968 self.request_autoscroll(Autoscroll::fit(), cx);
15969 SelectSyntaxNodeScrollBehavior::FitSelection
15970 } else if is_selection_reversed {
15971 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15972 SelectSyntaxNodeScrollBehavior::CursorTop
15973 } else {
15974 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15975 SelectSyntaxNodeScrollBehavior::CursorBottom
15976 };
15977
15978 self.select_syntax_node_history.push((
15979 old_selections,
15980 scroll_behavior,
15981 is_selection_reversed,
15982 ));
15983 }
15984
15985 pub fn select_smaller_syntax_node(
15986 &mut self,
15987 _: &SelectSmallerSyntaxNode,
15988 window: &mut Window,
15989 cx: &mut Context<Self>,
15990 ) {
15991 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15992
15993 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15994 self.select_syntax_node_history.pop()
15995 {
15996 if let Some(selection) = selections.last_mut() {
15997 selection.reversed = is_selection_reversed;
15998 }
15999
16000 self.select_syntax_node_history.disable_clearing = true;
16001 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16002 s.select(selections.to_vec());
16003 });
16004 self.select_syntax_node_history.disable_clearing = false;
16005
16006 match scroll_behavior {
16007 SelectSyntaxNodeScrollBehavior::CursorTop => {
16008 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16009 }
16010 SelectSyntaxNodeScrollBehavior::FitSelection => {
16011 self.request_autoscroll(Autoscroll::fit(), cx);
16012 }
16013 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16014 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16015 }
16016 }
16017 }
16018 }
16019
16020 pub fn unwrap_syntax_node(
16021 &mut self,
16022 _: &UnwrapSyntaxNode,
16023 window: &mut Window,
16024 cx: &mut Context<Self>,
16025 ) {
16026 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16027
16028 let buffer = self.buffer.read(cx).snapshot(cx);
16029 let selections = self
16030 .selections
16031 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16032 .into_iter()
16033 // subtracting the offset requires sorting
16034 .sorted_by_key(|i| i.start);
16035
16036 let full_edits = selections
16037 .into_iter()
16038 .filter_map(|selection| {
16039 let child = if selection.is_empty()
16040 && let Some((_, ancestor_range)) =
16041 buffer.syntax_ancestor(selection.start..selection.end)
16042 {
16043 ancestor_range
16044 } else {
16045 selection.range()
16046 };
16047
16048 let mut parent = child.clone();
16049 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16050 parent = ancestor_range;
16051 if parent.start < child.start || parent.end > child.end {
16052 break;
16053 }
16054 }
16055
16056 if parent == child {
16057 return None;
16058 }
16059 let text = buffer.text_for_range(child).collect::<String>();
16060 Some((selection.id, parent, text))
16061 })
16062 .collect::<Vec<_>>();
16063 if full_edits.is_empty() {
16064 return;
16065 }
16066
16067 self.transact(window, cx, |this, window, cx| {
16068 this.buffer.update(cx, |buffer, cx| {
16069 buffer.edit(
16070 full_edits
16071 .iter()
16072 .map(|(_, p, t)| (p.clone(), t.clone()))
16073 .collect::<Vec<_>>(),
16074 None,
16075 cx,
16076 );
16077 });
16078 this.change_selections(Default::default(), window, cx, |s| {
16079 let mut offset = 0;
16080 let mut selections = vec![];
16081 for (id, parent, text) in full_edits {
16082 let start = parent.start - offset;
16083 offset += (parent.end - parent.start) - text.len();
16084 selections.push(Selection {
16085 id,
16086 start,
16087 end: start + text.len(),
16088 reversed: false,
16089 goal: Default::default(),
16090 });
16091 }
16092 s.select(selections);
16093 });
16094 });
16095 }
16096
16097 pub fn select_next_syntax_node(
16098 &mut self,
16099 _: &SelectNextSyntaxNode,
16100 window: &mut Window,
16101 cx: &mut Context<Self>,
16102 ) {
16103 let old_selections: Box<[_]> = self
16104 .selections
16105 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16106 .into();
16107 if old_selections.is_empty() {
16108 return;
16109 }
16110
16111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16112
16113 let buffer = self.buffer.read(cx).snapshot(cx);
16114 let mut selected_sibling = false;
16115
16116 let new_selections = old_selections
16117 .iter()
16118 .map(|selection| {
16119 let old_range = selection.start..selection.end;
16120
16121 let old_range =
16122 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16123 let excerpt = buffer.excerpt_containing(old_range.clone());
16124
16125 if let Some(mut excerpt) = excerpt
16126 && let Some(node) = excerpt
16127 .buffer()
16128 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16129 {
16130 let new_range = excerpt.map_range_from_buffer(
16131 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16132 );
16133 selected_sibling = true;
16134 Selection {
16135 id: selection.id,
16136 start: new_range.start,
16137 end: new_range.end,
16138 goal: SelectionGoal::None,
16139 reversed: selection.reversed,
16140 }
16141 } else {
16142 selection.clone()
16143 }
16144 })
16145 .collect::<Vec<_>>();
16146
16147 if selected_sibling {
16148 self.change_selections(
16149 SelectionEffects::scroll(Autoscroll::fit()),
16150 window,
16151 cx,
16152 |s| {
16153 s.select(new_selections);
16154 },
16155 );
16156 }
16157 }
16158
16159 pub fn select_prev_syntax_node(
16160 &mut self,
16161 _: &SelectPreviousSyntaxNode,
16162 window: &mut Window,
16163 cx: &mut Context<Self>,
16164 ) {
16165 let old_selections: Box<[_]> = self
16166 .selections
16167 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16168 .into();
16169 if old_selections.is_empty() {
16170 return;
16171 }
16172
16173 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16174
16175 let buffer = self.buffer.read(cx).snapshot(cx);
16176 let mut selected_sibling = false;
16177
16178 let new_selections = old_selections
16179 .iter()
16180 .map(|selection| {
16181 let old_range = selection.start..selection.end;
16182 let old_range =
16183 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16184 let excerpt = buffer.excerpt_containing(old_range.clone());
16185
16186 if let Some(mut excerpt) = excerpt
16187 && let Some(node) = excerpt
16188 .buffer()
16189 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16190 {
16191 let new_range = excerpt.map_range_from_buffer(
16192 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16193 );
16194 selected_sibling = true;
16195 Selection {
16196 id: selection.id,
16197 start: new_range.start,
16198 end: new_range.end,
16199 goal: SelectionGoal::None,
16200 reversed: selection.reversed,
16201 }
16202 } else {
16203 selection.clone()
16204 }
16205 })
16206 .collect::<Vec<_>>();
16207
16208 if selected_sibling {
16209 self.change_selections(
16210 SelectionEffects::scroll(Autoscroll::fit()),
16211 window,
16212 cx,
16213 |s| {
16214 s.select(new_selections);
16215 },
16216 );
16217 }
16218 }
16219
16220 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16221 if !EditorSettings::get_global(cx).gutter.runnables {
16222 self.clear_tasks();
16223 return Task::ready(());
16224 }
16225 let project = self.project().map(Entity::downgrade);
16226 let task_sources = self.lsp_task_sources(cx);
16227 let multi_buffer = self.buffer.downgrade();
16228 cx.spawn_in(window, async move |editor, cx| {
16229 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16230 let Some(project) = project.and_then(|p| p.upgrade()) else {
16231 return;
16232 };
16233 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16234 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16235 }) else {
16236 return;
16237 };
16238
16239 let hide_runnables = project
16240 .update(cx, |project, _| project.is_via_collab())
16241 .unwrap_or(true);
16242 if hide_runnables {
16243 return;
16244 }
16245 let new_rows =
16246 cx.background_spawn({
16247 let snapshot = display_snapshot.clone();
16248 async move {
16249 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16250 }
16251 })
16252 .await;
16253 let Ok(lsp_tasks) =
16254 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16255 else {
16256 return;
16257 };
16258 let lsp_tasks = lsp_tasks.await;
16259
16260 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16261 lsp_tasks
16262 .into_iter()
16263 .flat_map(|(kind, tasks)| {
16264 tasks.into_iter().filter_map(move |(location, task)| {
16265 Some((kind.clone(), location?, task))
16266 })
16267 })
16268 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16269 let buffer = location.target.buffer;
16270 let buffer_snapshot = buffer.read(cx).snapshot();
16271 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16272 |(excerpt_id, snapshot, _)| {
16273 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16274 display_snapshot
16275 .buffer_snapshot()
16276 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16277 } else {
16278 None
16279 }
16280 },
16281 );
16282 if let Some(offset) = offset {
16283 let task_buffer_range =
16284 location.target.range.to_point(&buffer_snapshot);
16285 let context_buffer_range =
16286 task_buffer_range.to_offset(&buffer_snapshot);
16287 let context_range = BufferOffset(context_buffer_range.start)
16288 ..BufferOffset(context_buffer_range.end);
16289
16290 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16291 .or_insert_with(|| RunnableTasks {
16292 templates: Vec::new(),
16293 offset,
16294 column: task_buffer_range.start.column,
16295 extra_variables: HashMap::default(),
16296 context_range,
16297 })
16298 .templates
16299 .push((kind, task.original_task().clone()));
16300 }
16301
16302 acc
16303 })
16304 }) else {
16305 return;
16306 };
16307
16308 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16309 buffer.language_settings(cx).tasks.prefer_lsp
16310 }) else {
16311 return;
16312 };
16313
16314 let rows = Self::runnable_rows(
16315 project,
16316 display_snapshot,
16317 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16318 new_rows,
16319 cx.clone(),
16320 )
16321 .await;
16322 editor
16323 .update(cx, |editor, _| {
16324 editor.clear_tasks();
16325 for (key, mut value) in rows {
16326 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16327 value.templates.extend(lsp_tasks.templates);
16328 }
16329
16330 editor.insert_tasks(key, value);
16331 }
16332 for (key, value) in lsp_tasks_by_rows {
16333 editor.insert_tasks(key, value);
16334 }
16335 })
16336 .ok();
16337 })
16338 }
16339 fn fetch_runnable_ranges(
16340 snapshot: &DisplaySnapshot,
16341 range: Range<Anchor>,
16342 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16343 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16344 }
16345
16346 fn runnable_rows(
16347 project: Entity<Project>,
16348 snapshot: DisplaySnapshot,
16349 prefer_lsp: bool,
16350 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16351 cx: AsyncWindowContext,
16352 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16353 cx.spawn(async move |cx| {
16354 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16355 for (run_range, mut runnable) in runnable_ranges {
16356 let Some(tasks) = cx
16357 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16358 .ok()
16359 else {
16360 continue;
16361 };
16362 let mut tasks = tasks.await;
16363
16364 if prefer_lsp {
16365 tasks.retain(|(task_kind, _)| {
16366 !matches!(task_kind, TaskSourceKind::Language { .. })
16367 });
16368 }
16369 if tasks.is_empty() {
16370 continue;
16371 }
16372
16373 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16374 let Some(row) = snapshot
16375 .buffer_snapshot()
16376 .buffer_line_for_row(MultiBufferRow(point.row))
16377 .map(|(_, range)| range.start.row)
16378 else {
16379 continue;
16380 };
16381
16382 let context_range =
16383 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16384 runnable_rows.push((
16385 (runnable.buffer_id, row),
16386 RunnableTasks {
16387 templates: tasks,
16388 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16389 context_range,
16390 column: point.column,
16391 extra_variables: runnable.extra_captures,
16392 },
16393 ));
16394 }
16395 runnable_rows
16396 })
16397 }
16398
16399 fn templates_with_tags(
16400 project: &Entity<Project>,
16401 runnable: &mut Runnable,
16402 cx: &mut App,
16403 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16404 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16405 let (worktree_id, file) = project
16406 .buffer_for_id(runnable.buffer, cx)
16407 .and_then(|buffer| buffer.read(cx).file())
16408 .map(|file| (file.worktree_id(cx), file.clone()))
16409 .unzip();
16410
16411 (
16412 project.task_store().read(cx).task_inventory().cloned(),
16413 worktree_id,
16414 file,
16415 )
16416 });
16417
16418 let tags = mem::take(&mut runnable.tags);
16419 let language = runnable.language.clone();
16420 cx.spawn(async move |cx| {
16421 let mut templates_with_tags = Vec::new();
16422 if let Some(inventory) = inventory {
16423 for RunnableTag(tag) in tags {
16424 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16425 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16426 }) else {
16427 return templates_with_tags;
16428 };
16429 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16430 move |(_, template)| {
16431 template.tags.iter().any(|source_tag| source_tag == &tag)
16432 },
16433 ));
16434 }
16435 }
16436 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16437
16438 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16439 // Strongest source wins; if we have worktree tag binding, prefer that to
16440 // global and language bindings;
16441 // if we have a global binding, prefer that to language binding.
16442 let first_mismatch = templates_with_tags
16443 .iter()
16444 .position(|(tag_source, _)| tag_source != leading_tag_source);
16445 if let Some(index) = first_mismatch {
16446 templates_with_tags.truncate(index);
16447 }
16448 }
16449
16450 templates_with_tags
16451 })
16452 }
16453
16454 pub fn move_to_enclosing_bracket(
16455 &mut self,
16456 _: &MoveToEnclosingBracket,
16457 window: &mut Window,
16458 cx: &mut Context<Self>,
16459 ) {
16460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16461 self.change_selections(Default::default(), window, cx, |s| {
16462 s.move_offsets_with(|snapshot, selection| {
16463 let Some(enclosing_bracket_ranges) =
16464 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16465 else {
16466 return;
16467 };
16468
16469 let mut best_length = usize::MAX;
16470 let mut best_inside = false;
16471 let mut best_in_bracket_range = false;
16472 let mut best_destination = None;
16473 for (open, close) in enclosing_bracket_ranges {
16474 let close = close.to_inclusive();
16475 let length = *close.end() - open.start;
16476 let inside = selection.start >= open.end && selection.end <= *close.start();
16477 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16478 || close.contains(&selection.head());
16479
16480 // If best is next to a bracket and current isn't, skip
16481 if !in_bracket_range && best_in_bracket_range {
16482 continue;
16483 }
16484
16485 // Prefer smaller lengths unless best is inside and current isn't
16486 if length > best_length && (best_inside || !inside) {
16487 continue;
16488 }
16489
16490 best_length = length;
16491 best_inside = inside;
16492 best_in_bracket_range = in_bracket_range;
16493 best_destination = Some(
16494 if close.contains(&selection.start) && close.contains(&selection.end) {
16495 if inside { open.end } else { open.start }
16496 } else if inside {
16497 *close.start()
16498 } else {
16499 *close.end()
16500 },
16501 );
16502 }
16503
16504 if let Some(destination) = best_destination {
16505 selection.collapse_to(destination, SelectionGoal::None);
16506 }
16507 })
16508 });
16509 }
16510
16511 pub fn undo_selection(
16512 &mut self,
16513 _: &UndoSelection,
16514 window: &mut Window,
16515 cx: &mut Context<Self>,
16516 ) {
16517 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16518 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16519 self.selection_history.mode = SelectionHistoryMode::Undoing;
16520 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16521 this.end_selection(window, cx);
16522 this.change_selections(
16523 SelectionEffects::scroll(Autoscroll::newest()),
16524 window,
16525 cx,
16526 |s| s.select_anchors(entry.selections.to_vec()),
16527 );
16528 });
16529 self.selection_history.mode = SelectionHistoryMode::Normal;
16530
16531 self.select_next_state = entry.select_next_state;
16532 self.select_prev_state = entry.select_prev_state;
16533 self.add_selections_state = entry.add_selections_state;
16534 }
16535 }
16536
16537 pub fn redo_selection(
16538 &mut self,
16539 _: &RedoSelection,
16540 window: &mut Window,
16541 cx: &mut Context<Self>,
16542 ) {
16543 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16544 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16545 self.selection_history.mode = SelectionHistoryMode::Redoing;
16546 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16547 this.end_selection(window, cx);
16548 this.change_selections(
16549 SelectionEffects::scroll(Autoscroll::newest()),
16550 window,
16551 cx,
16552 |s| s.select_anchors(entry.selections.to_vec()),
16553 );
16554 });
16555 self.selection_history.mode = SelectionHistoryMode::Normal;
16556
16557 self.select_next_state = entry.select_next_state;
16558 self.select_prev_state = entry.select_prev_state;
16559 self.add_selections_state = entry.add_selections_state;
16560 }
16561 }
16562
16563 pub fn expand_excerpts(
16564 &mut self,
16565 action: &ExpandExcerpts,
16566 _: &mut Window,
16567 cx: &mut Context<Self>,
16568 ) {
16569 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16570 }
16571
16572 pub fn expand_excerpts_down(
16573 &mut self,
16574 action: &ExpandExcerptsDown,
16575 _: &mut Window,
16576 cx: &mut Context<Self>,
16577 ) {
16578 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16579 }
16580
16581 pub fn expand_excerpts_up(
16582 &mut self,
16583 action: &ExpandExcerptsUp,
16584 _: &mut Window,
16585 cx: &mut Context<Self>,
16586 ) {
16587 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16588 }
16589
16590 pub fn expand_excerpts_for_direction(
16591 &mut self,
16592 lines: u32,
16593 direction: ExpandExcerptDirection,
16594
16595 cx: &mut Context<Self>,
16596 ) {
16597 let selections = self.selections.disjoint_anchors_arc();
16598
16599 let lines = if lines == 0 {
16600 EditorSettings::get_global(cx).expand_excerpt_lines
16601 } else {
16602 lines
16603 };
16604
16605 self.buffer.update(cx, |buffer, cx| {
16606 let snapshot = buffer.snapshot(cx);
16607 let mut excerpt_ids = selections
16608 .iter()
16609 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16610 .collect::<Vec<_>>();
16611 excerpt_ids.sort();
16612 excerpt_ids.dedup();
16613 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16614 })
16615 }
16616
16617 pub fn expand_excerpt(
16618 &mut self,
16619 excerpt: ExcerptId,
16620 direction: ExpandExcerptDirection,
16621 window: &mut Window,
16622 cx: &mut Context<Self>,
16623 ) {
16624 let current_scroll_position = self.scroll_position(cx);
16625 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16626 let mut scroll = None;
16627
16628 if direction == ExpandExcerptDirection::Down {
16629 let multi_buffer = self.buffer.read(cx);
16630 let snapshot = multi_buffer.snapshot(cx);
16631 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16632 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16633 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16634 {
16635 let buffer_snapshot = buffer.read(cx).snapshot();
16636 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16637 let last_row = buffer_snapshot.max_point().row;
16638 let lines_below = last_row.saturating_sub(excerpt_end_row);
16639 if lines_below >= lines_to_expand {
16640 scroll = Some(
16641 current_scroll_position
16642 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16643 );
16644 }
16645 }
16646 }
16647 if direction == ExpandExcerptDirection::Up
16648 && self
16649 .buffer
16650 .read(cx)
16651 .snapshot(cx)
16652 .excerpt_before(excerpt)
16653 .is_none()
16654 {
16655 scroll = Some(current_scroll_position);
16656 }
16657
16658 self.buffer.update(cx, |buffer, cx| {
16659 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16660 });
16661
16662 if let Some(new_scroll_position) = scroll {
16663 self.set_scroll_position(new_scroll_position, window, cx);
16664 }
16665 }
16666
16667 pub fn go_to_singleton_buffer_point(
16668 &mut self,
16669 point: Point,
16670 window: &mut Window,
16671 cx: &mut Context<Self>,
16672 ) {
16673 self.go_to_singleton_buffer_range(point..point, window, cx);
16674 }
16675
16676 pub fn go_to_singleton_buffer_range(
16677 &mut self,
16678 range: Range<Point>,
16679 window: &mut Window,
16680 cx: &mut Context<Self>,
16681 ) {
16682 let multibuffer = self.buffer().read(cx);
16683 let Some(buffer) = multibuffer.as_singleton() else {
16684 return;
16685 };
16686 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16687 return;
16688 };
16689 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16690 return;
16691 };
16692 self.change_selections(
16693 SelectionEffects::default().nav_history(true),
16694 window,
16695 cx,
16696 |s| s.select_anchor_ranges([start..end]),
16697 );
16698 }
16699
16700 pub fn go_to_diagnostic(
16701 &mut self,
16702 action: &GoToDiagnostic,
16703 window: &mut Window,
16704 cx: &mut Context<Self>,
16705 ) {
16706 if !self.diagnostics_enabled() {
16707 return;
16708 }
16709 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16710 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16711 }
16712
16713 pub fn go_to_prev_diagnostic(
16714 &mut self,
16715 action: &GoToPreviousDiagnostic,
16716 window: &mut Window,
16717 cx: &mut Context<Self>,
16718 ) {
16719 if !self.diagnostics_enabled() {
16720 return;
16721 }
16722 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16723 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16724 }
16725
16726 pub fn go_to_diagnostic_impl(
16727 &mut self,
16728 direction: Direction,
16729 severity: GoToDiagnosticSeverityFilter,
16730 window: &mut Window,
16731 cx: &mut Context<Self>,
16732 ) {
16733 let buffer = self.buffer.read(cx).snapshot(cx);
16734 let selection = self
16735 .selections
16736 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16737
16738 let mut active_group_id = None;
16739 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16740 && active_group.active_range.start.to_offset(&buffer) == selection.start
16741 {
16742 active_group_id = Some(active_group.group_id);
16743 }
16744
16745 fn filtered<'a>(
16746 severity: GoToDiagnosticSeverityFilter,
16747 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16748 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16749 diagnostics
16750 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16751 .filter(|entry| entry.range.start != entry.range.end)
16752 .filter(|entry| !entry.diagnostic.is_unnecessary)
16753 }
16754
16755 let before = filtered(
16756 severity,
16757 buffer
16758 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16759 .filter(|entry| entry.range.start <= selection.start),
16760 );
16761 let after = filtered(
16762 severity,
16763 buffer
16764 .diagnostics_in_range(selection.start..buffer.len())
16765 .filter(|entry| entry.range.start >= selection.start),
16766 );
16767
16768 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16769 if direction == Direction::Prev {
16770 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16771 {
16772 for diagnostic in prev_diagnostics.into_iter().rev() {
16773 if diagnostic.range.start != selection.start
16774 || active_group_id
16775 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16776 {
16777 found = Some(diagnostic);
16778 break 'outer;
16779 }
16780 }
16781 }
16782 } else {
16783 for diagnostic in after.chain(before) {
16784 if diagnostic.range.start != selection.start
16785 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16786 {
16787 found = Some(diagnostic);
16788 break;
16789 }
16790 }
16791 }
16792 let Some(next_diagnostic) = found else {
16793 return;
16794 };
16795
16796 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16797 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16798 return;
16799 };
16800 let snapshot = self.snapshot(window, cx);
16801 if snapshot.intersects_fold(next_diagnostic.range.start) {
16802 self.unfold_ranges(
16803 std::slice::from_ref(&next_diagnostic.range),
16804 true,
16805 false,
16806 cx,
16807 );
16808 }
16809 self.change_selections(Default::default(), window, cx, |s| {
16810 s.select_ranges(vec![
16811 next_diagnostic.range.start..next_diagnostic.range.start,
16812 ])
16813 });
16814 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16815 self.refresh_edit_prediction(false, true, window, cx);
16816 }
16817
16818 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16819 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16820 let snapshot = self.snapshot(window, cx);
16821 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16822 self.go_to_hunk_before_or_after_position(
16823 &snapshot,
16824 selection.head(),
16825 Direction::Next,
16826 window,
16827 cx,
16828 );
16829 }
16830
16831 pub fn go_to_hunk_before_or_after_position(
16832 &mut self,
16833 snapshot: &EditorSnapshot,
16834 position: Point,
16835 direction: Direction,
16836 window: &mut Window,
16837 cx: &mut Context<Editor>,
16838 ) {
16839 let row = if direction == Direction::Next {
16840 self.hunk_after_position(snapshot, position)
16841 .map(|hunk| hunk.row_range.start)
16842 } else {
16843 self.hunk_before_position(snapshot, position)
16844 };
16845
16846 if let Some(row) = row {
16847 let destination = Point::new(row.0, 0);
16848 let autoscroll = Autoscroll::center();
16849
16850 self.unfold_ranges(&[destination..destination], false, false, cx);
16851 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16852 s.select_ranges([destination..destination]);
16853 });
16854 }
16855 }
16856
16857 fn hunk_after_position(
16858 &mut self,
16859 snapshot: &EditorSnapshot,
16860 position: Point,
16861 ) -> Option<MultiBufferDiffHunk> {
16862 snapshot
16863 .buffer_snapshot()
16864 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16865 .find(|hunk| hunk.row_range.start.0 > position.row)
16866 .or_else(|| {
16867 snapshot
16868 .buffer_snapshot()
16869 .diff_hunks_in_range(Point::zero()..position)
16870 .find(|hunk| hunk.row_range.end.0 < position.row)
16871 })
16872 }
16873
16874 fn go_to_prev_hunk(
16875 &mut self,
16876 _: &GoToPreviousHunk,
16877 window: &mut Window,
16878 cx: &mut Context<Self>,
16879 ) {
16880 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16881 let snapshot = self.snapshot(window, cx);
16882 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16883 self.go_to_hunk_before_or_after_position(
16884 &snapshot,
16885 selection.head(),
16886 Direction::Prev,
16887 window,
16888 cx,
16889 );
16890 }
16891
16892 fn hunk_before_position(
16893 &mut self,
16894 snapshot: &EditorSnapshot,
16895 position: Point,
16896 ) -> Option<MultiBufferRow> {
16897 snapshot
16898 .buffer_snapshot()
16899 .diff_hunk_before(position)
16900 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16901 }
16902
16903 fn go_to_next_change(
16904 &mut self,
16905 _: &GoToNextChange,
16906 window: &mut Window,
16907 cx: &mut Context<Self>,
16908 ) {
16909 if let Some(selections) = self
16910 .change_list
16911 .next_change(1, Direction::Next)
16912 .map(|s| s.to_vec())
16913 {
16914 self.change_selections(Default::default(), window, cx, |s| {
16915 let map = s.display_snapshot();
16916 s.select_display_ranges(selections.iter().map(|a| {
16917 let point = a.to_display_point(&map);
16918 point..point
16919 }))
16920 })
16921 }
16922 }
16923
16924 fn go_to_previous_change(
16925 &mut self,
16926 _: &GoToPreviousChange,
16927 window: &mut Window,
16928 cx: &mut Context<Self>,
16929 ) {
16930 if let Some(selections) = self
16931 .change_list
16932 .next_change(1, Direction::Prev)
16933 .map(|s| s.to_vec())
16934 {
16935 self.change_selections(Default::default(), window, cx, |s| {
16936 let map = s.display_snapshot();
16937 s.select_display_ranges(selections.iter().map(|a| {
16938 let point = a.to_display_point(&map);
16939 point..point
16940 }))
16941 })
16942 }
16943 }
16944
16945 pub fn go_to_next_document_highlight(
16946 &mut self,
16947 _: &GoToNextDocumentHighlight,
16948 window: &mut Window,
16949 cx: &mut Context<Self>,
16950 ) {
16951 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16952 }
16953
16954 pub fn go_to_prev_document_highlight(
16955 &mut self,
16956 _: &GoToPreviousDocumentHighlight,
16957 window: &mut Window,
16958 cx: &mut Context<Self>,
16959 ) {
16960 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16961 }
16962
16963 pub fn go_to_document_highlight_before_or_after_position(
16964 &mut self,
16965 direction: Direction,
16966 window: &mut Window,
16967 cx: &mut Context<Editor>,
16968 ) {
16969 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16970 let snapshot = self.snapshot(window, cx);
16971 let buffer = &snapshot.buffer_snapshot();
16972 let position = self
16973 .selections
16974 .newest::<Point>(&snapshot.display_snapshot)
16975 .head();
16976 let anchor_position = buffer.anchor_after(position);
16977
16978 // Get all document highlights (both read and write)
16979 let mut all_highlights = Vec::new();
16980
16981 if let Some((_, read_highlights)) = self
16982 .background_highlights
16983 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16984 {
16985 all_highlights.extend(read_highlights.iter());
16986 }
16987
16988 if let Some((_, write_highlights)) = self
16989 .background_highlights
16990 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16991 {
16992 all_highlights.extend(write_highlights.iter());
16993 }
16994
16995 if all_highlights.is_empty() {
16996 return;
16997 }
16998
16999 // Sort highlights by position
17000 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17001
17002 let target_highlight = match direction {
17003 Direction::Next => {
17004 // Find the first highlight after the current position
17005 all_highlights
17006 .iter()
17007 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17008 }
17009 Direction::Prev => {
17010 // Find the last highlight before the current position
17011 all_highlights
17012 .iter()
17013 .rev()
17014 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17015 }
17016 };
17017
17018 if let Some(highlight) = target_highlight {
17019 let destination = highlight.start.to_point(buffer);
17020 let autoscroll = Autoscroll::center();
17021
17022 self.unfold_ranges(&[destination..destination], false, false, cx);
17023 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17024 s.select_ranges([destination..destination]);
17025 });
17026 }
17027 }
17028
17029 fn go_to_line<T: 'static>(
17030 &mut self,
17031 position: Anchor,
17032 highlight_color: Option<Hsla>,
17033 window: &mut Window,
17034 cx: &mut Context<Self>,
17035 ) {
17036 let snapshot = self.snapshot(window, cx).display_snapshot;
17037 let position = position.to_point(&snapshot.buffer_snapshot());
17038 let start = snapshot
17039 .buffer_snapshot()
17040 .clip_point(Point::new(position.row, 0), Bias::Left);
17041 let end = start + Point::new(1, 0);
17042 let start = snapshot.buffer_snapshot().anchor_before(start);
17043 let end = snapshot.buffer_snapshot().anchor_before(end);
17044
17045 self.highlight_rows::<T>(
17046 start..end,
17047 highlight_color
17048 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17049 Default::default(),
17050 cx,
17051 );
17052
17053 if self.buffer.read(cx).is_singleton() {
17054 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17055 }
17056 }
17057
17058 pub fn go_to_definition(
17059 &mut self,
17060 _: &GoToDefinition,
17061 window: &mut Window,
17062 cx: &mut Context<Self>,
17063 ) -> Task<Result<Navigated>> {
17064 let definition =
17065 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17066 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17067 cx.spawn_in(window, async move |editor, cx| {
17068 if definition.await? == Navigated::Yes {
17069 return Ok(Navigated::Yes);
17070 }
17071 match fallback_strategy {
17072 GoToDefinitionFallback::None => Ok(Navigated::No),
17073 GoToDefinitionFallback::FindAllReferences => {
17074 match editor.update_in(cx, |editor, window, cx| {
17075 editor.find_all_references(&FindAllReferences::default(), window, cx)
17076 })? {
17077 Some(references) => references.await,
17078 None => Ok(Navigated::No),
17079 }
17080 }
17081 }
17082 })
17083 }
17084
17085 pub fn go_to_declaration(
17086 &mut self,
17087 _: &GoToDeclaration,
17088 window: &mut Window,
17089 cx: &mut Context<Self>,
17090 ) -> Task<Result<Navigated>> {
17091 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17092 }
17093
17094 pub fn go_to_declaration_split(
17095 &mut self,
17096 _: &GoToDeclaration,
17097 window: &mut Window,
17098 cx: &mut Context<Self>,
17099 ) -> Task<Result<Navigated>> {
17100 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17101 }
17102
17103 pub fn go_to_implementation(
17104 &mut self,
17105 _: &GoToImplementation,
17106 window: &mut Window,
17107 cx: &mut Context<Self>,
17108 ) -> Task<Result<Navigated>> {
17109 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17110 }
17111
17112 pub fn go_to_implementation_split(
17113 &mut self,
17114 _: &GoToImplementationSplit,
17115 window: &mut Window,
17116 cx: &mut Context<Self>,
17117 ) -> Task<Result<Navigated>> {
17118 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17119 }
17120
17121 pub fn go_to_type_definition(
17122 &mut self,
17123 _: &GoToTypeDefinition,
17124 window: &mut Window,
17125 cx: &mut Context<Self>,
17126 ) -> Task<Result<Navigated>> {
17127 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17128 }
17129
17130 pub fn go_to_definition_split(
17131 &mut self,
17132 _: &GoToDefinitionSplit,
17133 window: &mut Window,
17134 cx: &mut Context<Self>,
17135 ) -> Task<Result<Navigated>> {
17136 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17137 }
17138
17139 pub fn go_to_type_definition_split(
17140 &mut self,
17141 _: &GoToTypeDefinitionSplit,
17142 window: &mut Window,
17143 cx: &mut Context<Self>,
17144 ) -> Task<Result<Navigated>> {
17145 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17146 }
17147
17148 fn go_to_definition_of_kind(
17149 &mut self,
17150 kind: GotoDefinitionKind,
17151 split: bool,
17152 window: &mut Window,
17153 cx: &mut Context<Self>,
17154 ) -> Task<Result<Navigated>> {
17155 let Some(provider) = self.semantics_provider.clone() else {
17156 return Task::ready(Ok(Navigated::No));
17157 };
17158 let head = self
17159 .selections
17160 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17161 .head();
17162 let buffer = self.buffer.read(cx);
17163 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17164 return Task::ready(Ok(Navigated::No));
17165 };
17166 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17167 return Task::ready(Ok(Navigated::No));
17168 };
17169
17170 cx.spawn_in(window, async move |editor, cx| {
17171 let Some(definitions) = definitions.await? else {
17172 return Ok(Navigated::No);
17173 };
17174 let navigated = editor
17175 .update_in(cx, |editor, window, cx| {
17176 editor.navigate_to_hover_links(
17177 Some(kind),
17178 definitions
17179 .into_iter()
17180 .filter(|location| {
17181 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17182 })
17183 .map(HoverLink::Text)
17184 .collect::<Vec<_>>(),
17185 split,
17186 window,
17187 cx,
17188 )
17189 })?
17190 .await?;
17191 anyhow::Ok(navigated)
17192 })
17193 }
17194
17195 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17196 let selection = self.selections.newest_anchor();
17197 let head = selection.head();
17198 let tail = selection.tail();
17199
17200 let Some((buffer, start_position)) =
17201 self.buffer.read(cx).text_anchor_for_position(head, cx)
17202 else {
17203 return;
17204 };
17205
17206 let end_position = if head != tail {
17207 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17208 return;
17209 };
17210 Some(pos)
17211 } else {
17212 None
17213 };
17214
17215 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17216 let url = if let Some(end_pos) = end_position {
17217 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17218 } else {
17219 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17220 };
17221
17222 if let Some(url) = url {
17223 cx.update(|window, cx| {
17224 if parse_zed_link(&url, cx).is_some() {
17225 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17226 } else {
17227 cx.open_url(&url);
17228 }
17229 })?;
17230 }
17231
17232 anyhow::Ok(())
17233 });
17234
17235 url_finder.detach();
17236 }
17237
17238 pub fn open_selected_filename(
17239 &mut self,
17240 _: &OpenSelectedFilename,
17241 window: &mut Window,
17242 cx: &mut Context<Self>,
17243 ) {
17244 let Some(workspace) = self.workspace() else {
17245 return;
17246 };
17247
17248 let position = self.selections.newest_anchor().head();
17249
17250 let Some((buffer, buffer_position)) =
17251 self.buffer.read(cx).text_anchor_for_position(position, cx)
17252 else {
17253 return;
17254 };
17255
17256 let project = self.project.clone();
17257
17258 cx.spawn_in(window, async move |_, cx| {
17259 let result = find_file(&buffer, project, buffer_position, cx).await;
17260
17261 if let Some((_, path)) = result {
17262 workspace
17263 .update_in(cx, |workspace, window, cx| {
17264 workspace.open_resolved_path(path, window, cx)
17265 })?
17266 .await?;
17267 }
17268 anyhow::Ok(())
17269 })
17270 .detach();
17271 }
17272
17273 pub(crate) fn navigate_to_hover_links(
17274 &mut self,
17275 kind: Option<GotoDefinitionKind>,
17276 definitions: Vec<HoverLink>,
17277 split: bool,
17278 window: &mut Window,
17279 cx: &mut Context<Editor>,
17280 ) -> Task<Result<Navigated>> {
17281 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17282 let mut first_url_or_file = None;
17283 let definitions: Vec<_> = definitions
17284 .into_iter()
17285 .filter_map(|def| match def {
17286 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17287 HoverLink::InlayHint(lsp_location, server_id) => {
17288 let computation =
17289 self.compute_target_location(lsp_location, server_id, window, cx);
17290 Some(cx.background_spawn(computation))
17291 }
17292 HoverLink::Url(url) => {
17293 first_url_or_file = Some(Either::Left(url));
17294 None
17295 }
17296 HoverLink::File(path) => {
17297 first_url_or_file = Some(Either::Right(path));
17298 None
17299 }
17300 })
17301 .collect();
17302
17303 let workspace = self.workspace();
17304
17305 cx.spawn_in(window, async move |editor, cx| {
17306 let locations: Vec<Location> = future::join_all(definitions)
17307 .await
17308 .into_iter()
17309 .filter_map(|location| location.transpose())
17310 .collect::<Result<_>>()
17311 .context("location tasks")?;
17312 let mut locations = cx.update(|_, cx| {
17313 locations
17314 .into_iter()
17315 .map(|location| {
17316 let buffer = location.buffer.read(cx);
17317 (location.buffer, location.range.to_point(buffer))
17318 })
17319 .into_group_map()
17320 })?;
17321 let mut num_locations = 0;
17322 for ranges in locations.values_mut() {
17323 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17324 ranges.dedup();
17325 num_locations += ranges.len();
17326 }
17327
17328 if num_locations > 1 {
17329 let tab_kind = match kind {
17330 Some(GotoDefinitionKind::Implementation) => "Implementations",
17331 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17332 Some(GotoDefinitionKind::Declaration) => "Declarations",
17333 Some(GotoDefinitionKind::Type) => "Types",
17334 };
17335 let title = editor
17336 .update_in(cx, |_, _, cx| {
17337 let target = locations
17338 .iter()
17339 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17340 .map(|(buffer, location)| {
17341 buffer
17342 .read(cx)
17343 .text_for_range(location.clone())
17344 .collect::<String>()
17345 })
17346 .filter(|text| !text.contains('\n'))
17347 .unique()
17348 .take(3)
17349 .join(", ");
17350 if target.is_empty() {
17351 tab_kind.to_owned()
17352 } else {
17353 format!("{tab_kind} for {target}")
17354 }
17355 })
17356 .context("buffer title")?;
17357
17358 let Some(workspace) = workspace else {
17359 return Ok(Navigated::No);
17360 };
17361
17362 let opened = workspace
17363 .update_in(cx, |workspace, window, cx| {
17364 let allow_preview = PreviewTabsSettings::get_global(cx)
17365 .enable_preview_multibuffer_from_code_navigation;
17366 Self::open_locations_in_multibuffer(
17367 workspace,
17368 locations,
17369 title,
17370 split,
17371 allow_preview,
17372 MultibufferSelectionMode::First,
17373 window,
17374 cx,
17375 )
17376 })
17377 .is_ok();
17378
17379 anyhow::Ok(Navigated::from_bool(opened))
17380 } else if num_locations == 0 {
17381 // If there is one url or file, open it directly
17382 match first_url_or_file {
17383 Some(Either::Left(url)) => {
17384 cx.update(|window, cx| {
17385 if parse_zed_link(&url, cx).is_some() {
17386 window
17387 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17388 } else {
17389 cx.open_url(&url);
17390 }
17391 })?;
17392 Ok(Navigated::Yes)
17393 }
17394 Some(Either::Right(path)) => {
17395 // TODO(andrew): respect preview tab settings
17396 // `enable_keep_preview_on_code_navigation` and
17397 // `enable_preview_file_from_code_navigation`
17398 let Some(workspace) = workspace else {
17399 return Ok(Navigated::No);
17400 };
17401 workspace
17402 .update_in(cx, |workspace, window, cx| {
17403 workspace.open_resolved_path(path, window, cx)
17404 })?
17405 .await?;
17406 Ok(Navigated::Yes)
17407 }
17408 None => Ok(Navigated::No),
17409 }
17410 } else {
17411 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17412 let target_range = target_ranges.first().unwrap().clone();
17413
17414 editor.update_in(cx, |editor, window, cx| {
17415 let range = target_range.to_point(target_buffer.read(cx));
17416 let range = editor.range_for_match(&range);
17417 let range = collapse_multiline_range(range);
17418
17419 if !split
17420 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17421 {
17422 editor.go_to_singleton_buffer_range(range, window, cx);
17423 } else {
17424 let Some(workspace) = workspace else {
17425 return Navigated::No;
17426 };
17427 let pane = workspace.read(cx).active_pane().clone();
17428 window.defer(cx, move |window, cx| {
17429 let target_editor: Entity<Self> =
17430 workspace.update(cx, |workspace, cx| {
17431 let pane = if split {
17432 workspace.adjacent_pane(window, cx)
17433 } else {
17434 workspace.active_pane().clone()
17435 };
17436
17437 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17438 let keep_old_preview = preview_tabs_settings
17439 .enable_keep_preview_on_code_navigation;
17440 let allow_new_preview = preview_tabs_settings
17441 .enable_preview_file_from_code_navigation;
17442
17443 workspace.open_project_item(
17444 pane,
17445 target_buffer.clone(),
17446 true,
17447 true,
17448 keep_old_preview,
17449 allow_new_preview,
17450 window,
17451 cx,
17452 )
17453 });
17454 target_editor.update(cx, |target_editor, cx| {
17455 // When selecting a definition in a different buffer, disable the nav history
17456 // to avoid creating a history entry at the previous cursor location.
17457 pane.update(cx, |pane, _| pane.disable_history());
17458 target_editor.go_to_singleton_buffer_range(range, window, cx);
17459 pane.update(cx, |pane, _| pane.enable_history());
17460 });
17461 });
17462 }
17463 Navigated::Yes
17464 })
17465 }
17466 })
17467 }
17468
17469 fn compute_target_location(
17470 &self,
17471 lsp_location: lsp::Location,
17472 server_id: LanguageServerId,
17473 window: &mut Window,
17474 cx: &mut Context<Self>,
17475 ) -> Task<anyhow::Result<Option<Location>>> {
17476 let Some(project) = self.project.clone() else {
17477 return Task::ready(Ok(None));
17478 };
17479
17480 cx.spawn_in(window, async move |editor, cx| {
17481 let location_task = editor.update(cx, |_, cx| {
17482 project.update(cx, |project, cx| {
17483 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17484 })
17485 })?;
17486 let location = Some({
17487 let target_buffer_handle = location_task.await.context("open local buffer")?;
17488 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17489 let target_start = target_buffer
17490 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17491 let target_end = target_buffer
17492 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17493 target_buffer.anchor_after(target_start)
17494 ..target_buffer.anchor_before(target_end)
17495 })?;
17496 Location {
17497 buffer: target_buffer_handle,
17498 range,
17499 }
17500 });
17501 Ok(location)
17502 })
17503 }
17504
17505 fn go_to_next_reference(
17506 &mut self,
17507 _: &GoToNextReference,
17508 window: &mut Window,
17509 cx: &mut Context<Self>,
17510 ) {
17511 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17512 if let Some(task) = task {
17513 task.detach();
17514 };
17515 }
17516
17517 fn go_to_prev_reference(
17518 &mut self,
17519 _: &GoToPreviousReference,
17520 window: &mut Window,
17521 cx: &mut Context<Self>,
17522 ) {
17523 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17524 if let Some(task) = task {
17525 task.detach();
17526 };
17527 }
17528
17529 pub fn go_to_reference_before_or_after_position(
17530 &mut self,
17531 direction: Direction,
17532 count: usize,
17533 window: &mut Window,
17534 cx: &mut Context<Self>,
17535 ) -> Option<Task<Result<()>>> {
17536 let selection = self.selections.newest_anchor();
17537 let head = selection.head();
17538
17539 let multi_buffer = self.buffer.read(cx);
17540
17541 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17542 let workspace = self.workspace()?;
17543 let project = workspace.read(cx).project().clone();
17544 let references =
17545 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17546 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17547 let Some(locations) = references.await? else {
17548 return Ok(());
17549 };
17550
17551 if locations.is_empty() {
17552 // totally normal - the cursor may be on something which is not
17553 // a symbol (e.g. a keyword)
17554 log::info!("no references found under cursor");
17555 return Ok(());
17556 }
17557
17558 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17559
17560 let (locations, current_location_index) =
17561 multi_buffer.update(cx, |multi_buffer, cx| {
17562 let mut locations = locations
17563 .into_iter()
17564 .filter_map(|loc| {
17565 let start = multi_buffer.buffer_anchor_to_anchor(
17566 &loc.buffer,
17567 loc.range.start,
17568 cx,
17569 )?;
17570 let end = multi_buffer.buffer_anchor_to_anchor(
17571 &loc.buffer,
17572 loc.range.end,
17573 cx,
17574 )?;
17575 Some(start..end)
17576 })
17577 .collect::<Vec<_>>();
17578
17579 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17580 // There is an O(n) implementation, but given this list will be
17581 // small (usually <100 items), the extra O(log(n)) factor isn't
17582 // worth the (surprisingly large amount of) extra complexity.
17583 locations
17584 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17585
17586 let head_offset = head.to_offset(&multi_buffer_snapshot);
17587
17588 let current_location_index = locations.iter().position(|loc| {
17589 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17590 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17591 });
17592
17593 (locations, current_location_index)
17594 })?;
17595
17596 let Some(current_location_index) = current_location_index else {
17597 // This indicates something has gone wrong, because we already
17598 // handle the "no references" case above
17599 log::error!(
17600 "failed to find current reference under cursor. Total references: {}",
17601 locations.len()
17602 );
17603 return Ok(());
17604 };
17605
17606 let destination_location_index = match direction {
17607 Direction::Next => (current_location_index + count) % locations.len(),
17608 Direction::Prev => {
17609 (current_location_index + locations.len() - count % locations.len())
17610 % locations.len()
17611 }
17612 };
17613
17614 // TODO(cameron): is this needed?
17615 // the thinking is to avoid "jumping to the current location" (avoid
17616 // polluting "jumplist" in vim terms)
17617 if current_location_index == destination_location_index {
17618 return Ok(());
17619 }
17620
17621 let Range { start, end } = locations[destination_location_index];
17622
17623 editor.update_in(cx, |editor, window, cx| {
17624 let effects = SelectionEffects::default();
17625
17626 editor.unfold_ranges(&[start..end], false, false, cx);
17627 editor.change_selections(effects, window, cx, |s| {
17628 s.select_ranges([start..start]);
17629 });
17630 })?;
17631
17632 Ok(())
17633 }))
17634 }
17635
17636 pub fn find_all_references(
17637 &mut self,
17638 action: &FindAllReferences,
17639 window: &mut Window,
17640 cx: &mut Context<Self>,
17641 ) -> Option<Task<Result<Navigated>>> {
17642 let always_open_multibuffer = action.always_open_multibuffer;
17643 let selection = self.selections.newest_anchor();
17644 let multi_buffer = self.buffer.read(cx);
17645 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17646 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17647 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17648 let head = selection_offset.head();
17649
17650 let head_anchor = multi_buffer_snapshot.anchor_at(
17651 head,
17652 if head < selection_offset.tail() {
17653 Bias::Right
17654 } else {
17655 Bias::Left
17656 },
17657 );
17658
17659 match self
17660 .find_all_references_task_sources
17661 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17662 {
17663 Ok(_) => {
17664 log::info!(
17665 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17666 );
17667 return None;
17668 }
17669 Err(i) => {
17670 self.find_all_references_task_sources.insert(i, head_anchor);
17671 }
17672 }
17673
17674 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17675 let workspace = self.workspace()?;
17676 let project = workspace.read(cx).project().clone();
17677 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17678 Some(cx.spawn_in(window, async move |editor, cx| {
17679 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17680 if let Ok(i) = editor
17681 .find_all_references_task_sources
17682 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17683 {
17684 editor.find_all_references_task_sources.remove(i);
17685 }
17686 });
17687
17688 let Some(locations) = references.await? else {
17689 return anyhow::Ok(Navigated::No);
17690 };
17691 let mut locations = cx.update(|_, cx| {
17692 locations
17693 .into_iter()
17694 .map(|location| {
17695 let buffer = location.buffer.read(cx);
17696 (location.buffer, location.range.to_point(buffer))
17697 })
17698 // if special-casing the single-match case, remove ranges
17699 // that intersect current selection
17700 .filter(|(location_buffer, location)| {
17701 if always_open_multibuffer || &buffer != location_buffer {
17702 return true;
17703 }
17704
17705 !location.contains_inclusive(&selection_point.range())
17706 })
17707 .into_group_map()
17708 })?;
17709 if locations.is_empty() {
17710 return anyhow::Ok(Navigated::No);
17711 }
17712 for ranges in locations.values_mut() {
17713 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17714 ranges.dedup();
17715 }
17716 let mut num_locations = 0;
17717 for ranges in locations.values_mut() {
17718 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17719 ranges.dedup();
17720 num_locations += ranges.len();
17721 }
17722
17723 if num_locations == 1 && !always_open_multibuffer {
17724 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17725 let target_range = target_ranges.first().unwrap().clone();
17726
17727 return editor.update_in(cx, |editor, window, cx| {
17728 let range = target_range.to_point(target_buffer.read(cx));
17729 let range = editor.range_for_match(&range);
17730 let range = range.start..range.start;
17731
17732 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17733 editor.go_to_singleton_buffer_range(range, window, cx);
17734 } else {
17735 let pane = workspace.read(cx).active_pane().clone();
17736 window.defer(cx, move |window, cx| {
17737 let target_editor: Entity<Self> =
17738 workspace.update(cx, |workspace, cx| {
17739 let pane = workspace.active_pane().clone();
17740
17741 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17742 let keep_old_preview = preview_tabs_settings
17743 .enable_keep_preview_on_code_navigation;
17744 let allow_new_preview = preview_tabs_settings
17745 .enable_preview_file_from_code_navigation;
17746
17747 workspace.open_project_item(
17748 pane,
17749 target_buffer.clone(),
17750 true,
17751 true,
17752 keep_old_preview,
17753 allow_new_preview,
17754 window,
17755 cx,
17756 )
17757 });
17758 target_editor.update(cx, |target_editor, cx| {
17759 // When selecting a definition in a different buffer, disable the nav history
17760 // to avoid creating a history entry at the previous cursor location.
17761 pane.update(cx, |pane, _| pane.disable_history());
17762 target_editor.go_to_singleton_buffer_range(range, window, cx);
17763 pane.update(cx, |pane, _| pane.enable_history());
17764 });
17765 });
17766 }
17767 Navigated::No
17768 });
17769 }
17770
17771 workspace.update_in(cx, |workspace, window, cx| {
17772 let target = locations
17773 .iter()
17774 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17775 .map(|(buffer, location)| {
17776 buffer
17777 .read(cx)
17778 .text_for_range(location.clone())
17779 .collect::<String>()
17780 })
17781 .filter(|text| !text.contains('\n'))
17782 .unique()
17783 .take(3)
17784 .join(", ");
17785 let title = if target.is_empty() {
17786 "References".to_owned()
17787 } else {
17788 format!("References to {target}")
17789 };
17790 let allow_preview = PreviewTabsSettings::get_global(cx)
17791 .enable_preview_multibuffer_from_code_navigation;
17792 Self::open_locations_in_multibuffer(
17793 workspace,
17794 locations,
17795 title,
17796 false,
17797 allow_preview,
17798 MultibufferSelectionMode::First,
17799 window,
17800 cx,
17801 );
17802 Navigated::Yes
17803 })
17804 }))
17805 }
17806
17807 /// Opens a multibuffer with the given project locations in it.
17808 pub fn open_locations_in_multibuffer(
17809 workspace: &mut Workspace,
17810 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17811 title: String,
17812 split: bool,
17813 allow_preview: bool,
17814 multibuffer_selection_mode: MultibufferSelectionMode,
17815 window: &mut Window,
17816 cx: &mut Context<Workspace>,
17817 ) {
17818 if locations.is_empty() {
17819 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17820 return;
17821 }
17822
17823 let capability = workspace.project().read(cx).capability();
17824 let mut ranges = <Vec<Range<Anchor>>>::new();
17825
17826 // a key to find existing multibuffer editors with the same set of locations
17827 // to prevent us from opening more and more multibuffer tabs for searches and the like
17828 let mut key = (title.clone(), vec![]);
17829 let excerpt_buffer = cx.new(|cx| {
17830 let key = &mut key.1;
17831 let mut multibuffer = MultiBuffer::new(capability);
17832 for (buffer, mut ranges_for_buffer) in locations {
17833 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17834 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17835 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17836 PathKey::for_buffer(&buffer, cx),
17837 buffer.clone(),
17838 ranges_for_buffer,
17839 multibuffer_context_lines(cx),
17840 cx,
17841 );
17842 ranges.extend(new_ranges)
17843 }
17844
17845 multibuffer.with_title(title)
17846 });
17847 let existing = workspace.active_pane().update(cx, |pane, cx| {
17848 pane.items()
17849 .filter_map(|item| item.downcast::<Editor>())
17850 .find(|editor| {
17851 editor
17852 .read(cx)
17853 .lookup_key
17854 .as_ref()
17855 .and_then(|it| {
17856 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17857 })
17858 .is_some_and(|it| *it == key)
17859 })
17860 });
17861 let was_existing = existing.is_some();
17862 let editor = existing.unwrap_or_else(|| {
17863 cx.new(|cx| {
17864 let mut editor = Editor::for_multibuffer(
17865 excerpt_buffer,
17866 Some(workspace.project().clone()),
17867 window,
17868 cx,
17869 );
17870 editor.lookup_key = Some(Box::new(key));
17871 editor
17872 })
17873 });
17874 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17875 MultibufferSelectionMode::First => {
17876 if let Some(first_range) = ranges.first() {
17877 editor.change_selections(
17878 SelectionEffects::no_scroll(),
17879 window,
17880 cx,
17881 |selections| {
17882 selections.clear_disjoint();
17883 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17884 },
17885 );
17886 }
17887 editor.highlight_background::<Self>(
17888 &ranges,
17889 |_, theme| theme.colors().editor_highlighted_line_background,
17890 cx,
17891 );
17892 }
17893 MultibufferSelectionMode::All => {
17894 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17895 selections.clear_disjoint();
17896 selections.select_anchor_ranges(ranges);
17897 });
17898 }
17899 });
17900
17901 let item = Box::new(editor);
17902
17903 let pane = if split {
17904 workspace.adjacent_pane(window, cx)
17905 } else {
17906 workspace.active_pane().clone()
17907 };
17908 let activate_pane = split;
17909
17910 let mut destination_index = None;
17911 pane.update(cx, |pane, cx| {
17912 if allow_preview && !was_existing {
17913 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17914 }
17915 if was_existing && !allow_preview {
17916 pane.unpreview_item_if_preview(item.item_id());
17917 }
17918 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17919 });
17920 }
17921
17922 pub fn rename(
17923 &mut self,
17924 _: &Rename,
17925 window: &mut Window,
17926 cx: &mut Context<Self>,
17927 ) -> Option<Task<Result<()>>> {
17928 use language::ToOffset as _;
17929
17930 let provider = self.semantics_provider.clone()?;
17931 let selection = self.selections.newest_anchor().clone();
17932 let (cursor_buffer, cursor_buffer_position) = self
17933 .buffer
17934 .read(cx)
17935 .text_anchor_for_position(selection.head(), cx)?;
17936 let (tail_buffer, cursor_buffer_position_end) = self
17937 .buffer
17938 .read(cx)
17939 .text_anchor_for_position(selection.tail(), cx)?;
17940 if tail_buffer != cursor_buffer {
17941 return None;
17942 }
17943
17944 let snapshot = cursor_buffer.read(cx).snapshot();
17945 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17946 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17947 let prepare_rename = provider
17948 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17949 .unwrap_or_else(|| Task::ready(Ok(None)));
17950 drop(snapshot);
17951
17952 Some(cx.spawn_in(window, async move |this, cx| {
17953 let rename_range = if let Some(range) = prepare_rename.await? {
17954 Some(range)
17955 } else {
17956 this.update(cx, |this, cx| {
17957 let buffer = this.buffer.read(cx).snapshot(cx);
17958 let mut buffer_highlights = this
17959 .document_highlights_for_position(selection.head(), &buffer)
17960 .filter(|highlight| {
17961 highlight.start.excerpt_id == selection.head().excerpt_id
17962 && highlight.end.excerpt_id == selection.head().excerpt_id
17963 });
17964 buffer_highlights
17965 .next()
17966 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17967 })?
17968 };
17969 if let Some(rename_range) = rename_range {
17970 this.update_in(cx, |this, window, cx| {
17971 let snapshot = cursor_buffer.read(cx).snapshot();
17972 let rename_buffer_range = rename_range.to_offset(&snapshot);
17973 let cursor_offset_in_rename_range =
17974 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17975 let cursor_offset_in_rename_range_end =
17976 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17977
17978 this.take_rename(false, window, cx);
17979 let buffer = this.buffer.read(cx).read(cx);
17980 let cursor_offset = selection.head().to_offset(&buffer);
17981 let rename_start =
17982 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17983 let rename_end = rename_start + rename_buffer_range.len();
17984 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17985 let mut old_highlight_id = None;
17986 let old_name: Arc<str> = buffer
17987 .chunks(rename_start..rename_end, true)
17988 .map(|chunk| {
17989 if old_highlight_id.is_none() {
17990 old_highlight_id = chunk.syntax_highlight_id;
17991 }
17992 chunk.text
17993 })
17994 .collect::<String>()
17995 .into();
17996
17997 drop(buffer);
17998
17999 // Position the selection in the rename editor so that it matches the current selection.
18000 this.show_local_selections = false;
18001 let rename_editor = cx.new(|cx| {
18002 let mut editor = Editor::single_line(window, cx);
18003 editor.buffer.update(cx, |buffer, cx| {
18004 buffer.edit(
18005 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18006 None,
18007 cx,
18008 )
18009 });
18010 let cursor_offset_in_rename_range =
18011 MultiBufferOffset(cursor_offset_in_rename_range);
18012 let cursor_offset_in_rename_range_end =
18013 MultiBufferOffset(cursor_offset_in_rename_range_end);
18014 let rename_selection_range = match cursor_offset_in_rename_range
18015 .cmp(&cursor_offset_in_rename_range_end)
18016 {
18017 Ordering::Equal => {
18018 editor.select_all(&SelectAll, window, cx);
18019 return editor;
18020 }
18021 Ordering::Less => {
18022 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18023 }
18024 Ordering::Greater => {
18025 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18026 }
18027 };
18028 if rename_selection_range.end.0 > old_name.len() {
18029 editor.select_all(&SelectAll, window, cx);
18030 } else {
18031 editor.change_selections(Default::default(), window, cx, |s| {
18032 s.select_ranges([rename_selection_range]);
18033 });
18034 }
18035 editor
18036 });
18037 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18038 if e == &EditorEvent::Focused {
18039 cx.emit(EditorEvent::FocusedIn)
18040 }
18041 })
18042 .detach();
18043
18044 let write_highlights =
18045 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18046 let read_highlights =
18047 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18048 let ranges = write_highlights
18049 .iter()
18050 .flat_map(|(_, ranges)| ranges.iter())
18051 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18052 .cloned()
18053 .collect();
18054
18055 this.highlight_text::<Rename>(
18056 ranges,
18057 HighlightStyle {
18058 fade_out: Some(0.6),
18059 ..Default::default()
18060 },
18061 cx,
18062 );
18063 let rename_focus_handle = rename_editor.focus_handle(cx);
18064 window.focus(&rename_focus_handle, cx);
18065 let block_id = this.insert_blocks(
18066 [BlockProperties {
18067 style: BlockStyle::Flex,
18068 placement: BlockPlacement::Below(range.start),
18069 height: Some(1),
18070 render: Arc::new({
18071 let rename_editor = rename_editor.clone();
18072 move |cx: &mut BlockContext| {
18073 let mut text_style = cx.editor_style.text.clone();
18074 if let Some(highlight_style) = old_highlight_id
18075 .and_then(|h| h.style(&cx.editor_style.syntax))
18076 {
18077 text_style = text_style.highlight(highlight_style);
18078 }
18079 div()
18080 .block_mouse_except_scroll()
18081 .pl(cx.anchor_x)
18082 .child(EditorElement::new(
18083 &rename_editor,
18084 EditorStyle {
18085 background: cx.theme().system().transparent,
18086 local_player: cx.editor_style.local_player,
18087 text: text_style,
18088 scrollbar_width: cx.editor_style.scrollbar_width,
18089 syntax: cx.editor_style.syntax.clone(),
18090 status: cx.editor_style.status.clone(),
18091 inlay_hints_style: HighlightStyle {
18092 font_weight: Some(FontWeight::BOLD),
18093 ..make_inlay_hints_style(cx.app)
18094 },
18095 edit_prediction_styles: make_suggestion_styles(
18096 cx.app,
18097 ),
18098 ..EditorStyle::default()
18099 },
18100 ))
18101 .into_any_element()
18102 }
18103 }),
18104 priority: 0,
18105 }],
18106 Some(Autoscroll::fit()),
18107 cx,
18108 )[0];
18109 this.pending_rename = Some(RenameState {
18110 range,
18111 old_name,
18112 editor: rename_editor,
18113 block_id,
18114 });
18115 })?;
18116 }
18117
18118 Ok(())
18119 }))
18120 }
18121
18122 pub fn confirm_rename(
18123 &mut self,
18124 _: &ConfirmRename,
18125 window: &mut Window,
18126 cx: &mut Context<Self>,
18127 ) -> Option<Task<Result<()>>> {
18128 let rename = self.take_rename(false, window, cx)?;
18129 let workspace = self.workspace()?.downgrade();
18130 let (buffer, start) = self
18131 .buffer
18132 .read(cx)
18133 .text_anchor_for_position(rename.range.start, cx)?;
18134 let (end_buffer, _) = self
18135 .buffer
18136 .read(cx)
18137 .text_anchor_for_position(rename.range.end, cx)?;
18138 if buffer != end_buffer {
18139 return None;
18140 }
18141
18142 let old_name = rename.old_name;
18143 let new_name = rename.editor.read(cx).text(cx);
18144
18145 let rename = self.semantics_provider.as_ref()?.perform_rename(
18146 &buffer,
18147 start,
18148 new_name.clone(),
18149 cx,
18150 )?;
18151
18152 Some(cx.spawn_in(window, async move |editor, cx| {
18153 let project_transaction = rename.await?;
18154 Self::open_project_transaction(
18155 &editor,
18156 workspace,
18157 project_transaction,
18158 format!("Rename: {} → {}", old_name, new_name),
18159 cx,
18160 )
18161 .await?;
18162
18163 editor.update(cx, |editor, cx| {
18164 editor.refresh_document_highlights(cx);
18165 })?;
18166 Ok(())
18167 }))
18168 }
18169
18170 fn take_rename(
18171 &mut self,
18172 moving_cursor: bool,
18173 window: &mut Window,
18174 cx: &mut Context<Self>,
18175 ) -> Option<RenameState> {
18176 let rename = self.pending_rename.take()?;
18177 if rename.editor.focus_handle(cx).is_focused(window) {
18178 window.focus(&self.focus_handle, cx);
18179 }
18180
18181 self.remove_blocks(
18182 [rename.block_id].into_iter().collect(),
18183 Some(Autoscroll::fit()),
18184 cx,
18185 );
18186 self.clear_highlights::<Rename>(cx);
18187 self.show_local_selections = true;
18188
18189 if moving_cursor {
18190 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18191 editor
18192 .selections
18193 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18194 .head()
18195 });
18196
18197 // Update the selection to match the position of the selection inside
18198 // the rename editor.
18199 let snapshot = self.buffer.read(cx).read(cx);
18200 let rename_range = rename.range.to_offset(&snapshot);
18201 let cursor_in_editor = snapshot
18202 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18203 .min(rename_range.end);
18204 drop(snapshot);
18205
18206 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18207 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18208 });
18209 } else {
18210 self.refresh_document_highlights(cx);
18211 }
18212
18213 Some(rename)
18214 }
18215
18216 pub fn pending_rename(&self) -> Option<&RenameState> {
18217 self.pending_rename.as_ref()
18218 }
18219
18220 fn format(
18221 &mut self,
18222 _: &Format,
18223 window: &mut Window,
18224 cx: &mut Context<Self>,
18225 ) -> Option<Task<Result<()>>> {
18226 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18227
18228 let project = match &self.project {
18229 Some(project) => project.clone(),
18230 None => return None,
18231 };
18232
18233 Some(self.perform_format(
18234 project,
18235 FormatTrigger::Manual,
18236 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18237 window,
18238 cx,
18239 ))
18240 }
18241
18242 fn format_selections(
18243 &mut self,
18244 _: &FormatSelections,
18245 window: &mut Window,
18246 cx: &mut Context<Self>,
18247 ) -> Option<Task<Result<()>>> {
18248 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18249
18250 let project = match &self.project {
18251 Some(project) => project.clone(),
18252 None => return None,
18253 };
18254
18255 let ranges = self
18256 .selections
18257 .all_adjusted(&self.display_snapshot(cx))
18258 .into_iter()
18259 .map(|selection| selection.range())
18260 .collect_vec();
18261
18262 Some(self.perform_format(
18263 project,
18264 FormatTrigger::Manual,
18265 FormatTarget::Ranges(ranges),
18266 window,
18267 cx,
18268 ))
18269 }
18270
18271 fn perform_format(
18272 &mut self,
18273 project: Entity<Project>,
18274 trigger: FormatTrigger,
18275 target: FormatTarget,
18276 window: &mut Window,
18277 cx: &mut Context<Self>,
18278 ) -> Task<Result<()>> {
18279 let buffer = self.buffer.clone();
18280 let (buffers, target) = match target {
18281 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18282 FormatTarget::Ranges(selection_ranges) => {
18283 let multi_buffer = buffer.read(cx);
18284 let snapshot = multi_buffer.read(cx);
18285 let mut buffers = HashSet::default();
18286 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18287 BTreeMap::new();
18288 for selection_range in selection_ranges {
18289 for (buffer, buffer_range, _) in
18290 snapshot.range_to_buffer_ranges(selection_range)
18291 {
18292 let buffer_id = buffer.remote_id();
18293 let start = buffer.anchor_before(buffer_range.start);
18294 let end = buffer.anchor_after(buffer_range.end);
18295 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18296 buffer_id_to_ranges
18297 .entry(buffer_id)
18298 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18299 .or_insert_with(|| vec![start..end]);
18300 }
18301 }
18302 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18303 }
18304 };
18305
18306 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18307 let selections_prev = transaction_id_prev
18308 .and_then(|transaction_id_prev| {
18309 // default to selections as they were after the last edit, if we have them,
18310 // instead of how they are now.
18311 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18312 // will take you back to where you made the last edit, instead of staying where you scrolled
18313 self.selection_history
18314 .transaction(transaction_id_prev)
18315 .map(|t| t.0.clone())
18316 })
18317 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18318
18319 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18320 let format = project.update(cx, |project, cx| {
18321 project.format(buffers, target, true, trigger, cx)
18322 });
18323
18324 cx.spawn_in(window, async move |editor, cx| {
18325 let transaction = futures::select_biased! {
18326 transaction = format.log_err().fuse() => transaction,
18327 () = timeout => {
18328 log::warn!("timed out waiting for formatting");
18329 None
18330 }
18331 };
18332
18333 buffer
18334 .update(cx, |buffer, cx| {
18335 if let Some(transaction) = transaction
18336 && !buffer.is_singleton()
18337 {
18338 buffer.push_transaction(&transaction.0, cx);
18339 }
18340 cx.notify();
18341 })
18342 .ok();
18343
18344 if let Some(transaction_id_now) =
18345 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18346 {
18347 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18348 if has_new_transaction {
18349 _ = editor.update(cx, |editor, _| {
18350 editor
18351 .selection_history
18352 .insert_transaction(transaction_id_now, selections_prev);
18353 });
18354 }
18355 }
18356
18357 Ok(())
18358 })
18359 }
18360
18361 fn organize_imports(
18362 &mut self,
18363 _: &OrganizeImports,
18364 window: &mut Window,
18365 cx: &mut Context<Self>,
18366 ) -> Option<Task<Result<()>>> {
18367 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18368 let project = match &self.project {
18369 Some(project) => project.clone(),
18370 None => return None,
18371 };
18372 Some(self.perform_code_action_kind(
18373 project,
18374 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18375 window,
18376 cx,
18377 ))
18378 }
18379
18380 fn perform_code_action_kind(
18381 &mut self,
18382 project: Entity<Project>,
18383 kind: CodeActionKind,
18384 window: &mut Window,
18385 cx: &mut Context<Self>,
18386 ) -> Task<Result<()>> {
18387 let buffer = self.buffer.clone();
18388 let buffers = buffer.read(cx).all_buffers();
18389 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18390 let apply_action = project.update(cx, |project, cx| {
18391 project.apply_code_action_kind(buffers, kind, true, cx)
18392 });
18393 cx.spawn_in(window, async move |_, cx| {
18394 let transaction = futures::select_biased! {
18395 () = timeout => {
18396 log::warn!("timed out waiting for executing code action");
18397 None
18398 }
18399 transaction = apply_action.log_err().fuse() => transaction,
18400 };
18401 buffer
18402 .update(cx, |buffer, cx| {
18403 // check if we need this
18404 if let Some(transaction) = transaction
18405 && !buffer.is_singleton()
18406 {
18407 buffer.push_transaction(&transaction.0, cx);
18408 }
18409 cx.notify();
18410 })
18411 .ok();
18412 Ok(())
18413 })
18414 }
18415
18416 pub fn restart_language_server(
18417 &mut self,
18418 _: &RestartLanguageServer,
18419 _: &mut Window,
18420 cx: &mut Context<Self>,
18421 ) {
18422 if let Some(project) = self.project.clone() {
18423 self.buffer.update(cx, |multi_buffer, cx| {
18424 project.update(cx, |project, cx| {
18425 project.restart_language_servers_for_buffers(
18426 multi_buffer.all_buffers().into_iter().collect(),
18427 HashSet::default(),
18428 cx,
18429 );
18430 });
18431 })
18432 }
18433 }
18434
18435 pub fn stop_language_server(
18436 &mut self,
18437 _: &StopLanguageServer,
18438 _: &mut Window,
18439 cx: &mut Context<Self>,
18440 ) {
18441 if let Some(project) = self.project.clone() {
18442 self.buffer.update(cx, |multi_buffer, cx| {
18443 project.update(cx, |project, cx| {
18444 project.stop_language_servers_for_buffers(
18445 multi_buffer.all_buffers().into_iter().collect(),
18446 HashSet::default(),
18447 cx,
18448 );
18449 });
18450 });
18451 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18452 }
18453 }
18454
18455 fn cancel_language_server_work(
18456 workspace: &mut Workspace,
18457 _: &actions::CancelLanguageServerWork,
18458 _: &mut Window,
18459 cx: &mut Context<Workspace>,
18460 ) {
18461 let project = workspace.project();
18462 let buffers = workspace
18463 .active_item(cx)
18464 .and_then(|item| item.act_as::<Editor>(cx))
18465 .map_or(HashSet::default(), |editor| {
18466 editor.read(cx).buffer.read(cx).all_buffers()
18467 });
18468 project.update(cx, |project, cx| {
18469 project.cancel_language_server_work_for_buffers(buffers, cx);
18470 });
18471 }
18472
18473 fn show_character_palette(
18474 &mut self,
18475 _: &ShowCharacterPalette,
18476 window: &mut Window,
18477 _: &mut Context<Self>,
18478 ) {
18479 window.show_character_palette();
18480 }
18481
18482 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18483 if !self.diagnostics_enabled() {
18484 return;
18485 }
18486
18487 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18488 let buffer = self.buffer.read(cx).snapshot(cx);
18489 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18490 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18491 let is_valid = buffer
18492 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18493 .any(|entry| {
18494 entry.diagnostic.is_primary
18495 && !entry.range.is_empty()
18496 && entry.range.start == primary_range_start
18497 && entry.diagnostic.message == active_diagnostics.active_message
18498 });
18499
18500 if !is_valid {
18501 self.dismiss_diagnostics(cx);
18502 }
18503 }
18504 }
18505
18506 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18507 match &self.active_diagnostics {
18508 ActiveDiagnostic::Group(group) => Some(group),
18509 _ => None,
18510 }
18511 }
18512
18513 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18514 if !self.diagnostics_enabled() {
18515 return;
18516 }
18517 self.dismiss_diagnostics(cx);
18518 self.active_diagnostics = ActiveDiagnostic::All;
18519 }
18520
18521 fn activate_diagnostics(
18522 &mut self,
18523 buffer_id: BufferId,
18524 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18525 window: &mut Window,
18526 cx: &mut Context<Self>,
18527 ) {
18528 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18529 return;
18530 }
18531 self.dismiss_diagnostics(cx);
18532 let snapshot = self.snapshot(window, cx);
18533 let buffer = self.buffer.read(cx).snapshot(cx);
18534 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18535 return;
18536 };
18537
18538 let diagnostic_group = buffer
18539 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18540 .collect::<Vec<_>>();
18541
18542 let language_registry = self
18543 .project()
18544 .map(|project| project.read(cx).languages().clone());
18545
18546 let blocks = renderer.render_group(
18547 diagnostic_group,
18548 buffer_id,
18549 snapshot,
18550 cx.weak_entity(),
18551 language_registry,
18552 cx,
18553 );
18554
18555 let blocks = self.display_map.update(cx, |display_map, cx| {
18556 display_map.insert_blocks(blocks, cx).into_iter().collect()
18557 });
18558 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18559 active_range: buffer.anchor_before(diagnostic.range.start)
18560 ..buffer.anchor_after(diagnostic.range.end),
18561 active_message: diagnostic.diagnostic.message.clone(),
18562 group_id: diagnostic.diagnostic.group_id,
18563 blocks,
18564 });
18565 cx.notify();
18566 }
18567
18568 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18569 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18570 return;
18571 };
18572
18573 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18574 if let ActiveDiagnostic::Group(group) = prev {
18575 self.display_map.update(cx, |display_map, cx| {
18576 display_map.remove_blocks(group.blocks, cx);
18577 });
18578 cx.notify();
18579 }
18580 }
18581
18582 /// Disable inline diagnostics rendering for this editor.
18583 pub fn disable_inline_diagnostics(&mut self) {
18584 self.inline_diagnostics_enabled = false;
18585 self.inline_diagnostics_update = Task::ready(());
18586 self.inline_diagnostics.clear();
18587 }
18588
18589 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18590 self.diagnostics_enabled = false;
18591 self.dismiss_diagnostics(cx);
18592 self.inline_diagnostics_update = Task::ready(());
18593 self.inline_diagnostics.clear();
18594 }
18595
18596 pub fn disable_word_completions(&mut self) {
18597 self.word_completions_enabled = false;
18598 }
18599
18600 pub fn diagnostics_enabled(&self) -> bool {
18601 self.diagnostics_enabled && self.mode.is_full()
18602 }
18603
18604 pub fn inline_diagnostics_enabled(&self) -> bool {
18605 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18606 }
18607
18608 pub fn show_inline_diagnostics(&self) -> bool {
18609 self.show_inline_diagnostics
18610 }
18611
18612 pub fn toggle_inline_diagnostics(
18613 &mut self,
18614 _: &ToggleInlineDiagnostics,
18615 window: &mut Window,
18616 cx: &mut Context<Editor>,
18617 ) {
18618 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18619 self.refresh_inline_diagnostics(false, window, cx);
18620 }
18621
18622 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18623 self.diagnostics_max_severity = severity;
18624 self.display_map.update(cx, |display_map, _| {
18625 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18626 });
18627 }
18628
18629 pub fn toggle_diagnostics(
18630 &mut self,
18631 _: &ToggleDiagnostics,
18632 window: &mut Window,
18633 cx: &mut Context<Editor>,
18634 ) {
18635 if !self.diagnostics_enabled() {
18636 return;
18637 }
18638
18639 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18640 EditorSettings::get_global(cx)
18641 .diagnostics_max_severity
18642 .filter(|severity| severity != &DiagnosticSeverity::Off)
18643 .unwrap_or(DiagnosticSeverity::Hint)
18644 } else {
18645 DiagnosticSeverity::Off
18646 };
18647 self.set_max_diagnostics_severity(new_severity, cx);
18648 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18649 self.active_diagnostics = ActiveDiagnostic::None;
18650 self.inline_diagnostics_update = Task::ready(());
18651 self.inline_diagnostics.clear();
18652 } else {
18653 self.refresh_inline_diagnostics(false, window, cx);
18654 }
18655
18656 cx.notify();
18657 }
18658
18659 pub fn toggle_minimap(
18660 &mut self,
18661 _: &ToggleMinimap,
18662 window: &mut Window,
18663 cx: &mut Context<Editor>,
18664 ) {
18665 if self.supports_minimap(cx) {
18666 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18667 }
18668 }
18669
18670 fn refresh_inline_diagnostics(
18671 &mut self,
18672 debounce: bool,
18673 window: &mut Window,
18674 cx: &mut Context<Self>,
18675 ) {
18676 let max_severity = ProjectSettings::get_global(cx)
18677 .diagnostics
18678 .inline
18679 .max_severity
18680 .unwrap_or(self.diagnostics_max_severity);
18681
18682 if !self.inline_diagnostics_enabled()
18683 || !self.diagnostics_enabled()
18684 || !self.show_inline_diagnostics
18685 || max_severity == DiagnosticSeverity::Off
18686 {
18687 self.inline_diagnostics_update = Task::ready(());
18688 self.inline_diagnostics.clear();
18689 return;
18690 }
18691
18692 let debounce_ms = ProjectSettings::get_global(cx)
18693 .diagnostics
18694 .inline
18695 .update_debounce_ms;
18696 let debounce = if debounce && debounce_ms > 0 {
18697 Some(Duration::from_millis(debounce_ms))
18698 } else {
18699 None
18700 };
18701 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18702 if let Some(debounce) = debounce {
18703 cx.background_executor().timer(debounce).await;
18704 }
18705 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18706 editor
18707 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18708 .ok()
18709 }) else {
18710 return;
18711 };
18712
18713 let new_inline_diagnostics = cx
18714 .background_spawn(async move {
18715 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18716 for diagnostic_entry in
18717 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18718 {
18719 let message = diagnostic_entry
18720 .diagnostic
18721 .message
18722 .split_once('\n')
18723 .map(|(line, _)| line)
18724 .map(SharedString::new)
18725 .unwrap_or_else(|| {
18726 SharedString::new(&*diagnostic_entry.diagnostic.message)
18727 });
18728 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18729 let (Ok(i) | Err(i)) = inline_diagnostics
18730 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18731 inline_diagnostics.insert(
18732 i,
18733 (
18734 start_anchor,
18735 InlineDiagnostic {
18736 message,
18737 group_id: diagnostic_entry.diagnostic.group_id,
18738 start: diagnostic_entry.range.start.to_point(&snapshot),
18739 is_primary: diagnostic_entry.diagnostic.is_primary,
18740 severity: diagnostic_entry.diagnostic.severity,
18741 },
18742 ),
18743 );
18744 }
18745 inline_diagnostics
18746 })
18747 .await;
18748
18749 editor
18750 .update(cx, |editor, cx| {
18751 editor.inline_diagnostics = new_inline_diagnostics;
18752 cx.notify();
18753 })
18754 .ok();
18755 });
18756 }
18757
18758 fn pull_diagnostics(
18759 &mut self,
18760 buffer_id: Option<BufferId>,
18761 window: &Window,
18762 cx: &mut Context<Self>,
18763 ) -> Option<()> {
18764 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18765 return None;
18766 }
18767 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18768 .diagnostics
18769 .lsp_pull_diagnostics;
18770 if !pull_diagnostics_settings.enabled {
18771 return None;
18772 }
18773 let project = self.project()?.downgrade();
18774
18775 let mut edited_buffer_ids = HashSet::default();
18776 let mut edited_worktree_ids = HashSet::default();
18777 let edited_buffers = match buffer_id {
18778 Some(buffer_id) => {
18779 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18780 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18781 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18782 edited_worktree_ids.insert(worktree_id);
18783 vec![buffer]
18784 }
18785 None => self
18786 .buffer()
18787 .read(cx)
18788 .all_buffers()
18789 .into_iter()
18790 .filter(|buffer| {
18791 let buffer = buffer.read(cx);
18792 match buffer.file().map(|f| f.worktree_id(cx)) {
18793 Some(worktree_id) => {
18794 edited_buffer_ids.insert(buffer.remote_id());
18795 edited_worktree_ids.insert(worktree_id);
18796 true
18797 }
18798 None => false,
18799 }
18800 })
18801 .collect::<Vec<_>>(),
18802 };
18803
18804 if edited_buffers.is_empty() {
18805 self.pull_diagnostics_task = Task::ready(());
18806 self.pull_diagnostics_background_task = Task::ready(());
18807 return None;
18808 }
18809
18810 let mut already_used_buffers = HashSet::default();
18811 let related_open_buffers = self
18812 .workspace
18813 .as_ref()
18814 .and_then(|(workspace, _)| workspace.upgrade())
18815 .into_iter()
18816 .flat_map(|workspace| workspace.read(cx).panes())
18817 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18818 .filter(|editor| editor != &cx.entity())
18819 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18820 .filter(|buffer| {
18821 let buffer = buffer.read(cx);
18822 let buffer_id = buffer.remote_id();
18823 if already_used_buffers.insert(buffer_id) {
18824 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18825 return !edited_buffer_ids.contains(&buffer_id)
18826 && !edited_worktree_ids.contains(&worktree_id);
18827 }
18828 }
18829 false
18830 })
18831 .collect::<Vec<_>>();
18832
18833 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18834 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18835 if buffers.is_empty() {
18836 return Task::ready(());
18837 }
18838 let project_weak = project.clone();
18839 cx.spawn_in(window, async move |_, cx| {
18840 cx.background_executor().timer(delay).await;
18841
18842 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18843 buffers
18844 .into_iter()
18845 .filter_map(|buffer| {
18846 project_weak
18847 .update(cx, |project, cx| {
18848 project.lsp_store().update(cx, |lsp_store, cx| {
18849 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18850 })
18851 })
18852 .ok()
18853 })
18854 .collect::<FuturesUnordered<_>>()
18855 }) else {
18856 return;
18857 };
18858
18859 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18860 if let Err(e) = pull_task {
18861 log::error!("Failed to update project diagnostics: {e:#}");
18862 }
18863 }
18864 })
18865 };
18866
18867 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18868 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18869
18870 Some(())
18871 }
18872
18873 pub fn set_selections_from_remote(
18874 &mut self,
18875 selections: Vec<Selection<Anchor>>,
18876 pending_selection: Option<Selection<Anchor>>,
18877 window: &mut Window,
18878 cx: &mut Context<Self>,
18879 ) {
18880 let old_cursor_position = self.selections.newest_anchor().head();
18881 self.selections
18882 .change_with(&self.display_snapshot(cx), |s| {
18883 s.select_anchors(selections);
18884 if let Some(pending_selection) = pending_selection {
18885 s.set_pending(pending_selection, SelectMode::Character);
18886 } else {
18887 s.clear_pending();
18888 }
18889 });
18890 self.selections_did_change(
18891 false,
18892 &old_cursor_position,
18893 SelectionEffects::default(),
18894 window,
18895 cx,
18896 );
18897 }
18898
18899 pub fn transact(
18900 &mut self,
18901 window: &mut Window,
18902 cx: &mut Context<Self>,
18903 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18904 ) -> Option<TransactionId> {
18905 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18906 this.start_transaction_at(Instant::now(), window, cx);
18907 update(this, window, cx);
18908 this.end_transaction_at(Instant::now(), cx)
18909 })
18910 }
18911
18912 pub fn start_transaction_at(
18913 &mut self,
18914 now: Instant,
18915 window: &mut Window,
18916 cx: &mut Context<Self>,
18917 ) -> Option<TransactionId> {
18918 self.end_selection(window, cx);
18919 if let Some(tx_id) = self
18920 .buffer
18921 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18922 {
18923 self.selection_history
18924 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18925 cx.emit(EditorEvent::TransactionBegun {
18926 transaction_id: tx_id,
18927 });
18928 Some(tx_id)
18929 } else {
18930 None
18931 }
18932 }
18933
18934 pub fn end_transaction_at(
18935 &mut self,
18936 now: Instant,
18937 cx: &mut Context<Self>,
18938 ) -> Option<TransactionId> {
18939 if let Some(transaction_id) = self
18940 .buffer
18941 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18942 {
18943 if let Some((_, end_selections)) =
18944 self.selection_history.transaction_mut(transaction_id)
18945 {
18946 *end_selections = Some(self.selections.disjoint_anchors_arc());
18947 } else {
18948 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18949 }
18950
18951 cx.emit(EditorEvent::Edited { transaction_id });
18952 Some(transaction_id)
18953 } else {
18954 None
18955 }
18956 }
18957
18958 pub fn modify_transaction_selection_history(
18959 &mut self,
18960 transaction_id: TransactionId,
18961 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18962 ) -> bool {
18963 self.selection_history
18964 .transaction_mut(transaction_id)
18965 .map(modify)
18966 .is_some()
18967 }
18968
18969 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18970 if self.selection_mark_mode {
18971 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18972 s.move_with(|_, sel| {
18973 sel.collapse_to(sel.head(), SelectionGoal::None);
18974 });
18975 })
18976 }
18977 self.selection_mark_mode = true;
18978 cx.notify();
18979 }
18980
18981 pub fn swap_selection_ends(
18982 &mut self,
18983 _: &actions::SwapSelectionEnds,
18984 window: &mut Window,
18985 cx: &mut Context<Self>,
18986 ) {
18987 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18988 s.move_with(|_, sel| {
18989 if sel.start != sel.end {
18990 sel.reversed = !sel.reversed
18991 }
18992 });
18993 });
18994 self.request_autoscroll(Autoscroll::newest(), cx);
18995 cx.notify();
18996 }
18997
18998 pub fn toggle_focus(
18999 workspace: &mut Workspace,
19000 _: &actions::ToggleFocus,
19001 window: &mut Window,
19002 cx: &mut Context<Workspace>,
19003 ) {
19004 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19005 return;
19006 };
19007 workspace.activate_item(&item, true, true, window, cx);
19008 }
19009
19010 pub fn toggle_fold(
19011 &mut self,
19012 _: &actions::ToggleFold,
19013 window: &mut Window,
19014 cx: &mut Context<Self>,
19015 ) {
19016 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19017 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19018 let selection = self.selections.newest::<Point>(&display_map);
19019
19020 let range = if selection.is_empty() {
19021 let point = selection.head().to_display_point(&display_map);
19022 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19023 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19024 .to_point(&display_map);
19025 start..end
19026 } else {
19027 selection.range()
19028 };
19029 if display_map.folds_in_range(range).next().is_some() {
19030 self.unfold_lines(&Default::default(), window, cx)
19031 } else {
19032 self.fold(&Default::default(), window, cx)
19033 }
19034 } else {
19035 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19036 let buffer_ids: HashSet<_> = self
19037 .selections
19038 .disjoint_anchor_ranges()
19039 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19040 .collect();
19041
19042 let should_unfold = buffer_ids
19043 .iter()
19044 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19045
19046 for buffer_id in buffer_ids {
19047 if should_unfold {
19048 self.unfold_buffer(buffer_id, cx);
19049 } else {
19050 self.fold_buffer(buffer_id, cx);
19051 }
19052 }
19053 }
19054 }
19055
19056 pub fn toggle_fold_recursive(
19057 &mut self,
19058 _: &actions::ToggleFoldRecursive,
19059 window: &mut Window,
19060 cx: &mut Context<Self>,
19061 ) {
19062 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19063
19064 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19065 let range = if selection.is_empty() {
19066 let point = selection.head().to_display_point(&display_map);
19067 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19068 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19069 .to_point(&display_map);
19070 start..end
19071 } else {
19072 selection.range()
19073 };
19074 if display_map.folds_in_range(range).next().is_some() {
19075 self.unfold_recursive(&Default::default(), window, cx)
19076 } else {
19077 self.fold_recursive(&Default::default(), window, cx)
19078 }
19079 }
19080
19081 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19082 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19083 let mut to_fold = Vec::new();
19084 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19085 let selections = self.selections.all_adjusted(&display_map);
19086
19087 for selection in selections {
19088 let range = selection.range().sorted();
19089 let buffer_start_row = range.start.row;
19090
19091 if range.start.row != range.end.row {
19092 let mut found = false;
19093 let mut row = range.start.row;
19094 while row <= range.end.row {
19095 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19096 {
19097 found = true;
19098 row = crease.range().end.row + 1;
19099 to_fold.push(crease);
19100 } else {
19101 row += 1
19102 }
19103 }
19104 if found {
19105 continue;
19106 }
19107 }
19108
19109 for row in (0..=range.start.row).rev() {
19110 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19111 && crease.range().end.row >= buffer_start_row
19112 {
19113 to_fold.push(crease);
19114 if row <= range.start.row {
19115 break;
19116 }
19117 }
19118 }
19119 }
19120
19121 self.fold_creases(to_fold, true, window, cx);
19122 } else {
19123 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19124 let buffer_ids = self
19125 .selections
19126 .disjoint_anchor_ranges()
19127 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19128 .collect::<HashSet<_>>();
19129 for buffer_id in buffer_ids {
19130 self.fold_buffer(buffer_id, cx);
19131 }
19132 }
19133 }
19134
19135 pub fn toggle_fold_all(
19136 &mut self,
19137 _: &actions::ToggleFoldAll,
19138 window: &mut Window,
19139 cx: &mut Context<Self>,
19140 ) {
19141 let has_folds = if self.buffer.read(cx).is_singleton() {
19142 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19143 let has_folds = display_map
19144 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19145 .next()
19146 .is_some();
19147 has_folds
19148 } else {
19149 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19150 let has_folds = buffer_ids
19151 .iter()
19152 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19153 has_folds
19154 };
19155
19156 if has_folds {
19157 self.unfold_all(&actions::UnfoldAll, window, cx);
19158 } else {
19159 self.fold_all(&actions::FoldAll, window, cx);
19160 }
19161 }
19162
19163 fn fold_at_level(
19164 &mut self,
19165 fold_at: &FoldAtLevel,
19166 window: &mut Window,
19167 cx: &mut Context<Self>,
19168 ) {
19169 if !self.buffer.read(cx).is_singleton() {
19170 return;
19171 }
19172
19173 let fold_at_level = fold_at.0;
19174 let snapshot = self.buffer.read(cx).snapshot(cx);
19175 let mut to_fold = Vec::new();
19176 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19177
19178 let row_ranges_to_keep: Vec<Range<u32>> = self
19179 .selections
19180 .all::<Point>(&self.display_snapshot(cx))
19181 .into_iter()
19182 .map(|sel| sel.start.row..sel.end.row)
19183 .collect();
19184
19185 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19186 while start_row < end_row {
19187 match self
19188 .snapshot(window, cx)
19189 .crease_for_buffer_row(MultiBufferRow(start_row))
19190 {
19191 Some(crease) => {
19192 let nested_start_row = crease.range().start.row + 1;
19193 let nested_end_row = crease.range().end.row;
19194
19195 if current_level < fold_at_level {
19196 stack.push((nested_start_row, nested_end_row, current_level + 1));
19197 } else if current_level == fold_at_level {
19198 // Fold iff there is no selection completely contained within the fold region
19199 if !row_ranges_to_keep.iter().any(|selection| {
19200 selection.end >= nested_start_row
19201 && selection.start <= nested_end_row
19202 }) {
19203 to_fold.push(crease);
19204 }
19205 }
19206
19207 start_row = nested_end_row + 1;
19208 }
19209 None => start_row += 1,
19210 }
19211 }
19212 }
19213
19214 self.fold_creases(to_fold, true, window, cx);
19215 }
19216
19217 pub fn fold_at_level_1(
19218 &mut self,
19219 _: &actions::FoldAtLevel1,
19220 window: &mut Window,
19221 cx: &mut Context<Self>,
19222 ) {
19223 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19224 }
19225
19226 pub fn fold_at_level_2(
19227 &mut self,
19228 _: &actions::FoldAtLevel2,
19229 window: &mut Window,
19230 cx: &mut Context<Self>,
19231 ) {
19232 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19233 }
19234
19235 pub fn fold_at_level_3(
19236 &mut self,
19237 _: &actions::FoldAtLevel3,
19238 window: &mut Window,
19239 cx: &mut Context<Self>,
19240 ) {
19241 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19242 }
19243
19244 pub fn fold_at_level_4(
19245 &mut self,
19246 _: &actions::FoldAtLevel4,
19247 window: &mut Window,
19248 cx: &mut Context<Self>,
19249 ) {
19250 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19251 }
19252
19253 pub fn fold_at_level_5(
19254 &mut self,
19255 _: &actions::FoldAtLevel5,
19256 window: &mut Window,
19257 cx: &mut Context<Self>,
19258 ) {
19259 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19260 }
19261
19262 pub fn fold_at_level_6(
19263 &mut self,
19264 _: &actions::FoldAtLevel6,
19265 window: &mut Window,
19266 cx: &mut Context<Self>,
19267 ) {
19268 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19269 }
19270
19271 pub fn fold_at_level_7(
19272 &mut self,
19273 _: &actions::FoldAtLevel7,
19274 window: &mut Window,
19275 cx: &mut Context<Self>,
19276 ) {
19277 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19278 }
19279
19280 pub fn fold_at_level_8(
19281 &mut self,
19282 _: &actions::FoldAtLevel8,
19283 window: &mut Window,
19284 cx: &mut Context<Self>,
19285 ) {
19286 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19287 }
19288
19289 pub fn fold_at_level_9(
19290 &mut self,
19291 _: &actions::FoldAtLevel9,
19292 window: &mut Window,
19293 cx: &mut Context<Self>,
19294 ) {
19295 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19296 }
19297
19298 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19299 if self.buffer.read(cx).is_singleton() {
19300 let mut fold_ranges = Vec::new();
19301 let snapshot = self.buffer.read(cx).snapshot(cx);
19302
19303 for row in 0..snapshot.max_row().0 {
19304 if let Some(foldable_range) = self
19305 .snapshot(window, cx)
19306 .crease_for_buffer_row(MultiBufferRow(row))
19307 {
19308 fold_ranges.push(foldable_range);
19309 }
19310 }
19311
19312 self.fold_creases(fold_ranges, true, window, cx);
19313 } else {
19314 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19315 editor
19316 .update_in(cx, |editor, _, cx| {
19317 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19318 editor.fold_buffer(buffer_id, cx);
19319 }
19320 })
19321 .ok();
19322 });
19323 }
19324 cx.emit(SearchEvent::ResultsCollapsedChanged(
19325 CollapseDirection::Collapsed,
19326 ));
19327 }
19328
19329 pub fn fold_function_bodies(
19330 &mut self,
19331 _: &actions::FoldFunctionBodies,
19332 window: &mut Window,
19333 cx: &mut Context<Self>,
19334 ) {
19335 let snapshot = self.buffer.read(cx).snapshot(cx);
19336
19337 let ranges = snapshot
19338 .text_object_ranges(
19339 MultiBufferOffset(0)..snapshot.len(),
19340 TreeSitterOptions::default(),
19341 )
19342 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19343 .collect::<Vec<_>>();
19344
19345 let creases = ranges
19346 .into_iter()
19347 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19348 .collect();
19349
19350 self.fold_creases(creases, true, window, cx);
19351 }
19352
19353 pub fn fold_recursive(
19354 &mut self,
19355 _: &actions::FoldRecursive,
19356 window: &mut Window,
19357 cx: &mut Context<Self>,
19358 ) {
19359 let mut to_fold = Vec::new();
19360 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19361 let selections = self.selections.all_adjusted(&display_map);
19362
19363 for selection in selections {
19364 let range = selection.range().sorted();
19365 let buffer_start_row = range.start.row;
19366
19367 if range.start.row != range.end.row {
19368 let mut found = false;
19369 for row in range.start.row..=range.end.row {
19370 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19371 found = true;
19372 to_fold.push(crease);
19373 }
19374 }
19375 if found {
19376 continue;
19377 }
19378 }
19379
19380 for row in (0..=range.start.row).rev() {
19381 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19382 if crease.range().end.row >= buffer_start_row {
19383 to_fold.push(crease);
19384 } else {
19385 break;
19386 }
19387 }
19388 }
19389 }
19390
19391 self.fold_creases(to_fold, true, window, cx);
19392 }
19393
19394 pub fn fold_at(
19395 &mut self,
19396 buffer_row: MultiBufferRow,
19397 window: &mut Window,
19398 cx: &mut Context<Self>,
19399 ) {
19400 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19401
19402 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19403 let autoscroll = self
19404 .selections
19405 .all::<Point>(&display_map)
19406 .iter()
19407 .any(|selection| crease.range().overlaps(&selection.range()));
19408
19409 self.fold_creases(vec![crease], autoscroll, window, cx);
19410 }
19411 }
19412
19413 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19414 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19415 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19416 let buffer = display_map.buffer_snapshot();
19417 let selections = self.selections.all::<Point>(&display_map);
19418 let ranges = selections
19419 .iter()
19420 .map(|s| {
19421 let range = s.display_range(&display_map).sorted();
19422 let mut start = range.start.to_point(&display_map);
19423 let mut end = range.end.to_point(&display_map);
19424 start.column = 0;
19425 end.column = buffer.line_len(MultiBufferRow(end.row));
19426 start..end
19427 })
19428 .collect::<Vec<_>>();
19429
19430 self.unfold_ranges(&ranges, true, true, cx);
19431 } else {
19432 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19433 let buffer_ids = self
19434 .selections
19435 .disjoint_anchor_ranges()
19436 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19437 .collect::<HashSet<_>>();
19438 for buffer_id in buffer_ids {
19439 self.unfold_buffer(buffer_id, cx);
19440 }
19441 }
19442 }
19443
19444 pub fn unfold_recursive(
19445 &mut self,
19446 _: &UnfoldRecursive,
19447 _window: &mut Window,
19448 cx: &mut Context<Self>,
19449 ) {
19450 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19451 let selections = self.selections.all::<Point>(&display_map);
19452 let ranges = selections
19453 .iter()
19454 .map(|s| {
19455 let mut range = s.display_range(&display_map).sorted();
19456 *range.start.column_mut() = 0;
19457 *range.end.column_mut() = display_map.line_len(range.end.row());
19458 let start = range.start.to_point(&display_map);
19459 let end = range.end.to_point(&display_map);
19460 start..end
19461 })
19462 .collect::<Vec<_>>();
19463
19464 self.unfold_ranges(&ranges, true, true, cx);
19465 }
19466
19467 pub fn unfold_at(
19468 &mut self,
19469 buffer_row: MultiBufferRow,
19470 _window: &mut Window,
19471 cx: &mut Context<Self>,
19472 ) {
19473 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19474
19475 let intersection_range = Point::new(buffer_row.0, 0)
19476 ..Point::new(
19477 buffer_row.0,
19478 display_map.buffer_snapshot().line_len(buffer_row),
19479 );
19480
19481 let autoscroll = self
19482 .selections
19483 .all::<Point>(&display_map)
19484 .iter()
19485 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19486
19487 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19488 }
19489
19490 pub fn unfold_all(
19491 &mut self,
19492 _: &actions::UnfoldAll,
19493 _window: &mut Window,
19494 cx: &mut Context<Self>,
19495 ) {
19496 if self.buffer.read(cx).is_singleton() {
19497 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19498 self.unfold_ranges(
19499 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19500 true,
19501 true,
19502 cx,
19503 );
19504 } else {
19505 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19506 editor
19507 .update(cx, |editor, cx| {
19508 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19509 editor.unfold_buffer(buffer_id, cx);
19510 }
19511 })
19512 .ok();
19513 });
19514 }
19515 cx.emit(SearchEvent::ResultsCollapsedChanged(
19516 CollapseDirection::Expanded,
19517 ));
19518 }
19519
19520 pub fn fold_selected_ranges(
19521 &mut self,
19522 _: &FoldSelectedRanges,
19523 window: &mut Window,
19524 cx: &mut Context<Self>,
19525 ) {
19526 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19527 let selections = self.selections.all_adjusted(&display_map);
19528 let ranges = selections
19529 .into_iter()
19530 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19531 .collect::<Vec<_>>();
19532 self.fold_creases(ranges, true, window, cx);
19533 }
19534
19535 pub fn fold_ranges<T: ToOffset + Clone>(
19536 &mut self,
19537 ranges: Vec<Range<T>>,
19538 auto_scroll: bool,
19539 window: &mut Window,
19540 cx: &mut Context<Self>,
19541 ) {
19542 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19543 let ranges = ranges
19544 .into_iter()
19545 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19546 .collect::<Vec<_>>();
19547 self.fold_creases(ranges, auto_scroll, window, cx);
19548 }
19549
19550 pub fn fold_creases<T: ToOffset + Clone>(
19551 &mut self,
19552 creases: Vec<Crease<T>>,
19553 auto_scroll: bool,
19554 _window: &mut Window,
19555 cx: &mut Context<Self>,
19556 ) {
19557 if creases.is_empty() {
19558 return;
19559 }
19560
19561 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19562
19563 if auto_scroll {
19564 self.request_autoscroll(Autoscroll::fit(), cx);
19565 }
19566
19567 cx.notify();
19568
19569 self.scrollbar_marker_state.dirty = true;
19570 self.folds_did_change(cx);
19571 }
19572
19573 /// Removes any folds whose ranges intersect any of the given ranges.
19574 pub fn unfold_ranges<T: ToOffset + Clone>(
19575 &mut self,
19576 ranges: &[Range<T>],
19577 inclusive: bool,
19578 auto_scroll: bool,
19579 cx: &mut Context<Self>,
19580 ) {
19581 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19582 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19583 });
19584 self.folds_did_change(cx);
19585 }
19586
19587 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19588 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19589 return;
19590 }
19591
19592 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19593 self.display_map.update(cx, |display_map, cx| {
19594 display_map.fold_buffers([buffer_id], cx)
19595 });
19596
19597 let snapshot = self.display_snapshot(cx);
19598 self.selections.change_with(&snapshot, |selections| {
19599 selections.remove_selections_from_buffer(buffer_id);
19600 });
19601
19602 cx.emit(EditorEvent::BufferFoldToggled {
19603 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19604 folded: true,
19605 });
19606 cx.notify();
19607 }
19608
19609 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19610 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19611 return;
19612 }
19613 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19614 self.display_map.update(cx, |display_map, cx| {
19615 display_map.unfold_buffers([buffer_id], cx);
19616 });
19617 cx.emit(EditorEvent::BufferFoldToggled {
19618 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19619 folded: false,
19620 });
19621 cx.notify();
19622 }
19623
19624 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19625 self.display_map.read(cx).is_buffer_folded(buffer)
19626 }
19627
19628 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19629 self.display_map.read(cx).folded_buffers()
19630 }
19631
19632 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19633 self.display_map.update(cx, |display_map, cx| {
19634 display_map.disable_header_for_buffer(buffer_id, cx);
19635 });
19636 cx.notify();
19637 }
19638
19639 /// Removes any folds with the given ranges.
19640 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19641 &mut self,
19642 ranges: &[Range<T>],
19643 type_id: TypeId,
19644 auto_scroll: bool,
19645 cx: &mut Context<Self>,
19646 ) {
19647 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19648 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19649 });
19650 self.folds_did_change(cx);
19651 }
19652
19653 fn remove_folds_with<T: ToOffset + Clone>(
19654 &mut self,
19655 ranges: &[Range<T>],
19656 auto_scroll: bool,
19657 cx: &mut Context<Self>,
19658 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19659 ) {
19660 if ranges.is_empty() {
19661 return;
19662 }
19663
19664 let mut buffers_affected = HashSet::default();
19665 let multi_buffer = self.buffer().read(cx);
19666 for range in ranges {
19667 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19668 buffers_affected.insert(buffer.read(cx).remote_id());
19669 };
19670 }
19671
19672 self.display_map.update(cx, update);
19673
19674 if auto_scroll {
19675 self.request_autoscroll(Autoscroll::fit(), cx);
19676 }
19677
19678 cx.notify();
19679 self.scrollbar_marker_state.dirty = true;
19680 self.active_indent_guides_state.dirty = true;
19681 }
19682
19683 pub fn update_renderer_widths(
19684 &mut self,
19685 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19686 cx: &mut Context<Self>,
19687 ) -> bool {
19688 self.display_map
19689 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19690 }
19691
19692 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19693 self.display_map.read(cx).fold_placeholder.clone()
19694 }
19695
19696 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19697 self.use_base_text_line_numbers = show;
19698 }
19699
19700 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19701 self.buffer.update(cx, |buffer, cx| {
19702 buffer.set_all_diff_hunks_expanded(cx);
19703 });
19704 }
19705
19706 pub fn expand_all_diff_hunks(
19707 &mut self,
19708 _: &ExpandAllDiffHunks,
19709 _window: &mut Window,
19710 cx: &mut Context<Self>,
19711 ) {
19712 self.buffer.update(cx, |buffer, cx| {
19713 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19714 });
19715 }
19716
19717 pub fn collapse_all_diff_hunks(
19718 &mut self,
19719 _: &CollapseAllDiffHunks,
19720 _window: &mut Window,
19721 cx: &mut Context<Self>,
19722 ) {
19723 self.buffer.update(cx, |buffer, cx| {
19724 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19725 });
19726 }
19727
19728 pub fn toggle_selected_diff_hunks(
19729 &mut self,
19730 _: &ToggleSelectedDiffHunks,
19731 _window: &mut Window,
19732 cx: &mut Context<Self>,
19733 ) {
19734 let ranges: Vec<_> = self
19735 .selections
19736 .disjoint_anchors()
19737 .iter()
19738 .map(|s| s.range())
19739 .collect();
19740 self.toggle_diff_hunks_in_ranges(ranges, cx);
19741 }
19742
19743 pub fn diff_hunks_in_ranges<'a>(
19744 &'a self,
19745 ranges: &'a [Range<Anchor>],
19746 buffer: &'a MultiBufferSnapshot,
19747 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19748 ranges.iter().flat_map(move |range| {
19749 let end_excerpt_id = range.end.excerpt_id;
19750 let range = range.to_point(buffer);
19751 let mut peek_end = range.end;
19752 if range.end.row < buffer.max_row().0 {
19753 peek_end = Point::new(range.end.row + 1, 0);
19754 }
19755 buffer
19756 .diff_hunks_in_range(range.start..peek_end)
19757 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19758 })
19759 }
19760
19761 pub fn has_stageable_diff_hunks_in_ranges(
19762 &self,
19763 ranges: &[Range<Anchor>],
19764 snapshot: &MultiBufferSnapshot,
19765 ) -> bool {
19766 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19767 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19768 }
19769
19770 pub fn toggle_staged_selected_diff_hunks(
19771 &mut self,
19772 _: &::git::ToggleStaged,
19773 _: &mut Window,
19774 cx: &mut Context<Self>,
19775 ) {
19776 let snapshot = self.buffer.read(cx).snapshot(cx);
19777 let ranges: Vec<_> = self
19778 .selections
19779 .disjoint_anchors()
19780 .iter()
19781 .map(|s| s.range())
19782 .collect();
19783 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19784 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19785 }
19786
19787 pub fn set_render_diff_hunk_controls(
19788 &mut self,
19789 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19790 cx: &mut Context<Self>,
19791 ) {
19792 self.render_diff_hunk_controls = render_diff_hunk_controls;
19793 cx.notify();
19794 }
19795
19796 pub fn stage_and_next(
19797 &mut self,
19798 _: &::git::StageAndNext,
19799 window: &mut Window,
19800 cx: &mut Context<Self>,
19801 ) {
19802 self.do_stage_or_unstage_and_next(true, window, cx);
19803 }
19804
19805 pub fn unstage_and_next(
19806 &mut self,
19807 _: &::git::UnstageAndNext,
19808 window: &mut Window,
19809 cx: &mut Context<Self>,
19810 ) {
19811 self.do_stage_or_unstage_and_next(false, window, cx);
19812 }
19813
19814 pub fn stage_or_unstage_diff_hunks(
19815 &mut self,
19816 stage: bool,
19817 ranges: Vec<Range<Anchor>>,
19818 cx: &mut Context<Self>,
19819 ) {
19820 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19821 cx.spawn(async move |this, cx| {
19822 task.await?;
19823 this.update(cx, |this, cx| {
19824 let snapshot = this.buffer.read(cx).snapshot(cx);
19825 let chunk_by = this
19826 .diff_hunks_in_ranges(&ranges, &snapshot)
19827 .chunk_by(|hunk| hunk.buffer_id);
19828 for (buffer_id, hunks) in &chunk_by {
19829 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19830 }
19831 })
19832 })
19833 .detach_and_log_err(cx);
19834 }
19835
19836 fn save_buffers_for_ranges_if_needed(
19837 &mut self,
19838 ranges: &[Range<Anchor>],
19839 cx: &mut Context<Editor>,
19840 ) -> Task<Result<()>> {
19841 let multibuffer = self.buffer.read(cx);
19842 let snapshot = multibuffer.read(cx);
19843 let buffer_ids: HashSet<_> = ranges
19844 .iter()
19845 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19846 .collect();
19847 drop(snapshot);
19848
19849 let mut buffers = HashSet::default();
19850 for buffer_id in buffer_ids {
19851 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19852 let buffer = buffer_entity.read(cx);
19853 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19854 {
19855 buffers.insert(buffer_entity);
19856 }
19857 }
19858 }
19859
19860 if let Some(project) = &self.project {
19861 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19862 } else {
19863 Task::ready(Ok(()))
19864 }
19865 }
19866
19867 fn do_stage_or_unstage_and_next(
19868 &mut self,
19869 stage: bool,
19870 window: &mut Window,
19871 cx: &mut Context<Self>,
19872 ) {
19873 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19874
19875 if ranges.iter().any(|range| range.start != range.end) {
19876 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19877 return;
19878 }
19879
19880 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19881 let snapshot = self.snapshot(window, cx);
19882 let position = self
19883 .selections
19884 .newest::<Point>(&snapshot.display_snapshot)
19885 .head();
19886 let mut row = snapshot
19887 .buffer_snapshot()
19888 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19889 .find(|hunk| hunk.row_range.start.0 > position.row)
19890 .map(|hunk| hunk.row_range.start);
19891
19892 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19893 // Outside of the project diff editor, wrap around to the beginning.
19894 if !all_diff_hunks_expanded {
19895 row = row.or_else(|| {
19896 snapshot
19897 .buffer_snapshot()
19898 .diff_hunks_in_range(Point::zero()..position)
19899 .find(|hunk| hunk.row_range.end.0 < position.row)
19900 .map(|hunk| hunk.row_range.start)
19901 });
19902 }
19903
19904 if let Some(row) = row {
19905 let destination = Point::new(row.0, 0);
19906 let autoscroll = Autoscroll::center();
19907
19908 self.unfold_ranges(&[destination..destination], false, false, cx);
19909 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19910 s.select_ranges([destination..destination]);
19911 });
19912 }
19913 }
19914
19915 fn do_stage_or_unstage(
19916 &self,
19917 stage: bool,
19918 buffer_id: BufferId,
19919 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19920 cx: &mut App,
19921 ) -> Option<()> {
19922 let project = self.project()?;
19923 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19924 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19925 let buffer_snapshot = buffer.read(cx).snapshot();
19926 let file_exists = buffer_snapshot
19927 .file()
19928 .is_some_and(|file| file.disk_state().exists());
19929 diff.update(cx, |diff, cx| {
19930 diff.stage_or_unstage_hunks(
19931 stage,
19932 &hunks
19933 .map(|hunk| buffer_diff::DiffHunk {
19934 buffer_range: hunk.buffer_range,
19935 // We don't need to pass in word diffs here because they're only used for rendering and
19936 // this function changes internal state
19937 base_word_diffs: Vec::default(),
19938 buffer_word_diffs: Vec::default(),
19939 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19940 ..hunk.diff_base_byte_range.end.0,
19941 secondary_status: hunk.secondary_status,
19942 range: Point::zero()..Point::zero(), // unused
19943 })
19944 .collect::<Vec<_>>(),
19945 &buffer_snapshot,
19946 file_exists,
19947 cx,
19948 )
19949 });
19950 None
19951 }
19952
19953 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19954 let ranges: Vec<_> = self
19955 .selections
19956 .disjoint_anchors()
19957 .iter()
19958 .map(|s| s.range())
19959 .collect();
19960 self.buffer
19961 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19962 }
19963
19964 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19965 self.buffer.update(cx, |buffer, cx| {
19966 let ranges = vec![Anchor::min()..Anchor::max()];
19967 if !buffer.all_diff_hunks_expanded()
19968 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19969 {
19970 buffer.collapse_diff_hunks(ranges, cx);
19971 true
19972 } else {
19973 false
19974 }
19975 })
19976 }
19977
19978 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19979 if self.buffer.read(cx).all_diff_hunks_expanded() {
19980 return true;
19981 }
19982 let ranges = vec![Anchor::min()..Anchor::max()];
19983 self.buffer
19984 .read(cx)
19985 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19986 }
19987
19988 fn toggle_diff_hunks_in_ranges(
19989 &mut self,
19990 ranges: Vec<Range<Anchor>>,
19991 cx: &mut Context<Editor>,
19992 ) {
19993 self.buffer.update(cx, |buffer, cx| {
19994 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19995 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19996 })
19997 }
19998
19999 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20000 self.buffer.update(cx, |buffer, cx| {
20001 let snapshot = buffer.snapshot(cx);
20002 let excerpt_id = range.end.excerpt_id;
20003 let point_range = range.to_point(&snapshot);
20004 let expand = !buffer.single_hunk_is_expanded(range, cx);
20005 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20006 })
20007 }
20008
20009 pub(crate) fn apply_all_diff_hunks(
20010 &mut self,
20011 _: &ApplyAllDiffHunks,
20012 window: &mut Window,
20013 cx: &mut Context<Self>,
20014 ) {
20015 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20016
20017 let buffers = self.buffer.read(cx).all_buffers();
20018 for branch_buffer in buffers {
20019 branch_buffer.update(cx, |branch_buffer, cx| {
20020 branch_buffer.merge_into_base(Vec::new(), cx);
20021 });
20022 }
20023
20024 if let Some(project) = self.project.clone() {
20025 self.save(
20026 SaveOptions {
20027 format: true,
20028 autosave: false,
20029 },
20030 project,
20031 window,
20032 cx,
20033 )
20034 .detach_and_log_err(cx);
20035 }
20036 }
20037
20038 pub(crate) fn apply_selected_diff_hunks(
20039 &mut self,
20040 _: &ApplyDiffHunk,
20041 window: &mut Window,
20042 cx: &mut Context<Self>,
20043 ) {
20044 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20045 let snapshot = self.snapshot(window, cx);
20046 let hunks = snapshot.hunks_for_ranges(
20047 self.selections
20048 .all(&snapshot.display_snapshot)
20049 .into_iter()
20050 .map(|selection| selection.range()),
20051 );
20052 let mut ranges_by_buffer = HashMap::default();
20053 self.transact(window, cx, |editor, _window, cx| {
20054 for hunk in hunks {
20055 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20056 ranges_by_buffer
20057 .entry(buffer.clone())
20058 .or_insert_with(Vec::new)
20059 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20060 }
20061 }
20062
20063 for (buffer, ranges) in ranges_by_buffer {
20064 buffer.update(cx, |buffer, cx| {
20065 buffer.merge_into_base(ranges, cx);
20066 });
20067 }
20068 });
20069
20070 if let Some(project) = self.project.clone() {
20071 self.save(
20072 SaveOptions {
20073 format: true,
20074 autosave: false,
20075 },
20076 project,
20077 window,
20078 cx,
20079 )
20080 .detach_and_log_err(cx);
20081 }
20082 }
20083
20084 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20085 if hovered != self.gutter_hovered {
20086 self.gutter_hovered = hovered;
20087 cx.notify();
20088 }
20089 }
20090
20091 pub fn insert_blocks(
20092 &mut self,
20093 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20094 autoscroll: Option<Autoscroll>,
20095 cx: &mut Context<Self>,
20096 ) -> Vec<CustomBlockId> {
20097 let blocks = self
20098 .display_map
20099 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20100 if let Some(autoscroll) = autoscroll {
20101 self.request_autoscroll(autoscroll, cx);
20102 }
20103 cx.notify();
20104 blocks
20105 }
20106
20107 pub fn resize_blocks(
20108 &mut self,
20109 heights: HashMap<CustomBlockId, u32>,
20110 autoscroll: Option<Autoscroll>,
20111 cx: &mut Context<Self>,
20112 ) {
20113 self.display_map
20114 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20115 if let Some(autoscroll) = autoscroll {
20116 self.request_autoscroll(autoscroll, cx);
20117 }
20118 cx.notify();
20119 }
20120
20121 pub fn replace_blocks(
20122 &mut self,
20123 renderers: HashMap<CustomBlockId, RenderBlock>,
20124 autoscroll: Option<Autoscroll>,
20125 cx: &mut Context<Self>,
20126 ) {
20127 self.display_map
20128 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20129 if let Some(autoscroll) = autoscroll {
20130 self.request_autoscroll(autoscroll, cx);
20131 }
20132 cx.notify();
20133 }
20134
20135 pub fn remove_blocks(
20136 &mut self,
20137 block_ids: HashSet<CustomBlockId>,
20138 autoscroll: Option<Autoscroll>,
20139 cx: &mut Context<Self>,
20140 ) {
20141 self.display_map.update(cx, |display_map, cx| {
20142 display_map.remove_blocks(block_ids, cx)
20143 });
20144 if let Some(autoscroll) = autoscroll {
20145 self.request_autoscroll(autoscroll, cx);
20146 }
20147 cx.notify();
20148 }
20149
20150 pub fn row_for_block(
20151 &self,
20152 block_id: CustomBlockId,
20153 cx: &mut Context<Self>,
20154 ) -> Option<DisplayRow> {
20155 self.display_map
20156 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20157 }
20158
20159 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20160 self.focused_block = Some(focused_block);
20161 }
20162
20163 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20164 self.focused_block.take()
20165 }
20166
20167 pub fn insert_creases(
20168 &mut self,
20169 creases: impl IntoIterator<Item = Crease<Anchor>>,
20170 cx: &mut Context<Self>,
20171 ) -> Vec<CreaseId> {
20172 self.display_map
20173 .update(cx, |map, cx| map.insert_creases(creases, cx))
20174 }
20175
20176 pub fn remove_creases(
20177 &mut self,
20178 ids: impl IntoIterator<Item = CreaseId>,
20179 cx: &mut Context<Self>,
20180 ) -> Vec<(CreaseId, Range<Anchor>)> {
20181 self.display_map
20182 .update(cx, |map, cx| map.remove_creases(ids, cx))
20183 }
20184
20185 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20186 self.display_map
20187 .update(cx, |map, cx| map.snapshot(cx))
20188 .longest_row()
20189 }
20190
20191 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20192 self.display_map
20193 .update(cx, |map, cx| map.snapshot(cx))
20194 .max_point()
20195 }
20196
20197 pub fn text(&self, cx: &App) -> String {
20198 self.buffer.read(cx).read(cx).text()
20199 }
20200
20201 pub fn is_empty(&self, cx: &App) -> bool {
20202 self.buffer.read(cx).read(cx).is_empty()
20203 }
20204
20205 pub fn text_option(&self, cx: &App) -> Option<String> {
20206 let text = self.text(cx);
20207 let text = text.trim();
20208
20209 if text.is_empty() {
20210 return None;
20211 }
20212
20213 Some(text.to_string())
20214 }
20215
20216 pub fn set_text(
20217 &mut self,
20218 text: impl Into<Arc<str>>,
20219 window: &mut Window,
20220 cx: &mut Context<Self>,
20221 ) {
20222 self.transact(window, cx, |this, _, cx| {
20223 this.buffer
20224 .read(cx)
20225 .as_singleton()
20226 .expect("you can only call set_text on editors for singleton buffers")
20227 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20228 });
20229 }
20230
20231 pub fn display_text(&self, cx: &mut App) -> String {
20232 self.display_map
20233 .update(cx, |map, cx| map.snapshot(cx))
20234 .text()
20235 }
20236
20237 fn create_minimap(
20238 &self,
20239 minimap_settings: MinimapSettings,
20240 window: &mut Window,
20241 cx: &mut Context<Self>,
20242 ) -> Option<Entity<Self>> {
20243 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20244 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20245 }
20246
20247 fn initialize_new_minimap(
20248 &self,
20249 minimap_settings: MinimapSettings,
20250 window: &mut Window,
20251 cx: &mut Context<Self>,
20252 ) -> Entity<Self> {
20253 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20254
20255 let mut minimap = Editor::new_internal(
20256 EditorMode::Minimap {
20257 parent: cx.weak_entity(),
20258 },
20259 self.buffer.clone(),
20260 None,
20261 Some(self.display_map.clone()),
20262 window,
20263 cx,
20264 );
20265 minimap.scroll_manager.clone_state(&self.scroll_manager);
20266 minimap.set_text_style_refinement(TextStyleRefinement {
20267 font_size: Some(MINIMAP_FONT_SIZE),
20268 font_weight: Some(MINIMAP_FONT_WEIGHT),
20269 ..Default::default()
20270 });
20271 minimap.update_minimap_configuration(minimap_settings, cx);
20272 cx.new(|_| minimap)
20273 }
20274
20275 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20276 let current_line_highlight = minimap_settings
20277 .current_line_highlight
20278 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20279 self.set_current_line_highlight(Some(current_line_highlight));
20280 }
20281
20282 pub fn minimap(&self) -> Option<&Entity<Self>> {
20283 self.minimap
20284 .as_ref()
20285 .filter(|_| self.minimap_visibility.visible())
20286 }
20287
20288 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20289 let mut wrap_guides = smallvec![];
20290
20291 if self.show_wrap_guides == Some(false) {
20292 return wrap_guides;
20293 }
20294
20295 let settings = self.buffer.read(cx).language_settings(cx);
20296 if settings.show_wrap_guides {
20297 match self.soft_wrap_mode(cx) {
20298 SoftWrap::Column(soft_wrap) => {
20299 wrap_guides.push((soft_wrap as usize, true));
20300 }
20301 SoftWrap::Bounded(soft_wrap) => {
20302 wrap_guides.push((soft_wrap as usize, true));
20303 }
20304 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20305 }
20306 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20307 }
20308
20309 wrap_guides
20310 }
20311
20312 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20313 let settings = self.buffer.read(cx).language_settings(cx);
20314 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20315 match mode {
20316 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20317 SoftWrap::None
20318 }
20319 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20320 language_settings::SoftWrap::PreferredLineLength => {
20321 SoftWrap::Column(settings.preferred_line_length)
20322 }
20323 language_settings::SoftWrap::Bounded => {
20324 SoftWrap::Bounded(settings.preferred_line_length)
20325 }
20326 }
20327 }
20328
20329 pub fn set_soft_wrap_mode(
20330 &mut self,
20331 mode: language_settings::SoftWrap,
20332
20333 cx: &mut Context<Self>,
20334 ) {
20335 self.soft_wrap_mode_override = Some(mode);
20336 cx.notify();
20337 }
20338
20339 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20340 self.hard_wrap = hard_wrap;
20341 cx.notify();
20342 }
20343
20344 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20345 self.text_style_refinement = Some(style);
20346 }
20347
20348 /// called by the Element so we know what style we were most recently rendered with.
20349 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20350 // We intentionally do not inform the display map about the minimap style
20351 // so that wrapping is not recalculated and stays consistent for the editor
20352 // and its linked minimap.
20353 if !self.mode.is_minimap() {
20354 let font = style.text.font();
20355 let font_size = style.text.font_size.to_pixels(window.rem_size());
20356 let display_map = self
20357 .placeholder_display_map
20358 .as_ref()
20359 .filter(|_| self.is_empty(cx))
20360 .unwrap_or(&self.display_map);
20361
20362 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20363 }
20364 self.style = Some(style);
20365 }
20366
20367 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20368 if self.style.is_none() {
20369 self.style = Some(self.create_style(cx));
20370 }
20371 self.style.as_ref().unwrap()
20372 }
20373
20374 // Called by the element. This method is not designed to be called outside of the editor
20375 // element's layout code because it does not notify when rewrapping is computed synchronously.
20376 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20377 if self.is_empty(cx) {
20378 self.placeholder_display_map
20379 .as_ref()
20380 .map_or(false, |display_map| {
20381 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20382 })
20383 } else {
20384 self.display_map
20385 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20386 }
20387 }
20388
20389 pub fn set_soft_wrap(&mut self) {
20390 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20391 }
20392
20393 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20394 if self.soft_wrap_mode_override.is_some() {
20395 self.soft_wrap_mode_override.take();
20396 } else {
20397 let soft_wrap = match self.soft_wrap_mode(cx) {
20398 SoftWrap::GitDiff => return,
20399 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20400 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20401 language_settings::SoftWrap::None
20402 }
20403 };
20404 self.soft_wrap_mode_override = Some(soft_wrap);
20405 }
20406 cx.notify();
20407 }
20408
20409 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20410 let Some(workspace) = self.workspace() else {
20411 return;
20412 };
20413 let fs = workspace.read(cx).app_state().fs.clone();
20414 let current_show = TabBarSettings::get_global(cx).show;
20415 update_settings_file(fs, cx, move |setting, _| {
20416 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20417 });
20418 }
20419
20420 pub fn toggle_indent_guides(
20421 &mut self,
20422 _: &ToggleIndentGuides,
20423 _: &mut Window,
20424 cx: &mut Context<Self>,
20425 ) {
20426 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20427 self.buffer
20428 .read(cx)
20429 .language_settings(cx)
20430 .indent_guides
20431 .enabled
20432 });
20433 self.show_indent_guides = Some(!currently_enabled);
20434 cx.notify();
20435 }
20436
20437 fn should_show_indent_guides(&self) -> Option<bool> {
20438 self.show_indent_guides
20439 }
20440
20441 pub fn disable_indent_guides_for_buffer(
20442 &mut self,
20443 buffer_id: BufferId,
20444 cx: &mut Context<Self>,
20445 ) {
20446 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20447 cx.notify();
20448 }
20449
20450 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20451 self.buffers_with_disabled_indent_guides
20452 .contains(&buffer_id)
20453 }
20454
20455 pub fn toggle_line_numbers(
20456 &mut self,
20457 _: &ToggleLineNumbers,
20458 _: &mut Window,
20459 cx: &mut Context<Self>,
20460 ) {
20461 let mut editor_settings = EditorSettings::get_global(cx).clone();
20462 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20463 EditorSettings::override_global(editor_settings, cx);
20464 }
20465
20466 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20467 if let Some(show_line_numbers) = self.show_line_numbers {
20468 return show_line_numbers;
20469 }
20470 EditorSettings::get_global(cx).gutter.line_numbers
20471 }
20472
20473 pub fn relative_line_numbers(&self, cx: &App) -> RelativeLineNumbers {
20474 match (
20475 self.use_relative_line_numbers,
20476 EditorSettings::get_global(cx).relative_line_numbers,
20477 ) {
20478 (None, setting) => setting,
20479 (Some(false), _) => RelativeLineNumbers::Disabled,
20480 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20481 (Some(true), _) => RelativeLineNumbers::Enabled,
20482 }
20483 }
20484
20485 pub fn toggle_relative_line_numbers(
20486 &mut self,
20487 _: &ToggleRelativeLineNumbers,
20488 _: &mut Window,
20489 cx: &mut Context<Self>,
20490 ) {
20491 let is_relative = self.relative_line_numbers(cx);
20492 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20493 }
20494
20495 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20496 self.use_relative_line_numbers = is_relative;
20497 cx.notify();
20498 }
20499
20500 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20501 self.show_gutter = show_gutter;
20502 cx.notify();
20503 }
20504
20505 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20506 self.show_scrollbars = ScrollbarAxes {
20507 horizontal: show,
20508 vertical: show,
20509 };
20510 cx.notify();
20511 }
20512
20513 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20514 self.show_scrollbars.vertical = show;
20515 cx.notify();
20516 }
20517
20518 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20519 self.show_scrollbars.horizontal = show;
20520 cx.notify();
20521 }
20522
20523 pub fn set_minimap_visibility(
20524 &mut self,
20525 minimap_visibility: MinimapVisibility,
20526 window: &mut Window,
20527 cx: &mut Context<Self>,
20528 ) {
20529 if self.minimap_visibility != minimap_visibility {
20530 if minimap_visibility.visible() && self.minimap.is_none() {
20531 let minimap_settings = EditorSettings::get_global(cx).minimap;
20532 self.minimap =
20533 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20534 }
20535 self.minimap_visibility = minimap_visibility;
20536 cx.notify();
20537 }
20538 }
20539
20540 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20541 self.set_show_scrollbars(false, cx);
20542 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20543 }
20544
20545 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20546 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20547 }
20548
20549 /// Normally the text in full mode and auto height editors is padded on the
20550 /// left side by roughly half a character width for improved hit testing.
20551 ///
20552 /// Use this method to disable this for cases where this is not wanted (e.g.
20553 /// if you want to align the editor text with some other text above or below)
20554 /// or if you want to add this padding to single-line editors.
20555 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20556 self.offset_content = offset_content;
20557 cx.notify();
20558 }
20559
20560 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20561 self.show_line_numbers = Some(show_line_numbers);
20562 cx.notify();
20563 }
20564
20565 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20566 self.disable_expand_excerpt_buttons = true;
20567 cx.notify();
20568 }
20569
20570 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20571 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20572 cx.notify();
20573 }
20574
20575 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20576 self.show_code_actions = Some(show_code_actions);
20577 cx.notify();
20578 }
20579
20580 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20581 self.show_runnables = Some(show_runnables);
20582 cx.notify();
20583 }
20584
20585 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20586 self.show_breakpoints = Some(show_breakpoints);
20587 cx.notify();
20588 }
20589
20590 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20591 if self.display_map.read(cx).masked != masked {
20592 self.display_map.update(cx, |map, _| map.masked = masked);
20593 }
20594 cx.notify()
20595 }
20596
20597 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20598 self.show_wrap_guides = Some(show_wrap_guides);
20599 cx.notify();
20600 }
20601
20602 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20603 self.show_indent_guides = Some(show_indent_guides);
20604 cx.notify();
20605 }
20606
20607 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20608 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20609 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20610 && let Some(dir) = file.abs_path(cx).parent()
20611 {
20612 return Some(dir.to_owned());
20613 }
20614 }
20615
20616 None
20617 }
20618
20619 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20620 self.active_excerpt(cx)?
20621 .1
20622 .read(cx)
20623 .file()
20624 .and_then(|f| f.as_local())
20625 }
20626
20627 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20628 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20629 let buffer = buffer.read(cx);
20630 if let Some(project_path) = buffer.project_path(cx) {
20631 let project = self.project()?.read(cx);
20632 project.absolute_path(&project_path, cx)
20633 } else {
20634 buffer
20635 .file()
20636 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20637 }
20638 })
20639 }
20640
20641 pub fn reveal_in_finder(
20642 &mut self,
20643 _: &RevealInFileManager,
20644 _window: &mut Window,
20645 cx: &mut Context<Self>,
20646 ) {
20647 if let Some(target) = self.target_file(cx) {
20648 cx.reveal_path(&target.abs_path(cx));
20649 }
20650 }
20651
20652 pub fn copy_path(
20653 &mut self,
20654 _: &zed_actions::workspace::CopyPath,
20655 _window: &mut Window,
20656 cx: &mut Context<Self>,
20657 ) {
20658 if let Some(path) = self.target_file_abs_path(cx)
20659 && let Some(path) = path.to_str()
20660 {
20661 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20662 } else {
20663 cx.propagate();
20664 }
20665 }
20666
20667 pub fn copy_relative_path(
20668 &mut self,
20669 _: &zed_actions::workspace::CopyRelativePath,
20670 _window: &mut Window,
20671 cx: &mut Context<Self>,
20672 ) {
20673 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20674 let project = self.project()?.read(cx);
20675 let path = buffer.read(cx).file()?.path();
20676 let path = path.display(project.path_style(cx));
20677 Some(path)
20678 }) {
20679 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20680 } else {
20681 cx.propagate();
20682 }
20683 }
20684
20685 /// Returns the project path for the editor's buffer, if any buffer is
20686 /// opened in the editor.
20687 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20688 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20689 buffer.read(cx).project_path(cx)
20690 } else {
20691 None
20692 }
20693 }
20694
20695 // Returns true if the editor handled a go-to-line request
20696 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20697 maybe!({
20698 let breakpoint_store = self.breakpoint_store.as_ref()?;
20699
20700 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20701 else {
20702 self.clear_row_highlights::<ActiveDebugLine>();
20703 return None;
20704 };
20705
20706 let position = active_stack_frame.position;
20707 let buffer_id = position.buffer_id?;
20708 let snapshot = self
20709 .project
20710 .as_ref()?
20711 .read(cx)
20712 .buffer_for_id(buffer_id, cx)?
20713 .read(cx)
20714 .snapshot();
20715
20716 let mut handled = false;
20717 for (id, ExcerptRange { context, .. }) in
20718 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20719 {
20720 if context.start.cmp(&position, &snapshot).is_ge()
20721 || context.end.cmp(&position, &snapshot).is_lt()
20722 {
20723 continue;
20724 }
20725 let snapshot = self.buffer.read(cx).snapshot(cx);
20726 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20727
20728 handled = true;
20729 self.clear_row_highlights::<ActiveDebugLine>();
20730
20731 self.go_to_line::<ActiveDebugLine>(
20732 multibuffer_anchor,
20733 Some(cx.theme().colors().editor_debugger_active_line_background),
20734 window,
20735 cx,
20736 );
20737
20738 cx.notify();
20739 }
20740
20741 handled.then_some(())
20742 })
20743 .is_some()
20744 }
20745
20746 pub fn copy_file_name_without_extension(
20747 &mut self,
20748 _: &CopyFileNameWithoutExtension,
20749 _: &mut Window,
20750 cx: &mut Context<Self>,
20751 ) {
20752 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20753 let file = buffer.read(cx).file()?;
20754 file.path().file_stem()
20755 }) {
20756 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20757 }
20758 }
20759
20760 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20761 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20762 let file = buffer.read(cx).file()?;
20763 Some(file.file_name(cx))
20764 }) {
20765 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20766 }
20767 }
20768
20769 pub fn toggle_git_blame(
20770 &mut self,
20771 _: &::git::Blame,
20772 window: &mut Window,
20773 cx: &mut Context<Self>,
20774 ) {
20775 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20776
20777 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20778 self.start_git_blame(true, window, cx);
20779 }
20780
20781 cx.notify();
20782 }
20783
20784 pub fn toggle_git_blame_inline(
20785 &mut self,
20786 _: &ToggleGitBlameInline,
20787 window: &mut Window,
20788 cx: &mut Context<Self>,
20789 ) {
20790 self.toggle_git_blame_inline_internal(true, window, cx);
20791 cx.notify();
20792 }
20793
20794 pub fn open_git_blame_commit(
20795 &mut self,
20796 _: &OpenGitBlameCommit,
20797 window: &mut Window,
20798 cx: &mut Context<Self>,
20799 ) {
20800 self.open_git_blame_commit_internal(window, cx);
20801 }
20802
20803 fn open_git_blame_commit_internal(
20804 &mut self,
20805 window: &mut Window,
20806 cx: &mut Context<Self>,
20807 ) -> Option<()> {
20808 let blame = self.blame.as_ref()?;
20809 let snapshot = self.snapshot(window, cx);
20810 let cursor = self
20811 .selections
20812 .newest::<Point>(&snapshot.display_snapshot)
20813 .head();
20814 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20815 let (_, blame_entry) = blame
20816 .update(cx, |blame, cx| {
20817 blame
20818 .blame_for_rows(
20819 &[RowInfo {
20820 buffer_id: Some(buffer.remote_id()),
20821 buffer_row: Some(point.row),
20822 ..Default::default()
20823 }],
20824 cx,
20825 )
20826 .next()
20827 })
20828 .flatten()?;
20829 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20830 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20831 let workspace = self.workspace()?.downgrade();
20832 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20833 None
20834 }
20835
20836 pub fn git_blame_inline_enabled(&self) -> bool {
20837 self.git_blame_inline_enabled
20838 }
20839
20840 pub fn toggle_selection_menu(
20841 &mut self,
20842 _: &ToggleSelectionMenu,
20843 _: &mut Window,
20844 cx: &mut Context<Self>,
20845 ) {
20846 self.show_selection_menu = self
20847 .show_selection_menu
20848 .map(|show_selections_menu| !show_selections_menu)
20849 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20850
20851 cx.notify();
20852 }
20853
20854 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20855 self.show_selection_menu
20856 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20857 }
20858
20859 fn start_git_blame(
20860 &mut self,
20861 user_triggered: bool,
20862 window: &mut Window,
20863 cx: &mut Context<Self>,
20864 ) {
20865 if let Some(project) = self.project() {
20866 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20867 && buffer.read(cx).file().is_none()
20868 {
20869 return;
20870 }
20871
20872 let focused = self.focus_handle(cx).contains_focused(window, cx);
20873
20874 let project = project.clone();
20875 let blame = cx
20876 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20877 self.blame_subscription =
20878 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20879 self.blame = Some(blame);
20880 }
20881 }
20882
20883 fn toggle_git_blame_inline_internal(
20884 &mut self,
20885 user_triggered: bool,
20886 window: &mut Window,
20887 cx: &mut Context<Self>,
20888 ) {
20889 if self.git_blame_inline_enabled {
20890 self.git_blame_inline_enabled = false;
20891 self.show_git_blame_inline = false;
20892 self.show_git_blame_inline_delay_task.take();
20893 } else {
20894 self.git_blame_inline_enabled = true;
20895 self.start_git_blame_inline(user_triggered, window, cx);
20896 }
20897
20898 cx.notify();
20899 }
20900
20901 fn start_git_blame_inline(
20902 &mut self,
20903 user_triggered: bool,
20904 window: &mut Window,
20905 cx: &mut Context<Self>,
20906 ) {
20907 self.start_git_blame(user_triggered, window, cx);
20908
20909 if ProjectSettings::get_global(cx)
20910 .git
20911 .inline_blame_delay()
20912 .is_some()
20913 {
20914 self.start_inline_blame_timer(window, cx);
20915 } else {
20916 self.show_git_blame_inline = true
20917 }
20918 }
20919
20920 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20921 self.blame.as_ref()
20922 }
20923
20924 pub fn show_git_blame_gutter(&self) -> bool {
20925 self.show_git_blame_gutter
20926 }
20927
20928 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20929 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20930 }
20931
20932 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20933 self.show_git_blame_inline
20934 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20935 && !self.newest_selection_head_on_empty_line(cx)
20936 && self.has_blame_entries(cx)
20937 }
20938
20939 fn has_blame_entries(&self, cx: &App) -> bool {
20940 self.blame()
20941 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20942 }
20943
20944 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20945 let cursor_anchor = self.selections.newest_anchor().head();
20946
20947 let snapshot = self.buffer.read(cx).snapshot(cx);
20948 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20949
20950 snapshot.line_len(buffer_row) == 0
20951 }
20952
20953 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20954 let buffer_and_selection = maybe!({
20955 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20956 let selection_range = selection.range();
20957
20958 let multi_buffer = self.buffer().read(cx);
20959 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20960 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20961
20962 let (buffer, range, _) = if selection.reversed {
20963 buffer_ranges.first()
20964 } else {
20965 buffer_ranges.last()
20966 }?;
20967
20968 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
20969 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
20970
20971 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
20972 let selection = start_row_in_buffer..end_row_in_buffer;
20973
20974 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
20975 };
20976
20977 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
20978
20979 Some((
20980 multi_buffer.buffer(buffer.remote_id()).unwrap(),
20981 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, buffer)
20982 ..buffer_diff_snapshot.row_to_base_text_row(end_row_in_buffer, buffer),
20983 ))
20984 });
20985
20986 let Some((buffer, selection)) = buffer_and_selection else {
20987 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20988 };
20989
20990 let Some(project) = self.project() else {
20991 return Task::ready(Err(anyhow!("editor does not have project")));
20992 };
20993
20994 project.update(cx, |project, cx| {
20995 project.get_permalink_to_line(&buffer, selection, cx)
20996 })
20997 }
20998
20999 pub fn copy_permalink_to_line(
21000 &mut self,
21001 _: &CopyPermalinkToLine,
21002 window: &mut Window,
21003 cx: &mut Context<Self>,
21004 ) {
21005 let permalink_task = self.get_permalink_to_line(cx);
21006 let workspace = self.workspace();
21007
21008 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21009 Ok(permalink) => {
21010 cx.update(|_, cx| {
21011 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21012 })
21013 .ok();
21014 }
21015 Err(err) => {
21016 let message = format!("Failed to copy permalink: {err}");
21017
21018 anyhow::Result::<()>::Err(err).log_err();
21019
21020 if let Some(workspace) = workspace {
21021 workspace
21022 .update_in(cx, |workspace, _, cx| {
21023 struct CopyPermalinkToLine;
21024
21025 workspace.show_toast(
21026 Toast::new(
21027 NotificationId::unique::<CopyPermalinkToLine>(),
21028 message,
21029 ),
21030 cx,
21031 )
21032 })
21033 .ok();
21034 }
21035 }
21036 })
21037 .detach();
21038 }
21039
21040 pub fn copy_file_location(
21041 &mut self,
21042 _: &CopyFileLocation,
21043 _: &mut Window,
21044 cx: &mut Context<Self>,
21045 ) {
21046 let selection = self
21047 .selections
21048 .newest::<Point>(&self.display_snapshot(cx))
21049 .start
21050 .row
21051 + 1;
21052 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21053 let project = self.project()?.read(cx);
21054 let file = buffer.read(cx).file()?;
21055 let path = file.path().display(project.path_style(cx));
21056
21057 Some(format!("{path}:{selection}"))
21058 }) {
21059 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21060 }
21061 }
21062
21063 pub fn open_permalink_to_line(
21064 &mut self,
21065 _: &OpenPermalinkToLine,
21066 window: &mut Window,
21067 cx: &mut Context<Self>,
21068 ) {
21069 let permalink_task = self.get_permalink_to_line(cx);
21070 let workspace = self.workspace();
21071
21072 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21073 Ok(permalink) => {
21074 cx.update(|_, cx| {
21075 cx.open_url(permalink.as_ref());
21076 })
21077 .ok();
21078 }
21079 Err(err) => {
21080 let message = format!("Failed to open permalink: {err}");
21081
21082 anyhow::Result::<()>::Err(err).log_err();
21083
21084 if let Some(workspace) = workspace {
21085 workspace
21086 .update(cx, |workspace, cx| {
21087 struct OpenPermalinkToLine;
21088
21089 workspace.show_toast(
21090 Toast::new(
21091 NotificationId::unique::<OpenPermalinkToLine>(),
21092 message,
21093 ),
21094 cx,
21095 )
21096 })
21097 .ok();
21098 }
21099 }
21100 })
21101 .detach();
21102 }
21103
21104 pub fn insert_uuid_v4(
21105 &mut self,
21106 _: &InsertUuidV4,
21107 window: &mut Window,
21108 cx: &mut Context<Self>,
21109 ) {
21110 self.insert_uuid(UuidVersion::V4, window, cx);
21111 }
21112
21113 pub fn insert_uuid_v7(
21114 &mut self,
21115 _: &InsertUuidV7,
21116 window: &mut Window,
21117 cx: &mut Context<Self>,
21118 ) {
21119 self.insert_uuid(UuidVersion::V7, window, cx);
21120 }
21121
21122 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21123 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21124 self.transact(window, cx, |this, window, cx| {
21125 let edits = this
21126 .selections
21127 .all::<Point>(&this.display_snapshot(cx))
21128 .into_iter()
21129 .map(|selection| {
21130 let uuid = match version {
21131 UuidVersion::V4 => uuid::Uuid::new_v4(),
21132 UuidVersion::V7 => uuid::Uuid::now_v7(),
21133 };
21134
21135 (selection.range(), uuid.to_string())
21136 });
21137 this.edit(edits, cx);
21138 this.refresh_edit_prediction(true, false, window, cx);
21139 });
21140 }
21141
21142 pub fn open_selections_in_multibuffer(
21143 &mut self,
21144 _: &OpenSelectionsInMultibuffer,
21145 window: &mut Window,
21146 cx: &mut Context<Self>,
21147 ) {
21148 let multibuffer = self.buffer.read(cx);
21149
21150 let Some(buffer) = multibuffer.as_singleton() else {
21151 return;
21152 };
21153
21154 let Some(workspace) = self.workspace() else {
21155 return;
21156 };
21157
21158 let title = multibuffer.title(cx).to_string();
21159
21160 let locations = self
21161 .selections
21162 .all_anchors(&self.display_snapshot(cx))
21163 .iter()
21164 .map(|selection| {
21165 (
21166 buffer.clone(),
21167 (selection.start.text_anchor..selection.end.text_anchor)
21168 .to_point(buffer.read(cx)),
21169 )
21170 })
21171 .into_group_map();
21172
21173 cx.spawn_in(window, async move |_, cx| {
21174 workspace.update_in(cx, |workspace, window, cx| {
21175 Self::open_locations_in_multibuffer(
21176 workspace,
21177 locations,
21178 format!("Selections for '{title}'"),
21179 false,
21180 false,
21181 MultibufferSelectionMode::All,
21182 window,
21183 cx,
21184 );
21185 })
21186 })
21187 .detach();
21188 }
21189
21190 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21191 /// last highlight added will be used.
21192 ///
21193 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21194 pub fn highlight_rows<T: 'static>(
21195 &mut self,
21196 range: Range<Anchor>,
21197 color: Hsla,
21198 options: RowHighlightOptions,
21199 cx: &mut Context<Self>,
21200 ) {
21201 let snapshot = self.buffer().read(cx).snapshot(cx);
21202 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21203 let ix = row_highlights.binary_search_by(|highlight| {
21204 Ordering::Equal
21205 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21206 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21207 });
21208
21209 if let Err(mut ix) = ix {
21210 let index = post_inc(&mut self.highlight_order);
21211
21212 // If this range intersects with the preceding highlight, then merge it with
21213 // the preceding highlight. Otherwise insert a new highlight.
21214 let mut merged = false;
21215 if ix > 0 {
21216 let prev_highlight = &mut row_highlights[ix - 1];
21217 if prev_highlight
21218 .range
21219 .end
21220 .cmp(&range.start, &snapshot)
21221 .is_ge()
21222 {
21223 ix -= 1;
21224 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21225 prev_highlight.range.end = range.end;
21226 }
21227 merged = true;
21228 prev_highlight.index = index;
21229 prev_highlight.color = color;
21230 prev_highlight.options = options;
21231 }
21232 }
21233
21234 if !merged {
21235 row_highlights.insert(
21236 ix,
21237 RowHighlight {
21238 range,
21239 index,
21240 color,
21241 options,
21242 type_id: TypeId::of::<T>(),
21243 },
21244 );
21245 }
21246
21247 // If any of the following highlights intersect with this one, merge them.
21248 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21249 let highlight = &row_highlights[ix];
21250 if next_highlight
21251 .range
21252 .start
21253 .cmp(&highlight.range.end, &snapshot)
21254 .is_le()
21255 {
21256 if next_highlight
21257 .range
21258 .end
21259 .cmp(&highlight.range.end, &snapshot)
21260 .is_gt()
21261 {
21262 row_highlights[ix].range.end = next_highlight.range.end;
21263 }
21264 row_highlights.remove(ix + 1);
21265 } else {
21266 break;
21267 }
21268 }
21269 }
21270 }
21271
21272 /// Remove any highlighted row ranges of the given type that intersect the
21273 /// given ranges.
21274 pub fn remove_highlighted_rows<T: 'static>(
21275 &mut self,
21276 ranges_to_remove: Vec<Range<Anchor>>,
21277 cx: &mut Context<Self>,
21278 ) {
21279 let snapshot = self.buffer().read(cx).snapshot(cx);
21280 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21281 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21282 row_highlights.retain(|highlight| {
21283 while let Some(range_to_remove) = ranges_to_remove.peek() {
21284 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21285 Ordering::Less | Ordering::Equal => {
21286 ranges_to_remove.next();
21287 }
21288 Ordering::Greater => {
21289 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21290 Ordering::Less | Ordering::Equal => {
21291 return false;
21292 }
21293 Ordering::Greater => break,
21294 }
21295 }
21296 }
21297 }
21298
21299 true
21300 })
21301 }
21302
21303 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21304 pub fn clear_row_highlights<T: 'static>(&mut self) {
21305 self.highlighted_rows.remove(&TypeId::of::<T>());
21306 }
21307
21308 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21309 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21310 self.highlighted_rows
21311 .get(&TypeId::of::<T>())
21312 .map_or(&[] as &[_], |vec| vec.as_slice())
21313 .iter()
21314 .map(|highlight| (highlight.range.clone(), highlight.color))
21315 }
21316
21317 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21318 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21319 /// Allows to ignore certain kinds of highlights.
21320 pub fn highlighted_display_rows(
21321 &self,
21322 window: &mut Window,
21323 cx: &mut App,
21324 ) -> BTreeMap<DisplayRow, LineHighlight> {
21325 let snapshot = self.snapshot(window, cx);
21326 let mut used_highlight_orders = HashMap::default();
21327 self.highlighted_rows
21328 .iter()
21329 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21330 .fold(
21331 BTreeMap::<DisplayRow, LineHighlight>::new(),
21332 |mut unique_rows, highlight| {
21333 let start = highlight.range.start.to_display_point(&snapshot);
21334 let end = highlight.range.end.to_display_point(&snapshot);
21335 let start_row = start.row().0;
21336 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21337 {
21338 end.row().0.saturating_sub(1)
21339 } else {
21340 end.row().0
21341 };
21342 for row in start_row..=end_row {
21343 let used_index =
21344 used_highlight_orders.entry(row).or_insert(highlight.index);
21345 if highlight.index >= *used_index {
21346 *used_index = highlight.index;
21347 unique_rows.insert(
21348 DisplayRow(row),
21349 LineHighlight {
21350 include_gutter: highlight.options.include_gutter,
21351 border: None,
21352 background: highlight.color.into(),
21353 type_id: Some(highlight.type_id),
21354 },
21355 );
21356 }
21357 }
21358 unique_rows
21359 },
21360 )
21361 }
21362
21363 pub fn highlighted_display_row_for_autoscroll(
21364 &self,
21365 snapshot: &DisplaySnapshot,
21366 ) -> Option<DisplayRow> {
21367 self.highlighted_rows
21368 .values()
21369 .flat_map(|highlighted_rows| highlighted_rows.iter())
21370 .filter_map(|highlight| {
21371 if highlight.options.autoscroll {
21372 Some(highlight.range.start.to_display_point(snapshot).row())
21373 } else {
21374 None
21375 }
21376 })
21377 .min()
21378 }
21379
21380 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21381 self.highlight_background::<SearchWithinRange>(
21382 ranges,
21383 |_, colors| colors.colors().editor_document_highlight_read_background,
21384 cx,
21385 )
21386 }
21387
21388 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21389 self.breadcrumb_header = Some(new_header);
21390 }
21391
21392 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21393 self.clear_background_highlights::<SearchWithinRange>(cx);
21394 }
21395
21396 pub fn highlight_background<T: 'static>(
21397 &mut self,
21398 ranges: &[Range<Anchor>],
21399 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21400 cx: &mut Context<Self>,
21401 ) {
21402 self.background_highlights.insert(
21403 HighlightKey::Type(TypeId::of::<T>()),
21404 (Arc::new(color_fetcher), Arc::from(ranges)),
21405 );
21406 self.scrollbar_marker_state.dirty = true;
21407 cx.notify();
21408 }
21409
21410 pub fn highlight_background_key<T: 'static>(
21411 &mut self,
21412 key: usize,
21413 ranges: &[Range<Anchor>],
21414 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21415 cx: &mut Context<Self>,
21416 ) {
21417 self.background_highlights.insert(
21418 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21419 (Arc::new(color_fetcher), Arc::from(ranges)),
21420 );
21421 self.scrollbar_marker_state.dirty = true;
21422 cx.notify();
21423 }
21424
21425 pub fn clear_background_highlights<T: 'static>(
21426 &mut self,
21427 cx: &mut Context<Self>,
21428 ) -> Option<BackgroundHighlight> {
21429 let text_highlights = self
21430 .background_highlights
21431 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21432 if !text_highlights.1.is_empty() {
21433 self.scrollbar_marker_state.dirty = true;
21434 cx.notify();
21435 }
21436 Some(text_highlights)
21437 }
21438
21439 pub fn highlight_gutter<T: 'static>(
21440 &mut self,
21441 ranges: impl Into<Vec<Range<Anchor>>>,
21442 color_fetcher: fn(&App) -> Hsla,
21443 cx: &mut Context<Self>,
21444 ) {
21445 self.gutter_highlights
21446 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21447 cx.notify();
21448 }
21449
21450 pub fn clear_gutter_highlights<T: 'static>(
21451 &mut self,
21452 cx: &mut Context<Self>,
21453 ) -> Option<GutterHighlight> {
21454 cx.notify();
21455 self.gutter_highlights.remove(&TypeId::of::<T>())
21456 }
21457
21458 pub fn insert_gutter_highlight<T: 'static>(
21459 &mut self,
21460 range: Range<Anchor>,
21461 color_fetcher: fn(&App) -> Hsla,
21462 cx: &mut Context<Self>,
21463 ) {
21464 let snapshot = self.buffer().read(cx).snapshot(cx);
21465 let mut highlights = self
21466 .gutter_highlights
21467 .remove(&TypeId::of::<T>())
21468 .map(|(_, highlights)| highlights)
21469 .unwrap_or_default();
21470 let ix = highlights.binary_search_by(|highlight| {
21471 Ordering::Equal
21472 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21473 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21474 });
21475 if let Err(ix) = ix {
21476 highlights.insert(ix, range);
21477 }
21478 self.gutter_highlights
21479 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21480 }
21481
21482 pub fn remove_gutter_highlights<T: 'static>(
21483 &mut self,
21484 ranges_to_remove: Vec<Range<Anchor>>,
21485 cx: &mut Context<Self>,
21486 ) {
21487 let snapshot = self.buffer().read(cx).snapshot(cx);
21488 let Some((color_fetcher, mut gutter_highlights)) =
21489 self.gutter_highlights.remove(&TypeId::of::<T>())
21490 else {
21491 return;
21492 };
21493 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21494 gutter_highlights.retain(|highlight| {
21495 while let Some(range_to_remove) = ranges_to_remove.peek() {
21496 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21497 Ordering::Less | Ordering::Equal => {
21498 ranges_to_remove.next();
21499 }
21500 Ordering::Greater => {
21501 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21502 Ordering::Less | Ordering::Equal => {
21503 return false;
21504 }
21505 Ordering::Greater => break,
21506 }
21507 }
21508 }
21509 }
21510
21511 true
21512 });
21513 self.gutter_highlights
21514 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21515 }
21516
21517 #[cfg(feature = "test-support")]
21518 pub fn all_text_highlights(
21519 &self,
21520 window: &mut Window,
21521 cx: &mut Context<Self>,
21522 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21523 let snapshot = self.snapshot(window, cx);
21524 self.display_map.update(cx, |display_map, _| {
21525 display_map
21526 .all_text_highlights()
21527 .map(|highlight| {
21528 let (style, ranges) = highlight.as_ref();
21529 (
21530 *style,
21531 ranges
21532 .iter()
21533 .map(|range| range.clone().to_display_points(&snapshot))
21534 .collect(),
21535 )
21536 })
21537 .collect()
21538 })
21539 }
21540
21541 #[cfg(feature = "test-support")]
21542 pub fn all_text_background_highlights(
21543 &self,
21544 window: &mut Window,
21545 cx: &mut Context<Self>,
21546 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21547 let snapshot = self.snapshot(window, cx);
21548 let buffer = &snapshot.buffer_snapshot();
21549 let start = buffer.anchor_before(MultiBufferOffset(0));
21550 let end = buffer.anchor_after(buffer.len());
21551 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21552 }
21553
21554 #[cfg(any(test, feature = "test-support"))]
21555 pub fn sorted_background_highlights_in_range(
21556 &self,
21557 search_range: Range<Anchor>,
21558 display_snapshot: &DisplaySnapshot,
21559 theme: &Theme,
21560 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21561 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21562 res.sort_by(|a, b| {
21563 a.0.start
21564 .cmp(&b.0.start)
21565 .then_with(|| a.0.end.cmp(&b.0.end))
21566 .then_with(|| a.1.cmp(&b.1))
21567 });
21568 res
21569 }
21570
21571 #[cfg(feature = "test-support")]
21572 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21573 let snapshot = self.buffer().read(cx).snapshot(cx);
21574
21575 let highlights = self
21576 .background_highlights
21577 .get(&HighlightKey::Type(TypeId::of::<
21578 items::BufferSearchHighlights,
21579 >()));
21580
21581 if let Some((_color, ranges)) = highlights {
21582 ranges
21583 .iter()
21584 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21585 .collect_vec()
21586 } else {
21587 vec![]
21588 }
21589 }
21590
21591 fn document_highlights_for_position<'a>(
21592 &'a self,
21593 position: Anchor,
21594 buffer: &'a MultiBufferSnapshot,
21595 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21596 let read_highlights = self
21597 .background_highlights
21598 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21599 .map(|h| &h.1);
21600 let write_highlights = self
21601 .background_highlights
21602 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21603 .map(|h| &h.1);
21604 let left_position = position.bias_left(buffer);
21605 let right_position = position.bias_right(buffer);
21606 read_highlights
21607 .into_iter()
21608 .chain(write_highlights)
21609 .flat_map(move |ranges| {
21610 let start_ix = match ranges.binary_search_by(|probe| {
21611 let cmp = probe.end.cmp(&left_position, buffer);
21612 if cmp.is_ge() {
21613 Ordering::Greater
21614 } else {
21615 Ordering::Less
21616 }
21617 }) {
21618 Ok(i) | Err(i) => i,
21619 };
21620
21621 ranges[start_ix..]
21622 .iter()
21623 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21624 })
21625 }
21626
21627 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21628 self.background_highlights
21629 .get(&HighlightKey::Type(TypeId::of::<T>()))
21630 .is_some_and(|(_, highlights)| !highlights.is_empty())
21631 }
21632
21633 /// Returns all background highlights for a given range.
21634 ///
21635 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21636 pub fn background_highlights_in_range(
21637 &self,
21638 search_range: Range<Anchor>,
21639 display_snapshot: &DisplaySnapshot,
21640 theme: &Theme,
21641 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21642 let mut results = Vec::new();
21643 for (color_fetcher, ranges) in self.background_highlights.values() {
21644 let start_ix = match ranges.binary_search_by(|probe| {
21645 let cmp = probe
21646 .end
21647 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21648 if cmp.is_gt() {
21649 Ordering::Greater
21650 } else {
21651 Ordering::Less
21652 }
21653 }) {
21654 Ok(i) | Err(i) => i,
21655 };
21656 for (index, range) in ranges[start_ix..].iter().enumerate() {
21657 if range
21658 .start
21659 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21660 .is_ge()
21661 {
21662 break;
21663 }
21664
21665 let color = color_fetcher(&(start_ix + index), theme);
21666 let start = range.start.to_display_point(display_snapshot);
21667 let end = range.end.to_display_point(display_snapshot);
21668 results.push((start..end, color))
21669 }
21670 }
21671 results
21672 }
21673
21674 pub fn gutter_highlights_in_range(
21675 &self,
21676 search_range: Range<Anchor>,
21677 display_snapshot: &DisplaySnapshot,
21678 cx: &App,
21679 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21680 let mut results = Vec::new();
21681 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21682 let color = color_fetcher(cx);
21683 let start_ix = match ranges.binary_search_by(|probe| {
21684 let cmp = probe
21685 .end
21686 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21687 if cmp.is_gt() {
21688 Ordering::Greater
21689 } else {
21690 Ordering::Less
21691 }
21692 }) {
21693 Ok(i) | Err(i) => i,
21694 };
21695 for range in &ranges[start_ix..] {
21696 if range
21697 .start
21698 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21699 .is_ge()
21700 {
21701 break;
21702 }
21703
21704 let start = range.start.to_display_point(display_snapshot);
21705 let end = range.end.to_display_point(display_snapshot);
21706 results.push((start..end, color))
21707 }
21708 }
21709 results
21710 }
21711
21712 /// Get the text ranges corresponding to the redaction query
21713 pub fn redacted_ranges(
21714 &self,
21715 search_range: Range<Anchor>,
21716 display_snapshot: &DisplaySnapshot,
21717 cx: &App,
21718 ) -> Vec<Range<DisplayPoint>> {
21719 display_snapshot
21720 .buffer_snapshot()
21721 .redacted_ranges(search_range, |file| {
21722 if let Some(file) = file {
21723 file.is_private()
21724 && EditorSettings::get(
21725 Some(SettingsLocation {
21726 worktree_id: file.worktree_id(cx),
21727 path: file.path().as_ref(),
21728 }),
21729 cx,
21730 )
21731 .redact_private_values
21732 } else {
21733 false
21734 }
21735 })
21736 .map(|range| {
21737 range.start.to_display_point(display_snapshot)
21738 ..range.end.to_display_point(display_snapshot)
21739 })
21740 .collect()
21741 }
21742
21743 pub fn highlight_text_key<T: 'static>(
21744 &mut self,
21745 key: usize,
21746 ranges: Vec<Range<Anchor>>,
21747 style: HighlightStyle,
21748 merge: bool,
21749 cx: &mut Context<Self>,
21750 ) {
21751 self.display_map.update(cx, |map, cx| {
21752 map.highlight_text(
21753 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21754 ranges,
21755 style,
21756 merge,
21757 cx,
21758 );
21759 });
21760 cx.notify();
21761 }
21762
21763 pub fn highlight_text<T: 'static>(
21764 &mut self,
21765 ranges: Vec<Range<Anchor>>,
21766 style: HighlightStyle,
21767 cx: &mut Context<Self>,
21768 ) {
21769 self.display_map.update(cx, |map, cx| {
21770 map.highlight_text(
21771 HighlightKey::Type(TypeId::of::<T>()),
21772 ranges,
21773 style,
21774 false,
21775 cx,
21776 )
21777 });
21778 cx.notify();
21779 }
21780
21781 pub fn text_highlights<'a, T: 'static>(
21782 &'a self,
21783 cx: &'a App,
21784 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21785 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21786 }
21787
21788 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21789 let cleared = self
21790 .display_map
21791 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21792 if cleared {
21793 cx.notify();
21794 }
21795 }
21796
21797 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21798 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21799 && self.focus_handle.is_focused(window)
21800 }
21801
21802 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21803 self.show_cursor_when_unfocused = is_enabled;
21804 cx.notify();
21805 }
21806
21807 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21808 cx.notify();
21809 }
21810
21811 fn on_debug_session_event(
21812 &mut self,
21813 _session: Entity<Session>,
21814 event: &SessionEvent,
21815 cx: &mut Context<Self>,
21816 ) {
21817 if let SessionEvent::InvalidateInlineValue = event {
21818 self.refresh_inline_values(cx);
21819 }
21820 }
21821
21822 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21823 let Some(project) = self.project.clone() else {
21824 return;
21825 };
21826
21827 if !self.inline_value_cache.enabled {
21828 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21829 self.splice_inlays(&inlays, Vec::new(), cx);
21830 return;
21831 }
21832
21833 let current_execution_position = self
21834 .highlighted_rows
21835 .get(&TypeId::of::<ActiveDebugLine>())
21836 .and_then(|lines| lines.last().map(|line| line.range.end));
21837
21838 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21839 let inline_values = editor
21840 .update(cx, |editor, cx| {
21841 let Some(current_execution_position) = current_execution_position else {
21842 return Some(Task::ready(Ok(Vec::new())));
21843 };
21844
21845 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21846 let snapshot = buffer.snapshot(cx);
21847
21848 let excerpt = snapshot.excerpt_containing(
21849 current_execution_position..current_execution_position,
21850 )?;
21851
21852 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21853 })?;
21854
21855 let range =
21856 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21857
21858 project.inline_values(buffer, range, cx)
21859 })
21860 .ok()
21861 .flatten()?
21862 .await
21863 .context("refreshing debugger inlays")
21864 .log_err()?;
21865
21866 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21867
21868 for (buffer_id, inline_value) in inline_values
21869 .into_iter()
21870 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21871 {
21872 buffer_inline_values
21873 .entry(buffer_id)
21874 .or_default()
21875 .push(inline_value);
21876 }
21877
21878 editor
21879 .update(cx, |editor, cx| {
21880 let snapshot = editor.buffer.read(cx).snapshot(cx);
21881 let mut new_inlays = Vec::default();
21882
21883 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21884 let buffer_id = buffer_snapshot.remote_id();
21885 buffer_inline_values
21886 .get(&buffer_id)
21887 .into_iter()
21888 .flatten()
21889 .for_each(|hint| {
21890 let inlay = Inlay::debugger(
21891 post_inc(&mut editor.next_inlay_id),
21892 Anchor::in_buffer(excerpt_id, hint.position),
21893 hint.text(),
21894 );
21895 if !inlay.text().chars().contains(&'\n') {
21896 new_inlays.push(inlay);
21897 }
21898 });
21899 }
21900
21901 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21902 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21903
21904 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21905 })
21906 .ok()?;
21907 Some(())
21908 });
21909 }
21910
21911 fn on_buffer_event(
21912 &mut self,
21913 multibuffer: &Entity<MultiBuffer>,
21914 event: &multi_buffer::Event,
21915 window: &mut Window,
21916 cx: &mut Context<Self>,
21917 ) {
21918 match event {
21919 multi_buffer::Event::Edited { edited_buffer } => {
21920 self.scrollbar_marker_state.dirty = true;
21921 self.active_indent_guides_state.dirty = true;
21922 self.refresh_active_diagnostics(cx);
21923 self.refresh_code_actions(window, cx);
21924 self.refresh_single_line_folds(window, cx);
21925 self.refresh_matching_bracket_highlights(window, cx);
21926 if self.has_active_edit_prediction() {
21927 self.update_visible_edit_prediction(window, cx);
21928 }
21929
21930 if let Some(buffer) = edited_buffer {
21931 if buffer.read(cx).file().is_none() {
21932 cx.emit(EditorEvent::TitleChanged);
21933 }
21934
21935 if self.project.is_some() {
21936 let buffer_id = buffer.read(cx).remote_id();
21937 self.register_buffer(buffer_id, cx);
21938 self.update_lsp_data(Some(buffer_id), window, cx);
21939 self.refresh_inlay_hints(
21940 InlayHintRefreshReason::BufferEdited(buffer_id),
21941 cx,
21942 );
21943 }
21944 }
21945
21946 cx.emit(EditorEvent::BufferEdited);
21947 cx.emit(SearchEvent::MatchesInvalidated);
21948
21949 let Some(project) = &self.project else { return };
21950 let (telemetry, is_via_ssh) = {
21951 let project = project.read(cx);
21952 let telemetry = project.client().telemetry().clone();
21953 let is_via_ssh = project.is_via_remote_server();
21954 (telemetry, is_via_ssh)
21955 };
21956 telemetry.log_edit_event("editor", is_via_ssh);
21957 }
21958 multi_buffer::Event::ExcerptsAdded {
21959 buffer,
21960 predecessor,
21961 excerpts,
21962 } => {
21963 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21964 let buffer_id = buffer.read(cx).remote_id();
21965 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21966 && let Some(project) = &self.project
21967 {
21968 update_uncommitted_diff_for_buffer(
21969 cx.entity(),
21970 project,
21971 [buffer.clone()],
21972 self.buffer.clone(),
21973 cx,
21974 )
21975 .detach();
21976 }
21977 self.update_lsp_data(Some(buffer_id), window, cx);
21978 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21979 self.colorize_brackets(false, cx);
21980 cx.emit(EditorEvent::ExcerptsAdded {
21981 buffer: buffer.clone(),
21982 predecessor: *predecessor,
21983 excerpts: excerpts.clone(),
21984 });
21985 }
21986 multi_buffer::Event::ExcerptsRemoved {
21987 ids,
21988 removed_buffer_ids,
21989 } => {
21990 if let Some(inlay_hints) = &mut self.inlay_hints {
21991 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21992 }
21993 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21994 for buffer_id in removed_buffer_ids {
21995 self.registered_buffers.remove(buffer_id);
21996 }
21997 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21998 cx.emit(EditorEvent::ExcerptsRemoved {
21999 ids: ids.clone(),
22000 removed_buffer_ids: removed_buffer_ids.clone(),
22001 });
22002 }
22003 multi_buffer::Event::ExcerptsEdited {
22004 excerpt_ids,
22005 buffer_ids,
22006 } => {
22007 self.display_map.update(cx, |map, cx| {
22008 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22009 });
22010 cx.emit(EditorEvent::ExcerptsEdited {
22011 ids: excerpt_ids.clone(),
22012 });
22013 }
22014 multi_buffer::Event::ExcerptsExpanded { ids } => {
22015 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22016 self.refresh_document_highlights(cx);
22017 for id in ids {
22018 self.fetched_tree_sitter_chunks.remove(id);
22019 }
22020 self.colorize_brackets(false, cx);
22021 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22022 }
22023 multi_buffer::Event::Reparsed(buffer_id) => {
22024 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22025 self.refresh_selected_text_highlights(true, window, cx);
22026 self.colorize_brackets(true, cx);
22027 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22028
22029 cx.emit(EditorEvent::Reparsed(*buffer_id));
22030 }
22031 multi_buffer::Event::DiffHunksToggled => {
22032 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22033 }
22034 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22035 if !is_fresh_language {
22036 self.registered_buffers.remove(&buffer_id);
22037 }
22038 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22039 cx.emit(EditorEvent::Reparsed(*buffer_id));
22040 cx.notify();
22041 }
22042 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22043 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22044 multi_buffer::Event::FileHandleChanged
22045 | multi_buffer::Event::Reloaded
22046 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22047 multi_buffer::Event::DiagnosticsUpdated => {
22048 self.update_diagnostics_state(window, cx);
22049 }
22050 _ => {}
22051 };
22052 }
22053
22054 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22055 if !self.diagnostics_enabled() {
22056 return;
22057 }
22058 self.refresh_active_diagnostics(cx);
22059 self.refresh_inline_diagnostics(true, window, cx);
22060 self.scrollbar_marker_state.dirty = true;
22061 cx.notify();
22062 }
22063
22064 pub fn start_temporary_diff_override(&mut self) {
22065 self.load_diff_task.take();
22066 self.temporary_diff_override = true;
22067 }
22068
22069 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22070 self.temporary_diff_override = false;
22071 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22072 self.buffer.update(cx, |buffer, cx| {
22073 buffer.set_all_diff_hunks_collapsed(cx);
22074 });
22075
22076 if let Some(project) = self.project.clone() {
22077 self.load_diff_task = Some(
22078 update_uncommitted_diff_for_buffer(
22079 cx.entity(),
22080 &project,
22081 self.buffer.read(cx).all_buffers(),
22082 self.buffer.clone(),
22083 cx,
22084 )
22085 .shared(),
22086 );
22087 }
22088 }
22089
22090 fn on_display_map_changed(
22091 &mut self,
22092 _: Entity<DisplayMap>,
22093 _: &mut Window,
22094 cx: &mut Context<Self>,
22095 ) {
22096 cx.notify();
22097 }
22098
22099 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22100 if !self.mode.is_full() {
22101 return None;
22102 }
22103
22104 let theme_settings = theme::ThemeSettings::get_global(cx);
22105 let theme = cx.theme();
22106 let accent_colors = theme.accents().clone();
22107
22108 let accent_overrides = theme_settings
22109 .theme_overrides
22110 .get(theme.name.as_ref())
22111 .map(|theme_style| &theme_style.accents)
22112 .into_iter()
22113 .flatten()
22114 .chain(
22115 theme_settings
22116 .experimental_theme_overrides
22117 .as_ref()
22118 .map(|overrides| &overrides.accents)
22119 .into_iter()
22120 .flatten(),
22121 )
22122 .flat_map(|accent| accent.0.clone())
22123 .collect();
22124
22125 Some(AccentData {
22126 colors: accent_colors,
22127 overrides: accent_overrides,
22128 })
22129 }
22130
22131 fn fetch_applicable_language_settings(
22132 &self,
22133 cx: &App,
22134 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22135 if !self.mode.is_full() {
22136 return HashMap::default();
22137 }
22138
22139 self.buffer().read(cx).all_buffers().into_iter().fold(
22140 HashMap::default(),
22141 |mut acc, buffer| {
22142 let buffer = buffer.read(cx);
22143 let language = buffer.language().map(|language| language.name());
22144 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22145 let file = buffer.file();
22146 v.insert(language_settings(language, file, cx).into_owned());
22147 }
22148 acc
22149 },
22150 )
22151 }
22152
22153 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22154 let new_language_settings = self.fetch_applicable_language_settings(cx);
22155 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22156 self.applicable_language_settings = new_language_settings;
22157
22158 let new_accents = self.fetch_accent_data(cx);
22159 let accents_changed = new_accents != self.accent_data;
22160 self.accent_data = new_accents;
22161
22162 if self.diagnostics_enabled() {
22163 let new_severity = EditorSettings::get_global(cx)
22164 .diagnostics_max_severity
22165 .unwrap_or(DiagnosticSeverity::Hint);
22166 self.set_max_diagnostics_severity(new_severity, cx);
22167 }
22168 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22169 self.update_edit_prediction_settings(cx);
22170 self.refresh_edit_prediction(true, false, window, cx);
22171 self.refresh_inline_values(cx);
22172 self.refresh_inlay_hints(
22173 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22174 self.selections.newest_anchor().head(),
22175 &self.buffer.read(cx).snapshot(cx),
22176 cx,
22177 )),
22178 cx,
22179 );
22180
22181 let old_cursor_shape = self.cursor_shape;
22182 let old_show_breadcrumbs = self.show_breadcrumbs;
22183
22184 {
22185 let editor_settings = EditorSettings::get_global(cx);
22186 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22187 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22188 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22189 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22190 }
22191
22192 if old_cursor_shape != self.cursor_shape {
22193 cx.emit(EditorEvent::CursorShapeChanged);
22194 }
22195
22196 if old_show_breadcrumbs != self.show_breadcrumbs {
22197 cx.emit(EditorEvent::BreadcrumbsChanged);
22198 }
22199
22200 let project_settings = ProjectSettings::get_global(cx);
22201 self.buffer_serialization = self
22202 .should_serialize_buffer()
22203 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22204
22205 if self.mode.is_full() {
22206 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22207 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22208 if self.show_inline_diagnostics != show_inline_diagnostics {
22209 self.show_inline_diagnostics = show_inline_diagnostics;
22210 self.refresh_inline_diagnostics(false, window, cx);
22211 }
22212
22213 if self.git_blame_inline_enabled != inline_blame_enabled {
22214 self.toggle_git_blame_inline_internal(false, window, cx);
22215 }
22216
22217 let minimap_settings = EditorSettings::get_global(cx).minimap;
22218 if self.minimap_visibility != MinimapVisibility::Disabled {
22219 if self.minimap_visibility.settings_visibility()
22220 != minimap_settings.minimap_enabled()
22221 {
22222 self.set_minimap_visibility(
22223 MinimapVisibility::for_mode(self.mode(), cx),
22224 window,
22225 cx,
22226 );
22227 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22228 minimap_entity.update(cx, |minimap_editor, cx| {
22229 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22230 })
22231 }
22232 }
22233
22234 if language_settings_changed || accents_changed {
22235 self.colorize_brackets(true, cx);
22236 }
22237
22238 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22239 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22240 }) {
22241 if !inlay_splice.is_empty() {
22242 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22243 }
22244 self.refresh_colors_for_visible_range(None, window, cx);
22245 }
22246 }
22247
22248 cx.notify();
22249 }
22250
22251 pub fn set_searchable(&mut self, searchable: bool) {
22252 self.searchable = searchable;
22253 }
22254
22255 pub fn searchable(&self) -> bool {
22256 self.searchable
22257 }
22258
22259 pub fn open_excerpts_in_split(
22260 &mut self,
22261 _: &OpenExcerptsSplit,
22262 window: &mut Window,
22263 cx: &mut Context<Self>,
22264 ) {
22265 self.open_excerpts_common(None, true, window, cx)
22266 }
22267
22268 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22269 self.open_excerpts_common(None, false, window, cx)
22270 }
22271
22272 fn open_excerpts_common(
22273 &mut self,
22274 jump_data: Option<JumpData>,
22275 split: bool,
22276 window: &mut Window,
22277 cx: &mut Context<Self>,
22278 ) {
22279 let Some(workspace) = self.workspace() else {
22280 cx.propagate();
22281 return;
22282 };
22283
22284 if self.buffer.read(cx).is_singleton() {
22285 cx.propagate();
22286 return;
22287 }
22288
22289 let mut new_selections_by_buffer = HashMap::default();
22290 match &jump_data {
22291 Some(JumpData::MultiBufferPoint {
22292 excerpt_id,
22293 position,
22294 anchor,
22295 line_offset_from_top,
22296 }) => {
22297 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22298 if let Some(buffer) = multi_buffer_snapshot
22299 .buffer_id_for_excerpt(*excerpt_id)
22300 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22301 {
22302 let buffer_snapshot = buffer.read(cx).snapshot();
22303 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22304 language::ToPoint::to_point(anchor, &buffer_snapshot)
22305 } else {
22306 buffer_snapshot.clip_point(*position, Bias::Left)
22307 };
22308 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22309 new_selections_by_buffer.insert(
22310 buffer,
22311 (
22312 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22313 Some(*line_offset_from_top),
22314 ),
22315 );
22316 }
22317 }
22318 Some(JumpData::MultiBufferRow {
22319 row,
22320 line_offset_from_top,
22321 }) => {
22322 let point = MultiBufferPoint::new(row.0, 0);
22323 if let Some((buffer, buffer_point, _)) =
22324 self.buffer.read(cx).point_to_buffer_point(point, cx)
22325 {
22326 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22327 new_selections_by_buffer
22328 .entry(buffer)
22329 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22330 .0
22331 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22332 }
22333 }
22334 None => {
22335 let selections = self
22336 .selections
22337 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22338 let multi_buffer = self.buffer.read(cx);
22339 for selection in selections {
22340 for (snapshot, range, _, anchor) in multi_buffer
22341 .snapshot(cx)
22342 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22343 {
22344 if let Some(anchor) = anchor {
22345 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22346 else {
22347 continue;
22348 };
22349 let offset = text::ToOffset::to_offset(
22350 &anchor.text_anchor,
22351 &buffer_handle.read(cx).snapshot(),
22352 );
22353 let range = BufferOffset(offset)..BufferOffset(offset);
22354 new_selections_by_buffer
22355 .entry(buffer_handle)
22356 .or_insert((Vec::new(), None))
22357 .0
22358 .push(range)
22359 } else {
22360 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22361 else {
22362 continue;
22363 };
22364 new_selections_by_buffer
22365 .entry(buffer_handle)
22366 .or_insert((Vec::new(), None))
22367 .0
22368 .push(range)
22369 }
22370 }
22371 }
22372 }
22373 }
22374
22375 new_selections_by_buffer
22376 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22377
22378 if new_selections_by_buffer.is_empty() {
22379 return;
22380 }
22381
22382 // We defer the pane interaction because we ourselves are a workspace item
22383 // and activating a new item causes the pane to call a method on us reentrantly,
22384 // which panics if we're on the stack.
22385 window.defer(cx, move |window, cx| {
22386 workspace.update(cx, |workspace, cx| {
22387 let pane = if split {
22388 workspace.adjacent_pane(window, cx)
22389 } else {
22390 workspace.active_pane().clone()
22391 };
22392
22393 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22394 let buffer_read = buffer.read(cx);
22395 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22396 (true, project::File::from_dyn(Some(file)).is_some())
22397 } else {
22398 (false, false)
22399 };
22400
22401 // If project file is none workspace.open_project_item will fail to open the excerpt
22402 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22403 // so we check if there's a tab match in that case first
22404 let editor = (!has_file || !is_project_file)
22405 .then(|| {
22406 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22407 // so `workspace.open_project_item` will never find them, always opening a new editor.
22408 // Instead, we try to activate the existing editor in the pane first.
22409 let (editor, pane_item_index, pane_item_id) =
22410 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22411 let editor = item.downcast::<Editor>()?;
22412 let singleton_buffer =
22413 editor.read(cx).buffer().read(cx).as_singleton()?;
22414 if singleton_buffer == buffer {
22415 Some((editor, i, item.item_id()))
22416 } else {
22417 None
22418 }
22419 })?;
22420 pane.update(cx, |pane, cx| {
22421 pane.activate_item(pane_item_index, true, true, window, cx);
22422 if !PreviewTabsSettings::get_global(cx)
22423 .enable_preview_from_multibuffer
22424 {
22425 pane.unpreview_item_if_preview(pane_item_id);
22426 }
22427 });
22428 Some(editor)
22429 })
22430 .flatten()
22431 .unwrap_or_else(|| {
22432 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22433 .enable_keep_preview_on_code_navigation;
22434 let allow_new_preview =
22435 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22436 workspace.open_project_item::<Self>(
22437 pane.clone(),
22438 buffer,
22439 true,
22440 true,
22441 keep_old_preview,
22442 allow_new_preview,
22443 window,
22444 cx,
22445 )
22446 });
22447
22448 editor.update(cx, |editor, cx| {
22449 if has_file && !is_project_file {
22450 editor.set_read_only(true);
22451 }
22452 let autoscroll = match scroll_offset {
22453 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22454 None => Autoscroll::newest(),
22455 };
22456 let nav_history = editor.nav_history.take();
22457 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22458 let Some((&excerpt_id, _, buffer_snapshot)) =
22459 multibuffer_snapshot.as_singleton()
22460 else {
22461 return;
22462 };
22463 editor.change_selections(
22464 SelectionEffects::scroll(autoscroll),
22465 window,
22466 cx,
22467 |s| {
22468 s.select_ranges(ranges.into_iter().map(|range| {
22469 let range = buffer_snapshot.anchor_before(range.start)
22470 ..buffer_snapshot.anchor_after(range.end);
22471 multibuffer_snapshot
22472 .anchor_range_in_excerpt(excerpt_id, range)
22473 .unwrap()
22474 }));
22475 },
22476 );
22477 editor.nav_history = nav_history;
22478 });
22479 }
22480 })
22481 });
22482 }
22483
22484 // Allow opening excerpts for buffers that either belong to the current project
22485 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22486 // are also supported so tests and other in-memory views keep working.
22487 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22488 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22489 }
22490
22491 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22492 let snapshot = self.buffer.read(cx).read(cx);
22493 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22494 Some(
22495 ranges
22496 .iter()
22497 .map(move |range| {
22498 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22499 })
22500 .collect(),
22501 )
22502 }
22503
22504 fn selection_replacement_ranges(
22505 &self,
22506 range: Range<MultiBufferOffsetUtf16>,
22507 cx: &mut App,
22508 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22509 let selections = self
22510 .selections
22511 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22512 let newest_selection = selections
22513 .iter()
22514 .max_by_key(|selection| selection.id)
22515 .unwrap();
22516 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22517 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22518 let snapshot = self.buffer.read(cx).read(cx);
22519 selections
22520 .into_iter()
22521 .map(|mut selection| {
22522 selection.start.0.0 =
22523 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22524 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22525 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22526 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22527 })
22528 .collect()
22529 }
22530
22531 fn report_editor_event(
22532 &self,
22533 reported_event: ReportEditorEvent,
22534 file_extension: Option<String>,
22535 cx: &App,
22536 ) {
22537 if cfg!(any(test, feature = "test-support")) {
22538 return;
22539 }
22540
22541 let Some(project) = &self.project else { return };
22542
22543 // If None, we are in a file without an extension
22544 let file = self
22545 .buffer
22546 .read(cx)
22547 .as_singleton()
22548 .and_then(|b| b.read(cx).file());
22549 let file_extension = file_extension.or(file
22550 .as_ref()
22551 .and_then(|file| Path::new(file.file_name(cx)).extension())
22552 .and_then(|e| e.to_str())
22553 .map(|a| a.to_string()));
22554
22555 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22556 .map(|vim_mode| vim_mode.0)
22557 .unwrap_or(false);
22558
22559 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22560 let copilot_enabled = edit_predictions_provider
22561 == language::language_settings::EditPredictionProvider::Copilot;
22562 let copilot_enabled_for_language = self
22563 .buffer
22564 .read(cx)
22565 .language_settings(cx)
22566 .show_edit_predictions;
22567
22568 let project = project.read(cx);
22569 let event_type = reported_event.event_type();
22570
22571 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22572 telemetry::event!(
22573 event_type,
22574 type = if auto_saved {"autosave"} else {"manual"},
22575 file_extension,
22576 vim_mode,
22577 copilot_enabled,
22578 copilot_enabled_for_language,
22579 edit_predictions_provider,
22580 is_via_ssh = project.is_via_remote_server(),
22581 );
22582 } else {
22583 telemetry::event!(
22584 event_type,
22585 file_extension,
22586 vim_mode,
22587 copilot_enabled,
22588 copilot_enabled_for_language,
22589 edit_predictions_provider,
22590 is_via_ssh = project.is_via_remote_server(),
22591 );
22592 };
22593 }
22594
22595 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22596 /// with each line being an array of {text, highlight} objects.
22597 fn copy_highlight_json(
22598 &mut self,
22599 _: &CopyHighlightJson,
22600 window: &mut Window,
22601 cx: &mut Context<Self>,
22602 ) {
22603 #[derive(Serialize)]
22604 struct Chunk<'a> {
22605 text: String,
22606 highlight: Option<&'a str>,
22607 }
22608
22609 let snapshot = self.buffer.read(cx).snapshot(cx);
22610 let range = self
22611 .selected_text_range(false, window, cx)
22612 .and_then(|selection| {
22613 if selection.range.is_empty() {
22614 None
22615 } else {
22616 Some(
22617 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22618 selection.range.start,
22619 )))
22620 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22621 selection.range.end,
22622 ))),
22623 )
22624 }
22625 })
22626 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22627
22628 let chunks = snapshot.chunks(range, true);
22629 let mut lines = Vec::new();
22630 let mut line: VecDeque<Chunk> = VecDeque::new();
22631
22632 let Some(style) = self.style.as_ref() else {
22633 return;
22634 };
22635
22636 for chunk in chunks {
22637 let highlight = chunk
22638 .syntax_highlight_id
22639 .and_then(|id| id.name(&style.syntax));
22640 let mut chunk_lines = chunk.text.split('\n').peekable();
22641 while let Some(text) = chunk_lines.next() {
22642 let mut merged_with_last_token = false;
22643 if let Some(last_token) = line.back_mut()
22644 && last_token.highlight == highlight
22645 {
22646 last_token.text.push_str(text);
22647 merged_with_last_token = true;
22648 }
22649
22650 if !merged_with_last_token {
22651 line.push_back(Chunk {
22652 text: text.into(),
22653 highlight,
22654 });
22655 }
22656
22657 if chunk_lines.peek().is_some() {
22658 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22659 line.pop_front();
22660 }
22661 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22662 line.pop_back();
22663 }
22664
22665 lines.push(mem::take(&mut line));
22666 }
22667 }
22668 }
22669
22670 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22671 return;
22672 };
22673 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22674 }
22675
22676 pub fn open_context_menu(
22677 &mut self,
22678 _: &OpenContextMenu,
22679 window: &mut Window,
22680 cx: &mut Context<Self>,
22681 ) {
22682 self.request_autoscroll(Autoscroll::newest(), cx);
22683 let position = self
22684 .selections
22685 .newest_display(&self.display_snapshot(cx))
22686 .start;
22687 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22688 }
22689
22690 pub fn replay_insert_event(
22691 &mut self,
22692 text: &str,
22693 relative_utf16_range: Option<Range<isize>>,
22694 window: &mut Window,
22695 cx: &mut Context<Self>,
22696 ) {
22697 if !self.input_enabled {
22698 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22699 return;
22700 }
22701 if let Some(relative_utf16_range) = relative_utf16_range {
22702 let selections = self
22703 .selections
22704 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22705 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22706 let new_ranges = selections.into_iter().map(|range| {
22707 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22708 range
22709 .head()
22710 .0
22711 .0
22712 .saturating_add_signed(relative_utf16_range.start),
22713 ));
22714 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22715 range
22716 .head()
22717 .0
22718 .0
22719 .saturating_add_signed(relative_utf16_range.end),
22720 ));
22721 start..end
22722 });
22723 s.select_ranges(new_ranges);
22724 });
22725 }
22726
22727 self.handle_input(text, window, cx);
22728 }
22729
22730 pub fn is_focused(&self, window: &Window) -> bool {
22731 self.focus_handle.is_focused(window)
22732 }
22733
22734 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22735 cx.emit(EditorEvent::Focused);
22736
22737 if let Some(descendant) = self
22738 .last_focused_descendant
22739 .take()
22740 .and_then(|descendant| descendant.upgrade())
22741 {
22742 window.focus(&descendant, cx);
22743 } else {
22744 if let Some(blame) = self.blame.as_ref() {
22745 blame.update(cx, GitBlame::focus)
22746 }
22747
22748 self.blink_manager.update(cx, BlinkManager::enable);
22749 self.show_cursor_names(window, cx);
22750 self.buffer.update(cx, |buffer, cx| {
22751 buffer.finalize_last_transaction(cx);
22752 if self.leader_id.is_none() {
22753 buffer.set_active_selections(
22754 &self.selections.disjoint_anchors_arc(),
22755 self.selections.line_mode(),
22756 self.cursor_shape,
22757 cx,
22758 );
22759 }
22760 });
22761
22762 if let Some(position_map) = self.last_position_map.clone() {
22763 EditorElement::mouse_moved(
22764 self,
22765 &MouseMoveEvent {
22766 position: window.mouse_position(),
22767 pressed_button: None,
22768 modifiers: window.modifiers(),
22769 },
22770 &position_map,
22771 window,
22772 cx,
22773 );
22774 }
22775 }
22776 }
22777
22778 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22779 cx.emit(EditorEvent::FocusedIn)
22780 }
22781
22782 fn handle_focus_out(
22783 &mut self,
22784 event: FocusOutEvent,
22785 _window: &mut Window,
22786 cx: &mut Context<Self>,
22787 ) {
22788 if event.blurred != self.focus_handle {
22789 self.last_focused_descendant = Some(event.blurred);
22790 }
22791 self.selection_drag_state = SelectionDragState::None;
22792 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22793 }
22794
22795 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22796 self.blink_manager.update(cx, BlinkManager::disable);
22797 self.buffer
22798 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22799
22800 if let Some(blame) = self.blame.as_ref() {
22801 blame.update(cx, GitBlame::blur)
22802 }
22803 if !self.hover_state.focused(window, cx) {
22804 hide_hover(self, cx);
22805 }
22806 if !self
22807 .context_menu
22808 .borrow()
22809 .as_ref()
22810 .is_some_and(|context_menu| context_menu.focused(window, cx))
22811 {
22812 self.hide_context_menu(window, cx);
22813 }
22814 self.take_active_edit_prediction(cx);
22815 cx.emit(EditorEvent::Blurred);
22816 cx.notify();
22817 }
22818
22819 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22820 let mut pending: String = window
22821 .pending_input_keystrokes()
22822 .into_iter()
22823 .flatten()
22824 .filter_map(|keystroke| keystroke.key_char.clone())
22825 .collect();
22826
22827 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22828 pending = "".to_string();
22829 }
22830
22831 let existing_pending = self
22832 .text_highlights::<PendingInput>(cx)
22833 .map(|(_, ranges)| ranges.to_vec());
22834 if existing_pending.is_none() && pending.is_empty() {
22835 return;
22836 }
22837 let transaction =
22838 self.transact(window, cx, |this, window, cx| {
22839 let selections = this
22840 .selections
22841 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22842 let edits = selections
22843 .iter()
22844 .map(|selection| (selection.end..selection.end, pending.clone()));
22845 this.edit(edits, cx);
22846 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22847 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22848 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22849 }));
22850 });
22851 if let Some(existing_ranges) = existing_pending {
22852 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22853 this.edit(edits, cx);
22854 }
22855 });
22856
22857 let snapshot = self.snapshot(window, cx);
22858 let ranges = self
22859 .selections
22860 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22861 .into_iter()
22862 .map(|selection| {
22863 snapshot.buffer_snapshot().anchor_after(selection.end)
22864 ..snapshot
22865 .buffer_snapshot()
22866 .anchor_before(selection.end + pending.len())
22867 })
22868 .collect();
22869
22870 if pending.is_empty() {
22871 self.clear_highlights::<PendingInput>(cx);
22872 } else {
22873 self.highlight_text::<PendingInput>(
22874 ranges,
22875 HighlightStyle {
22876 underline: Some(UnderlineStyle {
22877 thickness: px(1.),
22878 color: None,
22879 wavy: false,
22880 }),
22881 ..Default::default()
22882 },
22883 cx,
22884 );
22885 }
22886
22887 self.ime_transaction = self.ime_transaction.or(transaction);
22888 if let Some(transaction) = self.ime_transaction {
22889 self.buffer.update(cx, |buffer, cx| {
22890 buffer.group_until_transaction(transaction, cx);
22891 });
22892 }
22893
22894 if self.text_highlights::<PendingInput>(cx).is_none() {
22895 self.ime_transaction.take();
22896 }
22897 }
22898
22899 pub fn register_action_renderer(
22900 &mut self,
22901 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22902 ) -> Subscription {
22903 let id = self.next_editor_action_id.post_inc();
22904 self.editor_actions
22905 .borrow_mut()
22906 .insert(id, Box::new(listener));
22907
22908 let editor_actions = self.editor_actions.clone();
22909 Subscription::new(move || {
22910 editor_actions.borrow_mut().remove(&id);
22911 })
22912 }
22913
22914 pub fn register_action<A: Action>(
22915 &mut self,
22916 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22917 ) -> Subscription {
22918 let id = self.next_editor_action_id.post_inc();
22919 let listener = Arc::new(listener);
22920 self.editor_actions.borrow_mut().insert(
22921 id,
22922 Box::new(move |_, window, _| {
22923 let listener = listener.clone();
22924 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22925 let action = action.downcast_ref().unwrap();
22926 if phase == DispatchPhase::Bubble {
22927 listener(action, window, cx)
22928 }
22929 })
22930 }),
22931 );
22932
22933 let editor_actions = self.editor_actions.clone();
22934 Subscription::new(move || {
22935 editor_actions.borrow_mut().remove(&id);
22936 })
22937 }
22938
22939 pub fn file_header_size(&self) -> u32 {
22940 FILE_HEADER_HEIGHT
22941 }
22942
22943 pub fn restore(
22944 &mut self,
22945 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22946 window: &mut Window,
22947 cx: &mut Context<Self>,
22948 ) {
22949 self.buffer().update(cx, |multi_buffer, cx| {
22950 for (buffer_id, changes) in revert_changes {
22951 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22952 buffer.update(cx, |buffer, cx| {
22953 buffer.edit(
22954 changes
22955 .into_iter()
22956 .map(|(range, text)| (range, text.to_string())),
22957 None,
22958 cx,
22959 );
22960 });
22961 }
22962 }
22963 });
22964 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22965 selections.refresh()
22966 });
22967 }
22968
22969 pub fn to_pixel_point(
22970 &mut self,
22971 source: multi_buffer::Anchor,
22972 editor_snapshot: &EditorSnapshot,
22973 window: &mut Window,
22974 cx: &App,
22975 ) -> Option<gpui::Point<Pixels>> {
22976 let source_point = source.to_display_point(editor_snapshot);
22977 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
22978 }
22979
22980 pub fn display_to_pixel_point(
22981 &mut self,
22982 source: DisplayPoint,
22983 editor_snapshot: &EditorSnapshot,
22984 window: &mut Window,
22985 cx: &App,
22986 ) -> Option<gpui::Point<Pixels>> {
22987 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
22988 let text_layout_details = self.text_layout_details(window);
22989 let scroll_top = text_layout_details
22990 .scroll_anchor
22991 .scroll_position(editor_snapshot)
22992 .y;
22993
22994 if source.row().as_f64() < scroll_top.floor() {
22995 return None;
22996 }
22997 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22998 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22999 Some(gpui::Point::new(source_x, source_y))
23000 }
23001
23002 pub fn has_visible_completions_menu(&self) -> bool {
23003 !self.edit_prediction_preview_is_active()
23004 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23005 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23006 })
23007 }
23008
23009 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23010 if self.mode.is_minimap() {
23011 return;
23012 }
23013 self.addons
23014 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23015 }
23016
23017 pub fn unregister_addon<T: Addon>(&mut self) {
23018 self.addons.remove(&std::any::TypeId::of::<T>());
23019 }
23020
23021 pub fn addon<T: Addon>(&self) -> Option<&T> {
23022 let type_id = std::any::TypeId::of::<T>();
23023 self.addons
23024 .get(&type_id)
23025 .and_then(|item| item.to_any().downcast_ref::<T>())
23026 }
23027
23028 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23029 let type_id = std::any::TypeId::of::<T>();
23030 self.addons
23031 .get_mut(&type_id)
23032 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23033 }
23034
23035 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23036 let text_layout_details = self.text_layout_details(window);
23037 let style = &text_layout_details.editor_style;
23038 let font_id = window.text_system().resolve_font(&style.text.font());
23039 let font_size = style.text.font_size.to_pixels(window.rem_size());
23040 let line_height = style.text.line_height_in_pixels(window.rem_size());
23041 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23042 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23043
23044 CharacterDimensions {
23045 em_width,
23046 em_advance,
23047 line_height,
23048 }
23049 }
23050
23051 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23052 self.load_diff_task.clone()
23053 }
23054
23055 fn read_metadata_from_db(
23056 &mut self,
23057 item_id: u64,
23058 workspace_id: WorkspaceId,
23059 window: &mut Window,
23060 cx: &mut Context<Editor>,
23061 ) {
23062 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23063 && !self.mode.is_minimap()
23064 && WorkspaceSettings::get(None, cx).restore_on_startup
23065 != RestoreOnStartupBehavior::EmptyTab
23066 {
23067 let buffer_snapshot = OnceCell::new();
23068
23069 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23070 && !folds.is_empty()
23071 {
23072 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23073 self.fold_ranges(
23074 folds
23075 .into_iter()
23076 .map(|(start, end)| {
23077 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23078 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23079 })
23080 .collect(),
23081 false,
23082 window,
23083 cx,
23084 );
23085 }
23086
23087 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23088 && !selections.is_empty()
23089 {
23090 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23091 // skip adding the initial selection to selection history
23092 self.selection_history.mode = SelectionHistoryMode::Skipping;
23093 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23094 s.select_ranges(selections.into_iter().map(|(start, end)| {
23095 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23096 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23097 }));
23098 });
23099 self.selection_history.mode = SelectionHistoryMode::Normal;
23100 };
23101 }
23102
23103 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23104 }
23105
23106 fn update_lsp_data(
23107 &mut self,
23108 for_buffer: Option<BufferId>,
23109 window: &mut Window,
23110 cx: &mut Context<'_, Self>,
23111 ) {
23112 self.pull_diagnostics(for_buffer, window, cx);
23113 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23114 }
23115
23116 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23117 if self.ignore_lsp_data() {
23118 return;
23119 }
23120 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23121 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23122 }
23123 }
23124
23125 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23126 if self.ignore_lsp_data() {
23127 return;
23128 }
23129
23130 if !self.registered_buffers.contains_key(&buffer_id)
23131 && let Some(project) = self.project.as_ref()
23132 {
23133 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23134 project.update(cx, |project, cx| {
23135 self.registered_buffers.insert(
23136 buffer_id,
23137 project.register_buffer_with_language_servers(&buffer, cx),
23138 );
23139 });
23140 } else {
23141 self.registered_buffers.remove(&buffer_id);
23142 }
23143 }
23144 }
23145
23146 fn ignore_lsp_data(&self) -> bool {
23147 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23148 // skip any LSP updates for it.
23149 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23150 }
23151
23152 fn create_style(&self, cx: &App) -> EditorStyle {
23153 let settings = ThemeSettings::get_global(cx);
23154
23155 let mut text_style = match self.mode {
23156 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23157 color: cx.theme().colors().editor_foreground,
23158 font_family: settings.ui_font.family.clone(),
23159 font_features: settings.ui_font.features.clone(),
23160 font_fallbacks: settings.ui_font.fallbacks.clone(),
23161 font_size: rems(0.875).into(),
23162 font_weight: settings.ui_font.weight,
23163 line_height: relative(settings.buffer_line_height.value()),
23164 ..Default::default()
23165 },
23166 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23167 color: cx.theme().colors().editor_foreground,
23168 font_family: settings.buffer_font.family.clone(),
23169 font_features: settings.buffer_font.features.clone(),
23170 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23171 font_size: settings.buffer_font_size(cx).into(),
23172 font_weight: settings.buffer_font.weight,
23173 line_height: relative(settings.buffer_line_height.value()),
23174 ..Default::default()
23175 },
23176 };
23177 if let Some(text_style_refinement) = &self.text_style_refinement {
23178 text_style.refine(text_style_refinement)
23179 }
23180
23181 let background = match self.mode {
23182 EditorMode::SingleLine => cx.theme().system().transparent,
23183 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23184 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23185 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23186 };
23187
23188 EditorStyle {
23189 background,
23190 border: cx.theme().colors().border,
23191 local_player: cx.theme().players().local(),
23192 text: text_style,
23193 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23194 syntax: cx.theme().syntax().clone(),
23195 status: cx.theme().status().clone(),
23196 inlay_hints_style: make_inlay_hints_style(cx),
23197 edit_prediction_styles: make_suggestion_styles(cx),
23198 unnecessary_code_fade: settings.unnecessary_code_fade,
23199 show_underlines: self.diagnostics_enabled(),
23200 }
23201 }
23202 fn breadcrumbs_inner(&self, variant: &Theme, cx: &App) -> Option<Vec<BreadcrumbText>> {
23203 let cursor = self.selections.newest_anchor().head();
23204 let multibuffer = self.buffer().read(cx);
23205 let is_singleton = multibuffer.is_singleton();
23206 let (buffer_id, symbols) = multibuffer
23207 .read(cx)
23208 .symbols_containing(cursor, Some(variant.syntax()))?;
23209 let buffer = multibuffer.buffer(buffer_id)?;
23210
23211 let buffer = buffer.read(cx);
23212 let settings = ThemeSettings::get_global(cx);
23213 // In a multi-buffer layout, we don't want to include the filename in the breadcrumbs
23214 let mut breadcrumbs = if is_singleton {
23215 let text = self.breadcrumb_header.clone().unwrap_or_else(|| {
23216 buffer
23217 .snapshot()
23218 .resolve_file_path(
23219 self.project
23220 .as_ref()
23221 .map(|project| project.read(cx).visible_worktrees(cx).count() > 1)
23222 .unwrap_or_default(),
23223 cx,
23224 )
23225 .unwrap_or_else(|| {
23226 if multibuffer.is_singleton() {
23227 multibuffer.title(cx).to_string()
23228 } else {
23229 "untitled".to_string()
23230 }
23231 })
23232 });
23233 vec![BreadcrumbText {
23234 text,
23235 highlights: None,
23236 font: Some(settings.buffer_font.clone()),
23237 }]
23238 } else {
23239 vec![]
23240 };
23241
23242 breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
23243 text: symbol.text,
23244 highlights: Some(symbol.highlight_ranges),
23245 font: Some(settings.buffer_font.clone()),
23246 }));
23247 Some(breadcrumbs)
23248 }
23249}
23250
23251fn edit_for_markdown_paste<'a>(
23252 buffer: &MultiBufferSnapshot,
23253 range: Range<MultiBufferOffset>,
23254 to_insert: &'a str,
23255 url: Option<url::Url>,
23256) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23257 if url.is_none() {
23258 return (range, Cow::Borrowed(to_insert));
23259 };
23260
23261 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23262
23263 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23264 Cow::Borrowed(to_insert)
23265 } else {
23266 Cow::Owned(format!("[{old_text}]({to_insert})"))
23267 };
23268 (range, new_text)
23269}
23270
23271fn process_completion_for_edit(
23272 completion: &Completion,
23273 intent: CompletionIntent,
23274 buffer: &Entity<Buffer>,
23275 cursor_position: &text::Anchor,
23276 cx: &mut Context<Editor>,
23277) -> CompletionEdit {
23278 let buffer = buffer.read(cx);
23279 let buffer_snapshot = buffer.snapshot();
23280 let (snippet, new_text) = if completion.is_snippet() {
23281 let mut snippet_source = completion.new_text.clone();
23282 // Workaround for typescript language server issues so that methods don't expand within
23283 // strings and functions with type expressions. The previous point is used because the query
23284 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23285 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23286 let previous_point = if previous_point.column > 0 {
23287 cursor_position.to_previous_offset(&buffer_snapshot)
23288 } else {
23289 cursor_position.to_offset(&buffer_snapshot)
23290 };
23291 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23292 && scope.prefers_label_for_snippet_in_completion()
23293 && let Some(label) = completion.label()
23294 && matches!(
23295 completion.kind(),
23296 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23297 )
23298 {
23299 snippet_source = label;
23300 }
23301 match Snippet::parse(&snippet_source).log_err() {
23302 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23303 None => (None, completion.new_text.clone()),
23304 }
23305 } else {
23306 (None, completion.new_text.clone())
23307 };
23308
23309 let mut range_to_replace = {
23310 let replace_range = &completion.replace_range;
23311 if let CompletionSource::Lsp {
23312 insert_range: Some(insert_range),
23313 ..
23314 } = &completion.source
23315 {
23316 debug_assert_eq!(
23317 insert_range.start, replace_range.start,
23318 "insert_range and replace_range should start at the same position"
23319 );
23320 debug_assert!(
23321 insert_range
23322 .start
23323 .cmp(cursor_position, &buffer_snapshot)
23324 .is_le(),
23325 "insert_range should start before or at cursor position"
23326 );
23327 debug_assert!(
23328 replace_range
23329 .start
23330 .cmp(cursor_position, &buffer_snapshot)
23331 .is_le(),
23332 "replace_range should start before or at cursor position"
23333 );
23334
23335 let should_replace = match intent {
23336 CompletionIntent::CompleteWithInsert => false,
23337 CompletionIntent::CompleteWithReplace => true,
23338 CompletionIntent::Complete | CompletionIntent::Compose => {
23339 let insert_mode =
23340 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23341 .completions
23342 .lsp_insert_mode;
23343 match insert_mode {
23344 LspInsertMode::Insert => false,
23345 LspInsertMode::Replace => true,
23346 LspInsertMode::ReplaceSubsequence => {
23347 let mut text_to_replace = buffer.chars_for_range(
23348 buffer.anchor_before(replace_range.start)
23349 ..buffer.anchor_after(replace_range.end),
23350 );
23351 let mut current_needle = text_to_replace.next();
23352 for haystack_ch in completion.label.text.chars() {
23353 if let Some(needle_ch) = current_needle
23354 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23355 {
23356 current_needle = text_to_replace.next();
23357 }
23358 }
23359 current_needle.is_none()
23360 }
23361 LspInsertMode::ReplaceSuffix => {
23362 if replace_range
23363 .end
23364 .cmp(cursor_position, &buffer_snapshot)
23365 .is_gt()
23366 {
23367 let range_after_cursor = *cursor_position..replace_range.end;
23368 let text_after_cursor = buffer
23369 .text_for_range(
23370 buffer.anchor_before(range_after_cursor.start)
23371 ..buffer.anchor_after(range_after_cursor.end),
23372 )
23373 .collect::<String>()
23374 .to_ascii_lowercase();
23375 completion
23376 .label
23377 .text
23378 .to_ascii_lowercase()
23379 .ends_with(&text_after_cursor)
23380 } else {
23381 true
23382 }
23383 }
23384 }
23385 }
23386 };
23387
23388 if should_replace {
23389 replace_range.clone()
23390 } else {
23391 insert_range.clone()
23392 }
23393 } else {
23394 replace_range.clone()
23395 }
23396 };
23397
23398 if range_to_replace
23399 .end
23400 .cmp(cursor_position, &buffer_snapshot)
23401 .is_lt()
23402 {
23403 range_to_replace.end = *cursor_position;
23404 }
23405
23406 let replace_range = range_to_replace.to_offset(buffer);
23407 CompletionEdit {
23408 new_text,
23409 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23410 snippet,
23411 }
23412}
23413
23414struct CompletionEdit {
23415 new_text: String,
23416 replace_range: Range<BufferOffset>,
23417 snippet: Option<Snippet>,
23418}
23419
23420fn comment_delimiter_for_newline(
23421 start_point: &Point,
23422 buffer: &MultiBufferSnapshot,
23423 language: &LanguageScope,
23424) -> Option<Arc<str>> {
23425 let delimiters = language.line_comment_prefixes();
23426 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
23427 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23428
23429 let num_of_whitespaces = snapshot
23430 .chars_for_range(range.clone())
23431 .take_while(|c| c.is_whitespace())
23432 .count();
23433 let comment_candidate = snapshot
23434 .chars_for_range(range.clone())
23435 .skip(num_of_whitespaces)
23436 .take(max_len_of_delimiter)
23437 .collect::<String>();
23438 let (delimiter, trimmed_len) = delimiters
23439 .iter()
23440 .filter_map(|delimiter| {
23441 let prefix = delimiter.trim_end();
23442 if comment_candidate.starts_with(prefix) {
23443 Some((delimiter, prefix.len()))
23444 } else {
23445 None
23446 }
23447 })
23448 .max_by_key(|(_, len)| *len)?;
23449
23450 if let Some(BlockCommentConfig {
23451 start: block_start, ..
23452 }) = language.block_comment()
23453 {
23454 let block_start_trimmed = block_start.trim_end();
23455 if block_start_trimmed.starts_with(delimiter.trim_end()) {
23456 let line_content = snapshot
23457 .chars_for_range(range)
23458 .skip(num_of_whitespaces)
23459 .take(block_start_trimmed.len())
23460 .collect::<String>();
23461
23462 if line_content.starts_with(block_start_trimmed) {
23463 return None;
23464 }
23465 }
23466 }
23467
23468 let cursor_is_placed_after_comment_marker =
23469 num_of_whitespaces + trimmed_len <= start_point.column as usize;
23470 if cursor_is_placed_after_comment_marker {
23471 Some(delimiter.clone())
23472 } else {
23473 None
23474 }
23475}
23476
23477fn documentation_delimiter_for_newline(
23478 start_point: &Point,
23479 buffer: &MultiBufferSnapshot,
23480 language: &LanguageScope,
23481 newline_config: &mut NewlineConfig,
23482) -> Option<Arc<str>> {
23483 let BlockCommentConfig {
23484 start: start_tag,
23485 end: end_tag,
23486 prefix: delimiter,
23487 tab_size: len,
23488 } = language.documentation_comment()?;
23489 let is_within_block_comment = buffer
23490 .language_scope_at(*start_point)
23491 .is_some_and(|scope| scope.override_name() == Some("comment"));
23492 if !is_within_block_comment {
23493 return None;
23494 }
23495
23496 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23497
23498 let num_of_whitespaces = snapshot
23499 .chars_for_range(range.clone())
23500 .take_while(|c| c.is_whitespace())
23501 .count();
23502
23503 // 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.
23504 let column = start_point.column;
23505 let cursor_is_after_start_tag = {
23506 let start_tag_len = start_tag.len();
23507 let start_tag_line = snapshot
23508 .chars_for_range(range.clone())
23509 .skip(num_of_whitespaces)
23510 .take(start_tag_len)
23511 .collect::<String>();
23512 if start_tag_line.starts_with(start_tag.as_ref()) {
23513 num_of_whitespaces + start_tag_len <= column as usize
23514 } else {
23515 false
23516 }
23517 };
23518
23519 let cursor_is_after_delimiter = {
23520 let delimiter_trim = delimiter.trim_end();
23521 let delimiter_line = snapshot
23522 .chars_for_range(range.clone())
23523 .skip(num_of_whitespaces)
23524 .take(delimiter_trim.len())
23525 .collect::<String>();
23526 if delimiter_line.starts_with(delimiter_trim) {
23527 num_of_whitespaces + delimiter_trim.len() <= column as usize
23528 } else {
23529 false
23530 }
23531 };
23532
23533 let mut needs_extra_line = false;
23534 let mut extra_line_additional_indent = IndentSize::spaces(0);
23535
23536 let cursor_is_before_end_tag_if_exists = {
23537 let mut char_position = 0u32;
23538 let mut end_tag_offset = None;
23539
23540 'outer: for chunk in snapshot.text_for_range(range) {
23541 if let Some(byte_pos) = chunk.find(&**end_tag) {
23542 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
23543 end_tag_offset = Some(char_position + chars_before_match);
23544 break 'outer;
23545 }
23546 char_position += chunk.chars().count() as u32;
23547 }
23548
23549 if let Some(end_tag_offset) = end_tag_offset {
23550 let cursor_is_before_end_tag = column <= end_tag_offset;
23551 if cursor_is_after_start_tag {
23552 if cursor_is_before_end_tag {
23553 needs_extra_line = true;
23554 }
23555 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
23556 if cursor_is_at_start_of_end_tag {
23557 extra_line_additional_indent.len = *len;
23558 }
23559 }
23560 cursor_is_before_end_tag
23561 } else {
23562 true
23563 }
23564 };
23565
23566 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
23567 && cursor_is_before_end_tag_if_exists
23568 {
23569 let additional_indent = if cursor_is_after_start_tag {
23570 IndentSize::spaces(*len)
23571 } else {
23572 IndentSize::spaces(0)
23573 };
23574
23575 *newline_config = NewlineConfig::Newline {
23576 additional_indent,
23577 extra_line_additional_indent: if needs_extra_line {
23578 Some(extra_line_additional_indent)
23579 } else {
23580 None
23581 },
23582 prevent_auto_indent: true,
23583 };
23584 Some(delimiter.clone())
23585 } else {
23586 None
23587 }
23588}
23589
23590const ORDERED_LIST_MAX_MARKER_LEN: usize = 16;
23591
23592fn list_delimiter_for_newline(
23593 start_point: &Point,
23594 buffer: &MultiBufferSnapshot,
23595 language: &LanguageScope,
23596 newline_config: &mut NewlineConfig,
23597) -> Option<Arc<str>> {
23598 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23599
23600 let num_of_whitespaces = snapshot
23601 .chars_for_range(range.clone())
23602 .take_while(|c| c.is_whitespace())
23603 .count();
23604
23605 let task_list_entries: Vec<_> = language
23606 .task_list()
23607 .into_iter()
23608 .flat_map(|config| {
23609 config
23610 .prefixes
23611 .iter()
23612 .map(|prefix| (prefix.as_ref(), config.continuation.as_ref()))
23613 })
23614 .collect();
23615 let unordered_list_entries: Vec<_> = language
23616 .unordered_list()
23617 .iter()
23618 .map(|marker| (marker.as_ref(), marker.as_ref()))
23619 .collect();
23620
23621 let all_entries: Vec<_> = task_list_entries
23622 .into_iter()
23623 .chain(unordered_list_entries)
23624 .collect();
23625
23626 if let Some(max_prefix_len) = all_entries.iter().map(|(p, _)| p.len()).max() {
23627 let candidate: String = snapshot
23628 .chars_for_range(range.clone())
23629 .skip(num_of_whitespaces)
23630 .take(max_prefix_len)
23631 .collect();
23632
23633 if let Some((prefix, continuation)) = all_entries
23634 .iter()
23635 .filter(|(prefix, _)| candidate.starts_with(*prefix))
23636 .max_by_key(|(prefix, _)| prefix.len())
23637 {
23638 let end_of_prefix = num_of_whitespaces + prefix.len();
23639 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
23640 let has_content_after_marker = snapshot
23641 .chars_for_range(range)
23642 .skip(end_of_prefix)
23643 .any(|c| !c.is_whitespace());
23644
23645 if has_content_after_marker && cursor_is_after_prefix {
23646 return Some((*continuation).into());
23647 }
23648
23649 if start_point.column as usize == end_of_prefix {
23650 if num_of_whitespaces == 0 {
23651 *newline_config = NewlineConfig::ClearCurrentLine;
23652 } else {
23653 *newline_config = NewlineConfig::UnindentCurrentLine {
23654 continuation: (*continuation).into(),
23655 };
23656 }
23657 }
23658
23659 return None;
23660 }
23661 }
23662
23663 let candidate: String = snapshot
23664 .chars_for_range(range.clone())
23665 .skip(num_of_whitespaces)
23666 .take(ORDERED_LIST_MAX_MARKER_LEN)
23667 .collect();
23668
23669 for ordered_config in language.ordered_list() {
23670 let regex = match Regex::new(&ordered_config.pattern) {
23671 Ok(r) => r,
23672 Err(_) => continue,
23673 };
23674
23675 if let Some(captures) = regex.captures(&candidate) {
23676 let full_match = captures.get(0)?;
23677 let marker_len = full_match.len();
23678 let end_of_prefix = num_of_whitespaces + marker_len;
23679 let cursor_is_after_prefix = end_of_prefix <= start_point.column as usize;
23680
23681 let has_content_after_marker = snapshot
23682 .chars_for_range(range)
23683 .skip(end_of_prefix)
23684 .any(|c| !c.is_whitespace());
23685
23686 if has_content_after_marker && cursor_is_after_prefix {
23687 let number: u32 = captures.get(1)?.as_str().parse().ok()?;
23688 let continuation = ordered_config
23689 .format
23690 .replace("{1}", &(number + 1).to_string());
23691 return Some(continuation.into());
23692 }
23693
23694 if start_point.column as usize == end_of_prefix {
23695 let continuation = ordered_config.format.replace("{1}", "1");
23696 if num_of_whitespaces == 0 {
23697 *newline_config = NewlineConfig::ClearCurrentLine;
23698 } else {
23699 *newline_config = NewlineConfig::UnindentCurrentLine {
23700 continuation: continuation.into(),
23701 };
23702 }
23703 }
23704
23705 return None;
23706 }
23707 }
23708
23709 None
23710}
23711
23712fn is_list_prefix_row(
23713 row: MultiBufferRow,
23714 buffer: &MultiBufferSnapshot,
23715 language: &LanguageScope,
23716) -> bool {
23717 let Some((snapshot, range)) = buffer.buffer_line_for_row(row) else {
23718 return false;
23719 };
23720
23721 let num_of_whitespaces = snapshot
23722 .chars_for_range(range.clone())
23723 .take_while(|c| c.is_whitespace())
23724 .count();
23725
23726 let task_list_prefixes: Vec<_> = language
23727 .task_list()
23728 .into_iter()
23729 .flat_map(|config| {
23730 config
23731 .prefixes
23732 .iter()
23733 .map(|p| p.as_ref())
23734 .collect::<Vec<_>>()
23735 })
23736 .collect();
23737 let unordered_list_markers: Vec<_> = language
23738 .unordered_list()
23739 .iter()
23740 .map(|marker| marker.as_ref())
23741 .collect();
23742 let all_prefixes: Vec<_> = task_list_prefixes
23743 .into_iter()
23744 .chain(unordered_list_markers)
23745 .collect();
23746 if let Some(max_prefix_len) = all_prefixes.iter().map(|p| p.len()).max() {
23747 let candidate: String = snapshot
23748 .chars_for_range(range.clone())
23749 .skip(num_of_whitespaces)
23750 .take(max_prefix_len)
23751 .collect();
23752 if all_prefixes
23753 .iter()
23754 .any(|prefix| candidate.starts_with(*prefix))
23755 {
23756 return true;
23757 }
23758 }
23759
23760 let ordered_list_candidate: String = snapshot
23761 .chars_for_range(range)
23762 .skip(num_of_whitespaces)
23763 .take(ORDERED_LIST_MAX_MARKER_LEN)
23764 .collect();
23765 for ordered_config in language.ordered_list() {
23766 let regex = match Regex::new(&ordered_config.pattern) {
23767 Ok(r) => r,
23768 Err(_) => continue,
23769 };
23770 if let Some(captures) = regex.captures(&ordered_list_candidate) {
23771 return captures.get(0).is_some();
23772 }
23773 }
23774
23775 false
23776}
23777
23778#[derive(Debug)]
23779enum NewlineConfig {
23780 /// Insert newline with optional additional indent and optional extra blank line
23781 Newline {
23782 additional_indent: IndentSize,
23783 extra_line_additional_indent: Option<IndentSize>,
23784 prevent_auto_indent: bool,
23785 },
23786 /// Clear the current line
23787 ClearCurrentLine,
23788 /// Unindent the current line and add continuation
23789 UnindentCurrentLine { continuation: Arc<str> },
23790}
23791
23792impl NewlineConfig {
23793 fn has_extra_line(&self) -> bool {
23794 matches!(
23795 self,
23796 Self::Newline {
23797 extra_line_additional_indent: Some(_),
23798 ..
23799 }
23800 )
23801 }
23802
23803 fn insert_extra_newline_brackets(
23804 buffer: &MultiBufferSnapshot,
23805 range: Range<MultiBufferOffset>,
23806 language: &language::LanguageScope,
23807 ) -> bool {
23808 let leading_whitespace_len = buffer
23809 .reversed_chars_at(range.start)
23810 .take_while(|c| c.is_whitespace() && *c != '\n')
23811 .map(|c| c.len_utf8())
23812 .sum::<usize>();
23813 let trailing_whitespace_len = buffer
23814 .chars_at(range.end)
23815 .take_while(|c| c.is_whitespace() && *c != '\n')
23816 .map(|c| c.len_utf8())
23817 .sum::<usize>();
23818 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23819
23820 language.brackets().any(|(pair, enabled)| {
23821 let pair_start = pair.start.trim_end();
23822 let pair_end = pair.end.trim_start();
23823
23824 enabled
23825 && pair.newline
23826 && buffer.contains_str_at(range.end, pair_end)
23827 && buffer.contains_str_at(
23828 range.start.saturating_sub_usize(pair_start.len()),
23829 pair_start,
23830 )
23831 })
23832 }
23833
23834 fn insert_extra_newline_tree_sitter(
23835 buffer: &MultiBufferSnapshot,
23836 range: Range<MultiBufferOffset>,
23837 ) -> bool {
23838 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23839 [(buffer, range, _)] => (*buffer, range.clone()),
23840 _ => return false,
23841 };
23842 let pair = {
23843 let mut result: Option<BracketMatch<usize>> = None;
23844
23845 for pair in buffer
23846 .all_bracket_ranges(range.start.0..range.end.0)
23847 .filter(move |pair| {
23848 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23849 })
23850 {
23851 let len = pair.close_range.end - pair.open_range.start;
23852
23853 if let Some(existing) = &result {
23854 let existing_len = existing.close_range.end - existing.open_range.start;
23855 if len > existing_len {
23856 continue;
23857 }
23858 }
23859
23860 result = Some(pair);
23861 }
23862
23863 result
23864 };
23865 let Some(pair) = pair else {
23866 return false;
23867 };
23868 pair.newline_only
23869 && buffer
23870 .chars_for_range(pair.open_range.end..range.start.0)
23871 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23872 .all(|c| c.is_whitespace() && c != '\n')
23873 }
23874}
23875
23876fn update_uncommitted_diff_for_buffer(
23877 editor: Entity<Editor>,
23878 project: &Entity<Project>,
23879 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23880 buffer: Entity<MultiBuffer>,
23881 cx: &mut App,
23882) -> Task<()> {
23883 let mut tasks = Vec::new();
23884 project.update(cx, |project, cx| {
23885 for buffer in buffers {
23886 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23887 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23888 }
23889 }
23890 });
23891 cx.spawn(async move |cx| {
23892 let diffs = future::join_all(tasks).await;
23893 if editor
23894 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23895 .unwrap_or(false)
23896 {
23897 return;
23898 }
23899
23900 buffer
23901 .update(cx, |buffer, cx| {
23902 for diff in diffs.into_iter().flatten() {
23903 buffer.add_diff(diff, cx);
23904 }
23905 })
23906 .ok();
23907 })
23908}
23909
23910fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23911 let tab_size = tab_size.get() as usize;
23912 let mut width = offset;
23913
23914 for ch in text.chars() {
23915 width += if ch == '\t' {
23916 tab_size - (width % tab_size)
23917 } else {
23918 1
23919 };
23920 }
23921
23922 width - offset
23923}
23924
23925#[cfg(test)]
23926mod tests {
23927 use super::*;
23928
23929 #[test]
23930 fn test_string_size_with_expanded_tabs() {
23931 let nz = |val| NonZeroU32::new(val).unwrap();
23932 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23933 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23934 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23935 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23936 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23937 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23938 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23939 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23940 }
23941}
23942
23943/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23944struct WordBreakingTokenizer<'a> {
23945 input: &'a str,
23946}
23947
23948impl<'a> WordBreakingTokenizer<'a> {
23949 fn new(input: &'a str) -> Self {
23950 Self { input }
23951 }
23952}
23953
23954fn is_char_ideographic(ch: char) -> bool {
23955 use unicode_script::Script::*;
23956 use unicode_script::UnicodeScript;
23957 matches!(ch.script(), Han | Tangut | Yi)
23958}
23959
23960fn is_grapheme_ideographic(text: &str) -> bool {
23961 text.chars().any(is_char_ideographic)
23962}
23963
23964fn is_grapheme_whitespace(text: &str) -> bool {
23965 text.chars().any(|x| x.is_whitespace())
23966}
23967
23968fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23969 text.chars()
23970 .next()
23971 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23972}
23973
23974#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23975enum WordBreakToken<'a> {
23976 Word { token: &'a str, grapheme_len: usize },
23977 InlineWhitespace { token: &'a str, grapheme_len: usize },
23978 Newline,
23979}
23980
23981impl<'a> Iterator for WordBreakingTokenizer<'a> {
23982 /// Yields a span, the count of graphemes in the token, and whether it was
23983 /// whitespace. Note that it also breaks at word boundaries.
23984 type Item = WordBreakToken<'a>;
23985
23986 fn next(&mut self) -> Option<Self::Item> {
23987 use unicode_segmentation::UnicodeSegmentation;
23988 if self.input.is_empty() {
23989 return None;
23990 }
23991
23992 let mut iter = self.input.graphemes(true).peekable();
23993 let mut offset = 0;
23994 let mut grapheme_len = 0;
23995 if let Some(first_grapheme) = iter.next() {
23996 let is_newline = first_grapheme == "\n";
23997 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23998 offset += first_grapheme.len();
23999 grapheme_len += 1;
24000 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
24001 if let Some(grapheme) = iter.peek().copied()
24002 && should_stay_with_preceding_ideograph(grapheme)
24003 {
24004 offset += grapheme.len();
24005 grapheme_len += 1;
24006 }
24007 } else {
24008 let mut words = self.input[offset..].split_word_bound_indices().peekable();
24009 let mut next_word_bound = words.peek().copied();
24010 if next_word_bound.is_some_and(|(i, _)| i == 0) {
24011 next_word_bound = words.next();
24012 }
24013 while let Some(grapheme) = iter.peek().copied() {
24014 if next_word_bound.is_some_and(|(i, _)| i == offset) {
24015 break;
24016 };
24017 if is_grapheme_whitespace(grapheme) != is_whitespace
24018 || (grapheme == "\n") != is_newline
24019 {
24020 break;
24021 };
24022 offset += grapheme.len();
24023 grapheme_len += 1;
24024 iter.next();
24025 }
24026 }
24027 let token = &self.input[..offset];
24028 self.input = &self.input[offset..];
24029 if token == "\n" {
24030 Some(WordBreakToken::Newline)
24031 } else if is_whitespace {
24032 Some(WordBreakToken::InlineWhitespace {
24033 token,
24034 grapheme_len,
24035 })
24036 } else {
24037 Some(WordBreakToken::Word {
24038 token,
24039 grapheme_len,
24040 })
24041 }
24042 } else {
24043 None
24044 }
24045 }
24046}
24047
24048#[test]
24049fn test_word_breaking_tokenizer() {
24050 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
24051 ("", &[]),
24052 (" ", &[whitespace(" ", 2)]),
24053 ("Ʒ", &[word("Ʒ", 1)]),
24054 ("Ǽ", &[word("Ǽ", 1)]),
24055 ("⋑", &[word("⋑", 1)]),
24056 ("⋑⋑", &[word("⋑⋑", 2)]),
24057 (
24058 "原理,进而",
24059 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
24060 ),
24061 (
24062 "hello world",
24063 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
24064 ),
24065 (
24066 "hello, world",
24067 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
24068 ),
24069 (
24070 " hello world",
24071 &[
24072 whitespace(" ", 2),
24073 word("hello", 5),
24074 whitespace(" ", 1),
24075 word("world", 5),
24076 ],
24077 ),
24078 (
24079 "这是什么 \n 钢笔",
24080 &[
24081 word("这", 1),
24082 word("是", 1),
24083 word("什", 1),
24084 word("么", 1),
24085 whitespace(" ", 1),
24086 newline(),
24087 whitespace(" ", 1),
24088 word("钢", 1),
24089 word("笔", 1),
24090 ],
24091 ),
24092 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
24093 ];
24094
24095 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24096 WordBreakToken::Word {
24097 token,
24098 grapheme_len,
24099 }
24100 }
24101
24102 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
24103 WordBreakToken::InlineWhitespace {
24104 token,
24105 grapheme_len,
24106 }
24107 }
24108
24109 fn newline() -> WordBreakToken<'static> {
24110 WordBreakToken::Newline
24111 }
24112
24113 for (input, result) in tests {
24114 assert_eq!(
24115 WordBreakingTokenizer::new(input)
24116 .collect::<Vec<_>>()
24117 .as_slice(),
24118 *result,
24119 );
24120 }
24121}
24122
24123fn wrap_with_prefix(
24124 first_line_prefix: String,
24125 subsequent_lines_prefix: String,
24126 unwrapped_text: String,
24127 wrap_column: usize,
24128 tab_size: NonZeroU32,
24129 preserve_existing_whitespace: bool,
24130) -> String {
24131 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
24132 let subsequent_lines_prefix_len =
24133 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
24134 let mut wrapped_text = String::new();
24135 let mut current_line = first_line_prefix;
24136 let mut is_first_line = true;
24137
24138 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
24139 let mut current_line_len = first_line_prefix_len;
24140 let mut in_whitespace = false;
24141 for token in tokenizer {
24142 let have_preceding_whitespace = in_whitespace;
24143 match token {
24144 WordBreakToken::Word {
24145 token,
24146 grapheme_len,
24147 } => {
24148 in_whitespace = false;
24149 let current_prefix_len = if is_first_line {
24150 first_line_prefix_len
24151 } else {
24152 subsequent_lines_prefix_len
24153 };
24154 if current_line_len + grapheme_len > wrap_column
24155 && current_line_len != current_prefix_len
24156 {
24157 wrapped_text.push_str(current_line.trim_end());
24158 wrapped_text.push('\n');
24159 is_first_line = false;
24160 current_line = subsequent_lines_prefix.clone();
24161 current_line_len = subsequent_lines_prefix_len;
24162 }
24163 current_line.push_str(token);
24164 current_line_len += grapheme_len;
24165 }
24166 WordBreakToken::InlineWhitespace {
24167 mut token,
24168 mut grapheme_len,
24169 } => {
24170 in_whitespace = true;
24171 if have_preceding_whitespace && !preserve_existing_whitespace {
24172 continue;
24173 }
24174 if !preserve_existing_whitespace {
24175 // Keep a single whitespace grapheme as-is
24176 if let Some(first) =
24177 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
24178 {
24179 token = first;
24180 } else {
24181 token = " ";
24182 }
24183 grapheme_len = 1;
24184 }
24185 let current_prefix_len = if is_first_line {
24186 first_line_prefix_len
24187 } else {
24188 subsequent_lines_prefix_len
24189 };
24190 if current_line_len + grapheme_len > wrap_column {
24191 wrapped_text.push_str(current_line.trim_end());
24192 wrapped_text.push('\n');
24193 is_first_line = false;
24194 current_line = subsequent_lines_prefix.clone();
24195 current_line_len = subsequent_lines_prefix_len;
24196 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
24197 current_line.push_str(token);
24198 current_line_len += grapheme_len;
24199 }
24200 }
24201 WordBreakToken::Newline => {
24202 in_whitespace = true;
24203 let current_prefix_len = if is_first_line {
24204 first_line_prefix_len
24205 } else {
24206 subsequent_lines_prefix_len
24207 };
24208 if preserve_existing_whitespace {
24209 wrapped_text.push_str(current_line.trim_end());
24210 wrapped_text.push('\n');
24211 is_first_line = false;
24212 current_line = subsequent_lines_prefix.clone();
24213 current_line_len = subsequent_lines_prefix_len;
24214 } else if have_preceding_whitespace {
24215 continue;
24216 } else if current_line_len + 1 > wrap_column
24217 && current_line_len != current_prefix_len
24218 {
24219 wrapped_text.push_str(current_line.trim_end());
24220 wrapped_text.push('\n');
24221 is_first_line = false;
24222 current_line = subsequent_lines_prefix.clone();
24223 current_line_len = subsequent_lines_prefix_len;
24224 } else if current_line_len != current_prefix_len {
24225 current_line.push(' ');
24226 current_line_len += 1;
24227 }
24228 }
24229 }
24230 }
24231
24232 if !current_line.is_empty() {
24233 wrapped_text.push_str(¤t_line);
24234 }
24235 wrapped_text
24236}
24237
24238#[test]
24239fn test_wrap_with_prefix() {
24240 assert_eq!(
24241 wrap_with_prefix(
24242 "# ".to_string(),
24243 "# ".to_string(),
24244 "abcdefg".to_string(),
24245 4,
24246 NonZeroU32::new(4).unwrap(),
24247 false,
24248 ),
24249 "# abcdefg"
24250 );
24251 assert_eq!(
24252 wrap_with_prefix(
24253 "".to_string(),
24254 "".to_string(),
24255 "\thello world".to_string(),
24256 8,
24257 NonZeroU32::new(4).unwrap(),
24258 false,
24259 ),
24260 "hello\nworld"
24261 );
24262 assert_eq!(
24263 wrap_with_prefix(
24264 "// ".to_string(),
24265 "// ".to_string(),
24266 "xx \nyy zz aa bb cc".to_string(),
24267 12,
24268 NonZeroU32::new(4).unwrap(),
24269 false,
24270 ),
24271 "// xx yy zz\n// aa bb cc"
24272 );
24273 assert_eq!(
24274 wrap_with_prefix(
24275 String::new(),
24276 String::new(),
24277 "这是什么 \n 钢笔".to_string(),
24278 3,
24279 NonZeroU32::new(4).unwrap(),
24280 false,
24281 ),
24282 "这是什\n么 钢\n笔"
24283 );
24284 assert_eq!(
24285 wrap_with_prefix(
24286 String::new(),
24287 String::new(),
24288 format!("foo{}bar", '\u{2009}'), // thin space
24289 80,
24290 NonZeroU32::new(4).unwrap(),
24291 false,
24292 ),
24293 format!("foo{}bar", '\u{2009}')
24294 );
24295}
24296
24297pub trait CollaborationHub {
24298 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
24299 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
24300 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
24301}
24302
24303impl CollaborationHub for Entity<Project> {
24304 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
24305 self.read(cx).collaborators()
24306 }
24307
24308 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
24309 self.read(cx).user_store().read(cx).participant_indices()
24310 }
24311
24312 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
24313 let this = self.read(cx);
24314 let user_ids = this.collaborators().values().map(|c| c.user_id);
24315 this.user_store().read(cx).participant_names(user_ids, cx)
24316 }
24317}
24318
24319pub trait SemanticsProvider {
24320 fn hover(
24321 &self,
24322 buffer: &Entity<Buffer>,
24323 position: text::Anchor,
24324 cx: &mut App,
24325 ) -> Option<Task<Option<Vec<project::Hover>>>>;
24326
24327 fn inline_values(
24328 &self,
24329 buffer_handle: Entity<Buffer>,
24330 range: Range<text::Anchor>,
24331 cx: &mut App,
24332 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24333
24334 fn applicable_inlay_chunks(
24335 &self,
24336 buffer: &Entity<Buffer>,
24337 ranges: &[Range<text::Anchor>],
24338 cx: &mut App,
24339 ) -> Vec<Range<BufferRow>>;
24340
24341 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24342
24343 fn inlay_hints(
24344 &self,
24345 invalidate: InvalidationStrategy,
24346 buffer: Entity<Buffer>,
24347 ranges: Vec<Range<text::Anchor>>,
24348 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24349 cx: &mut App,
24350 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24351
24352 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24353
24354 fn document_highlights(
24355 &self,
24356 buffer: &Entity<Buffer>,
24357 position: text::Anchor,
24358 cx: &mut App,
24359 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24360
24361 fn definitions(
24362 &self,
24363 buffer: &Entity<Buffer>,
24364 position: text::Anchor,
24365 kind: GotoDefinitionKind,
24366 cx: &mut App,
24367 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24368
24369 fn range_for_rename(
24370 &self,
24371 buffer: &Entity<Buffer>,
24372 position: text::Anchor,
24373 cx: &mut App,
24374 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24375
24376 fn perform_rename(
24377 &self,
24378 buffer: &Entity<Buffer>,
24379 position: text::Anchor,
24380 new_name: String,
24381 cx: &mut App,
24382 ) -> Option<Task<Result<ProjectTransaction>>>;
24383}
24384
24385pub trait CompletionProvider {
24386 fn completions(
24387 &self,
24388 excerpt_id: ExcerptId,
24389 buffer: &Entity<Buffer>,
24390 buffer_position: text::Anchor,
24391 trigger: CompletionContext,
24392 window: &mut Window,
24393 cx: &mut Context<Editor>,
24394 ) -> Task<Result<Vec<CompletionResponse>>>;
24395
24396 fn resolve_completions(
24397 &self,
24398 _buffer: Entity<Buffer>,
24399 _completion_indices: Vec<usize>,
24400 _completions: Rc<RefCell<Box<[Completion]>>>,
24401 _cx: &mut Context<Editor>,
24402 ) -> Task<Result<bool>> {
24403 Task::ready(Ok(false))
24404 }
24405
24406 fn apply_additional_edits_for_completion(
24407 &self,
24408 _buffer: Entity<Buffer>,
24409 _completions: Rc<RefCell<Box<[Completion]>>>,
24410 _completion_index: usize,
24411 _push_to_history: bool,
24412 _cx: &mut Context<Editor>,
24413 ) -> Task<Result<Option<language::Transaction>>> {
24414 Task::ready(Ok(None))
24415 }
24416
24417 fn is_completion_trigger(
24418 &self,
24419 buffer: &Entity<Buffer>,
24420 position: language::Anchor,
24421 text: &str,
24422 trigger_in_words: bool,
24423 cx: &mut Context<Editor>,
24424 ) -> bool;
24425
24426 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24427
24428 fn sort_completions(&self) -> bool {
24429 true
24430 }
24431
24432 fn filter_completions(&self) -> bool {
24433 true
24434 }
24435
24436 fn show_snippets(&self) -> bool {
24437 false
24438 }
24439}
24440
24441pub trait CodeActionProvider {
24442 fn id(&self) -> Arc<str>;
24443
24444 fn code_actions(
24445 &self,
24446 buffer: &Entity<Buffer>,
24447 range: Range<text::Anchor>,
24448 window: &mut Window,
24449 cx: &mut App,
24450 ) -> Task<Result<Vec<CodeAction>>>;
24451
24452 fn apply_code_action(
24453 &self,
24454 buffer_handle: Entity<Buffer>,
24455 action: CodeAction,
24456 excerpt_id: ExcerptId,
24457 push_to_history: bool,
24458 window: &mut Window,
24459 cx: &mut App,
24460 ) -> Task<Result<ProjectTransaction>>;
24461}
24462
24463impl CodeActionProvider for Entity<Project> {
24464 fn id(&self) -> Arc<str> {
24465 "project".into()
24466 }
24467
24468 fn code_actions(
24469 &self,
24470 buffer: &Entity<Buffer>,
24471 range: Range<text::Anchor>,
24472 _window: &mut Window,
24473 cx: &mut App,
24474 ) -> Task<Result<Vec<CodeAction>>> {
24475 self.update(cx, |project, cx| {
24476 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24477 let code_actions = project.code_actions(buffer, range, None, cx);
24478 cx.background_spawn(async move {
24479 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24480 Ok(code_lens_actions
24481 .context("code lens fetch")?
24482 .into_iter()
24483 .flatten()
24484 .chain(
24485 code_actions
24486 .context("code action fetch")?
24487 .into_iter()
24488 .flatten(),
24489 )
24490 .collect())
24491 })
24492 })
24493 }
24494
24495 fn apply_code_action(
24496 &self,
24497 buffer_handle: Entity<Buffer>,
24498 action: CodeAction,
24499 _excerpt_id: ExcerptId,
24500 push_to_history: bool,
24501 _window: &mut Window,
24502 cx: &mut App,
24503 ) -> Task<Result<ProjectTransaction>> {
24504 self.update(cx, |project, cx| {
24505 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24506 })
24507 }
24508}
24509
24510fn snippet_completions(
24511 project: &Project,
24512 buffer: &Entity<Buffer>,
24513 buffer_anchor: text::Anchor,
24514 classifier: CharClassifier,
24515 cx: &mut App,
24516) -> Task<Result<CompletionResponse>> {
24517 let languages = buffer.read(cx).languages_at(buffer_anchor);
24518 let snippet_store = project.snippets().read(cx);
24519
24520 let scopes: Vec<_> = languages
24521 .iter()
24522 .filter_map(|language| {
24523 let language_name = language.lsp_id();
24524 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24525
24526 if snippets.is_empty() {
24527 None
24528 } else {
24529 Some((language.default_scope(), snippets))
24530 }
24531 })
24532 .collect();
24533
24534 if scopes.is_empty() {
24535 return Task::ready(Ok(CompletionResponse {
24536 completions: vec![],
24537 display_options: CompletionDisplayOptions::default(),
24538 is_incomplete: false,
24539 }));
24540 }
24541
24542 let snapshot = buffer.read(cx).text_snapshot();
24543 let executor = cx.background_executor().clone();
24544
24545 cx.background_spawn(async move {
24546 let is_word_char = |c| classifier.is_word(c);
24547
24548 let mut is_incomplete = false;
24549 let mut completions: Vec<Completion> = Vec::new();
24550
24551 const MAX_PREFIX_LEN: usize = 128;
24552 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24553 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24554 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24555
24556 let max_buffer_window: String = snapshot
24557 .text_for_range(window_start..buffer_offset)
24558 .collect();
24559
24560 if max_buffer_window.is_empty() {
24561 return Ok(CompletionResponse {
24562 completions: vec![],
24563 display_options: CompletionDisplayOptions::default(),
24564 is_incomplete: true,
24565 });
24566 }
24567
24568 for (_scope, snippets) in scopes.into_iter() {
24569 // Sort snippets by word count to match longer snippet prefixes first.
24570 let mut sorted_snippet_candidates = snippets
24571 .iter()
24572 .enumerate()
24573 .flat_map(|(snippet_ix, snippet)| {
24574 snippet
24575 .prefix
24576 .iter()
24577 .enumerate()
24578 .map(move |(prefix_ix, prefix)| {
24579 let word_count =
24580 snippet_candidate_suffixes(prefix, is_word_char).count();
24581 ((snippet_ix, prefix_ix), prefix, word_count)
24582 })
24583 })
24584 .collect_vec();
24585 sorted_snippet_candidates
24586 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24587
24588 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24589
24590 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24591 .take(
24592 sorted_snippet_candidates
24593 .first()
24594 .map(|(_, _, word_count)| *word_count)
24595 .unwrap_or_default(),
24596 )
24597 .collect_vec();
24598
24599 const MAX_RESULTS: usize = 100;
24600 // Each match also remembers how many characters from the buffer it consumed
24601 let mut matches: Vec<(StringMatch, usize)> = vec![];
24602
24603 let mut snippet_list_cutoff_index = 0;
24604 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24605 let word_count = buffer_index + 1;
24606 // Increase `snippet_list_cutoff_index` until we have all of the
24607 // snippets with sufficiently many words.
24608 while sorted_snippet_candidates
24609 .get(snippet_list_cutoff_index)
24610 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24611 *snippet_word_count >= word_count
24612 })
24613 {
24614 snippet_list_cutoff_index += 1;
24615 }
24616
24617 // Take only the candidates with at least `word_count` many words
24618 let snippet_candidates_at_word_len =
24619 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24620
24621 let candidates = snippet_candidates_at_word_len
24622 .iter()
24623 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24624 .enumerate() // index in `sorted_snippet_candidates`
24625 // First char must match
24626 .filter(|(_ix, prefix)| {
24627 itertools::equal(
24628 prefix
24629 .chars()
24630 .next()
24631 .into_iter()
24632 .flat_map(|c| c.to_lowercase()),
24633 buffer_window
24634 .chars()
24635 .next()
24636 .into_iter()
24637 .flat_map(|c| c.to_lowercase()),
24638 )
24639 })
24640 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24641 .collect::<Vec<StringMatchCandidate>>();
24642
24643 matches.extend(
24644 fuzzy::match_strings(
24645 &candidates,
24646 &buffer_window,
24647 buffer_window.chars().any(|c| c.is_uppercase()),
24648 true,
24649 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24650 &Default::default(),
24651 executor.clone(),
24652 )
24653 .await
24654 .into_iter()
24655 .map(|string_match| (string_match, buffer_window.len())),
24656 );
24657
24658 if matches.len() >= MAX_RESULTS {
24659 break;
24660 }
24661 }
24662
24663 let to_lsp = |point: &text::Anchor| {
24664 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24665 point_to_lsp(end)
24666 };
24667 let lsp_end = to_lsp(&buffer_anchor);
24668
24669 if matches.len() >= MAX_RESULTS {
24670 is_incomplete = true;
24671 }
24672
24673 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24674 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24675 sorted_snippet_candidates[string_match.candidate_id];
24676 let snippet = &snippets[snippet_index];
24677 let start = buffer_offset - buffer_window_len;
24678 let start = snapshot.anchor_before(start);
24679 let range = start..buffer_anchor;
24680 let lsp_start = to_lsp(&start);
24681 let lsp_range = lsp::Range {
24682 start: lsp_start,
24683 end: lsp_end,
24684 };
24685 Completion {
24686 replace_range: range,
24687 new_text: snippet.body.clone(),
24688 source: CompletionSource::Lsp {
24689 insert_range: None,
24690 server_id: LanguageServerId(usize::MAX),
24691 resolved: true,
24692 lsp_completion: Box::new(lsp::CompletionItem {
24693 label: snippet.prefix.first().unwrap().clone(),
24694 kind: Some(CompletionItemKind::SNIPPET),
24695 label_details: snippet.description.as_ref().map(|description| {
24696 lsp::CompletionItemLabelDetails {
24697 detail: Some(description.clone()),
24698 description: None,
24699 }
24700 }),
24701 insert_text_format: Some(InsertTextFormat::SNIPPET),
24702 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24703 lsp::InsertReplaceEdit {
24704 new_text: snippet.body.clone(),
24705 insert: lsp_range,
24706 replace: lsp_range,
24707 },
24708 )),
24709 filter_text: Some(snippet.body.clone()),
24710 sort_text: Some(char::MAX.to_string()),
24711 ..lsp::CompletionItem::default()
24712 }),
24713 lsp_defaults: None,
24714 },
24715 label: CodeLabel {
24716 text: matching_prefix.clone(),
24717 runs: Vec::new(),
24718 filter_range: 0..matching_prefix.len(),
24719 },
24720 icon_path: None,
24721 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24722 single_line: snippet.name.clone().into(),
24723 plain_text: snippet
24724 .description
24725 .clone()
24726 .map(|description| description.into()),
24727 }),
24728 insert_text_mode: None,
24729 confirm: None,
24730 match_start: Some(start),
24731 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24732 }
24733 }));
24734 }
24735
24736 Ok(CompletionResponse {
24737 completions,
24738 display_options: CompletionDisplayOptions::default(),
24739 is_incomplete,
24740 })
24741 })
24742}
24743
24744impl CompletionProvider for Entity<Project> {
24745 fn completions(
24746 &self,
24747 _excerpt_id: ExcerptId,
24748 buffer: &Entity<Buffer>,
24749 buffer_position: text::Anchor,
24750 options: CompletionContext,
24751 _window: &mut Window,
24752 cx: &mut Context<Editor>,
24753 ) -> Task<Result<Vec<CompletionResponse>>> {
24754 self.update(cx, |project, cx| {
24755 let task = project.completions(buffer, buffer_position, options, cx);
24756 cx.background_spawn(task)
24757 })
24758 }
24759
24760 fn resolve_completions(
24761 &self,
24762 buffer: Entity<Buffer>,
24763 completion_indices: Vec<usize>,
24764 completions: Rc<RefCell<Box<[Completion]>>>,
24765 cx: &mut Context<Editor>,
24766 ) -> Task<Result<bool>> {
24767 self.update(cx, |project, cx| {
24768 project.lsp_store().update(cx, |lsp_store, cx| {
24769 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24770 })
24771 })
24772 }
24773
24774 fn apply_additional_edits_for_completion(
24775 &self,
24776 buffer: Entity<Buffer>,
24777 completions: Rc<RefCell<Box<[Completion]>>>,
24778 completion_index: usize,
24779 push_to_history: bool,
24780 cx: &mut Context<Editor>,
24781 ) -> Task<Result<Option<language::Transaction>>> {
24782 self.update(cx, |project, cx| {
24783 project.lsp_store().update(cx, |lsp_store, cx| {
24784 lsp_store.apply_additional_edits_for_completion(
24785 buffer,
24786 completions,
24787 completion_index,
24788 push_to_history,
24789 cx,
24790 )
24791 })
24792 })
24793 }
24794
24795 fn is_completion_trigger(
24796 &self,
24797 buffer: &Entity<Buffer>,
24798 position: language::Anchor,
24799 text: &str,
24800 trigger_in_words: bool,
24801 cx: &mut Context<Editor>,
24802 ) -> bool {
24803 let mut chars = text.chars();
24804 let char = if let Some(char) = chars.next() {
24805 char
24806 } else {
24807 return false;
24808 };
24809 if chars.next().is_some() {
24810 return false;
24811 }
24812
24813 let buffer = buffer.read(cx);
24814 let snapshot = buffer.snapshot();
24815 let classifier = snapshot
24816 .char_classifier_at(position)
24817 .scope_context(Some(CharScopeContext::Completion));
24818 if trigger_in_words && classifier.is_word(char) {
24819 return true;
24820 }
24821
24822 buffer.completion_triggers().contains(text)
24823 }
24824
24825 fn show_snippets(&self) -> bool {
24826 true
24827 }
24828}
24829
24830impl SemanticsProvider for Entity<Project> {
24831 fn hover(
24832 &self,
24833 buffer: &Entity<Buffer>,
24834 position: text::Anchor,
24835 cx: &mut App,
24836 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24837 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24838 }
24839
24840 fn document_highlights(
24841 &self,
24842 buffer: &Entity<Buffer>,
24843 position: text::Anchor,
24844 cx: &mut App,
24845 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24846 Some(self.update(cx, |project, cx| {
24847 project.document_highlights(buffer, position, cx)
24848 }))
24849 }
24850
24851 fn definitions(
24852 &self,
24853 buffer: &Entity<Buffer>,
24854 position: text::Anchor,
24855 kind: GotoDefinitionKind,
24856 cx: &mut App,
24857 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24858 Some(self.update(cx, |project, cx| match kind {
24859 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24860 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24861 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24862 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24863 }))
24864 }
24865
24866 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24867 self.update(cx, |project, cx| {
24868 if project
24869 .active_debug_session(cx)
24870 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24871 {
24872 return true;
24873 }
24874
24875 buffer.update(cx, |buffer, cx| {
24876 project.any_language_server_supports_inlay_hints(buffer, cx)
24877 })
24878 })
24879 }
24880
24881 fn inline_values(
24882 &self,
24883 buffer_handle: Entity<Buffer>,
24884 range: Range<text::Anchor>,
24885 cx: &mut App,
24886 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24887 self.update(cx, |project, cx| {
24888 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24889
24890 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24891 })
24892 }
24893
24894 fn applicable_inlay_chunks(
24895 &self,
24896 buffer: &Entity<Buffer>,
24897 ranges: &[Range<text::Anchor>],
24898 cx: &mut App,
24899 ) -> Vec<Range<BufferRow>> {
24900 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24901 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24902 })
24903 }
24904
24905 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24906 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24907 lsp_store.invalidate_inlay_hints(for_buffers)
24908 });
24909 }
24910
24911 fn inlay_hints(
24912 &self,
24913 invalidate: InvalidationStrategy,
24914 buffer: Entity<Buffer>,
24915 ranges: Vec<Range<text::Anchor>>,
24916 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24917 cx: &mut App,
24918 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24919 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24920 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24921 }))
24922 }
24923
24924 fn range_for_rename(
24925 &self,
24926 buffer: &Entity<Buffer>,
24927 position: text::Anchor,
24928 cx: &mut App,
24929 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24930 Some(self.update(cx, |project, cx| {
24931 let buffer = buffer.clone();
24932 let task = project.prepare_rename(buffer.clone(), position, cx);
24933 cx.spawn(async move |_, cx| {
24934 Ok(match task.await? {
24935 PrepareRenameResponse::Success(range) => Some(range),
24936 PrepareRenameResponse::InvalidPosition => None,
24937 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24938 // Fallback on using TreeSitter info to determine identifier range
24939 buffer.read_with(cx, |buffer, _| {
24940 let snapshot = buffer.snapshot();
24941 let (range, kind) = snapshot.surrounding_word(position, None);
24942 if kind != Some(CharKind::Word) {
24943 return None;
24944 }
24945 Some(
24946 snapshot.anchor_before(range.start)
24947 ..snapshot.anchor_after(range.end),
24948 )
24949 })?
24950 }
24951 })
24952 })
24953 }))
24954 }
24955
24956 fn perform_rename(
24957 &self,
24958 buffer: &Entity<Buffer>,
24959 position: text::Anchor,
24960 new_name: String,
24961 cx: &mut App,
24962 ) -> Option<Task<Result<ProjectTransaction>>> {
24963 Some(self.update(cx, |project, cx| {
24964 project.perform_rename(buffer.clone(), position, new_name, cx)
24965 }))
24966 }
24967}
24968
24969fn consume_contiguous_rows(
24970 contiguous_row_selections: &mut Vec<Selection<Point>>,
24971 selection: &Selection<Point>,
24972 display_map: &DisplaySnapshot,
24973 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24974) -> (MultiBufferRow, MultiBufferRow) {
24975 contiguous_row_selections.push(selection.clone());
24976 let start_row = starting_row(selection, display_map);
24977 let mut end_row = ending_row(selection, display_map);
24978
24979 while let Some(next_selection) = selections.peek() {
24980 if next_selection.start.row <= end_row.0 {
24981 end_row = ending_row(next_selection, display_map);
24982 contiguous_row_selections.push(selections.next().unwrap().clone());
24983 } else {
24984 break;
24985 }
24986 }
24987 (start_row, end_row)
24988}
24989
24990fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24991 if selection.start.column > 0 {
24992 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24993 } else {
24994 MultiBufferRow(selection.start.row)
24995 }
24996}
24997
24998fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24999 if next_selection.end.column > 0 || next_selection.is_empty() {
25000 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
25001 } else {
25002 MultiBufferRow(next_selection.end.row)
25003 }
25004}
25005
25006impl EditorSnapshot {
25007 pub fn remote_selections_in_range<'a>(
25008 &'a self,
25009 range: &'a Range<Anchor>,
25010 collaboration_hub: &dyn CollaborationHub,
25011 cx: &'a App,
25012 ) -> impl 'a + Iterator<Item = RemoteSelection> {
25013 let participant_names = collaboration_hub.user_names(cx);
25014 let participant_indices = collaboration_hub.user_participant_indices(cx);
25015 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
25016 let collaborators_by_replica_id = collaborators_by_peer_id
25017 .values()
25018 .map(|collaborator| (collaborator.replica_id, collaborator))
25019 .collect::<HashMap<_, _>>();
25020 self.buffer_snapshot()
25021 .selections_in_range(range, false)
25022 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
25023 if replica_id == ReplicaId::AGENT {
25024 Some(RemoteSelection {
25025 replica_id,
25026 selection,
25027 cursor_shape,
25028 line_mode,
25029 collaborator_id: CollaboratorId::Agent,
25030 user_name: Some("Agent".into()),
25031 color: cx.theme().players().agent(),
25032 })
25033 } else {
25034 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
25035 let participant_index = participant_indices.get(&collaborator.user_id).copied();
25036 let user_name = participant_names.get(&collaborator.user_id).cloned();
25037 Some(RemoteSelection {
25038 replica_id,
25039 selection,
25040 cursor_shape,
25041 line_mode,
25042 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
25043 user_name,
25044 color: if let Some(index) = participant_index {
25045 cx.theme().players().color_for_participant(index.0)
25046 } else {
25047 cx.theme().players().absent()
25048 },
25049 })
25050 }
25051 })
25052 }
25053
25054 pub fn hunks_for_ranges(
25055 &self,
25056 ranges: impl IntoIterator<Item = Range<Point>>,
25057 ) -> Vec<MultiBufferDiffHunk> {
25058 let mut hunks = Vec::new();
25059 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
25060 HashMap::default();
25061 for query_range in ranges {
25062 let query_rows =
25063 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
25064 for hunk in self.buffer_snapshot().diff_hunks_in_range(
25065 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
25066 ) {
25067 // Include deleted hunks that are adjacent to the query range, because
25068 // otherwise they would be missed.
25069 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
25070 if hunk.status().is_deleted() {
25071 intersects_range |= hunk.row_range.start == query_rows.end;
25072 intersects_range |= hunk.row_range.end == query_rows.start;
25073 }
25074 if intersects_range {
25075 if !processed_buffer_rows
25076 .entry(hunk.buffer_id)
25077 .or_default()
25078 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
25079 {
25080 continue;
25081 }
25082 hunks.push(hunk);
25083 }
25084 }
25085 }
25086
25087 hunks
25088 }
25089
25090 fn display_diff_hunks_for_rows<'a>(
25091 &'a self,
25092 display_rows: Range<DisplayRow>,
25093 folded_buffers: &'a HashSet<BufferId>,
25094 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
25095 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
25096 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
25097
25098 self.buffer_snapshot()
25099 .diff_hunks_in_range(buffer_start..buffer_end)
25100 .filter_map(|hunk| {
25101 if folded_buffers.contains(&hunk.buffer_id) {
25102 return None;
25103 }
25104
25105 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
25106 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
25107
25108 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
25109 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
25110
25111 let display_hunk = if hunk_display_start.column() != 0 {
25112 DisplayDiffHunk::Folded {
25113 display_row: hunk_display_start.row(),
25114 }
25115 } else {
25116 let mut end_row = hunk_display_end.row();
25117 if hunk_display_end.column() > 0 {
25118 end_row.0 += 1;
25119 }
25120 let is_created_file = hunk.is_created_file();
25121
25122 DisplayDiffHunk::Unfolded {
25123 status: hunk.status(),
25124 diff_base_byte_range: hunk.diff_base_byte_range.start.0
25125 ..hunk.diff_base_byte_range.end.0,
25126 word_diffs: hunk.word_diffs,
25127 display_row_range: hunk_display_start.row()..end_row,
25128 multi_buffer_range: Anchor::range_in_buffer(
25129 hunk.excerpt_id,
25130 hunk.buffer_range,
25131 ),
25132 is_created_file,
25133 }
25134 };
25135
25136 Some(display_hunk)
25137 })
25138 }
25139
25140 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
25141 self.display_snapshot
25142 .buffer_snapshot()
25143 .language_at(position)
25144 }
25145
25146 pub fn is_focused(&self) -> bool {
25147 self.is_focused
25148 }
25149
25150 pub fn placeholder_text(&self) -> Option<String> {
25151 self.placeholder_display_snapshot
25152 .as_ref()
25153 .map(|display_map| display_map.text())
25154 }
25155
25156 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
25157 self.scroll_anchor.scroll_position(&self.display_snapshot)
25158 }
25159
25160 pub fn gutter_dimensions(
25161 &self,
25162 font_id: FontId,
25163 font_size: Pixels,
25164 style: &EditorStyle,
25165 window: &mut Window,
25166 cx: &App,
25167 ) -> GutterDimensions {
25168 if self.show_gutter
25169 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
25170 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
25171 {
25172 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
25173 matches!(
25174 ProjectSettings::get_global(cx).git.git_gutter,
25175 GitGutterSetting::TrackedFiles
25176 )
25177 });
25178 let gutter_settings = EditorSettings::get_global(cx).gutter;
25179 let show_line_numbers = self
25180 .show_line_numbers
25181 .unwrap_or(gutter_settings.line_numbers);
25182 let line_gutter_width = if show_line_numbers {
25183 // Avoid flicker-like gutter resizes when the line number gains another digit by
25184 // only resizing the gutter on files with > 10**min_line_number_digits lines.
25185 let min_width_for_number_on_gutter =
25186 ch_advance * gutter_settings.min_line_number_digits as f32;
25187 self.max_line_number_width(style, window)
25188 .max(min_width_for_number_on_gutter)
25189 } else {
25190 0.0.into()
25191 };
25192
25193 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
25194 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
25195
25196 let git_blame_entries_width =
25197 self.git_blame_gutter_max_author_length
25198 .map(|max_author_length| {
25199 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
25200 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
25201
25202 /// The number of characters to dedicate to gaps and margins.
25203 const SPACING_WIDTH: usize = 4;
25204
25205 let max_char_count = max_author_length.min(renderer.max_author_length())
25206 + ::git::SHORT_SHA_LENGTH
25207 + MAX_RELATIVE_TIMESTAMP.len()
25208 + SPACING_WIDTH;
25209
25210 ch_advance * max_char_count
25211 });
25212
25213 let is_singleton = self.buffer_snapshot().is_singleton();
25214
25215 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
25216 left_padding += if !is_singleton {
25217 ch_width * 4.0
25218 } else if show_runnables || show_breakpoints {
25219 ch_width * 3.0
25220 } else if show_git_gutter && show_line_numbers {
25221 ch_width * 2.0
25222 } else if show_git_gutter || show_line_numbers {
25223 ch_width
25224 } else {
25225 px(0.)
25226 };
25227
25228 let shows_folds = is_singleton && gutter_settings.folds;
25229
25230 let right_padding = if shows_folds && show_line_numbers {
25231 ch_width * 4.0
25232 } else if shows_folds || (!is_singleton && show_line_numbers) {
25233 ch_width * 3.0
25234 } else if show_line_numbers {
25235 ch_width
25236 } else {
25237 px(0.)
25238 };
25239
25240 GutterDimensions {
25241 left_padding,
25242 right_padding,
25243 width: line_gutter_width + left_padding + right_padding,
25244 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
25245 git_blame_entries_width,
25246 }
25247 } else if self.offset_content {
25248 GutterDimensions::default_with_margin(font_id, font_size, cx)
25249 } else {
25250 GutterDimensions::default()
25251 }
25252 }
25253
25254 pub fn render_crease_toggle(
25255 &self,
25256 buffer_row: MultiBufferRow,
25257 row_contains_cursor: bool,
25258 editor: Entity<Editor>,
25259 window: &mut Window,
25260 cx: &mut App,
25261 ) -> Option<AnyElement> {
25262 let folded = self.is_line_folded(buffer_row);
25263 let mut is_foldable = false;
25264
25265 if let Some(crease) = self
25266 .crease_snapshot
25267 .query_row(buffer_row, self.buffer_snapshot())
25268 {
25269 is_foldable = true;
25270 match crease {
25271 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
25272 if let Some(render_toggle) = render_toggle {
25273 let toggle_callback =
25274 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
25275 if folded {
25276 editor.update(cx, |editor, cx| {
25277 editor.fold_at(buffer_row, window, cx)
25278 });
25279 } else {
25280 editor.update(cx, |editor, cx| {
25281 editor.unfold_at(buffer_row, window, cx)
25282 });
25283 }
25284 });
25285 return Some((render_toggle)(
25286 buffer_row,
25287 folded,
25288 toggle_callback,
25289 window,
25290 cx,
25291 ));
25292 }
25293 }
25294 }
25295 }
25296
25297 is_foldable |= self.starts_indent(buffer_row);
25298
25299 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
25300 Some(
25301 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
25302 .toggle_state(folded)
25303 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
25304 if folded {
25305 this.unfold_at(buffer_row, window, cx);
25306 } else {
25307 this.fold_at(buffer_row, window, cx);
25308 }
25309 }))
25310 .into_any_element(),
25311 )
25312 } else {
25313 None
25314 }
25315 }
25316
25317 pub fn render_crease_trailer(
25318 &self,
25319 buffer_row: MultiBufferRow,
25320 window: &mut Window,
25321 cx: &mut App,
25322 ) -> Option<AnyElement> {
25323 let folded = self.is_line_folded(buffer_row);
25324 if let Crease::Inline { render_trailer, .. } = self
25325 .crease_snapshot
25326 .query_row(buffer_row, self.buffer_snapshot())?
25327 {
25328 let render_trailer = render_trailer.as_ref()?;
25329 Some(render_trailer(buffer_row, folded, window, cx))
25330 } else {
25331 None
25332 }
25333 }
25334
25335 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25336 let digit_count = self.widest_line_number().ilog10() + 1;
25337 column_pixels(style, digit_count as usize, window)
25338 }
25339
25340 /// Returns the line delta from `base` to `line` in the multibuffer, ignoring wrapped lines.
25341 ///
25342 /// This is positive if `base` is before `line`.
25343 fn relative_line_delta(&self, base: DisplayRow, line: DisplayRow) -> i64 {
25344 let point = DisplayPoint::new(line, 0).to_point(self);
25345 self.relative_line_delta_to_point(base, point)
25346 }
25347
25348 /// Returns the line delta from `base` to `point` in the multibuffer, ignoring wrapped lines.
25349 ///
25350 /// This is positive if `base` is before `point`.
25351 pub fn relative_line_delta_to_point(&self, base: DisplayRow, point: Point) -> i64 {
25352 let base_point = DisplayPoint::new(base, 0).to_point(self);
25353 point.row as i64 - base_point.row as i64
25354 }
25355
25356 /// Returns the line delta from `base` to `line` in the multibuffer, counting wrapped lines.
25357 ///
25358 /// This is positive if `base` is before `line`.
25359 fn relative_wrapped_line_delta(&self, base: DisplayRow, line: DisplayRow) -> i64 {
25360 let point = DisplayPoint::new(line, 0).to_point(self);
25361 self.relative_wrapped_line_delta_to_point(base, point)
25362 }
25363
25364 /// Returns the line delta from `base` to `point` in the multibuffer, counting wrapped lines.
25365 ///
25366 /// This is positive if `base` is before `point`.
25367 pub fn relative_wrapped_line_delta_to_point(&self, base: DisplayRow, point: Point) -> i64 {
25368 let base_point = DisplayPoint::new(base, 0).to_point(self);
25369 let wrap_snapshot = self.wrap_snapshot();
25370 let base_wrap_row = wrap_snapshot.make_wrap_point(base_point, Bias::Left).row();
25371 let wrap_row = wrap_snapshot.make_wrap_point(point, Bias::Left).row();
25372 wrap_row.0 as i64 - base_wrap_row.0 as i64
25373 }
25374
25375 /// Returns the unsigned relative line number to display for each row in `rows`.
25376 ///
25377 /// Wrapped rows are excluded from the hashmap if `count_relative_lines` is `false`.
25378 pub fn calculate_relative_line_numbers(
25379 &self,
25380 rows: &Range<DisplayRow>,
25381 relative_to: DisplayRow,
25382 count_wrapped_lines: bool,
25383 ) -> HashMap<DisplayRow, u32> {
25384 let initial_offset = if count_wrapped_lines {
25385 self.relative_wrapped_line_delta(relative_to, rows.start)
25386 } else {
25387 self.relative_line_delta(relative_to, rows.start)
25388 };
25389 let display_row_infos = self
25390 .row_infos(rows.start)
25391 .take(rows.len())
25392 .enumerate()
25393 .map(|(i, row_info)| (DisplayRow(rows.start.0 + i as u32), row_info));
25394 display_row_infos
25395 .filter(|(_row, row_info)| {
25396 row_info.buffer_row.is_some()
25397 || (count_wrapped_lines && row_info.wrapped_buffer_row.is_some())
25398 })
25399 .enumerate()
25400 .map(|(i, (row, _row_info))| (row, (initial_offset + i as i64).unsigned_abs() as u32))
25401 .collect()
25402 }
25403}
25404
25405pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25406 let font_size = style.text.font_size.to_pixels(window.rem_size());
25407 let layout = window.text_system().shape_line(
25408 SharedString::from(" ".repeat(column)),
25409 font_size,
25410 &[TextRun {
25411 len: column,
25412 font: style.text.font(),
25413 color: Hsla::default(),
25414 ..Default::default()
25415 }],
25416 None,
25417 );
25418
25419 layout.width
25420}
25421
25422impl Deref for EditorSnapshot {
25423 type Target = DisplaySnapshot;
25424
25425 fn deref(&self) -> &Self::Target {
25426 &self.display_snapshot
25427 }
25428}
25429
25430#[derive(Clone, Debug, PartialEq, Eq)]
25431pub enum EditorEvent {
25432 InputIgnored {
25433 text: Arc<str>,
25434 },
25435 InputHandled {
25436 utf16_range_to_replace: Option<Range<isize>>,
25437 text: Arc<str>,
25438 },
25439 ExcerptsAdded {
25440 buffer: Entity<Buffer>,
25441 predecessor: ExcerptId,
25442 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25443 },
25444 ExcerptsRemoved {
25445 ids: Vec<ExcerptId>,
25446 removed_buffer_ids: Vec<BufferId>,
25447 },
25448 BufferFoldToggled {
25449 ids: Vec<ExcerptId>,
25450 folded: bool,
25451 },
25452 ExcerptsEdited {
25453 ids: Vec<ExcerptId>,
25454 },
25455 ExcerptsExpanded {
25456 ids: Vec<ExcerptId>,
25457 },
25458 BufferEdited,
25459 Edited {
25460 transaction_id: clock::Lamport,
25461 },
25462 Reparsed(BufferId),
25463 Focused,
25464 FocusedIn,
25465 Blurred,
25466 DirtyChanged,
25467 Saved,
25468 TitleChanged,
25469 SelectionsChanged {
25470 local: bool,
25471 },
25472 ScrollPositionChanged {
25473 local: bool,
25474 autoscroll: bool,
25475 },
25476 TransactionUndone {
25477 transaction_id: clock::Lamport,
25478 },
25479 TransactionBegun {
25480 transaction_id: clock::Lamport,
25481 },
25482 CursorShapeChanged,
25483 BreadcrumbsChanged,
25484 PushedToNavHistory {
25485 anchor: Anchor,
25486 is_deactivate: bool,
25487 },
25488}
25489
25490impl EventEmitter<EditorEvent> for Editor {}
25491
25492impl Focusable for Editor {
25493 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25494 self.focus_handle.clone()
25495 }
25496}
25497
25498impl Render for Editor {
25499 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25500 EditorElement::new(&cx.entity(), self.create_style(cx))
25501 }
25502}
25503
25504impl EntityInputHandler for Editor {
25505 fn text_for_range(
25506 &mut self,
25507 range_utf16: Range<usize>,
25508 adjusted_range: &mut Option<Range<usize>>,
25509 _: &mut Window,
25510 cx: &mut Context<Self>,
25511 ) -> Option<String> {
25512 let snapshot = self.buffer.read(cx).read(cx);
25513 let start = snapshot.clip_offset_utf16(
25514 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25515 Bias::Left,
25516 );
25517 let end = snapshot.clip_offset_utf16(
25518 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25519 Bias::Right,
25520 );
25521 if (start.0.0..end.0.0) != range_utf16 {
25522 adjusted_range.replace(start.0.0..end.0.0);
25523 }
25524 Some(snapshot.text_for_range(start..end).collect())
25525 }
25526
25527 fn selected_text_range(
25528 &mut self,
25529 ignore_disabled_input: bool,
25530 _: &mut Window,
25531 cx: &mut Context<Self>,
25532 ) -> Option<UTF16Selection> {
25533 // Prevent the IME menu from appearing when holding down an alphabetic key
25534 // while input is disabled.
25535 if !ignore_disabled_input && !self.input_enabled {
25536 return None;
25537 }
25538
25539 let selection = self
25540 .selections
25541 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25542 let range = selection.range();
25543
25544 Some(UTF16Selection {
25545 range: range.start.0.0..range.end.0.0,
25546 reversed: selection.reversed,
25547 })
25548 }
25549
25550 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25551 let snapshot = self.buffer.read(cx).read(cx);
25552 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25553 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25554 }
25555
25556 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25557 self.clear_highlights::<InputComposition>(cx);
25558 self.ime_transaction.take();
25559 }
25560
25561 fn replace_text_in_range(
25562 &mut self,
25563 range_utf16: Option<Range<usize>>,
25564 text: &str,
25565 window: &mut Window,
25566 cx: &mut Context<Self>,
25567 ) {
25568 if !self.input_enabled {
25569 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25570 return;
25571 }
25572
25573 self.transact(window, cx, |this, window, cx| {
25574 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25575 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25576 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25577 Some(this.selection_replacement_ranges(range_utf16, cx))
25578 } else {
25579 this.marked_text_ranges(cx)
25580 };
25581
25582 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25583 let newest_selection_id = this.selections.newest_anchor().id;
25584 this.selections
25585 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25586 .iter()
25587 .zip(ranges_to_replace.iter())
25588 .find_map(|(selection, range)| {
25589 if selection.id == newest_selection_id {
25590 Some(
25591 (range.start.0.0 as isize - selection.head().0.0 as isize)
25592 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25593 )
25594 } else {
25595 None
25596 }
25597 })
25598 });
25599
25600 cx.emit(EditorEvent::InputHandled {
25601 utf16_range_to_replace: range_to_replace,
25602 text: text.into(),
25603 });
25604
25605 if let Some(new_selected_ranges) = new_selected_ranges {
25606 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25607 selections.select_ranges(new_selected_ranges)
25608 });
25609 this.backspace(&Default::default(), window, cx);
25610 }
25611
25612 this.handle_input(text, window, cx);
25613 });
25614
25615 if let Some(transaction) = self.ime_transaction {
25616 self.buffer.update(cx, |buffer, cx| {
25617 buffer.group_until_transaction(transaction, cx);
25618 });
25619 }
25620
25621 self.unmark_text(window, cx);
25622 }
25623
25624 fn replace_and_mark_text_in_range(
25625 &mut self,
25626 range_utf16: Option<Range<usize>>,
25627 text: &str,
25628 new_selected_range_utf16: Option<Range<usize>>,
25629 window: &mut Window,
25630 cx: &mut Context<Self>,
25631 ) {
25632 if !self.input_enabled {
25633 return;
25634 }
25635
25636 let transaction = self.transact(window, cx, |this, window, cx| {
25637 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25638 let snapshot = this.buffer.read(cx).read(cx);
25639 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25640 for marked_range in &mut marked_ranges {
25641 marked_range.end = marked_range.start + relative_range_utf16.end;
25642 marked_range.start += relative_range_utf16.start;
25643 marked_range.start =
25644 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25645 marked_range.end =
25646 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25647 }
25648 }
25649 Some(marked_ranges)
25650 } else if let Some(range_utf16) = range_utf16 {
25651 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25652 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25653 Some(this.selection_replacement_ranges(range_utf16, cx))
25654 } else {
25655 None
25656 };
25657
25658 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25659 let newest_selection_id = this.selections.newest_anchor().id;
25660 this.selections
25661 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25662 .iter()
25663 .zip(ranges_to_replace.iter())
25664 .find_map(|(selection, range)| {
25665 if selection.id == newest_selection_id {
25666 Some(
25667 (range.start.0.0 as isize - selection.head().0.0 as isize)
25668 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25669 )
25670 } else {
25671 None
25672 }
25673 })
25674 });
25675
25676 cx.emit(EditorEvent::InputHandled {
25677 utf16_range_to_replace: range_to_replace,
25678 text: text.into(),
25679 });
25680
25681 if let Some(ranges) = ranges_to_replace {
25682 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25683 s.select_ranges(ranges)
25684 });
25685 }
25686
25687 let marked_ranges = {
25688 let snapshot = this.buffer.read(cx).read(cx);
25689 this.selections
25690 .disjoint_anchors_arc()
25691 .iter()
25692 .map(|selection| {
25693 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25694 })
25695 .collect::<Vec<_>>()
25696 };
25697
25698 if text.is_empty() {
25699 this.unmark_text(window, cx);
25700 } else {
25701 this.highlight_text::<InputComposition>(
25702 marked_ranges.clone(),
25703 HighlightStyle {
25704 underline: Some(UnderlineStyle {
25705 thickness: px(1.),
25706 color: None,
25707 wavy: false,
25708 }),
25709 ..Default::default()
25710 },
25711 cx,
25712 );
25713 }
25714
25715 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25716 let use_autoclose = this.use_autoclose;
25717 let use_auto_surround = this.use_auto_surround;
25718 this.set_use_autoclose(false);
25719 this.set_use_auto_surround(false);
25720 this.handle_input(text, window, cx);
25721 this.set_use_autoclose(use_autoclose);
25722 this.set_use_auto_surround(use_auto_surround);
25723
25724 if let Some(new_selected_range) = new_selected_range_utf16 {
25725 let snapshot = this.buffer.read(cx).read(cx);
25726 let new_selected_ranges = marked_ranges
25727 .into_iter()
25728 .map(|marked_range| {
25729 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25730 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25731 insertion_start.0 + new_selected_range.start,
25732 ));
25733 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25734 insertion_start.0 + new_selected_range.end,
25735 ));
25736 snapshot.clip_offset_utf16(new_start, Bias::Left)
25737 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25738 })
25739 .collect::<Vec<_>>();
25740
25741 drop(snapshot);
25742 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25743 selections.select_ranges(new_selected_ranges)
25744 });
25745 }
25746 });
25747
25748 self.ime_transaction = self.ime_transaction.or(transaction);
25749 if let Some(transaction) = self.ime_transaction {
25750 self.buffer.update(cx, |buffer, cx| {
25751 buffer.group_until_transaction(transaction, cx);
25752 });
25753 }
25754
25755 if self.text_highlights::<InputComposition>(cx).is_none() {
25756 self.ime_transaction.take();
25757 }
25758 }
25759
25760 fn bounds_for_range(
25761 &mut self,
25762 range_utf16: Range<usize>,
25763 element_bounds: gpui::Bounds<Pixels>,
25764 window: &mut Window,
25765 cx: &mut Context<Self>,
25766 ) -> Option<gpui::Bounds<Pixels>> {
25767 let text_layout_details = self.text_layout_details(window);
25768 let CharacterDimensions {
25769 em_width,
25770 em_advance,
25771 line_height,
25772 } = self.character_dimensions(window);
25773
25774 let snapshot = self.snapshot(window, cx);
25775 let scroll_position = snapshot.scroll_position();
25776 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25777
25778 let start =
25779 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25780 let x = Pixels::from(
25781 ScrollOffset::from(
25782 snapshot.x_for_display_point(start, &text_layout_details)
25783 + self.gutter_dimensions.full_width(),
25784 ) - scroll_left,
25785 );
25786 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25787
25788 Some(Bounds {
25789 origin: element_bounds.origin + point(x, y),
25790 size: size(em_width, line_height),
25791 })
25792 }
25793
25794 fn character_index_for_point(
25795 &mut self,
25796 point: gpui::Point<Pixels>,
25797 _window: &mut Window,
25798 _cx: &mut Context<Self>,
25799 ) -> Option<usize> {
25800 let position_map = self.last_position_map.as_ref()?;
25801 if !position_map.text_hitbox.contains(&point) {
25802 return None;
25803 }
25804 let display_point = position_map.point_for_position(point).previous_valid;
25805 let anchor = position_map
25806 .snapshot
25807 .display_point_to_anchor(display_point, Bias::Left);
25808 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25809 Some(utf16_offset.0.0)
25810 }
25811
25812 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25813 self.input_enabled
25814 }
25815}
25816
25817trait SelectionExt {
25818 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25819 fn spanned_rows(
25820 &self,
25821 include_end_if_at_line_start: bool,
25822 map: &DisplaySnapshot,
25823 ) -> Range<MultiBufferRow>;
25824}
25825
25826impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25827 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25828 let start = self
25829 .start
25830 .to_point(map.buffer_snapshot())
25831 .to_display_point(map);
25832 let end = self
25833 .end
25834 .to_point(map.buffer_snapshot())
25835 .to_display_point(map);
25836 if self.reversed {
25837 end..start
25838 } else {
25839 start..end
25840 }
25841 }
25842
25843 fn spanned_rows(
25844 &self,
25845 include_end_if_at_line_start: bool,
25846 map: &DisplaySnapshot,
25847 ) -> Range<MultiBufferRow> {
25848 let start = self.start.to_point(map.buffer_snapshot());
25849 let mut end = self.end.to_point(map.buffer_snapshot());
25850 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25851 end.row -= 1;
25852 }
25853
25854 let buffer_start = map.prev_line_boundary(start).0;
25855 let buffer_end = map.next_line_boundary(end).0;
25856 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25857 }
25858}
25859
25860impl<T: InvalidationRegion> InvalidationStack<T> {
25861 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25862 where
25863 S: Clone + ToOffset,
25864 {
25865 while let Some(region) = self.last() {
25866 let all_selections_inside_invalidation_ranges =
25867 if selections.len() == region.ranges().len() {
25868 selections
25869 .iter()
25870 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25871 .all(|(selection, invalidation_range)| {
25872 let head = selection.head().to_offset(buffer);
25873 invalidation_range.start <= head && invalidation_range.end >= head
25874 })
25875 } else {
25876 false
25877 };
25878
25879 if all_selections_inside_invalidation_ranges {
25880 break;
25881 } else {
25882 self.pop();
25883 }
25884 }
25885 }
25886}
25887
25888impl<T> Default for InvalidationStack<T> {
25889 fn default() -> Self {
25890 Self(Default::default())
25891 }
25892}
25893
25894impl<T> Deref for InvalidationStack<T> {
25895 type Target = Vec<T>;
25896
25897 fn deref(&self) -> &Self::Target {
25898 &self.0
25899 }
25900}
25901
25902impl<T> DerefMut for InvalidationStack<T> {
25903 fn deref_mut(&mut self) -> &mut Self::Target {
25904 &mut self.0
25905 }
25906}
25907
25908impl InvalidationRegion for SnippetState {
25909 fn ranges(&self) -> &[Range<Anchor>] {
25910 &self.ranges[self.active_index]
25911 }
25912}
25913
25914fn edit_prediction_edit_text(
25915 current_snapshot: &BufferSnapshot,
25916 edits: &[(Range<Anchor>, impl AsRef<str>)],
25917 edit_preview: &EditPreview,
25918 include_deletions: bool,
25919 cx: &App,
25920) -> HighlightedText {
25921 let edits = edits
25922 .iter()
25923 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25924 .collect::<Vec<_>>();
25925
25926 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25927}
25928
25929fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25930 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25931 // Just show the raw edit text with basic styling
25932 let mut text = String::new();
25933 let mut highlights = Vec::new();
25934
25935 let insertion_highlight_style = HighlightStyle {
25936 color: Some(cx.theme().colors().text),
25937 ..Default::default()
25938 };
25939
25940 for (_, edit_text) in edits {
25941 let start_offset = text.len();
25942 text.push_str(edit_text);
25943 let end_offset = text.len();
25944
25945 if start_offset < end_offset {
25946 highlights.push((start_offset..end_offset, insertion_highlight_style));
25947 }
25948 }
25949
25950 HighlightedText {
25951 text: text.into(),
25952 highlights,
25953 }
25954}
25955
25956pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25957 match severity {
25958 lsp::DiagnosticSeverity::ERROR => colors.error,
25959 lsp::DiagnosticSeverity::WARNING => colors.warning,
25960 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25961 lsp::DiagnosticSeverity::HINT => colors.info,
25962 _ => colors.ignored,
25963 }
25964}
25965
25966pub fn styled_runs_for_code_label<'a>(
25967 label: &'a CodeLabel,
25968 syntax_theme: &'a theme::SyntaxTheme,
25969 local_player: &'a theme::PlayerColor,
25970) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25971 let fade_out = HighlightStyle {
25972 fade_out: Some(0.35),
25973 ..Default::default()
25974 };
25975
25976 let mut prev_end = label.filter_range.end;
25977 label
25978 .runs
25979 .iter()
25980 .enumerate()
25981 .flat_map(move |(ix, (range, highlight_id))| {
25982 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25983 HighlightStyle {
25984 color: Some(local_player.cursor),
25985 ..Default::default()
25986 }
25987 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25988 HighlightStyle {
25989 background_color: Some(local_player.selection),
25990 ..Default::default()
25991 }
25992 } else if let Some(style) = highlight_id.style(syntax_theme) {
25993 style
25994 } else {
25995 return Default::default();
25996 };
25997 let muted_style = style.highlight(fade_out);
25998
25999 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
26000 if range.start >= label.filter_range.end {
26001 if range.start > prev_end {
26002 runs.push((prev_end..range.start, fade_out));
26003 }
26004 runs.push((range.clone(), muted_style));
26005 } else if range.end <= label.filter_range.end {
26006 runs.push((range.clone(), style));
26007 } else {
26008 runs.push((range.start..label.filter_range.end, style));
26009 runs.push((label.filter_range.end..range.end, muted_style));
26010 }
26011 prev_end = cmp::max(prev_end, range.end);
26012
26013 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
26014 runs.push((prev_end..label.text.len(), fade_out));
26015 }
26016
26017 runs
26018 })
26019}
26020
26021pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
26022 let mut prev_index = 0;
26023 let mut prev_codepoint: Option<char> = None;
26024 text.char_indices()
26025 .chain([(text.len(), '\0')])
26026 .filter_map(move |(index, codepoint)| {
26027 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26028 let is_boundary = index == text.len()
26029 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
26030 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
26031 if is_boundary {
26032 let chunk = &text[prev_index..index];
26033 prev_index = index;
26034 Some(chunk)
26035 } else {
26036 None
26037 }
26038 })
26039}
26040
26041/// Given a string of text immediately before the cursor, iterates over possible
26042/// strings a snippet could match to. More precisely: returns an iterator over
26043/// suffixes of `text` created by splitting at word boundaries (before & after
26044/// every non-word character).
26045///
26046/// Shorter suffixes are returned first.
26047pub(crate) fn snippet_candidate_suffixes(
26048 text: &str,
26049 is_word_char: impl Fn(char) -> bool,
26050) -> impl std::iter::Iterator<Item = &str> {
26051 let mut prev_index = text.len();
26052 let mut prev_codepoint = None;
26053 text.char_indices()
26054 .rev()
26055 .chain([(0, '\0')])
26056 .filter_map(move |(index, codepoint)| {
26057 let prev_index = std::mem::replace(&mut prev_index, index);
26058 let prev_codepoint = prev_codepoint.replace(codepoint)?;
26059 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
26060 None
26061 } else {
26062 let chunk = &text[prev_index..]; // go to end of string
26063 Some(chunk)
26064 }
26065 })
26066}
26067
26068pub trait RangeToAnchorExt: Sized {
26069 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
26070
26071 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
26072 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
26073 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
26074 }
26075}
26076
26077impl<T: ToOffset> RangeToAnchorExt for Range<T> {
26078 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
26079 let start_offset = self.start.to_offset(snapshot);
26080 let end_offset = self.end.to_offset(snapshot);
26081 if start_offset == end_offset {
26082 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
26083 } else {
26084 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
26085 }
26086 }
26087}
26088
26089pub trait RowExt {
26090 fn as_f64(&self) -> f64;
26091
26092 fn next_row(&self) -> Self;
26093
26094 fn previous_row(&self) -> Self;
26095
26096 fn minus(&self, other: Self) -> u32;
26097}
26098
26099impl RowExt for DisplayRow {
26100 fn as_f64(&self) -> f64 {
26101 self.0 as _
26102 }
26103
26104 fn next_row(&self) -> Self {
26105 Self(self.0 + 1)
26106 }
26107
26108 fn previous_row(&self) -> Self {
26109 Self(self.0.saturating_sub(1))
26110 }
26111
26112 fn minus(&self, other: Self) -> u32 {
26113 self.0 - other.0
26114 }
26115}
26116
26117impl RowExt for MultiBufferRow {
26118 fn as_f64(&self) -> f64 {
26119 self.0 as _
26120 }
26121
26122 fn next_row(&self) -> Self {
26123 Self(self.0 + 1)
26124 }
26125
26126 fn previous_row(&self) -> Self {
26127 Self(self.0.saturating_sub(1))
26128 }
26129
26130 fn minus(&self, other: Self) -> u32 {
26131 self.0 - other.0
26132 }
26133}
26134
26135trait RowRangeExt {
26136 type Row;
26137
26138 fn len(&self) -> usize;
26139
26140 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
26141}
26142
26143impl RowRangeExt for Range<MultiBufferRow> {
26144 type Row = MultiBufferRow;
26145
26146 fn len(&self) -> usize {
26147 (self.end.0 - self.start.0) as usize
26148 }
26149
26150 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
26151 (self.start.0..self.end.0).map(MultiBufferRow)
26152 }
26153}
26154
26155impl RowRangeExt for Range<DisplayRow> {
26156 type Row = DisplayRow;
26157
26158 fn len(&self) -> usize {
26159 (self.end.0 - self.start.0) as usize
26160 }
26161
26162 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
26163 (self.start.0..self.end.0).map(DisplayRow)
26164 }
26165}
26166
26167/// If select range has more than one line, we
26168/// just point the cursor to range.start.
26169fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
26170 if range.start.row == range.end.row {
26171 range
26172 } else {
26173 range.start..range.start
26174 }
26175}
26176pub struct KillRing(ClipboardItem);
26177impl Global for KillRing {}
26178
26179const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
26180
26181enum BreakpointPromptEditAction {
26182 Log,
26183 Condition,
26184 HitCondition,
26185}
26186
26187struct BreakpointPromptEditor {
26188 pub(crate) prompt: Entity<Editor>,
26189 editor: WeakEntity<Editor>,
26190 breakpoint_anchor: Anchor,
26191 breakpoint: Breakpoint,
26192 edit_action: BreakpointPromptEditAction,
26193 block_ids: HashSet<CustomBlockId>,
26194 editor_margins: Arc<Mutex<EditorMargins>>,
26195 _subscriptions: Vec<Subscription>,
26196}
26197
26198impl BreakpointPromptEditor {
26199 const MAX_LINES: u8 = 4;
26200
26201 fn new(
26202 editor: WeakEntity<Editor>,
26203 breakpoint_anchor: Anchor,
26204 breakpoint: Breakpoint,
26205 edit_action: BreakpointPromptEditAction,
26206 window: &mut Window,
26207 cx: &mut Context<Self>,
26208 ) -> Self {
26209 let base_text = match edit_action {
26210 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
26211 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
26212 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
26213 }
26214 .map(|msg| msg.to_string())
26215 .unwrap_or_default();
26216
26217 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
26218 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
26219
26220 let prompt = cx.new(|cx| {
26221 let mut prompt = Editor::new(
26222 EditorMode::AutoHeight {
26223 min_lines: 1,
26224 max_lines: Some(Self::MAX_LINES as usize),
26225 },
26226 buffer,
26227 None,
26228 window,
26229 cx,
26230 );
26231 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
26232 prompt.set_show_cursor_when_unfocused(false, cx);
26233 prompt.set_placeholder_text(
26234 match edit_action {
26235 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
26236 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
26237 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
26238 },
26239 window,
26240 cx,
26241 );
26242
26243 prompt
26244 });
26245
26246 Self {
26247 prompt,
26248 editor,
26249 breakpoint_anchor,
26250 breakpoint,
26251 edit_action,
26252 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
26253 block_ids: Default::default(),
26254 _subscriptions: vec![],
26255 }
26256 }
26257
26258 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
26259 self.block_ids.extend(block_ids)
26260 }
26261
26262 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
26263 if let Some(editor) = self.editor.upgrade() {
26264 let message = self
26265 .prompt
26266 .read(cx)
26267 .buffer
26268 .read(cx)
26269 .as_singleton()
26270 .expect("A multi buffer in breakpoint prompt isn't possible")
26271 .read(cx)
26272 .as_rope()
26273 .to_string();
26274
26275 editor.update(cx, |editor, cx| {
26276 editor.edit_breakpoint_at_anchor(
26277 self.breakpoint_anchor,
26278 self.breakpoint.clone(),
26279 match self.edit_action {
26280 BreakpointPromptEditAction::Log => {
26281 BreakpointEditAction::EditLogMessage(message.into())
26282 }
26283 BreakpointPromptEditAction::Condition => {
26284 BreakpointEditAction::EditCondition(message.into())
26285 }
26286 BreakpointPromptEditAction::HitCondition => {
26287 BreakpointEditAction::EditHitCondition(message.into())
26288 }
26289 },
26290 cx,
26291 );
26292
26293 editor.remove_blocks(self.block_ids.clone(), None, cx);
26294 cx.focus_self(window);
26295 });
26296 }
26297 }
26298
26299 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
26300 self.editor
26301 .update(cx, |editor, cx| {
26302 editor.remove_blocks(self.block_ids.clone(), None, cx);
26303 window.focus(&editor.focus_handle, cx);
26304 })
26305 .log_err();
26306 }
26307
26308 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
26309 let settings = ThemeSettings::get_global(cx);
26310 let text_style = TextStyle {
26311 color: if self.prompt.read(cx).read_only(cx) {
26312 cx.theme().colors().text_disabled
26313 } else {
26314 cx.theme().colors().text
26315 },
26316 font_family: settings.buffer_font.family.clone(),
26317 font_fallbacks: settings.buffer_font.fallbacks.clone(),
26318 font_size: settings.buffer_font_size(cx).into(),
26319 font_weight: settings.buffer_font.weight,
26320 line_height: relative(settings.buffer_line_height.value()),
26321 ..Default::default()
26322 };
26323 EditorElement::new(
26324 &self.prompt,
26325 EditorStyle {
26326 background: cx.theme().colors().editor_background,
26327 local_player: cx.theme().players().local(),
26328 text: text_style,
26329 ..Default::default()
26330 },
26331 )
26332 }
26333}
26334
26335impl Render for BreakpointPromptEditor {
26336 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26337 let editor_margins = *self.editor_margins.lock();
26338 let gutter_dimensions = editor_margins.gutter;
26339 h_flex()
26340 .key_context("Editor")
26341 .bg(cx.theme().colors().editor_background)
26342 .border_y_1()
26343 .border_color(cx.theme().status().info_border)
26344 .size_full()
26345 .py(window.line_height() / 2.5)
26346 .on_action(cx.listener(Self::confirm))
26347 .on_action(cx.listener(Self::cancel))
26348 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
26349 .child(div().flex_1().child(self.render_prompt_editor(cx)))
26350 }
26351}
26352
26353impl Focusable for BreakpointPromptEditor {
26354 fn focus_handle(&self, cx: &App) -> FocusHandle {
26355 self.prompt.focus_handle(cx)
26356 }
26357}
26358
26359fn all_edits_insertions_or_deletions(
26360 edits: &Vec<(Range<Anchor>, Arc<str>)>,
26361 snapshot: &MultiBufferSnapshot,
26362) -> bool {
26363 let mut all_insertions = true;
26364 let mut all_deletions = true;
26365
26366 for (range, new_text) in edits.iter() {
26367 let range_is_empty = range.to_offset(snapshot).is_empty();
26368 let text_is_empty = new_text.is_empty();
26369
26370 if range_is_empty != text_is_empty {
26371 if range_is_empty {
26372 all_deletions = false;
26373 } else {
26374 all_insertions = false;
26375 }
26376 } else {
26377 return false;
26378 }
26379
26380 if !all_insertions && !all_deletions {
26381 return false;
26382 }
26383 }
26384 all_insertions || all_deletions
26385}
26386
26387struct MissingEditPredictionKeybindingTooltip;
26388
26389impl Render for MissingEditPredictionKeybindingTooltip {
26390 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26391 ui::tooltip_container(cx, |container, cx| {
26392 container
26393 .flex_shrink_0()
26394 .max_w_80()
26395 .min_h(rems_from_px(124.))
26396 .justify_between()
26397 .child(
26398 v_flex()
26399 .flex_1()
26400 .text_ui_sm(cx)
26401 .child(Label::new("Conflict with Accept Keybinding"))
26402 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26403 )
26404 .child(
26405 h_flex()
26406 .pb_1()
26407 .gap_1()
26408 .items_end()
26409 .w_full()
26410 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26411 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26412 }))
26413 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26414 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26415 })),
26416 )
26417 })
26418 }
26419}
26420
26421#[derive(Debug, Clone, Copy, PartialEq)]
26422pub struct LineHighlight {
26423 pub background: Background,
26424 pub border: Option<gpui::Hsla>,
26425 pub include_gutter: bool,
26426 pub type_id: Option<TypeId>,
26427}
26428
26429struct LineManipulationResult {
26430 pub new_text: String,
26431 pub line_count_before: usize,
26432 pub line_count_after: usize,
26433}
26434
26435fn render_diff_hunk_controls(
26436 row: u32,
26437 status: &DiffHunkStatus,
26438 hunk_range: Range<Anchor>,
26439 is_created_file: bool,
26440 line_height: Pixels,
26441 editor: &Entity<Editor>,
26442 _window: &mut Window,
26443 cx: &mut App,
26444) -> AnyElement {
26445 h_flex()
26446 .h(line_height)
26447 .mr_1()
26448 .gap_1()
26449 .px_0p5()
26450 .pb_1()
26451 .border_x_1()
26452 .border_b_1()
26453 .border_color(cx.theme().colors().border_variant)
26454 .rounded_b_lg()
26455 .bg(cx.theme().colors().editor_background)
26456 .gap_1()
26457 .block_mouse_except_scroll()
26458 .shadow_md()
26459 .child(if status.has_secondary_hunk() {
26460 Button::new(("stage", row as u64), "Stage")
26461 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26462 .tooltip({
26463 let focus_handle = editor.focus_handle(cx);
26464 move |_window, cx| {
26465 Tooltip::for_action_in(
26466 "Stage Hunk",
26467 &::git::ToggleStaged,
26468 &focus_handle,
26469 cx,
26470 )
26471 }
26472 })
26473 .on_click({
26474 let editor = editor.clone();
26475 move |_event, _window, cx| {
26476 editor.update(cx, |editor, cx| {
26477 editor.stage_or_unstage_diff_hunks(
26478 true,
26479 vec![hunk_range.start..hunk_range.start],
26480 cx,
26481 );
26482 });
26483 }
26484 })
26485 } else {
26486 Button::new(("unstage", row as u64), "Unstage")
26487 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26488 .tooltip({
26489 let focus_handle = editor.focus_handle(cx);
26490 move |_window, cx| {
26491 Tooltip::for_action_in(
26492 "Unstage Hunk",
26493 &::git::ToggleStaged,
26494 &focus_handle,
26495 cx,
26496 )
26497 }
26498 })
26499 .on_click({
26500 let editor = editor.clone();
26501 move |_event, _window, cx| {
26502 editor.update(cx, |editor, cx| {
26503 editor.stage_or_unstage_diff_hunks(
26504 false,
26505 vec![hunk_range.start..hunk_range.start],
26506 cx,
26507 );
26508 });
26509 }
26510 })
26511 })
26512 .child(
26513 Button::new(("restore", row as u64), "Restore")
26514 .tooltip({
26515 let focus_handle = editor.focus_handle(cx);
26516 move |_window, cx| {
26517 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26518 }
26519 })
26520 .on_click({
26521 let editor = editor.clone();
26522 move |_event, window, cx| {
26523 editor.update(cx, |editor, cx| {
26524 let snapshot = editor.snapshot(window, cx);
26525 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26526 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26527 });
26528 }
26529 })
26530 .disabled(is_created_file),
26531 )
26532 .when(
26533 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26534 |el| {
26535 el.child(
26536 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26537 .shape(IconButtonShape::Square)
26538 .icon_size(IconSize::Small)
26539 // .disabled(!has_multiple_hunks)
26540 .tooltip({
26541 let focus_handle = editor.focus_handle(cx);
26542 move |_window, cx| {
26543 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26544 }
26545 })
26546 .on_click({
26547 let editor = editor.clone();
26548 move |_event, window, cx| {
26549 editor.update(cx, |editor, cx| {
26550 let snapshot = editor.snapshot(window, cx);
26551 let position =
26552 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26553 editor.go_to_hunk_before_or_after_position(
26554 &snapshot,
26555 position,
26556 Direction::Next,
26557 window,
26558 cx,
26559 );
26560 editor.expand_selected_diff_hunks(cx);
26561 });
26562 }
26563 }),
26564 )
26565 .child(
26566 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26567 .shape(IconButtonShape::Square)
26568 .icon_size(IconSize::Small)
26569 // .disabled(!has_multiple_hunks)
26570 .tooltip({
26571 let focus_handle = editor.focus_handle(cx);
26572 move |_window, cx| {
26573 Tooltip::for_action_in(
26574 "Previous Hunk",
26575 &GoToPreviousHunk,
26576 &focus_handle,
26577 cx,
26578 )
26579 }
26580 })
26581 .on_click({
26582 let editor = editor.clone();
26583 move |_event, window, cx| {
26584 editor.update(cx, |editor, cx| {
26585 let snapshot = editor.snapshot(window, cx);
26586 let point =
26587 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26588 editor.go_to_hunk_before_or_after_position(
26589 &snapshot,
26590 point,
26591 Direction::Prev,
26592 window,
26593 cx,
26594 );
26595 editor.expand_selected_diff_hunks(cx);
26596 });
26597 }
26598 }),
26599 )
26600 },
26601 )
26602 .into_any_element()
26603}
26604
26605pub fn multibuffer_context_lines(cx: &App) -> u32 {
26606 EditorSettings::try_get(cx)
26607 .map(|settings| settings.excerpt_context_lines)
26608 .unwrap_or(2)
26609 .min(32)
26610}