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;
15mod blink_manager;
16mod clangd_ext;
17pub mod code_context_menus;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod git;
22mod highlight_matching_bracket;
23mod hover_links;
24pub mod hover_popover;
25mod indent_guides;
26mod inlays;
27pub mod items;
28mod jsx_tag_auto_close;
29mod linked_editing_ranges;
30mod lsp_colors;
31mod lsp_ext;
32mod mouse_context_menu;
33pub mod movement;
34mod persistence;
35mod rust_analyzer_ext;
36pub mod scroll;
37mod selections_collection;
38pub mod tasks;
39
40#[cfg(test)]
41mod code_completion_tests;
42#[cfg(test)]
43mod edit_prediction_tests;
44#[cfg(test)]
45mod editor_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
52pub use edit_prediction::Direction;
53pub use editor_settings::{
54 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
55 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
56};
57pub use element::{
58 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
59};
60pub use git::blame::BlameRenderer;
61pub use hover_popover::hover_markdown_style;
62pub use inlays::Inlay;
63pub use items::MAX_TAB_TITLE_LEN;
64pub use lsp::CompletionContext;
65pub use lsp_ext::lsp_tasks;
66pub use multi_buffer::{
67 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
68 RowInfo, ToOffset, ToPoint,
69};
70pub use text::Bias;
71
72use ::git::{
73 Restore,
74 blame::{BlameEntry, ParsedCommitMessage},
75 status::FileStatus,
76};
77use aho_corasick::AhoCorasick;
78use anyhow::{Context as _, Result, anyhow};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
92use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
93use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
94use futures::{
95 FutureExt, StreamExt as _,
96 future::{self, Shared, join},
97 stream::FuturesUnordered,
98};
99use fuzzy::{StringMatch, StringMatchCandidate};
100use git::blame::{GitBlame, GlobalBlameRenderer};
101use gpui::{
102 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
103 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
104 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
105 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
106 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
107 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
108 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
109 div, point, prelude::*, pulsating_between, px, relative, size,
110};
111use hover_links::{HoverLink, HoveredLinkState, find_file};
112use hover_popover::{HoverState, hide_hover};
113use indent_guides::ActiveIndentGuidesState;
114use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
115use itertools::{Either, Itertools};
116use language::{
117 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
118 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
119 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
120 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
121 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
122 language_settings::{
123 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
124 language_settings,
125 },
126 point_from_lsp, point_to_lsp, text_diff_with_options,
127};
128use linked_editing_ranges::refresh_linked_ranges;
129use lsp::{
130 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
131 LanguageServerId,
132};
133use lsp_colors::LspColorData;
134use markdown::Markdown;
135use mouse_context_menu::MouseContextMenu;
136use movement::TextLayoutDetails;
137use multi_buffer::{
138 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
139};
140use parking_lot::Mutex;
141use persistence::DB;
142use project::{
143 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
144 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
145 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
146 ProjectPath, ProjectTransaction, TaskSourceKind,
147 debugger::{
148 breakpoint_store::{
149 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
150 BreakpointStore, BreakpointStoreEvent,
151 },
152 session::{Session, SessionEvent},
153 },
154 git_store::GitStoreEvent,
155 lsp_store::{
156 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
157 OpenLspBufferHandle,
158 },
159 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
160};
161use rand::seq::SliceRandom;
162use rpc::{ErrorCode, ErrorExt, proto::PeerId};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
164use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
165use serde::{Deserialize, Serialize};
166use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
167use smallvec::{SmallVec, smallvec};
168use snippet::Snippet;
169use std::{
170 any::{Any, TypeId},
171 borrow::Cow,
172 cell::{OnceCell, RefCell},
173 cmp::{self, Ordering, Reverse},
174 iter::{self, Peekable},
175 mem,
176 num::NonZeroU32,
177 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
178 path::{Path, PathBuf},
179 rc::Rc,
180 sync::Arc,
181 time::{Duration, Instant},
182};
183use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
184use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
185use theme::{
186 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
187 observe_buffer_font_size_adjustment,
188};
189use ui::{
190 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
191 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
192};
193use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
194use workspace::{
195 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
196 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
197 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
198 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
199 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
200 searchable::SearchEvent,
201};
202
203use crate::{
204 code_context_menus::CompletionsMenuSource,
205 editor_settings::MultiCursorModifier,
206 hover_links::{find_url, find_url_from_range},
207 inlays::{
208 InlineValueCache,
209 inlay_hints::{LspInlayHintData, inlay_hint_settings},
210 },
211 scroll::{ScrollOffset, ScrollPixelOffset},
212 selections_collection::resolve_selections_wrapping_blocks,
213 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
214};
215
216pub const FILE_HEADER_HEIGHT: u32 = 2;
217pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
218const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
219const MAX_LINE_LEN: usize = 1024;
220const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
221const MAX_SELECTION_HISTORY_LEN: usize = 1024;
222pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
223#[doc(hidden)]
224pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
225pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
226
227pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
228pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
229pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
230pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
231
232pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
233pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
234pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
235
236pub type RenderDiffHunkControlsFn = Arc<
237 dyn Fn(
238 u32,
239 &DiffHunkStatus,
240 Range<Anchor>,
241 bool,
242 Pixels,
243 &Entity<Editor>,
244 &mut Window,
245 &mut App,
246 ) -> AnyElement,
247>;
248
249enum ReportEditorEvent {
250 Saved { auto_saved: bool },
251 EditorOpened,
252 Closed,
253}
254
255impl ReportEditorEvent {
256 pub fn event_type(&self) -> &'static str {
257 match self {
258 Self::Saved { .. } => "Editor Saved",
259 Self::EditorOpened => "Editor Opened",
260 Self::Closed => "Editor Closed",
261 }
262 }
263}
264
265pub enum ActiveDebugLine {}
266pub enum DebugStackFrameLine {}
267enum DocumentHighlightRead {}
268enum DocumentHighlightWrite {}
269enum InputComposition {}
270pub enum PendingInput {}
271enum SelectedTextHighlight {}
272
273pub enum ConflictsOuter {}
274pub enum ConflictsOurs {}
275pub enum ConflictsTheirs {}
276pub enum ConflictsOursMarker {}
277pub enum ConflictsTheirsMarker {}
278
279#[derive(Debug, Copy, Clone, PartialEq, Eq)]
280pub enum Navigated {
281 Yes,
282 No,
283}
284
285impl Navigated {
286 pub fn from_bool(yes: bool) -> Navigated {
287 if yes { Navigated::Yes } else { Navigated::No }
288 }
289}
290
291#[derive(Debug, Clone, PartialEq, Eq)]
292enum DisplayDiffHunk {
293 Folded {
294 display_row: DisplayRow,
295 },
296 Unfolded {
297 is_created_file: bool,
298 diff_base_byte_range: Range<usize>,
299 display_row_range: Range<DisplayRow>,
300 multi_buffer_range: Range<Anchor>,
301 status: DiffHunkStatus,
302 },
303}
304
305pub enum HideMouseCursorOrigin {
306 TypingAction,
307 MovementAction,
308}
309
310pub fn init_settings(cx: &mut App) {
311 EditorSettings::register(cx);
312}
313
314pub fn init(cx: &mut App) {
315 init_settings(cx);
316
317 cx.set_global(GlobalBlameRenderer(Arc::new(())));
318
319 workspace::register_project_item::<Editor>(cx);
320 workspace::FollowableViewRegistry::register::<Editor>(cx);
321 workspace::register_serializable_item::<Editor>(cx);
322
323 cx.observe_new(
324 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
325 workspace.register_action(Editor::new_file);
326 workspace.register_action(Editor::new_file_split);
327 workspace.register_action(Editor::new_file_vertical);
328 workspace.register_action(Editor::new_file_horizontal);
329 workspace.register_action(Editor::cancel_language_server_work);
330 workspace.register_action(Editor::toggle_focus);
331 },
332 )
333 .detach();
334
335 cx.on_action(move |_: &workspace::NewFile, cx| {
336 let app_state = workspace::AppState::global(cx);
337 if let Some(app_state) = app_state.upgrade() {
338 workspace::open_new(
339 Default::default(),
340 app_state,
341 cx,
342 |workspace, window, cx| {
343 Editor::new_file(workspace, &Default::default(), window, cx)
344 },
345 )
346 .detach();
347 }
348 });
349 cx.on_action(move |_: &workspace::NewWindow, cx| {
350 let app_state = workspace::AppState::global(cx);
351 if let Some(app_state) = app_state.upgrade() {
352 workspace::open_new(
353 Default::default(),
354 app_state,
355 cx,
356 |workspace, window, cx| {
357 cx.activate(true);
358 Editor::new_file(workspace, &Default::default(), window, cx)
359 },
360 )
361 .detach();
362 }
363 });
364}
365
366pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
367 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
368}
369
370pub trait DiagnosticRenderer {
371 fn render_group(
372 &self,
373 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
374 buffer_id: BufferId,
375 snapshot: EditorSnapshot,
376 editor: WeakEntity<Editor>,
377 cx: &mut App,
378 ) -> Vec<BlockProperties<Anchor>>;
379
380 fn render_hover(
381 &self,
382 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
383 range: Range<Point>,
384 buffer_id: BufferId,
385 cx: &mut App,
386 ) -> Option<Entity<markdown::Markdown>>;
387
388 fn open_link(
389 &self,
390 editor: &mut Editor,
391 link: SharedString,
392 window: &mut Window,
393 cx: &mut Context<Editor>,
394 );
395}
396
397pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
398
399impl GlobalDiagnosticRenderer {
400 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
401 cx.try_global::<Self>().map(|g| g.0.clone())
402 }
403}
404
405impl gpui::Global for GlobalDiagnosticRenderer {}
406pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
407 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
408}
409
410pub struct SearchWithinRange;
411
412trait InvalidationRegion {
413 fn ranges(&self) -> &[Range<Anchor>];
414}
415
416#[derive(Clone, Debug, PartialEq)]
417pub enum SelectPhase {
418 Begin {
419 position: DisplayPoint,
420 add: bool,
421 click_count: usize,
422 },
423 BeginColumnar {
424 position: DisplayPoint,
425 reset: bool,
426 mode: ColumnarMode,
427 goal_column: u32,
428 },
429 Extend {
430 position: DisplayPoint,
431 click_count: usize,
432 },
433 Update {
434 position: DisplayPoint,
435 goal_column: u32,
436 scroll_delta: gpui::Point<f32>,
437 },
438 End,
439}
440
441#[derive(Clone, Debug, PartialEq)]
442pub enum ColumnarMode {
443 FromMouse,
444 FromSelection,
445}
446
447#[derive(Clone, Debug)]
448pub enum SelectMode {
449 Character,
450 Word(Range<Anchor>),
451 Line(Range<Anchor>),
452 All,
453}
454
455#[derive(Clone, PartialEq, Eq, Debug)]
456pub enum EditorMode {
457 SingleLine,
458 AutoHeight {
459 min_lines: usize,
460 max_lines: Option<usize>,
461 },
462 Full {
463 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
464 scale_ui_elements_with_buffer_font_size: bool,
465 /// When set to `true`, the editor will render a background for the active line.
466 show_active_line_background: bool,
467 /// When set to `true`, the editor's height will be determined by its content.
468 sized_by_content: bool,
469 },
470 Minimap {
471 parent: WeakEntity<Editor>,
472 },
473}
474
475impl EditorMode {
476 pub fn full() -> Self {
477 Self::Full {
478 scale_ui_elements_with_buffer_font_size: true,
479 show_active_line_background: true,
480 sized_by_content: false,
481 }
482 }
483
484 #[inline]
485 pub fn is_full(&self) -> bool {
486 matches!(self, Self::Full { .. })
487 }
488
489 #[inline]
490 pub fn is_single_line(&self) -> bool {
491 matches!(self, Self::SingleLine { .. })
492 }
493
494 #[inline]
495 fn is_minimap(&self) -> bool {
496 matches!(self, Self::Minimap { .. })
497 }
498}
499
500#[derive(Copy, Clone, Debug)]
501pub enum SoftWrap {
502 /// Prefer not to wrap at all.
503 ///
504 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
505 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
506 GitDiff,
507 /// Prefer a single line generally, unless an overly long line is encountered.
508 None,
509 /// Soft wrap lines that exceed the editor width.
510 EditorWidth,
511 /// Soft wrap lines at the preferred line length.
512 Column(u32),
513 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
514 Bounded(u32),
515}
516
517#[derive(Clone)]
518pub struct EditorStyle {
519 pub background: Hsla,
520 pub border: Hsla,
521 pub local_player: PlayerColor,
522 pub text: TextStyle,
523 pub scrollbar_width: Pixels,
524 pub syntax: Arc<SyntaxTheme>,
525 pub status: StatusColors,
526 pub inlay_hints_style: HighlightStyle,
527 pub edit_prediction_styles: EditPredictionStyles,
528 pub unnecessary_code_fade: f32,
529 pub show_underlines: bool,
530}
531
532impl Default for EditorStyle {
533 fn default() -> Self {
534 Self {
535 background: Hsla::default(),
536 border: Hsla::default(),
537 local_player: PlayerColor::default(),
538 text: TextStyle::default(),
539 scrollbar_width: Pixels::default(),
540 syntax: Default::default(),
541 // HACK: Status colors don't have a real default.
542 // We should look into removing the status colors from the editor
543 // style and retrieve them directly from the theme.
544 status: StatusColors::dark(),
545 inlay_hints_style: HighlightStyle::default(),
546 edit_prediction_styles: EditPredictionStyles {
547 insertion: HighlightStyle::default(),
548 whitespace: HighlightStyle::default(),
549 },
550 unnecessary_code_fade: Default::default(),
551 show_underlines: true,
552 }
553 }
554}
555
556pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
557 let show_background = language_settings::language_settings(None, None, cx)
558 .inlay_hints
559 .show_background;
560
561 let mut style = cx.theme().syntax().get("hint");
562
563 if style.color.is_none() {
564 style.color = Some(cx.theme().status().hint);
565 }
566
567 if !show_background {
568 style.background_color = None;
569 return style;
570 }
571
572 if style.background_color.is_none() {
573 style.background_color = Some(cx.theme().status().hint_background);
574 }
575
576 style
577}
578
579pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
580 EditPredictionStyles {
581 insertion: HighlightStyle {
582 color: Some(cx.theme().status().predictive),
583 ..HighlightStyle::default()
584 },
585 whitespace: HighlightStyle {
586 background_color: Some(cx.theme().status().created_background),
587 ..HighlightStyle::default()
588 },
589 }
590}
591
592type CompletionId = usize;
593
594pub(crate) enum EditDisplayMode {
595 TabAccept,
596 DiffPopover,
597 Inline,
598}
599
600enum EditPrediction {
601 Edit {
602 edits: Vec<(Range<Anchor>, String)>,
603 edit_preview: Option<EditPreview>,
604 display_mode: EditDisplayMode,
605 snapshot: BufferSnapshot,
606 },
607 /// Move to a specific location in the active editor
608 MoveWithin {
609 target: Anchor,
610 snapshot: BufferSnapshot,
611 },
612 /// Move to a specific location in a different editor (not the active one)
613 MoveOutside {
614 target: language::Anchor,
615 snapshot: BufferSnapshot,
616 },
617}
618
619struct EditPredictionState {
620 inlay_ids: Vec<InlayId>,
621 completion: EditPrediction,
622 completion_id: Option<SharedString>,
623 invalidation_range: Option<Range<Anchor>>,
624}
625
626enum EditPredictionSettings {
627 Disabled,
628 Enabled {
629 show_in_menu: bool,
630 preview_requires_modifier: bool,
631 },
632}
633
634enum EditPredictionHighlight {}
635
636#[derive(Debug, Clone)]
637struct InlineDiagnostic {
638 message: SharedString,
639 group_id: usize,
640 is_primary: bool,
641 start: Point,
642 severity: lsp::DiagnosticSeverity,
643}
644
645pub enum MenuEditPredictionsPolicy {
646 Never,
647 ByProvider,
648}
649
650pub enum EditPredictionPreview {
651 /// Modifier is not pressed
652 Inactive { released_too_fast: bool },
653 /// Modifier pressed
654 Active {
655 since: Instant,
656 previous_scroll_position: Option<ScrollAnchor>,
657 },
658}
659
660impl EditPredictionPreview {
661 pub fn released_too_fast(&self) -> bool {
662 match self {
663 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
664 EditPredictionPreview::Active { .. } => false,
665 }
666 }
667
668 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
669 if let EditPredictionPreview::Active {
670 previous_scroll_position,
671 ..
672 } = self
673 {
674 *previous_scroll_position = scroll_position;
675 }
676 }
677}
678
679pub struct ContextMenuOptions {
680 pub min_entries_visible: usize,
681 pub max_entries_visible: usize,
682 pub placement: Option<ContextMenuPlacement>,
683}
684
685#[derive(Debug, Clone, PartialEq, Eq)]
686pub enum ContextMenuPlacement {
687 Above,
688 Below,
689}
690
691#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
692struct EditorActionId(usize);
693
694impl EditorActionId {
695 pub fn post_inc(&mut self) -> Self {
696 let answer = self.0;
697
698 *self = Self(answer + 1);
699
700 Self(answer)
701 }
702}
703
704// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
705// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
706
707type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
708type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
709
710#[derive(Default)]
711struct ScrollbarMarkerState {
712 scrollbar_size: Size<Pixels>,
713 dirty: bool,
714 markers: Arc<[PaintQuad]>,
715 pending_refresh: Option<Task<Result<()>>>,
716}
717
718impl ScrollbarMarkerState {
719 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
720 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
721 }
722}
723
724#[derive(Clone, Copy, PartialEq, Eq)]
725pub enum MinimapVisibility {
726 Disabled,
727 Enabled {
728 /// The configuration currently present in the users settings.
729 setting_configuration: bool,
730 /// Whether to override the currently set visibility from the users setting.
731 toggle_override: bool,
732 },
733}
734
735impl MinimapVisibility {
736 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
737 if mode.is_full() {
738 Self::Enabled {
739 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
740 toggle_override: false,
741 }
742 } else {
743 Self::Disabled
744 }
745 }
746
747 fn hidden(&self) -> Self {
748 match *self {
749 Self::Enabled {
750 setting_configuration,
751 ..
752 } => Self::Enabled {
753 setting_configuration,
754 toggle_override: setting_configuration,
755 },
756 Self::Disabled => Self::Disabled,
757 }
758 }
759
760 fn disabled(&self) -> bool {
761 matches!(*self, Self::Disabled)
762 }
763
764 fn settings_visibility(&self) -> bool {
765 match *self {
766 Self::Enabled {
767 setting_configuration,
768 ..
769 } => setting_configuration,
770 _ => false,
771 }
772 }
773
774 fn visible(&self) -> bool {
775 match *self {
776 Self::Enabled {
777 setting_configuration,
778 toggle_override,
779 } => setting_configuration ^ toggle_override,
780 _ => false,
781 }
782 }
783
784 fn toggle_visibility(&self) -> Self {
785 match *self {
786 Self::Enabled {
787 toggle_override,
788 setting_configuration,
789 } => Self::Enabled {
790 setting_configuration,
791 toggle_override: !toggle_override,
792 },
793 Self::Disabled => Self::Disabled,
794 }
795 }
796}
797
798#[derive(Clone, Debug)]
799struct RunnableTasks {
800 templates: Vec<(TaskSourceKind, TaskTemplate)>,
801 offset: multi_buffer::Anchor,
802 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
803 column: u32,
804 // Values of all named captures, including those starting with '_'
805 extra_variables: HashMap<String, String>,
806 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
807 context_range: Range<BufferOffset>,
808}
809
810impl RunnableTasks {
811 fn resolve<'a>(
812 &'a self,
813 cx: &'a task::TaskContext,
814 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
815 self.templates.iter().filter_map(|(kind, template)| {
816 template
817 .resolve_task(&kind.to_id_base(), cx)
818 .map(|task| (kind.clone(), task))
819 })
820 }
821}
822
823#[derive(Clone)]
824pub struct ResolvedTasks {
825 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
826 position: Anchor,
827}
828
829#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
830struct BufferOffset(usize);
831
832/// Addons allow storing per-editor state in other crates (e.g. Vim)
833pub trait Addon: 'static {
834 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
835
836 fn render_buffer_header_controls(
837 &self,
838 _: &ExcerptInfo,
839 _: &Window,
840 _: &App,
841 ) -> Option<AnyElement> {
842 None
843 }
844
845 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
846 None
847 }
848
849 fn to_any(&self) -> &dyn std::any::Any;
850
851 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
852 None
853 }
854}
855
856struct ChangeLocation {
857 current: Option<Vec<Anchor>>,
858 original: Vec<Anchor>,
859}
860impl ChangeLocation {
861 fn locations(&self) -> &[Anchor] {
862 self.current.as_ref().unwrap_or(&self.original)
863 }
864}
865
866/// A set of caret positions, registered when the editor was edited.
867pub struct ChangeList {
868 changes: Vec<ChangeLocation>,
869 /// Currently "selected" change.
870 position: Option<usize>,
871}
872
873impl ChangeList {
874 pub fn new() -> Self {
875 Self {
876 changes: Vec::new(),
877 position: None,
878 }
879 }
880
881 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
882 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
883 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
884 if self.changes.is_empty() {
885 return None;
886 }
887
888 let prev = self.position.unwrap_or(self.changes.len());
889 let next = if direction == Direction::Prev {
890 prev.saturating_sub(count)
891 } else {
892 (prev + count).min(self.changes.len() - 1)
893 };
894 self.position = Some(next);
895 self.changes.get(next).map(|change| change.locations())
896 }
897
898 /// Adds a new change to the list, resetting the change list position.
899 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
900 self.position.take();
901 if let Some(last) = self.changes.last_mut()
902 && group
903 {
904 last.current = Some(new_positions)
905 } else {
906 self.changes.push(ChangeLocation {
907 original: new_positions,
908 current: None,
909 });
910 }
911 }
912
913 pub fn last(&self) -> Option<&[Anchor]> {
914 self.changes.last().map(|change| change.locations())
915 }
916
917 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
918 self.changes.last().map(|change| change.original.as_slice())
919 }
920
921 pub fn invert_last_group(&mut self) {
922 if let Some(last) = self.changes.last_mut()
923 && let Some(current) = last.current.as_mut()
924 {
925 mem::swap(&mut last.original, current);
926 }
927 }
928}
929
930#[derive(Clone)]
931struct InlineBlamePopoverState {
932 scroll_handle: ScrollHandle,
933 commit_message: Option<ParsedCommitMessage>,
934 markdown: Entity<Markdown>,
935}
936
937struct InlineBlamePopover {
938 position: gpui::Point<Pixels>,
939 hide_task: Option<Task<()>>,
940 popover_bounds: Option<Bounds<Pixels>>,
941 popover_state: InlineBlamePopoverState,
942 keyboard_grace: bool,
943}
944
945enum SelectionDragState {
946 /// State when no drag related activity is detected.
947 None,
948 /// State when the mouse is down on a selection that is about to be dragged.
949 ReadyToDrag {
950 selection: Selection<Anchor>,
951 click_position: gpui::Point<Pixels>,
952 mouse_down_time: Instant,
953 },
954 /// State when the mouse is dragging the selection in the editor.
955 Dragging {
956 selection: Selection<Anchor>,
957 drop_cursor: Selection<Anchor>,
958 hide_drop_cursor: bool,
959 },
960}
961
962enum ColumnarSelectionState {
963 FromMouse {
964 selection_tail: Anchor,
965 display_point: Option<DisplayPoint>,
966 },
967 FromSelection {
968 selection_tail: Anchor,
969 },
970}
971
972/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
973/// a breakpoint on them.
974#[derive(Clone, Copy, Debug, PartialEq, Eq)]
975struct PhantomBreakpointIndicator {
976 display_row: DisplayRow,
977 /// There's a small debounce between hovering over the line and showing the indicator.
978 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
979 is_active: bool,
980 collides_with_existing_breakpoint: bool,
981}
982
983/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
984///
985/// See the [module level documentation](self) for more information.
986pub struct Editor {
987 focus_handle: FocusHandle,
988 last_focused_descendant: Option<WeakFocusHandle>,
989 /// The text buffer being edited
990 buffer: Entity<MultiBuffer>,
991 /// Map of how text in the buffer should be displayed.
992 /// Handles soft wraps, folds, fake inlay text insertions, etc.
993 pub display_map: Entity<DisplayMap>,
994 placeholder_display_map: Option<Entity<DisplayMap>>,
995 pub selections: SelectionsCollection,
996 pub scroll_manager: ScrollManager,
997 /// When inline assist editors are linked, they all render cursors because
998 /// typing enters text into each of them, even the ones that aren't focused.
999 pub(crate) show_cursor_when_unfocused: bool,
1000 columnar_selection_state: Option<ColumnarSelectionState>,
1001 add_selections_state: Option<AddSelectionsState>,
1002 select_next_state: Option<SelectNextState>,
1003 select_prev_state: Option<SelectNextState>,
1004 selection_history: SelectionHistory,
1005 defer_selection_effects: bool,
1006 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1007 autoclose_regions: Vec<AutocloseRegion>,
1008 snippet_stack: InvalidationStack<SnippetState>,
1009 select_syntax_node_history: SelectSyntaxNodeHistory,
1010 ime_transaction: Option<TransactionId>,
1011 pub diagnostics_max_severity: DiagnosticSeverity,
1012 active_diagnostics: ActiveDiagnostic,
1013 show_inline_diagnostics: bool,
1014 inline_diagnostics_update: Task<()>,
1015 inline_diagnostics_enabled: bool,
1016 diagnostics_enabled: bool,
1017 word_completions_enabled: bool,
1018 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1019 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1020 hard_wrap: Option<usize>,
1021 project: Option<Entity<Project>>,
1022 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1023 completion_provider: Option<Rc<dyn CompletionProvider>>,
1024 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1025 blink_manager: Entity<BlinkManager>,
1026 show_cursor_names: bool,
1027 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1028 pub show_local_selections: bool,
1029 mode: EditorMode,
1030 show_breadcrumbs: bool,
1031 show_gutter: bool,
1032 show_scrollbars: ScrollbarAxes,
1033 minimap_visibility: MinimapVisibility,
1034 offset_content: bool,
1035 disable_expand_excerpt_buttons: bool,
1036 show_line_numbers: Option<bool>,
1037 use_relative_line_numbers: Option<bool>,
1038 show_git_diff_gutter: Option<bool>,
1039 show_code_actions: Option<bool>,
1040 show_runnables: Option<bool>,
1041 show_breakpoints: Option<bool>,
1042 show_wrap_guides: Option<bool>,
1043 show_indent_guides: Option<bool>,
1044 highlight_order: usize,
1045 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1046 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1047 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1048 scrollbar_marker_state: ScrollbarMarkerState,
1049 active_indent_guides_state: ActiveIndentGuidesState,
1050 nav_history: Option<ItemNavHistory>,
1051 context_menu: RefCell<Option<CodeContextMenu>>,
1052 context_menu_options: Option<ContextMenuOptions>,
1053 mouse_context_menu: Option<MouseContextMenu>,
1054 completion_tasks: Vec<(CompletionId, Task<()>)>,
1055 inline_blame_popover: Option<InlineBlamePopover>,
1056 inline_blame_popover_show_task: Option<Task<()>>,
1057 signature_help_state: SignatureHelpState,
1058 auto_signature_help: Option<bool>,
1059 find_all_references_task_sources: Vec<Anchor>,
1060 next_completion_id: CompletionId,
1061 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1062 code_actions_task: Option<Task<Result<()>>>,
1063 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1064 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1065 document_highlights_task: Option<Task<()>>,
1066 linked_editing_range_task: Option<Task<Option<()>>>,
1067 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1068 pending_rename: Option<RenameState>,
1069 searchable: bool,
1070 cursor_shape: CursorShape,
1071 current_line_highlight: Option<CurrentLineHighlight>,
1072 collapse_matches: bool,
1073 autoindent_mode: Option<AutoindentMode>,
1074 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1075 input_enabled: bool,
1076 use_modal_editing: bool,
1077 read_only: bool,
1078 leader_id: Option<CollaboratorId>,
1079 remote_id: Option<ViewId>,
1080 pub hover_state: HoverState,
1081 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1082 gutter_hovered: bool,
1083 hovered_link_state: Option<HoveredLinkState>,
1084 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1085 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1086 active_edit_prediction: Option<EditPredictionState>,
1087 /// Used to prevent flickering as the user types while the menu is open
1088 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1089 edit_prediction_settings: EditPredictionSettings,
1090 edit_predictions_hidden_for_vim_mode: bool,
1091 show_edit_predictions_override: Option<bool>,
1092 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1093 edit_prediction_preview: EditPredictionPreview,
1094 edit_prediction_indent_conflict: bool,
1095 edit_prediction_requires_modifier_in_indent_conflict: bool,
1096 next_inlay_id: usize,
1097 next_color_inlay_id: usize,
1098 _subscriptions: Vec<Subscription>,
1099 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1100 gutter_dimensions: GutterDimensions,
1101 style: Option<EditorStyle>,
1102 text_style_refinement: Option<TextStyleRefinement>,
1103 next_editor_action_id: EditorActionId,
1104 editor_actions: Rc<
1105 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1106 >,
1107 use_autoclose: bool,
1108 use_auto_surround: bool,
1109 auto_replace_emoji_shortcode: bool,
1110 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1111 show_git_blame_gutter: bool,
1112 show_git_blame_inline: bool,
1113 show_git_blame_inline_delay_task: Option<Task<()>>,
1114 git_blame_inline_enabled: bool,
1115 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1116 serialize_dirty_buffers: bool,
1117 show_selection_menu: Option<bool>,
1118 blame: Option<Entity<GitBlame>>,
1119 blame_subscription: Option<Subscription>,
1120 custom_context_menu: Option<
1121 Box<
1122 dyn 'static
1123 + Fn(
1124 &mut Self,
1125 DisplayPoint,
1126 &mut Window,
1127 &mut Context<Self>,
1128 ) -> Option<Entity<ui::ContextMenu>>,
1129 >,
1130 >,
1131 last_bounds: Option<Bounds<Pixels>>,
1132 last_position_map: Option<Rc<PositionMap>>,
1133 expect_bounds_change: Option<Bounds<Pixels>>,
1134 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1135 tasks_update_task: Option<Task<()>>,
1136 breakpoint_store: Option<Entity<BreakpointStore>>,
1137 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1138 hovered_diff_hunk_row: Option<DisplayRow>,
1139 pull_diagnostics_task: Task<()>,
1140 in_project_search: bool,
1141 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1142 breadcrumb_header: Option<String>,
1143 focused_block: Option<FocusedBlock>,
1144 next_scroll_position: NextScrollCursorCenterTopBottom,
1145 addons: HashMap<TypeId, Box<dyn Addon>>,
1146 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1147 load_diff_task: Option<Shared<Task<()>>>,
1148 /// Whether we are temporarily displaying a diff other than git's
1149 temporary_diff_override: bool,
1150 selection_mark_mode: bool,
1151 toggle_fold_multiple_buffers: Task<()>,
1152 _scroll_cursor_center_top_bottom_task: Task<()>,
1153 serialize_selections: Task<()>,
1154 serialize_folds: Task<()>,
1155 mouse_cursor_hidden: bool,
1156 minimap: Option<Entity<Self>>,
1157 hide_mouse_mode: HideMouseMode,
1158 pub change_list: ChangeList,
1159 inline_value_cache: InlineValueCache,
1160 selection_drag_state: SelectionDragState,
1161 colors: Option<LspColorData>,
1162 post_scroll_update: Task<()>,
1163 refresh_colors_task: Task<()>,
1164 inlay_hints: Option<LspInlayHintData>,
1165 folding_newlines: Task<()>,
1166 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1167}
1168
1169fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1170 if debounce_ms > 0 {
1171 Some(Duration::from_millis(debounce_ms))
1172 } else {
1173 None
1174 }
1175}
1176
1177#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1178enum NextScrollCursorCenterTopBottom {
1179 #[default]
1180 Center,
1181 Top,
1182 Bottom,
1183}
1184
1185impl NextScrollCursorCenterTopBottom {
1186 fn next(&self) -> Self {
1187 match self {
1188 Self::Center => Self::Top,
1189 Self::Top => Self::Bottom,
1190 Self::Bottom => Self::Center,
1191 }
1192 }
1193}
1194
1195#[derive(Clone)]
1196pub struct EditorSnapshot {
1197 pub mode: EditorMode,
1198 show_gutter: bool,
1199 show_line_numbers: Option<bool>,
1200 show_git_diff_gutter: Option<bool>,
1201 show_code_actions: Option<bool>,
1202 show_runnables: Option<bool>,
1203 show_breakpoints: Option<bool>,
1204 git_blame_gutter_max_author_length: Option<usize>,
1205 pub display_snapshot: DisplaySnapshot,
1206 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1207 is_focused: bool,
1208 scroll_anchor: ScrollAnchor,
1209 ongoing_scroll: OngoingScroll,
1210 current_line_highlight: CurrentLineHighlight,
1211 gutter_hovered: bool,
1212}
1213
1214#[derive(Default, Debug, Clone, Copy)]
1215pub struct GutterDimensions {
1216 pub left_padding: Pixels,
1217 pub right_padding: Pixels,
1218 pub width: Pixels,
1219 pub margin: Pixels,
1220 pub git_blame_entries_width: Option<Pixels>,
1221}
1222
1223impl GutterDimensions {
1224 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1225 Self {
1226 margin: Self::default_gutter_margin(font_id, font_size, cx),
1227 ..Default::default()
1228 }
1229 }
1230
1231 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1232 -cx.text_system().descent(font_id, font_size)
1233 }
1234 /// The full width of the space taken up by the gutter.
1235 pub fn full_width(&self) -> Pixels {
1236 self.margin + self.width
1237 }
1238
1239 /// The width of the space reserved for the fold indicators,
1240 /// use alongside 'justify_end' and `gutter_width` to
1241 /// right align content with the line numbers
1242 pub fn fold_area_width(&self) -> Pixels {
1243 self.margin + self.right_padding
1244 }
1245}
1246
1247struct CharacterDimensions {
1248 em_width: Pixels,
1249 em_advance: Pixels,
1250 line_height: Pixels,
1251}
1252
1253#[derive(Debug)]
1254pub struct RemoteSelection {
1255 pub replica_id: ReplicaId,
1256 pub selection: Selection<Anchor>,
1257 pub cursor_shape: CursorShape,
1258 pub collaborator_id: CollaboratorId,
1259 pub line_mode: bool,
1260 pub user_name: Option<SharedString>,
1261 pub color: PlayerColor,
1262}
1263
1264#[derive(Clone, Debug)]
1265struct SelectionHistoryEntry {
1266 selections: Arc<[Selection<Anchor>]>,
1267 select_next_state: Option<SelectNextState>,
1268 select_prev_state: Option<SelectNextState>,
1269 add_selections_state: Option<AddSelectionsState>,
1270}
1271
1272#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1273enum SelectionHistoryMode {
1274 Normal,
1275 Undoing,
1276 Redoing,
1277 Skipping,
1278}
1279
1280#[derive(Clone, PartialEq, Eq, Hash)]
1281struct HoveredCursor {
1282 replica_id: ReplicaId,
1283 selection_id: usize,
1284}
1285
1286impl Default for SelectionHistoryMode {
1287 fn default() -> Self {
1288 Self::Normal
1289 }
1290}
1291
1292#[derive(Debug)]
1293/// SelectionEffects controls the side-effects of updating the selection.
1294///
1295/// The default behaviour does "what you mostly want":
1296/// - it pushes to the nav history if the cursor moved by >10 lines
1297/// - it re-triggers completion requests
1298/// - it scrolls to fit
1299///
1300/// You might want to modify these behaviours. For example when doing a "jump"
1301/// like go to definition, we always want to add to nav history; but when scrolling
1302/// in vim mode we never do.
1303///
1304/// Similarly, you might want to disable scrolling if you don't want the viewport to
1305/// move.
1306#[derive(Clone)]
1307pub struct SelectionEffects {
1308 nav_history: Option<bool>,
1309 completions: bool,
1310 scroll: Option<Autoscroll>,
1311}
1312
1313impl Default for SelectionEffects {
1314 fn default() -> Self {
1315 Self {
1316 nav_history: None,
1317 completions: true,
1318 scroll: Some(Autoscroll::fit()),
1319 }
1320 }
1321}
1322impl SelectionEffects {
1323 pub fn scroll(scroll: Autoscroll) -> Self {
1324 Self {
1325 scroll: Some(scroll),
1326 ..Default::default()
1327 }
1328 }
1329
1330 pub fn no_scroll() -> Self {
1331 Self {
1332 scroll: None,
1333 ..Default::default()
1334 }
1335 }
1336
1337 pub fn completions(self, completions: bool) -> Self {
1338 Self {
1339 completions,
1340 ..self
1341 }
1342 }
1343
1344 pub fn nav_history(self, nav_history: bool) -> Self {
1345 Self {
1346 nav_history: Some(nav_history),
1347 ..self
1348 }
1349 }
1350}
1351
1352struct DeferredSelectionEffectsState {
1353 changed: bool,
1354 effects: SelectionEffects,
1355 old_cursor_position: Anchor,
1356 history_entry: SelectionHistoryEntry,
1357}
1358
1359#[derive(Default)]
1360struct SelectionHistory {
1361 #[allow(clippy::type_complexity)]
1362 selections_by_transaction:
1363 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1364 mode: SelectionHistoryMode,
1365 undo_stack: VecDeque<SelectionHistoryEntry>,
1366 redo_stack: VecDeque<SelectionHistoryEntry>,
1367}
1368
1369impl SelectionHistory {
1370 #[track_caller]
1371 fn insert_transaction(
1372 &mut self,
1373 transaction_id: TransactionId,
1374 selections: Arc<[Selection<Anchor>]>,
1375 ) {
1376 if selections.is_empty() {
1377 log::error!(
1378 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1379 std::panic::Location::caller()
1380 );
1381 return;
1382 }
1383 self.selections_by_transaction
1384 .insert(transaction_id, (selections, None));
1385 }
1386
1387 #[allow(clippy::type_complexity)]
1388 fn transaction(
1389 &self,
1390 transaction_id: TransactionId,
1391 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1392 self.selections_by_transaction.get(&transaction_id)
1393 }
1394
1395 #[allow(clippy::type_complexity)]
1396 fn transaction_mut(
1397 &mut self,
1398 transaction_id: TransactionId,
1399 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1400 self.selections_by_transaction.get_mut(&transaction_id)
1401 }
1402
1403 fn push(&mut self, entry: SelectionHistoryEntry) {
1404 if !entry.selections.is_empty() {
1405 match self.mode {
1406 SelectionHistoryMode::Normal => {
1407 self.push_undo(entry);
1408 self.redo_stack.clear();
1409 }
1410 SelectionHistoryMode::Undoing => self.push_redo(entry),
1411 SelectionHistoryMode::Redoing => self.push_undo(entry),
1412 SelectionHistoryMode::Skipping => {}
1413 }
1414 }
1415 }
1416
1417 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1418 if self
1419 .undo_stack
1420 .back()
1421 .is_none_or(|e| e.selections != entry.selections)
1422 {
1423 self.undo_stack.push_back(entry);
1424 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1425 self.undo_stack.pop_front();
1426 }
1427 }
1428 }
1429
1430 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1431 if self
1432 .redo_stack
1433 .back()
1434 .is_none_or(|e| e.selections != entry.selections)
1435 {
1436 self.redo_stack.push_back(entry);
1437 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1438 self.redo_stack.pop_front();
1439 }
1440 }
1441 }
1442}
1443
1444#[derive(Clone, Copy)]
1445pub struct RowHighlightOptions {
1446 pub autoscroll: bool,
1447 pub include_gutter: bool,
1448}
1449
1450impl Default for RowHighlightOptions {
1451 fn default() -> Self {
1452 Self {
1453 autoscroll: Default::default(),
1454 include_gutter: true,
1455 }
1456 }
1457}
1458
1459struct RowHighlight {
1460 index: usize,
1461 range: Range<Anchor>,
1462 color: Hsla,
1463 options: RowHighlightOptions,
1464 type_id: TypeId,
1465}
1466
1467#[derive(Clone, Debug)]
1468struct AddSelectionsState {
1469 groups: Vec<AddSelectionsGroup>,
1470}
1471
1472#[derive(Clone, Debug)]
1473struct AddSelectionsGroup {
1474 above: bool,
1475 stack: Vec<usize>,
1476}
1477
1478#[derive(Clone)]
1479struct SelectNextState {
1480 query: AhoCorasick,
1481 wordwise: bool,
1482 done: bool,
1483}
1484
1485impl std::fmt::Debug for SelectNextState {
1486 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1487 f.debug_struct(std::any::type_name::<Self>())
1488 .field("wordwise", &self.wordwise)
1489 .field("done", &self.done)
1490 .finish()
1491 }
1492}
1493
1494#[derive(Debug)]
1495struct AutocloseRegion {
1496 selection_id: usize,
1497 range: Range<Anchor>,
1498 pair: BracketPair,
1499}
1500
1501#[derive(Debug)]
1502struct SnippetState {
1503 ranges: Vec<Vec<Range<Anchor>>>,
1504 active_index: usize,
1505 choices: Vec<Option<Vec<String>>>,
1506}
1507
1508#[doc(hidden)]
1509pub struct RenameState {
1510 pub range: Range<Anchor>,
1511 pub old_name: Arc<str>,
1512 pub editor: Entity<Editor>,
1513 block_id: CustomBlockId,
1514}
1515
1516struct InvalidationStack<T>(Vec<T>);
1517
1518struct RegisteredEditPredictionProvider {
1519 provider: Arc<dyn EditPredictionProviderHandle>,
1520 _subscription: Subscription,
1521}
1522
1523#[derive(Debug, PartialEq, Eq)]
1524pub struct ActiveDiagnosticGroup {
1525 pub active_range: Range<Anchor>,
1526 pub active_message: String,
1527 pub group_id: usize,
1528 pub blocks: HashSet<CustomBlockId>,
1529}
1530
1531#[derive(Debug, PartialEq, Eq)]
1532
1533pub(crate) enum ActiveDiagnostic {
1534 None,
1535 All,
1536 Group(ActiveDiagnosticGroup),
1537}
1538
1539#[derive(Serialize, Deserialize, Clone, Debug)]
1540pub struct ClipboardSelection {
1541 /// The number of bytes in this selection.
1542 pub len: usize,
1543 /// Whether this was a full-line selection.
1544 pub is_entire_line: bool,
1545 /// The indentation of the first line when this content was originally copied.
1546 pub first_line_indent: u32,
1547}
1548
1549// selections, scroll behavior, was newest selection reversed
1550type SelectSyntaxNodeHistoryState = (
1551 Box<[Selection<usize>]>,
1552 SelectSyntaxNodeScrollBehavior,
1553 bool,
1554);
1555
1556#[derive(Default)]
1557struct SelectSyntaxNodeHistory {
1558 stack: Vec<SelectSyntaxNodeHistoryState>,
1559 // disable temporarily to allow changing selections without losing the stack
1560 pub disable_clearing: bool,
1561}
1562
1563impl SelectSyntaxNodeHistory {
1564 pub fn try_clear(&mut self) {
1565 if !self.disable_clearing {
1566 self.stack.clear();
1567 }
1568 }
1569
1570 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1571 self.stack.push(selection);
1572 }
1573
1574 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1575 self.stack.pop()
1576 }
1577}
1578
1579enum SelectSyntaxNodeScrollBehavior {
1580 CursorTop,
1581 FitSelection,
1582 CursorBottom,
1583}
1584
1585#[derive(Debug)]
1586pub(crate) struct NavigationData {
1587 cursor_anchor: Anchor,
1588 cursor_position: Point,
1589 scroll_anchor: ScrollAnchor,
1590 scroll_top_row: u32,
1591}
1592
1593#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1594pub enum GotoDefinitionKind {
1595 Symbol,
1596 Declaration,
1597 Type,
1598 Implementation,
1599}
1600
1601pub enum FormatTarget {
1602 Buffers(HashSet<Entity<Buffer>>),
1603 Ranges(Vec<Range<MultiBufferPoint>>),
1604}
1605
1606pub(crate) struct FocusedBlock {
1607 id: BlockId,
1608 focus_handle: WeakFocusHandle,
1609}
1610
1611#[derive(Clone)]
1612enum JumpData {
1613 MultiBufferRow {
1614 row: MultiBufferRow,
1615 line_offset_from_top: u32,
1616 },
1617 MultiBufferPoint {
1618 excerpt_id: ExcerptId,
1619 position: Point,
1620 anchor: text::Anchor,
1621 line_offset_from_top: u32,
1622 },
1623}
1624
1625pub enum MultibufferSelectionMode {
1626 First,
1627 All,
1628}
1629
1630#[derive(Clone, Copy, Debug, Default)]
1631pub struct RewrapOptions {
1632 pub override_language_settings: bool,
1633 pub preserve_existing_whitespace: bool,
1634}
1635
1636impl Editor {
1637 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1638 let buffer = cx.new(|cx| Buffer::local("", cx));
1639 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1640 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1641 }
1642
1643 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1644 let buffer = cx.new(|cx| Buffer::local("", cx));
1645 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1646 Self::new(EditorMode::full(), buffer, None, window, cx)
1647 }
1648
1649 pub fn auto_height(
1650 min_lines: usize,
1651 max_lines: usize,
1652 window: &mut Window,
1653 cx: &mut Context<Self>,
1654 ) -> Self {
1655 let buffer = cx.new(|cx| Buffer::local("", cx));
1656 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1657 Self::new(
1658 EditorMode::AutoHeight {
1659 min_lines,
1660 max_lines: Some(max_lines),
1661 },
1662 buffer,
1663 None,
1664 window,
1665 cx,
1666 )
1667 }
1668
1669 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1670 /// The editor grows as tall as needed to fit its content.
1671 pub fn auto_height_unbounded(
1672 min_lines: usize,
1673 window: &mut Window,
1674 cx: &mut Context<Self>,
1675 ) -> Self {
1676 let buffer = cx.new(|cx| Buffer::local("", cx));
1677 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1678 Self::new(
1679 EditorMode::AutoHeight {
1680 min_lines,
1681 max_lines: None,
1682 },
1683 buffer,
1684 None,
1685 window,
1686 cx,
1687 )
1688 }
1689
1690 pub fn for_buffer(
1691 buffer: Entity<Buffer>,
1692 project: Option<Entity<Project>>,
1693 window: &mut Window,
1694 cx: &mut Context<Self>,
1695 ) -> Self {
1696 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1697 Self::new(EditorMode::full(), buffer, project, window, cx)
1698 }
1699
1700 pub fn for_multibuffer(
1701 buffer: Entity<MultiBuffer>,
1702 project: Option<Entity<Project>>,
1703 window: &mut Window,
1704 cx: &mut Context<Self>,
1705 ) -> Self {
1706 Self::new(EditorMode::full(), buffer, project, window, cx)
1707 }
1708
1709 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1710 let mut clone = Self::new(
1711 self.mode.clone(),
1712 self.buffer.clone(),
1713 self.project.clone(),
1714 window,
1715 cx,
1716 );
1717 self.display_map.update(cx, |display_map, cx| {
1718 let snapshot = display_map.snapshot(cx);
1719 clone.display_map.update(cx, |display_map, cx| {
1720 display_map.set_state(&snapshot, cx);
1721 });
1722 });
1723 clone.folds_did_change(cx);
1724 clone.selections.clone_state(&self.selections);
1725 clone.scroll_manager.clone_state(&self.scroll_manager);
1726 clone.searchable = self.searchable;
1727 clone.read_only = self.read_only;
1728 clone
1729 }
1730
1731 pub fn new(
1732 mode: EditorMode,
1733 buffer: Entity<MultiBuffer>,
1734 project: Option<Entity<Project>>,
1735 window: &mut Window,
1736 cx: &mut Context<Self>,
1737 ) -> Self {
1738 Editor::new_internal(mode, buffer, project, None, window, cx)
1739 }
1740
1741 fn new_internal(
1742 mode: EditorMode,
1743 multi_buffer: Entity<MultiBuffer>,
1744 project: Option<Entity<Project>>,
1745 display_map: Option<Entity<DisplayMap>>,
1746 window: &mut Window,
1747 cx: &mut Context<Self>,
1748 ) -> Self {
1749 debug_assert!(
1750 display_map.is_none() || mode.is_minimap(),
1751 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1752 );
1753
1754 let full_mode = mode.is_full();
1755 let is_minimap = mode.is_minimap();
1756 let diagnostics_max_severity = if full_mode {
1757 EditorSettings::get_global(cx)
1758 .diagnostics_max_severity
1759 .unwrap_or(DiagnosticSeverity::Hint)
1760 } else {
1761 DiagnosticSeverity::Off
1762 };
1763 let style = window.text_style();
1764 let font_size = style.font_size.to_pixels(window.rem_size());
1765 let editor = cx.entity().downgrade();
1766 let fold_placeholder = FoldPlaceholder {
1767 constrain_width: false,
1768 render: Arc::new(move |fold_id, fold_range, cx| {
1769 let editor = editor.clone();
1770 div()
1771 .id(fold_id)
1772 .bg(cx.theme().colors().ghost_element_background)
1773 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1774 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1775 .rounded_xs()
1776 .size_full()
1777 .cursor_pointer()
1778 .child("⋯")
1779 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1780 .on_click(move |_, _window, cx| {
1781 editor
1782 .update(cx, |editor, cx| {
1783 editor.unfold_ranges(
1784 &[fold_range.start..fold_range.end],
1785 true,
1786 false,
1787 cx,
1788 );
1789 cx.stop_propagation();
1790 })
1791 .ok();
1792 })
1793 .into_any()
1794 }),
1795 merge_adjacent: true,
1796 ..FoldPlaceholder::default()
1797 };
1798 let display_map = display_map.unwrap_or_else(|| {
1799 cx.new(|cx| {
1800 DisplayMap::new(
1801 multi_buffer.clone(),
1802 style.font(),
1803 font_size,
1804 None,
1805 FILE_HEADER_HEIGHT,
1806 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1807 fold_placeholder,
1808 diagnostics_max_severity,
1809 cx,
1810 )
1811 })
1812 });
1813
1814 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1815
1816 let blink_manager = cx.new(|cx| {
1817 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1818 if is_minimap {
1819 blink_manager.disable(cx);
1820 }
1821 blink_manager
1822 });
1823
1824 let soft_wrap_mode_override =
1825 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1826
1827 let mut project_subscriptions = Vec::new();
1828 if full_mode && let Some(project) = project.as_ref() {
1829 project_subscriptions.push(cx.subscribe_in(
1830 project,
1831 window,
1832 |editor, _, event, window, cx| match event {
1833 project::Event::RefreshCodeLens => {
1834 // we always query lens with actions, without storing them, always refreshing them
1835 }
1836 project::Event::RefreshInlayHints(server_id) => {
1837 editor.refresh_inlay_hints(
1838 InlayHintRefreshReason::RefreshRequested(*server_id),
1839 cx,
1840 );
1841 }
1842 project::Event::LanguageServerRemoved(..) => {
1843 if editor.tasks_update_task.is_none() {
1844 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1845 }
1846 editor.registered_buffers.clear();
1847 editor.register_visible_buffers(cx);
1848 }
1849 project::Event::LanguageServerAdded(..) => {
1850 if editor.tasks_update_task.is_none() {
1851 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1852 }
1853 }
1854 project::Event::SnippetEdit(id, snippet_edits) => {
1855 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1856 let focus_handle = editor.focus_handle(cx);
1857 if focus_handle.is_focused(window) {
1858 let snapshot = buffer.read(cx).snapshot();
1859 for (range, snippet) in snippet_edits {
1860 let editor_range =
1861 language::range_from_lsp(*range).to_offset(&snapshot);
1862 editor
1863 .insert_snippet(
1864 &[editor_range],
1865 snippet.clone(),
1866 window,
1867 cx,
1868 )
1869 .ok();
1870 }
1871 }
1872 }
1873 }
1874 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1875 let buffer_id = *buffer_id;
1876 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1877 editor.register_buffer(buffer_id, cx);
1878 editor.update_lsp_data(Some(buffer_id), window, cx);
1879 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1880 refresh_linked_ranges(editor, window, cx);
1881 editor.refresh_code_actions(window, cx);
1882 editor.refresh_document_highlights(cx);
1883 }
1884 }
1885
1886 project::Event::EntryRenamed(transaction) => {
1887 let Some(workspace) = editor.workspace() else {
1888 return;
1889 };
1890 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1891 else {
1892 return;
1893 };
1894 if active_editor.entity_id() == cx.entity_id() {
1895 let edited_buffers_already_open = {
1896 let other_editors: Vec<Entity<Editor>> = workspace
1897 .read(cx)
1898 .panes()
1899 .iter()
1900 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1901 .filter(|editor| editor.entity_id() != cx.entity_id())
1902 .collect();
1903
1904 transaction.0.keys().all(|buffer| {
1905 other_editors.iter().any(|editor| {
1906 let multi_buffer = editor.read(cx).buffer();
1907 multi_buffer.read(cx).is_singleton()
1908 && multi_buffer.read(cx).as_singleton().map_or(
1909 false,
1910 |singleton| {
1911 singleton.entity_id() == buffer.entity_id()
1912 },
1913 )
1914 })
1915 })
1916 };
1917
1918 if !edited_buffers_already_open {
1919 let workspace = workspace.downgrade();
1920 let transaction = transaction.clone();
1921 cx.defer_in(window, move |_, window, cx| {
1922 cx.spawn_in(window, async move |editor, cx| {
1923 Self::open_project_transaction(
1924 &editor,
1925 workspace,
1926 transaction,
1927 "Rename".to_string(),
1928 cx,
1929 )
1930 .await
1931 .ok()
1932 })
1933 .detach();
1934 });
1935 }
1936 }
1937 }
1938
1939 _ => {}
1940 },
1941 ));
1942 if let Some(task_inventory) = project
1943 .read(cx)
1944 .task_store()
1945 .read(cx)
1946 .task_inventory()
1947 .cloned()
1948 {
1949 project_subscriptions.push(cx.observe_in(
1950 &task_inventory,
1951 window,
1952 |editor, _, window, cx| {
1953 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1954 },
1955 ));
1956 };
1957
1958 project_subscriptions.push(cx.subscribe_in(
1959 &project.read(cx).breakpoint_store(),
1960 window,
1961 |editor, _, event, window, cx| match event {
1962 BreakpointStoreEvent::ClearDebugLines => {
1963 editor.clear_row_highlights::<ActiveDebugLine>();
1964 editor.refresh_inline_values(cx);
1965 }
1966 BreakpointStoreEvent::SetDebugLine => {
1967 if editor.go_to_active_debug_line(window, cx) {
1968 cx.stop_propagation();
1969 }
1970
1971 editor.refresh_inline_values(cx);
1972 }
1973 _ => {}
1974 },
1975 ));
1976 let git_store = project.read(cx).git_store().clone();
1977 let project = project.clone();
1978 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
1979 if let GitStoreEvent::RepositoryAdded = event {
1980 this.load_diff_task = Some(
1981 update_uncommitted_diff_for_buffer(
1982 cx.entity(),
1983 &project,
1984 this.buffer.read(cx).all_buffers(),
1985 this.buffer.clone(),
1986 cx,
1987 )
1988 .shared(),
1989 );
1990 }
1991 }));
1992 }
1993
1994 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
1995
1996 let inlay_hint_settings =
1997 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
1998 let focus_handle = cx.focus_handle();
1999 if !is_minimap {
2000 cx.on_focus(&focus_handle, window, Self::handle_focus)
2001 .detach();
2002 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2003 .detach();
2004 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2005 .detach();
2006 cx.on_blur(&focus_handle, window, Self::handle_blur)
2007 .detach();
2008 cx.observe_pending_input(window, Self::observe_pending_input)
2009 .detach();
2010 }
2011
2012 let show_indent_guides =
2013 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2014 Some(false)
2015 } else {
2016 None
2017 };
2018
2019 let breakpoint_store = match (&mode, project.as_ref()) {
2020 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2021 _ => None,
2022 };
2023
2024 let mut code_action_providers = Vec::new();
2025 let mut load_uncommitted_diff = None;
2026 if let Some(project) = project.clone() {
2027 load_uncommitted_diff = Some(
2028 update_uncommitted_diff_for_buffer(
2029 cx.entity(),
2030 &project,
2031 multi_buffer.read(cx).all_buffers(),
2032 multi_buffer.clone(),
2033 cx,
2034 )
2035 .shared(),
2036 );
2037 code_action_providers.push(Rc::new(project) as Rc<_>);
2038 }
2039
2040 let mut editor = Self {
2041 focus_handle,
2042 show_cursor_when_unfocused: false,
2043 last_focused_descendant: None,
2044 buffer: multi_buffer.clone(),
2045 display_map: display_map.clone(),
2046 placeholder_display_map: None,
2047 selections,
2048 scroll_manager: ScrollManager::new(cx),
2049 columnar_selection_state: None,
2050 add_selections_state: None,
2051 select_next_state: None,
2052 select_prev_state: None,
2053 selection_history: SelectionHistory::default(),
2054 defer_selection_effects: false,
2055 deferred_selection_effects_state: None,
2056 autoclose_regions: Vec::new(),
2057 snippet_stack: InvalidationStack::default(),
2058 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2059 ime_transaction: None,
2060 active_diagnostics: ActiveDiagnostic::None,
2061 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2062 inline_diagnostics_update: Task::ready(()),
2063 inline_diagnostics: Vec::new(),
2064 soft_wrap_mode_override,
2065 diagnostics_max_severity,
2066 hard_wrap: None,
2067 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2068 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2069 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2070 project,
2071 blink_manager: blink_manager.clone(),
2072 show_local_selections: true,
2073 show_scrollbars: ScrollbarAxes {
2074 horizontal: full_mode,
2075 vertical: full_mode,
2076 },
2077 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2078 offset_content: !matches!(mode, EditorMode::SingleLine),
2079 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2080 show_gutter: full_mode,
2081 show_line_numbers: (!full_mode).then_some(false),
2082 use_relative_line_numbers: None,
2083 disable_expand_excerpt_buttons: !full_mode,
2084 show_git_diff_gutter: None,
2085 show_code_actions: None,
2086 show_runnables: None,
2087 show_breakpoints: None,
2088 show_wrap_guides: None,
2089 show_indent_guides,
2090 highlight_order: 0,
2091 highlighted_rows: HashMap::default(),
2092 background_highlights: HashMap::default(),
2093 gutter_highlights: HashMap::default(),
2094 scrollbar_marker_state: ScrollbarMarkerState::default(),
2095 active_indent_guides_state: ActiveIndentGuidesState::default(),
2096 nav_history: None,
2097 context_menu: RefCell::new(None),
2098 context_menu_options: None,
2099 mouse_context_menu: None,
2100 completion_tasks: Vec::new(),
2101 inline_blame_popover: None,
2102 inline_blame_popover_show_task: None,
2103 signature_help_state: SignatureHelpState::default(),
2104 auto_signature_help: None,
2105 find_all_references_task_sources: Vec::new(),
2106 next_completion_id: 0,
2107 next_inlay_id: 0,
2108 code_action_providers,
2109 available_code_actions: None,
2110 code_actions_task: None,
2111 quick_selection_highlight_task: None,
2112 debounced_selection_highlight_task: None,
2113 document_highlights_task: None,
2114 linked_editing_range_task: None,
2115 pending_rename: None,
2116 searchable: !is_minimap,
2117 cursor_shape: EditorSettings::get_global(cx)
2118 .cursor_shape
2119 .unwrap_or_default(),
2120 current_line_highlight: None,
2121 autoindent_mode: Some(AutoindentMode::EachLine),
2122 collapse_matches: false,
2123 workspace: None,
2124 input_enabled: !is_minimap,
2125 use_modal_editing: full_mode,
2126 read_only: is_minimap,
2127 use_autoclose: true,
2128 use_auto_surround: true,
2129 auto_replace_emoji_shortcode: false,
2130 jsx_tag_auto_close_enabled_in_any_buffer: false,
2131 leader_id: None,
2132 remote_id: None,
2133 hover_state: HoverState::default(),
2134 pending_mouse_down: None,
2135 hovered_link_state: None,
2136 edit_prediction_provider: None,
2137 active_edit_prediction: None,
2138 stale_edit_prediction_in_menu: None,
2139 edit_prediction_preview: EditPredictionPreview::Inactive {
2140 released_too_fast: false,
2141 },
2142 inline_diagnostics_enabled: full_mode,
2143 diagnostics_enabled: full_mode,
2144 word_completions_enabled: full_mode,
2145 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2146 gutter_hovered: false,
2147 pixel_position_of_newest_cursor: None,
2148 last_bounds: None,
2149 last_position_map: None,
2150 expect_bounds_change: None,
2151 gutter_dimensions: GutterDimensions::default(),
2152 style: None,
2153 show_cursor_names: false,
2154 hovered_cursors: HashMap::default(),
2155 next_editor_action_id: EditorActionId::default(),
2156 editor_actions: Rc::default(),
2157 edit_predictions_hidden_for_vim_mode: false,
2158 show_edit_predictions_override: None,
2159 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2160 edit_prediction_settings: EditPredictionSettings::Disabled,
2161 edit_prediction_indent_conflict: false,
2162 edit_prediction_requires_modifier_in_indent_conflict: true,
2163 custom_context_menu: None,
2164 show_git_blame_gutter: false,
2165 show_git_blame_inline: false,
2166 show_selection_menu: None,
2167 show_git_blame_inline_delay_task: None,
2168 git_blame_inline_enabled: full_mode
2169 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2170 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2171 serialize_dirty_buffers: !is_minimap
2172 && ProjectSettings::get_global(cx)
2173 .session
2174 .restore_unsaved_buffers,
2175 blame: None,
2176 blame_subscription: None,
2177 tasks: BTreeMap::default(),
2178
2179 breakpoint_store,
2180 gutter_breakpoint_indicator: (None, None),
2181 hovered_diff_hunk_row: None,
2182 _subscriptions: (!is_minimap)
2183 .then(|| {
2184 vec![
2185 cx.observe(&multi_buffer, Self::on_buffer_changed),
2186 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2187 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2188 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2189 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2190 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2191 cx.observe_window_activation(window, |editor, window, cx| {
2192 let active = window.is_window_active();
2193 editor.blink_manager.update(cx, |blink_manager, cx| {
2194 if active {
2195 blink_manager.enable(cx);
2196 } else {
2197 blink_manager.disable(cx);
2198 }
2199 });
2200 if active {
2201 editor.show_mouse_cursor(cx);
2202 }
2203 }),
2204 ]
2205 })
2206 .unwrap_or_default(),
2207 tasks_update_task: None,
2208 pull_diagnostics_task: Task::ready(()),
2209 colors: None,
2210 refresh_colors_task: Task::ready(()),
2211 inlay_hints: None,
2212 next_color_inlay_id: 0,
2213 post_scroll_update: Task::ready(()),
2214 linked_edit_ranges: Default::default(),
2215 in_project_search: false,
2216 previous_search_ranges: None,
2217 breadcrumb_header: None,
2218 focused_block: None,
2219 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2220 addons: HashMap::default(),
2221 registered_buffers: HashMap::default(),
2222 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2223 selection_mark_mode: false,
2224 toggle_fold_multiple_buffers: Task::ready(()),
2225 serialize_selections: Task::ready(()),
2226 serialize_folds: Task::ready(()),
2227 text_style_refinement: None,
2228 load_diff_task: load_uncommitted_diff,
2229 temporary_diff_override: false,
2230 mouse_cursor_hidden: false,
2231 minimap: None,
2232 hide_mouse_mode: EditorSettings::get_global(cx)
2233 .hide_mouse
2234 .unwrap_or_default(),
2235 change_list: ChangeList::new(),
2236 mode,
2237 selection_drag_state: SelectionDragState::None,
2238 folding_newlines: Task::ready(()),
2239 lookup_key: None,
2240 };
2241
2242 if is_minimap {
2243 return editor;
2244 }
2245
2246 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2247 editor
2248 ._subscriptions
2249 .push(cx.observe(breakpoints, |_, _, cx| {
2250 cx.notify();
2251 }));
2252 }
2253 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2254 editor._subscriptions.extend(project_subscriptions);
2255
2256 editor._subscriptions.push(cx.subscribe_in(
2257 &cx.entity(),
2258 window,
2259 |editor, _, e: &EditorEvent, window, cx| match e {
2260 EditorEvent::ScrollPositionChanged { local, .. } => {
2261 if *local {
2262 let new_anchor = editor.scroll_manager.anchor();
2263 let snapshot = editor.snapshot(window, cx);
2264 editor.update_restoration_data(cx, move |data| {
2265 data.scroll_position = (
2266 new_anchor.top_row(snapshot.buffer_snapshot()),
2267 new_anchor.offset,
2268 );
2269 });
2270 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2271 editor.inline_blame_popover.take();
2272 }
2273 }
2274 EditorEvent::Edited { .. } => {
2275 if !vim_enabled(cx) {
2276 let display_map = editor.display_snapshot(cx);
2277 let selections = editor.selections.all_adjusted_display(&display_map);
2278 let pop_state = editor
2279 .change_list
2280 .last()
2281 .map(|previous| {
2282 previous.len() == selections.len()
2283 && previous.iter().enumerate().all(|(ix, p)| {
2284 p.to_display_point(&display_map).row()
2285 == selections[ix].head().row()
2286 })
2287 })
2288 .unwrap_or(false);
2289 let new_positions = selections
2290 .into_iter()
2291 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2292 .collect();
2293 editor
2294 .change_list
2295 .push_to_change_list(pop_state, new_positions);
2296 }
2297 }
2298 _ => (),
2299 },
2300 ));
2301
2302 if let Some(dap_store) = editor
2303 .project
2304 .as_ref()
2305 .map(|project| project.read(cx).dap_store())
2306 {
2307 let weak_editor = cx.weak_entity();
2308
2309 editor
2310 ._subscriptions
2311 .push(
2312 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2313 let session_entity = cx.entity();
2314 weak_editor
2315 .update(cx, |editor, cx| {
2316 editor._subscriptions.push(
2317 cx.subscribe(&session_entity, Self::on_debug_session_event),
2318 );
2319 })
2320 .ok();
2321 }),
2322 );
2323
2324 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2325 editor
2326 ._subscriptions
2327 .push(cx.subscribe(&session, Self::on_debug_session_event));
2328 }
2329 }
2330
2331 // skip adding the initial selection to selection history
2332 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2333 editor.end_selection(window, cx);
2334 editor.selection_history.mode = SelectionHistoryMode::Normal;
2335
2336 editor.scroll_manager.show_scrollbars(window, cx);
2337 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2338
2339 if full_mode {
2340 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2341 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2342
2343 if editor.git_blame_inline_enabled {
2344 editor.start_git_blame_inline(false, window, cx);
2345 }
2346
2347 editor.go_to_active_debug_line(window, cx);
2348
2349 editor.minimap =
2350 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2351 editor.colors = Some(LspColorData::new(cx));
2352 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2353
2354 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2355 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2356 }
2357 editor.update_lsp_data(None, window, cx);
2358 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2359 }
2360
2361 editor
2362 }
2363
2364 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2365 self.selections.display_map(cx)
2366 }
2367
2368 pub fn deploy_mouse_context_menu(
2369 &mut self,
2370 position: gpui::Point<Pixels>,
2371 context_menu: Entity<ContextMenu>,
2372 window: &mut Window,
2373 cx: &mut Context<Self>,
2374 ) {
2375 self.mouse_context_menu = Some(MouseContextMenu::new(
2376 self,
2377 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2378 context_menu,
2379 window,
2380 cx,
2381 ));
2382 }
2383
2384 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2385 self.mouse_context_menu
2386 .as_ref()
2387 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2388 }
2389
2390 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2391 if self
2392 .selections
2393 .pending_anchor()
2394 .is_some_and(|pending_selection| {
2395 let snapshot = self.buffer().read(cx).snapshot(cx);
2396 pending_selection.range().includes(range, &snapshot)
2397 })
2398 {
2399 return true;
2400 }
2401
2402 self.selections
2403 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2404 .into_iter()
2405 .any(|selection| {
2406 // This is needed to cover a corner case, if we just check for an existing
2407 // selection in the fold range, having a cursor at the start of the fold
2408 // marks it as selected. Non-empty selections don't cause this.
2409 let length = selection.end - selection.start;
2410 length > 0
2411 })
2412 }
2413
2414 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2415 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2416 }
2417
2418 fn key_context_internal(
2419 &self,
2420 has_active_edit_prediction: bool,
2421 window: &mut Window,
2422 cx: &mut App,
2423 ) -> KeyContext {
2424 let mut key_context = KeyContext::new_with_defaults();
2425 key_context.add("Editor");
2426 let mode = match self.mode {
2427 EditorMode::SingleLine => "single_line",
2428 EditorMode::AutoHeight { .. } => "auto_height",
2429 EditorMode::Minimap { .. } => "minimap",
2430 EditorMode::Full { .. } => "full",
2431 };
2432
2433 if EditorSettings::jupyter_enabled(cx) {
2434 key_context.add("jupyter");
2435 }
2436
2437 key_context.set("mode", mode);
2438 if self.pending_rename.is_some() {
2439 key_context.add("renaming");
2440 }
2441
2442 match self.context_menu.borrow().as_ref() {
2443 Some(CodeContextMenu::Completions(menu)) => {
2444 if menu.visible() {
2445 key_context.add("menu");
2446 key_context.add("showing_completions");
2447 }
2448 }
2449 Some(CodeContextMenu::CodeActions(menu)) => {
2450 if menu.visible() {
2451 key_context.add("menu");
2452 key_context.add("showing_code_actions")
2453 }
2454 }
2455 None => {}
2456 }
2457
2458 if self.signature_help_state.has_multiple_signatures() {
2459 key_context.add("showing_signature_help");
2460 }
2461
2462 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2463 if !self.focus_handle(cx).contains_focused(window, cx)
2464 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2465 {
2466 for addon in self.addons.values() {
2467 addon.extend_key_context(&mut key_context, cx)
2468 }
2469 }
2470
2471 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2472 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2473 Some(
2474 file.full_path(cx)
2475 .extension()?
2476 .to_string_lossy()
2477 .into_owned(),
2478 )
2479 }) {
2480 key_context.set("extension", extension);
2481 }
2482 } else {
2483 key_context.add("multibuffer");
2484 }
2485
2486 if has_active_edit_prediction {
2487 if self.edit_prediction_in_conflict() {
2488 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2489 } else {
2490 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2491 key_context.add("copilot_suggestion");
2492 }
2493 }
2494
2495 if self.selection_mark_mode {
2496 key_context.add("selection_mode");
2497 }
2498
2499 let disjoint = self.selections.disjoint_anchors();
2500 let snapshot = self.snapshot(window, cx);
2501 let snapshot = snapshot.buffer_snapshot();
2502 if self.mode == EditorMode::SingleLine
2503 && let [selection] = disjoint
2504 && selection.start == selection.end
2505 && selection.end.to_offset(snapshot) == snapshot.len()
2506 {
2507 key_context.add("end_of_input");
2508 }
2509
2510 key_context
2511 }
2512
2513 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2514 self.last_bounds.as_ref()
2515 }
2516
2517 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2518 if self.mouse_cursor_hidden {
2519 self.mouse_cursor_hidden = false;
2520 cx.notify();
2521 }
2522 }
2523
2524 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2525 let hide_mouse_cursor = match origin {
2526 HideMouseCursorOrigin::TypingAction => {
2527 matches!(
2528 self.hide_mouse_mode,
2529 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2530 )
2531 }
2532 HideMouseCursorOrigin::MovementAction => {
2533 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2534 }
2535 };
2536 if self.mouse_cursor_hidden != hide_mouse_cursor {
2537 self.mouse_cursor_hidden = hide_mouse_cursor;
2538 cx.notify();
2539 }
2540 }
2541
2542 pub fn edit_prediction_in_conflict(&self) -> bool {
2543 if !self.show_edit_predictions_in_menu() {
2544 return false;
2545 }
2546
2547 let showing_completions = self
2548 .context_menu
2549 .borrow()
2550 .as_ref()
2551 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2552
2553 showing_completions
2554 || self.edit_prediction_requires_modifier()
2555 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2556 // bindings to insert tab characters.
2557 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2558 }
2559
2560 pub fn accept_edit_prediction_keybind(
2561 &self,
2562 accept_partial: bool,
2563 window: &mut Window,
2564 cx: &mut App,
2565 ) -> AcceptEditPredictionBinding {
2566 let key_context = self.key_context_internal(true, window, cx);
2567 let in_conflict = self.edit_prediction_in_conflict();
2568
2569 let bindings = if accept_partial {
2570 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2571 } else {
2572 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2573 };
2574
2575 // TODO: if the binding contains multiple keystrokes, display all of them, not
2576 // just the first one.
2577 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2578 !in_conflict
2579 || binding
2580 .keystrokes()
2581 .first()
2582 .is_some_and(|keystroke| keystroke.modifiers().modified())
2583 }))
2584 }
2585
2586 pub fn new_file(
2587 workspace: &mut Workspace,
2588 _: &workspace::NewFile,
2589 window: &mut Window,
2590 cx: &mut Context<Workspace>,
2591 ) {
2592 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2593 "Failed to create buffer",
2594 window,
2595 cx,
2596 |e, _, _| match e.error_code() {
2597 ErrorCode::RemoteUpgradeRequired => Some(format!(
2598 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2599 e.error_tag("required").unwrap_or("the latest version")
2600 )),
2601 _ => None,
2602 },
2603 );
2604 }
2605
2606 pub fn new_in_workspace(
2607 workspace: &mut Workspace,
2608 window: &mut Window,
2609 cx: &mut Context<Workspace>,
2610 ) -> Task<Result<Entity<Editor>>> {
2611 let project = workspace.project().clone();
2612 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2613
2614 cx.spawn_in(window, async move |workspace, cx| {
2615 let buffer = create.await?;
2616 workspace.update_in(cx, |workspace, window, cx| {
2617 let editor =
2618 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2619 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2620 editor
2621 })
2622 })
2623 }
2624
2625 fn new_file_vertical(
2626 workspace: &mut Workspace,
2627 _: &workspace::NewFileSplitVertical,
2628 window: &mut Window,
2629 cx: &mut Context<Workspace>,
2630 ) {
2631 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2632 }
2633
2634 fn new_file_horizontal(
2635 workspace: &mut Workspace,
2636 _: &workspace::NewFileSplitHorizontal,
2637 window: &mut Window,
2638 cx: &mut Context<Workspace>,
2639 ) {
2640 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2641 }
2642
2643 fn new_file_split(
2644 workspace: &mut Workspace,
2645 action: &workspace::NewFileSplit,
2646 window: &mut Window,
2647 cx: &mut Context<Workspace>,
2648 ) {
2649 Self::new_file_in_direction(workspace, action.0, window, cx)
2650 }
2651
2652 fn new_file_in_direction(
2653 workspace: &mut Workspace,
2654 direction: SplitDirection,
2655 window: &mut Window,
2656 cx: &mut Context<Workspace>,
2657 ) {
2658 let project = workspace.project().clone();
2659 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2660
2661 cx.spawn_in(window, async move |workspace, cx| {
2662 let buffer = create.await?;
2663 workspace.update_in(cx, move |workspace, window, cx| {
2664 workspace.split_item(
2665 direction,
2666 Box::new(
2667 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2668 ),
2669 window,
2670 cx,
2671 )
2672 })?;
2673 anyhow::Ok(())
2674 })
2675 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2676 match e.error_code() {
2677 ErrorCode::RemoteUpgradeRequired => Some(format!(
2678 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2679 e.error_tag("required").unwrap_or("the latest version")
2680 )),
2681 _ => None,
2682 }
2683 });
2684 }
2685
2686 pub fn leader_id(&self) -> Option<CollaboratorId> {
2687 self.leader_id
2688 }
2689
2690 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2691 &self.buffer
2692 }
2693
2694 pub fn project(&self) -> Option<&Entity<Project>> {
2695 self.project.as_ref()
2696 }
2697
2698 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2699 self.workspace.as_ref()?.0.upgrade()
2700 }
2701
2702 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2703 self.buffer().read(cx).title(cx)
2704 }
2705
2706 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2707 let git_blame_gutter_max_author_length = self
2708 .render_git_blame_gutter(cx)
2709 .then(|| {
2710 if let Some(blame) = self.blame.as_ref() {
2711 let max_author_length =
2712 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2713 Some(max_author_length)
2714 } else {
2715 None
2716 }
2717 })
2718 .flatten();
2719
2720 EditorSnapshot {
2721 mode: self.mode.clone(),
2722 show_gutter: self.show_gutter,
2723 show_line_numbers: self.show_line_numbers,
2724 show_git_diff_gutter: self.show_git_diff_gutter,
2725 show_code_actions: self.show_code_actions,
2726 show_runnables: self.show_runnables,
2727 show_breakpoints: self.show_breakpoints,
2728 git_blame_gutter_max_author_length,
2729 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2730 placeholder_display_snapshot: self
2731 .placeholder_display_map
2732 .as_ref()
2733 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2734 scroll_anchor: self.scroll_manager.anchor(),
2735 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2736 is_focused: self.focus_handle.is_focused(window),
2737 current_line_highlight: self
2738 .current_line_highlight
2739 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2740 gutter_hovered: self.gutter_hovered,
2741 }
2742 }
2743
2744 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2745 self.buffer.read(cx).language_at(point, cx)
2746 }
2747
2748 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2749 self.buffer.read(cx).read(cx).file_at(point).cloned()
2750 }
2751
2752 pub fn active_excerpt(
2753 &self,
2754 cx: &App,
2755 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2756 self.buffer
2757 .read(cx)
2758 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2759 }
2760
2761 pub fn mode(&self) -> &EditorMode {
2762 &self.mode
2763 }
2764
2765 pub fn set_mode(&mut self, mode: EditorMode) {
2766 self.mode = mode;
2767 }
2768
2769 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2770 self.collaboration_hub.as_deref()
2771 }
2772
2773 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2774 self.collaboration_hub = Some(hub);
2775 }
2776
2777 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2778 self.in_project_search = in_project_search;
2779 }
2780
2781 pub fn set_custom_context_menu(
2782 &mut self,
2783 f: impl 'static
2784 + Fn(
2785 &mut Self,
2786 DisplayPoint,
2787 &mut Window,
2788 &mut Context<Self>,
2789 ) -> Option<Entity<ui::ContextMenu>>,
2790 ) {
2791 self.custom_context_menu = Some(Box::new(f))
2792 }
2793
2794 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2795 self.completion_provider = provider;
2796 }
2797
2798 #[cfg(any(test, feature = "test-support"))]
2799 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2800 self.completion_provider.clone()
2801 }
2802
2803 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2804 self.semantics_provider.clone()
2805 }
2806
2807 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2808 self.semantics_provider = provider;
2809 }
2810
2811 pub fn set_edit_prediction_provider<T>(
2812 &mut self,
2813 provider: Option<Entity<T>>,
2814 window: &mut Window,
2815 cx: &mut Context<Self>,
2816 ) where
2817 T: EditPredictionProvider,
2818 {
2819 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2820 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2821 if this.focus_handle.is_focused(window) {
2822 this.update_visible_edit_prediction(window, cx);
2823 }
2824 }),
2825 provider: Arc::new(provider),
2826 });
2827 self.update_edit_prediction_settings(cx);
2828 self.refresh_edit_prediction(false, false, window, cx);
2829 }
2830
2831 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2832 self.placeholder_display_map
2833 .as_ref()
2834 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2835 }
2836
2837 pub fn set_placeholder_text(
2838 &mut self,
2839 placeholder_text: &str,
2840 window: &mut Window,
2841 cx: &mut Context<Self>,
2842 ) {
2843 let multibuffer = cx
2844 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2845
2846 let style = window.text_style();
2847
2848 self.placeholder_display_map = Some(cx.new(|cx| {
2849 DisplayMap::new(
2850 multibuffer,
2851 style.font(),
2852 style.font_size.to_pixels(window.rem_size()),
2853 None,
2854 FILE_HEADER_HEIGHT,
2855 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2856 Default::default(),
2857 DiagnosticSeverity::Off,
2858 cx,
2859 )
2860 }));
2861 cx.notify();
2862 }
2863
2864 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2865 self.cursor_shape = cursor_shape;
2866
2867 // Disrupt blink for immediate user feedback that the cursor shape has changed
2868 self.blink_manager.update(cx, BlinkManager::show_cursor);
2869
2870 cx.notify();
2871 }
2872
2873 pub fn set_current_line_highlight(
2874 &mut self,
2875 current_line_highlight: Option<CurrentLineHighlight>,
2876 ) {
2877 self.current_line_highlight = current_line_highlight;
2878 }
2879
2880 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2881 self.collapse_matches = collapse_matches;
2882 }
2883
2884 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2885 if self.collapse_matches {
2886 return range.start..range.start;
2887 }
2888 range.clone()
2889 }
2890
2891 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2892 if self.display_map.read(cx).clip_at_line_ends != clip {
2893 self.display_map
2894 .update(cx, |map, _| map.clip_at_line_ends = clip);
2895 }
2896 }
2897
2898 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2899 self.input_enabled = input_enabled;
2900 }
2901
2902 pub fn set_edit_predictions_hidden_for_vim_mode(
2903 &mut self,
2904 hidden: bool,
2905 window: &mut Window,
2906 cx: &mut Context<Self>,
2907 ) {
2908 if hidden != self.edit_predictions_hidden_for_vim_mode {
2909 self.edit_predictions_hidden_for_vim_mode = hidden;
2910 if hidden {
2911 self.update_visible_edit_prediction(window, cx);
2912 } else {
2913 self.refresh_edit_prediction(true, false, window, cx);
2914 }
2915 }
2916 }
2917
2918 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2919 self.menu_edit_predictions_policy = value;
2920 }
2921
2922 pub fn set_autoindent(&mut self, autoindent: bool) {
2923 if autoindent {
2924 self.autoindent_mode = Some(AutoindentMode::EachLine);
2925 } else {
2926 self.autoindent_mode = None;
2927 }
2928 }
2929
2930 pub fn read_only(&self, cx: &App) -> bool {
2931 self.read_only || self.buffer.read(cx).read_only()
2932 }
2933
2934 pub fn set_read_only(&mut self, read_only: bool) {
2935 self.read_only = read_only;
2936 }
2937
2938 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2939 self.use_autoclose = autoclose;
2940 }
2941
2942 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2943 self.use_auto_surround = auto_surround;
2944 }
2945
2946 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
2947 self.auto_replace_emoji_shortcode = auto_replace;
2948 }
2949
2950 pub fn toggle_edit_predictions(
2951 &mut self,
2952 _: &ToggleEditPrediction,
2953 window: &mut Window,
2954 cx: &mut Context<Self>,
2955 ) {
2956 if self.show_edit_predictions_override.is_some() {
2957 self.set_show_edit_predictions(None, window, cx);
2958 } else {
2959 let show_edit_predictions = !self.edit_predictions_enabled();
2960 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
2961 }
2962 }
2963
2964 pub fn set_show_edit_predictions(
2965 &mut self,
2966 show_edit_predictions: Option<bool>,
2967 window: &mut Window,
2968 cx: &mut Context<Self>,
2969 ) {
2970 self.show_edit_predictions_override = show_edit_predictions;
2971 self.update_edit_prediction_settings(cx);
2972
2973 if let Some(false) = show_edit_predictions {
2974 self.discard_edit_prediction(false, cx);
2975 } else {
2976 self.refresh_edit_prediction(false, true, window, cx);
2977 }
2978 }
2979
2980 fn edit_predictions_disabled_in_scope(
2981 &self,
2982 buffer: &Entity<Buffer>,
2983 buffer_position: language::Anchor,
2984 cx: &App,
2985 ) -> bool {
2986 let snapshot = buffer.read(cx).snapshot();
2987 let settings = snapshot.settings_at(buffer_position, cx);
2988
2989 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
2990 return false;
2991 };
2992
2993 scope.override_name().is_some_and(|scope_name| {
2994 settings
2995 .edit_predictions_disabled_in
2996 .iter()
2997 .any(|s| s == scope_name)
2998 })
2999 }
3000
3001 pub fn set_use_modal_editing(&mut self, to: bool) {
3002 self.use_modal_editing = to;
3003 }
3004
3005 pub fn use_modal_editing(&self) -> bool {
3006 self.use_modal_editing
3007 }
3008
3009 fn selections_did_change(
3010 &mut self,
3011 local: bool,
3012 old_cursor_position: &Anchor,
3013 effects: SelectionEffects,
3014 window: &mut Window,
3015 cx: &mut Context<Self>,
3016 ) {
3017 window.invalidate_character_coordinates();
3018
3019 // Copy selections to primary selection buffer
3020 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3021 if local {
3022 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3023 let buffer_handle = self.buffer.read(cx).read(cx);
3024
3025 let mut text = String::new();
3026 for (index, selection) in selections.iter().enumerate() {
3027 let text_for_selection = buffer_handle
3028 .text_for_range(selection.start..selection.end)
3029 .collect::<String>();
3030
3031 text.push_str(&text_for_selection);
3032 if index != selections.len() - 1 {
3033 text.push('\n');
3034 }
3035 }
3036
3037 if !text.is_empty() {
3038 cx.write_to_primary(ClipboardItem::new_string(text));
3039 }
3040 }
3041
3042 let selection_anchors = self.selections.disjoint_anchors_arc();
3043
3044 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3045 self.buffer.update(cx, |buffer, cx| {
3046 buffer.set_active_selections(
3047 &selection_anchors,
3048 self.selections.line_mode(),
3049 self.cursor_shape,
3050 cx,
3051 )
3052 });
3053 }
3054 let display_map = self
3055 .display_map
3056 .update(cx, |display_map, cx| display_map.snapshot(cx));
3057 let buffer = display_map.buffer_snapshot();
3058 if self.selections.count() == 1 {
3059 self.add_selections_state = None;
3060 }
3061 self.select_next_state = None;
3062 self.select_prev_state = None;
3063 self.select_syntax_node_history.try_clear();
3064 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3065 self.snippet_stack.invalidate(&selection_anchors, buffer);
3066 self.take_rename(false, window, cx);
3067
3068 let newest_selection = self.selections.newest_anchor();
3069 let new_cursor_position = newest_selection.head();
3070 let selection_start = newest_selection.start;
3071
3072 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3073 self.push_to_nav_history(
3074 *old_cursor_position,
3075 Some(new_cursor_position.to_point(buffer)),
3076 false,
3077 effects.nav_history == Some(true),
3078 cx,
3079 );
3080 }
3081
3082 if local {
3083 if let Some(buffer_id) = new_cursor_position.buffer_id {
3084 self.register_buffer(buffer_id, cx);
3085 }
3086
3087 let mut context_menu = self.context_menu.borrow_mut();
3088 let completion_menu = match context_menu.as_ref() {
3089 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3090 Some(CodeContextMenu::CodeActions(_)) => {
3091 *context_menu = None;
3092 None
3093 }
3094 None => None,
3095 };
3096 let completion_position = completion_menu.map(|menu| menu.initial_position);
3097 drop(context_menu);
3098
3099 if effects.completions
3100 && let Some(completion_position) = completion_position
3101 {
3102 let start_offset = selection_start.to_offset(buffer);
3103 let position_matches = start_offset == completion_position.to_offset(buffer);
3104 let continue_showing = if position_matches {
3105 if self.snippet_stack.is_empty() {
3106 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3107 == Some(CharKind::Word)
3108 } else {
3109 // Snippet choices can be shown even when the cursor is in whitespace.
3110 // Dismissing the menu with actions like backspace is handled by
3111 // invalidation regions.
3112 true
3113 }
3114 } else {
3115 false
3116 };
3117
3118 if continue_showing {
3119 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3120 } else {
3121 self.hide_context_menu(window, cx);
3122 }
3123 }
3124
3125 hide_hover(self, cx);
3126
3127 if old_cursor_position.to_display_point(&display_map).row()
3128 != new_cursor_position.to_display_point(&display_map).row()
3129 {
3130 self.available_code_actions.take();
3131 }
3132 self.refresh_code_actions(window, cx);
3133 self.refresh_document_highlights(cx);
3134 refresh_linked_ranges(self, window, cx);
3135
3136 self.refresh_selected_text_highlights(false, window, cx);
3137 self.refresh_matching_bracket_highlights(window, cx);
3138 self.update_visible_edit_prediction(window, cx);
3139 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3140 self.inline_blame_popover.take();
3141 if self.git_blame_inline_enabled {
3142 self.start_inline_blame_timer(window, cx);
3143 }
3144 }
3145
3146 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3147 cx.emit(EditorEvent::SelectionsChanged { local });
3148
3149 let selections = &self.selections.disjoint_anchors_arc();
3150 if selections.len() == 1 {
3151 cx.emit(SearchEvent::ActiveMatchChanged)
3152 }
3153 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3154 let inmemory_selections = selections
3155 .iter()
3156 .map(|s| {
3157 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3158 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3159 })
3160 .collect();
3161 self.update_restoration_data(cx, |data| {
3162 data.selections = inmemory_selections;
3163 });
3164
3165 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3166 && let Some(workspace_id) =
3167 self.workspace.as_ref().and_then(|workspace| workspace.1)
3168 {
3169 let snapshot = self.buffer().read(cx).snapshot(cx);
3170 let selections = selections.clone();
3171 let background_executor = cx.background_executor().clone();
3172 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3173 self.serialize_selections = cx.background_spawn(async move {
3174 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3175 let db_selections = selections
3176 .iter()
3177 .map(|selection| {
3178 (
3179 selection.start.to_offset(&snapshot),
3180 selection.end.to_offset(&snapshot),
3181 )
3182 })
3183 .collect();
3184
3185 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3186 .await
3187 .with_context(|| {
3188 format!(
3189 "persisting editor selections for editor {editor_id}, \
3190 workspace {workspace_id:?}"
3191 )
3192 })
3193 .log_err();
3194 });
3195 }
3196 }
3197
3198 cx.notify();
3199 }
3200
3201 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3202 use text::ToOffset as _;
3203 use text::ToPoint as _;
3204
3205 if self.mode.is_minimap()
3206 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3207 {
3208 return;
3209 }
3210
3211 if !self.buffer().read(cx).is_singleton() {
3212 return;
3213 }
3214
3215 let display_snapshot = self
3216 .display_map
3217 .update(cx, |display_map, cx| display_map.snapshot(cx));
3218 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3219 return;
3220 };
3221 let inmemory_folds = display_snapshot
3222 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3223 .map(|fold| {
3224 fold.range.start.text_anchor.to_point(&snapshot)
3225 ..fold.range.end.text_anchor.to_point(&snapshot)
3226 })
3227 .collect();
3228 self.update_restoration_data(cx, |data| {
3229 data.folds = inmemory_folds;
3230 });
3231
3232 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3233 return;
3234 };
3235 let background_executor = cx.background_executor().clone();
3236 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3237 let db_folds = display_snapshot
3238 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3239 .map(|fold| {
3240 (
3241 fold.range.start.text_anchor.to_offset(&snapshot),
3242 fold.range.end.text_anchor.to_offset(&snapshot),
3243 )
3244 })
3245 .collect();
3246 self.serialize_folds = cx.background_spawn(async move {
3247 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3248 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3249 .await
3250 .with_context(|| {
3251 format!(
3252 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3253 )
3254 })
3255 .log_err();
3256 });
3257 }
3258
3259 pub fn sync_selections(
3260 &mut self,
3261 other: Entity<Editor>,
3262 cx: &mut Context<Self>,
3263 ) -> gpui::Subscription {
3264 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3265 if !other_selections.is_empty() {
3266 self.selections.change_with(cx, |selections| {
3267 selections.select_anchors(other_selections);
3268 });
3269 }
3270
3271 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3272 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3273 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3274 if other_selections.is_empty() {
3275 return;
3276 }
3277 this.selections.change_with(cx, |selections| {
3278 selections.select_anchors(other_selections);
3279 });
3280 }
3281 });
3282
3283 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3284 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3285 let these_selections = this.selections.disjoint_anchors().to_vec();
3286 if these_selections.is_empty() {
3287 return;
3288 }
3289 other.update(cx, |other_editor, cx| {
3290 other_editor.selections.change_with(cx, |selections| {
3291 selections.select_anchors(these_selections);
3292 })
3293 });
3294 }
3295 });
3296
3297 Subscription::join(other_subscription, this_subscription)
3298 }
3299
3300 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3301 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3302 /// effects of selection change occur at the end of the transaction.
3303 pub fn change_selections<R>(
3304 &mut self,
3305 effects: SelectionEffects,
3306 window: &mut Window,
3307 cx: &mut Context<Self>,
3308 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3309 ) -> R {
3310 if let Some(state) = &mut self.deferred_selection_effects_state {
3311 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3312 state.effects.completions = effects.completions;
3313 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3314 let (changed, result) = self.selections.change_with(cx, change);
3315 state.changed |= changed;
3316 return result;
3317 }
3318 let mut state = DeferredSelectionEffectsState {
3319 changed: false,
3320 effects,
3321 old_cursor_position: self.selections.newest_anchor().head(),
3322 history_entry: SelectionHistoryEntry {
3323 selections: self.selections.disjoint_anchors_arc(),
3324 select_next_state: self.select_next_state.clone(),
3325 select_prev_state: self.select_prev_state.clone(),
3326 add_selections_state: self.add_selections_state.clone(),
3327 },
3328 };
3329 let (changed, result) = self.selections.change_with(cx, change);
3330 state.changed = state.changed || changed;
3331 if self.defer_selection_effects {
3332 self.deferred_selection_effects_state = Some(state);
3333 } else {
3334 self.apply_selection_effects(state, window, cx);
3335 }
3336 result
3337 }
3338
3339 /// Defers the effects of selection change, so that the effects of multiple calls to
3340 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3341 /// to selection history and the state of popovers based on selection position aren't
3342 /// erroneously updated.
3343 pub fn with_selection_effects_deferred<R>(
3344 &mut self,
3345 window: &mut Window,
3346 cx: &mut Context<Self>,
3347 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3348 ) -> R {
3349 let already_deferred = self.defer_selection_effects;
3350 self.defer_selection_effects = true;
3351 let result = update(self, window, cx);
3352 if !already_deferred {
3353 self.defer_selection_effects = false;
3354 if let Some(state) = self.deferred_selection_effects_state.take() {
3355 self.apply_selection_effects(state, window, cx);
3356 }
3357 }
3358 result
3359 }
3360
3361 fn apply_selection_effects(
3362 &mut self,
3363 state: DeferredSelectionEffectsState,
3364 window: &mut Window,
3365 cx: &mut Context<Self>,
3366 ) {
3367 if state.changed {
3368 self.selection_history.push(state.history_entry);
3369
3370 if let Some(autoscroll) = state.effects.scroll {
3371 self.request_autoscroll(autoscroll, cx);
3372 }
3373
3374 let old_cursor_position = &state.old_cursor_position;
3375
3376 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3377
3378 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3379 self.show_signature_help(&ShowSignatureHelp, window, cx);
3380 }
3381 }
3382 }
3383
3384 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3385 where
3386 I: IntoIterator<Item = (Range<S>, T)>,
3387 S: ToOffset,
3388 T: Into<Arc<str>>,
3389 {
3390 if self.read_only(cx) {
3391 return;
3392 }
3393
3394 self.buffer
3395 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3396 }
3397
3398 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3399 where
3400 I: IntoIterator<Item = (Range<S>, T)>,
3401 S: ToOffset,
3402 T: Into<Arc<str>>,
3403 {
3404 if self.read_only(cx) {
3405 return;
3406 }
3407
3408 self.buffer.update(cx, |buffer, cx| {
3409 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3410 });
3411 }
3412
3413 pub fn edit_with_block_indent<I, S, T>(
3414 &mut self,
3415 edits: I,
3416 original_indent_columns: Vec<Option<u32>>,
3417 cx: &mut Context<Self>,
3418 ) where
3419 I: IntoIterator<Item = (Range<S>, T)>,
3420 S: ToOffset,
3421 T: Into<Arc<str>>,
3422 {
3423 if self.read_only(cx) {
3424 return;
3425 }
3426
3427 self.buffer.update(cx, |buffer, cx| {
3428 buffer.edit(
3429 edits,
3430 Some(AutoindentMode::Block {
3431 original_indent_columns,
3432 }),
3433 cx,
3434 )
3435 });
3436 }
3437
3438 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3439 self.hide_context_menu(window, cx);
3440
3441 match phase {
3442 SelectPhase::Begin {
3443 position,
3444 add,
3445 click_count,
3446 } => self.begin_selection(position, add, click_count, window, cx),
3447 SelectPhase::BeginColumnar {
3448 position,
3449 goal_column,
3450 reset,
3451 mode,
3452 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3453 SelectPhase::Extend {
3454 position,
3455 click_count,
3456 } => self.extend_selection(position, click_count, window, cx),
3457 SelectPhase::Update {
3458 position,
3459 goal_column,
3460 scroll_delta,
3461 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3462 SelectPhase::End => self.end_selection(window, cx),
3463 }
3464 }
3465
3466 fn extend_selection(
3467 &mut self,
3468 position: DisplayPoint,
3469 click_count: usize,
3470 window: &mut Window,
3471 cx: &mut Context<Self>,
3472 ) {
3473 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3474 let tail = self.selections.newest::<usize>(&display_map).tail();
3475 let click_count = click_count.max(match self.selections.select_mode() {
3476 SelectMode::Character => 1,
3477 SelectMode::Word(_) => 2,
3478 SelectMode::Line(_) => 3,
3479 SelectMode::All => 4,
3480 });
3481 self.begin_selection(position, false, click_count, window, cx);
3482
3483 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3484
3485 let current_selection = match self.selections.select_mode() {
3486 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3487 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3488 };
3489
3490 let mut pending_selection = self
3491 .selections
3492 .pending_anchor()
3493 .cloned()
3494 .expect("extend_selection not called with pending selection");
3495
3496 if pending_selection
3497 .start
3498 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3499 == Ordering::Greater
3500 {
3501 pending_selection.start = current_selection.start;
3502 }
3503 if pending_selection
3504 .end
3505 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3506 == Ordering::Less
3507 {
3508 pending_selection.end = current_selection.end;
3509 pending_selection.reversed = true;
3510 }
3511
3512 let mut pending_mode = self.selections.pending_mode().unwrap();
3513 match &mut pending_mode {
3514 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3515 _ => {}
3516 }
3517
3518 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3519 SelectionEffects::scroll(Autoscroll::fit())
3520 } else {
3521 SelectionEffects::no_scroll()
3522 };
3523
3524 self.change_selections(effects, window, cx, |s| {
3525 s.set_pending(pending_selection.clone(), pending_mode);
3526 s.set_is_extending(true);
3527 });
3528 }
3529
3530 fn begin_selection(
3531 &mut self,
3532 position: DisplayPoint,
3533 add: bool,
3534 click_count: usize,
3535 window: &mut Window,
3536 cx: &mut Context<Self>,
3537 ) {
3538 if !self.focus_handle.is_focused(window) {
3539 self.last_focused_descendant = None;
3540 window.focus(&self.focus_handle);
3541 }
3542
3543 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3544 let buffer = display_map.buffer_snapshot();
3545 let position = display_map.clip_point(position, Bias::Left);
3546
3547 let start;
3548 let end;
3549 let mode;
3550 let mut auto_scroll;
3551 match click_count {
3552 1 => {
3553 start = buffer.anchor_before(position.to_point(&display_map));
3554 end = start;
3555 mode = SelectMode::Character;
3556 auto_scroll = true;
3557 }
3558 2 => {
3559 let position = display_map
3560 .clip_point(position, Bias::Left)
3561 .to_offset(&display_map, Bias::Left);
3562 let (range, _) = buffer.surrounding_word(position, None);
3563 start = buffer.anchor_before(range.start);
3564 end = buffer.anchor_before(range.end);
3565 mode = SelectMode::Word(start..end);
3566 auto_scroll = true;
3567 }
3568 3 => {
3569 let position = display_map
3570 .clip_point(position, Bias::Left)
3571 .to_point(&display_map);
3572 let line_start = display_map.prev_line_boundary(position).0;
3573 let next_line_start = buffer.clip_point(
3574 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3575 Bias::Left,
3576 );
3577 start = buffer.anchor_before(line_start);
3578 end = buffer.anchor_before(next_line_start);
3579 mode = SelectMode::Line(start..end);
3580 auto_scroll = true;
3581 }
3582 _ => {
3583 start = buffer.anchor_before(0);
3584 end = buffer.anchor_before(buffer.len());
3585 mode = SelectMode::All;
3586 auto_scroll = false;
3587 }
3588 }
3589 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3590
3591 let point_to_delete: Option<usize> = {
3592 let selected_points: Vec<Selection<Point>> =
3593 self.selections.disjoint_in_range(start..end, &display_map);
3594
3595 if !add || click_count > 1 {
3596 None
3597 } else if !selected_points.is_empty() {
3598 Some(selected_points[0].id)
3599 } else {
3600 let clicked_point_already_selected =
3601 self.selections.disjoint_anchors().iter().find(|selection| {
3602 selection.start.to_point(buffer) == start.to_point(buffer)
3603 || selection.end.to_point(buffer) == end.to_point(buffer)
3604 });
3605
3606 clicked_point_already_selected.map(|selection| selection.id)
3607 }
3608 };
3609
3610 let selections_count = self.selections.count();
3611 let effects = if auto_scroll {
3612 SelectionEffects::default()
3613 } else {
3614 SelectionEffects::no_scroll()
3615 };
3616
3617 self.change_selections(effects, window, cx, |s| {
3618 if let Some(point_to_delete) = point_to_delete {
3619 s.delete(point_to_delete);
3620
3621 if selections_count == 1 {
3622 s.set_pending_anchor_range(start..end, mode);
3623 }
3624 } else {
3625 if !add {
3626 s.clear_disjoint();
3627 }
3628
3629 s.set_pending_anchor_range(start..end, mode);
3630 }
3631 });
3632 }
3633
3634 fn begin_columnar_selection(
3635 &mut self,
3636 position: DisplayPoint,
3637 goal_column: u32,
3638 reset: bool,
3639 mode: ColumnarMode,
3640 window: &mut Window,
3641 cx: &mut Context<Self>,
3642 ) {
3643 if !self.focus_handle.is_focused(window) {
3644 self.last_focused_descendant = None;
3645 window.focus(&self.focus_handle);
3646 }
3647
3648 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3649
3650 if reset {
3651 let pointer_position = display_map
3652 .buffer_snapshot()
3653 .anchor_before(position.to_point(&display_map));
3654
3655 self.change_selections(
3656 SelectionEffects::scroll(Autoscroll::newest()),
3657 window,
3658 cx,
3659 |s| {
3660 s.clear_disjoint();
3661 s.set_pending_anchor_range(
3662 pointer_position..pointer_position,
3663 SelectMode::Character,
3664 );
3665 },
3666 );
3667 };
3668
3669 let tail = self.selections.newest::<Point>(&display_map).tail();
3670 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3671 self.columnar_selection_state = match mode {
3672 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3673 selection_tail: selection_anchor,
3674 display_point: if reset {
3675 if position.column() != goal_column {
3676 Some(DisplayPoint::new(position.row(), goal_column))
3677 } else {
3678 None
3679 }
3680 } else {
3681 None
3682 },
3683 }),
3684 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3685 selection_tail: selection_anchor,
3686 }),
3687 };
3688
3689 if !reset {
3690 self.select_columns(position, goal_column, &display_map, window, cx);
3691 }
3692 }
3693
3694 fn update_selection(
3695 &mut self,
3696 position: DisplayPoint,
3697 goal_column: u32,
3698 scroll_delta: gpui::Point<f32>,
3699 window: &mut Window,
3700 cx: &mut Context<Self>,
3701 ) {
3702 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3703
3704 if self.columnar_selection_state.is_some() {
3705 self.select_columns(position, goal_column, &display_map, window, cx);
3706 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3707 let buffer = display_map.buffer_snapshot();
3708 let head;
3709 let tail;
3710 let mode = self.selections.pending_mode().unwrap();
3711 match &mode {
3712 SelectMode::Character => {
3713 head = position.to_point(&display_map);
3714 tail = pending.tail().to_point(buffer);
3715 }
3716 SelectMode::Word(original_range) => {
3717 let offset = display_map
3718 .clip_point(position, Bias::Left)
3719 .to_offset(&display_map, Bias::Left);
3720 let original_range = original_range.to_offset(buffer);
3721
3722 let head_offset = if buffer.is_inside_word(offset, None)
3723 || original_range.contains(&offset)
3724 {
3725 let (word_range, _) = buffer.surrounding_word(offset, None);
3726 if word_range.start < original_range.start {
3727 word_range.start
3728 } else {
3729 word_range.end
3730 }
3731 } else {
3732 offset
3733 };
3734
3735 head = head_offset.to_point(buffer);
3736 if head_offset <= original_range.start {
3737 tail = original_range.end.to_point(buffer);
3738 } else {
3739 tail = original_range.start.to_point(buffer);
3740 }
3741 }
3742 SelectMode::Line(original_range) => {
3743 let original_range = original_range.to_point(display_map.buffer_snapshot());
3744
3745 let position = display_map
3746 .clip_point(position, Bias::Left)
3747 .to_point(&display_map);
3748 let line_start = display_map.prev_line_boundary(position).0;
3749 let next_line_start = buffer.clip_point(
3750 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3751 Bias::Left,
3752 );
3753
3754 if line_start < original_range.start {
3755 head = line_start
3756 } else {
3757 head = next_line_start
3758 }
3759
3760 if head <= original_range.start {
3761 tail = original_range.end;
3762 } else {
3763 tail = original_range.start;
3764 }
3765 }
3766 SelectMode::All => {
3767 return;
3768 }
3769 };
3770
3771 if head < tail {
3772 pending.start = buffer.anchor_before(head);
3773 pending.end = buffer.anchor_before(tail);
3774 pending.reversed = true;
3775 } else {
3776 pending.start = buffer.anchor_before(tail);
3777 pending.end = buffer.anchor_before(head);
3778 pending.reversed = false;
3779 }
3780
3781 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3782 s.set_pending(pending.clone(), mode);
3783 });
3784 } else {
3785 log::error!("update_selection dispatched with no pending selection");
3786 return;
3787 }
3788
3789 self.apply_scroll_delta(scroll_delta, window, cx);
3790 cx.notify();
3791 }
3792
3793 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3794 self.columnar_selection_state.take();
3795 if let Some(pending_mode) = self.selections.pending_mode() {
3796 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3797 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3798 s.select(selections);
3799 s.clear_pending();
3800 if s.is_extending() {
3801 s.set_is_extending(false);
3802 } else {
3803 s.set_select_mode(pending_mode);
3804 }
3805 });
3806 }
3807 }
3808
3809 fn select_columns(
3810 &mut self,
3811 head: DisplayPoint,
3812 goal_column: u32,
3813 display_map: &DisplaySnapshot,
3814 window: &mut Window,
3815 cx: &mut Context<Self>,
3816 ) {
3817 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3818 return;
3819 };
3820
3821 let tail = match columnar_state {
3822 ColumnarSelectionState::FromMouse {
3823 selection_tail,
3824 display_point,
3825 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3826 ColumnarSelectionState::FromSelection { selection_tail } => {
3827 selection_tail.to_display_point(display_map)
3828 }
3829 };
3830
3831 let start_row = cmp::min(tail.row(), head.row());
3832 let end_row = cmp::max(tail.row(), head.row());
3833 let start_column = cmp::min(tail.column(), goal_column);
3834 let end_column = cmp::max(tail.column(), goal_column);
3835 let reversed = start_column < tail.column();
3836
3837 let selection_ranges = (start_row.0..=end_row.0)
3838 .map(DisplayRow)
3839 .filter_map(|row| {
3840 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3841 || start_column <= display_map.line_len(row))
3842 && !display_map.is_block_line(row)
3843 {
3844 let start = display_map
3845 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3846 .to_point(display_map);
3847 let end = display_map
3848 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3849 .to_point(display_map);
3850 if reversed {
3851 Some(end..start)
3852 } else {
3853 Some(start..end)
3854 }
3855 } else {
3856 None
3857 }
3858 })
3859 .collect::<Vec<_>>();
3860 if selection_ranges.is_empty() {
3861 return;
3862 }
3863
3864 let ranges = match columnar_state {
3865 ColumnarSelectionState::FromMouse { .. } => {
3866 let mut non_empty_ranges = selection_ranges
3867 .iter()
3868 .filter(|selection_range| selection_range.start != selection_range.end)
3869 .peekable();
3870 if non_empty_ranges.peek().is_some() {
3871 non_empty_ranges.cloned().collect()
3872 } else {
3873 selection_ranges
3874 }
3875 }
3876 _ => selection_ranges,
3877 };
3878
3879 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3880 s.select_ranges(ranges);
3881 });
3882 cx.notify();
3883 }
3884
3885 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3886 self.selections
3887 .all_adjusted(snapshot)
3888 .iter()
3889 .any(|selection| !selection.is_empty())
3890 }
3891
3892 pub fn has_pending_nonempty_selection(&self) -> bool {
3893 let pending_nonempty_selection = match self.selections.pending_anchor() {
3894 Some(Selection { start, end, .. }) => start != end,
3895 None => false,
3896 };
3897
3898 pending_nonempty_selection
3899 || (self.columnar_selection_state.is_some()
3900 && self.selections.disjoint_anchors().len() > 1)
3901 }
3902
3903 pub fn has_pending_selection(&self) -> bool {
3904 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3905 }
3906
3907 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3908 self.selection_mark_mode = false;
3909 self.selection_drag_state = SelectionDragState::None;
3910
3911 if self.clear_expanded_diff_hunks(cx) {
3912 cx.notify();
3913 return;
3914 }
3915 if self.dismiss_menus_and_popups(true, window, cx) {
3916 return;
3917 }
3918
3919 if self.mode.is_full()
3920 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3921 {
3922 return;
3923 }
3924
3925 cx.propagate();
3926 }
3927
3928 pub fn dismiss_menus_and_popups(
3929 &mut self,
3930 is_user_requested: bool,
3931 window: &mut Window,
3932 cx: &mut Context<Self>,
3933 ) -> bool {
3934 if self.take_rename(false, window, cx).is_some() {
3935 return true;
3936 }
3937
3938 if self.hide_blame_popover(true, cx) {
3939 return true;
3940 }
3941
3942 if hide_hover(self, cx) {
3943 return true;
3944 }
3945
3946 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
3947 return true;
3948 }
3949
3950 if self.hide_context_menu(window, cx).is_some() {
3951 return true;
3952 }
3953
3954 if self.mouse_context_menu.take().is_some() {
3955 return true;
3956 }
3957
3958 if is_user_requested && self.discard_edit_prediction(true, cx) {
3959 return true;
3960 }
3961
3962 if self.snippet_stack.pop().is_some() {
3963 return true;
3964 }
3965
3966 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
3967 self.dismiss_diagnostics(cx);
3968 return true;
3969 }
3970
3971 false
3972 }
3973
3974 fn linked_editing_ranges_for(
3975 &self,
3976 selection: Range<text::Anchor>,
3977 cx: &App,
3978 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
3979 if self.linked_edit_ranges.is_empty() {
3980 return None;
3981 }
3982 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
3983 selection.end.buffer_id.and_then(|end_buffer_id| {
3984 if selection.start.buffer_id != Some(end_buffer_id) {
3985 return None;
3986 }
3987 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
3988 let snapshot = buffer.read(cx).snapshot();
3989 self.linked_edit_ranges
3990 .get(end_buffer_id, selection.start..selection.end, &snapshot)
3991 .map(|ranges| (ranges, snapshot, buffer))
3992 })?;
3993 use text::ToOffset as TO;
3994 // find offset from the start of current range to current cursor position
3995 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
3996
3997 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
3998 let start_difference = start_offset - start_byte_offset;
3999 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4000 let end_difference = end_offset - start_byte_offset;
4001 // Current range has associated linked ranges.
4002 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4003 for range in linked_ranges.iter() {
4004 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4005 let end_offset = start_offset + end_difference;
4006 let start_offset = start_offset + start_difference;
4007 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4008 continue;
4009 }
4010 if self.selections.disjoint_anchor_ranges().any(|s| {
4011 if s.start.buffer_id != selection.start.buffer_id
4012 || s.end.buffer_id != selection.end.buffer_id
4013 {
4014 return false;
4015 }
4016 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4017 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4018 }) {
4019 continue;
4020 }
4021 let start = buffer_snapshot.anchor_after(start_offset);
4022 let end = buffer_snapshot.anchor_after(end_offset);
4023 linked_edits
4024 .entry(buffer.clone())
4025 .or_default()
4026 .push(start..end);
4027 }
4028 Some(linked_edits)
4029 }
4030
4031 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4032 let text: Arc<str> = text.into();
4033
4034 if self.read_only(cx) {
4035 return;
4036 }
4037
4038 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4039
4040 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4041 let mut bracket_inserted = false;
4042 let mut edits = Vec::new();
4043 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4044 let mut new_selections = Vec::with_capacity(selections.len());
4045 let mut new_autoclose_regions = Vec::new();
4046 let snapshot = self.buffer.read(cx).read(cx);
4047 let mut clear_linked_edit_ranges = false;
4048
4049 for (selection, autoclose_region) in
4050 self.selections_with_autoclose_regions(selections, &snapshot)
4051 {
4052 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4053 // Determine if the inserted text matches the opening or closing
4054 // bracket of any of this language's bracket pairs.
4055 let mut bracket_pair = None;
4056 let mut is_bracket_pair_start = false;
4057 let mut is_bracket_pair_end = false;
4058 if !text.is_empty() {
4059 let mut bracket_pair_matching_end = None;
4060 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4061 // and they are removing the character that triggered IME popup.
4062 for (pair, enabled) in scope.brackets() {
4063 if !pair.close && !pair.surround {
4064 continue;
4065 }
4066
4067 if enabled && pair.start.ends_with(text.as_ref()) {
4068 let prefix_len = pair.start.len() - text.len();
4069 let preceding_text_matches_prefix = prefix_len == 0
4070 || (selection.start.column >= (prefix_len as u32)
4071 && snapshot.contains_str_at(
4072 Point::new(
4073 selection.start.row,
4074 selection.start.column - (prefix_len as u32),
4075 ),
4076 &pair.start[..prefix_len],
4077 ));
4078 if preceding_text_matches_prefix {
4079 bracket_pair = Some(pair.clone());
4080 is_bracket_pair_start = true;
4081 break;
4082 }
4083 }
4084 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4085 {
4086 // take first bracket pair matching end, but don't break in case a later bracket
4087 // pair matches start
4088 bracket_pair_matching_end = Some(pair.clone());
4089 }
4090 }
4091 if let Some(end) = bracket_pair_matching_end
4092 && bracket_pair.is_none()
4093 {
4094 bracket_pair = Some(end);
4095 is_bracket_pair_end = true;
4096 }
4097 }
4098
4099 if let Some(bracket_pair) = bracket_pair {
4100 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4101 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4102 let auto_surround =
4103 self.use_auto_surround && snapshot_settings.use_auto_surround;
4104 if selection.is_empty() {
4105 if is_bracket_pair_start {
4106 // If the inserted text is a suffix of an opening bracket and the
4107 // selection is preceded by the rest of the opening bracket, then
4108 // insert the closing bracket.
4109 let following_text_allows_autoclose = snapshot
4110 .chars_at(selection.start)
4111 .next()
4112 .is_none_or(|c| scope.should_autoclose_before(c));
4113
4114 let preceding_text_allows_autoclose = selection.start.column == 0
4115 || snapshot
4116 .reversed_chars_at(selection.start)
4117 .next()
4118 .is_none_or(|c| {
4119 bracket_pair.start != bracket_pair.end
4120 || !snapshot
4121 .char_classifier_at(selection.start)
4122 .is_word(c)
4123 });
4124
4125 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4126 && bracket_pair.start.len() == 1
4127 {
4128 let target = bracket_pair.start.chars().next().unwrap();
4129 let current_line_count = snapshot
4130 .reversed_chars_at(selection.start)
4131 .take_while(|&c| c != '\n')
4132 .filter(|&c| c == target)
4133 .count();
4134 current_line_count % 2 == 1
4135 } else {
4136 false
4137 };
4138
4139 if autoclose
4140 && bracket_pair.close
4141 && following_text_allows_autoclose
4142 && preceding_text_allows_autoclose
4143 && !is_closing_quote
4144 {
4145 let anchor = snapshot.anchor_before(selection.end);
4146 new_selections.push((selection.map(|_| anchor), text.len()));
4147 new_autoclose_regions.push((
4148 anchor,
4149 text.len(),
4150 selection.id,
4151 bracket_pair.clone(),
4152 ));
4153 edits.push((
4154 selection.range(),
4155 format!("{}{}", text, bracket_pair.end).into(),
4156 ));
4157 bracket_inserted = true;
4158 continue;
4159 }
4160 }
4161
4162 if let Some(region) = autoclose_region {
4163 // If the selection is followed by an auto-inserted closing bracket,
4164 // then don't insert that closing bracket again; just move the selection
4165 // past the closing bracket.
4166 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4167 && text.as_ref() == region.pair.end.as_str()
4168 && snapshot.contains_str_at(region.range.end, text.as_ref());
4169 if should_skip {
4170 let anchor = snapshot.anchor_after(selection.end);
4171 new_selections
4172 .push((selection.map(|_| anchor), region.pair.end.len()));
4173 continue;
4174 }
4175 }
4176
4177 let always_treat_brackets_as_autoclosed = snapshot
4178 .language_settings_at(selection.start, cx)
4179 .always_treat_brackets_as_autoclosed;
4180 if always_treat_brackets_as_autoclosed
4181 && is_bracket_pair_end
4182 && snapshot.contains_str_at(selection.end, text.as_ref())
4183 {
4184 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4185 // and the inserted text is a closing bracket and the selection is followed
4186 // by the closing bracket then move the selection past the closing bracket.
4187 let anchor = snapshot.anchor_after(selection.end);
4188 new_selections.push((selection.map(|_| anchor), text.len()));
4189 continue;
4190 }
4191 }
4192 // If an opening bracket is 1 character long and is typed while
4193 // text is selected, then surround that text with the bracket pair.
4194 else if auto_surround
4195 && bracket_pair.surround
4196 && is_bracket_pair_start
4197 && bracket_pair.start.chars().count() == 1
4198 {
4199 edits.push((selection.start..selection.start, text.clone()));
4200 edits.push((
4201 selection.end..selection.end,
4202 bracket_pair.end.as_str().into(),
4203 ));
4204 bracket_inserted = true;
4205 new_selections.push((
4206 Selection {
4207 id: selection.id,
4208 start: snapshot.anchor_after(selection.start),
4209 end: snapshot.anchor_before(selection.end),
4210 reversed: selection.reversed,
4211 goal: selection.goal,
4212 },
4213 0,
4214 ));
4215 continue;
4216 }
4217 }
4218 }
4219
4220 if self.auto_replace_emoji_shortcode
4221 && selection.is_empty()
4222 && text.as_ref().ends_with(':')
4223 && let Some(possible_emoji_short_code) =
4224 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4225 && !possible_emoji_short_code.is_empty()
4226 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4227 {
4228 let emoji_shortcode_start = Point::new(
4229 selection.start.row,
4230 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4231 );
4232
4233 // Remove shortcode from buffer
4234 edits.push((
4235 emoji_shortcode_start..selection.start,
4236 "".to_string().into(),
4237 ));
4238 new_selections.push((
4239 Selection {
4240 id: selection.id,
4241 start: snapshot.anchor_after(emoji_shortcode_start),
4242 end: snapshot.anchor_before(selection.start),
4243 reversed: selection.reversed,
4244 goal: selection.goal,
4245 },
4246 0,
4247 ));
4248
4249 // Insert emoji
4250 let selection_start_anchor = snapshot.anchor_after(selection.start);
4251 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4252 edits.push((selection.start..selection.end, emoji.to_string().into()));
4253
4254 continue;
4255 }
4256
4257 // If not handling any auto-close operation, then just replace the selected
4258 // text with the given input and move the selection to the end of the
4259 // newly inserted text.
4260 let anchor = snapshot.anchor_after(selection.end);
4261 if !self.linked_edit_ranges.is_empty() {
4262 let start_anchor = snapshot.anchor_before(selection.start);
4263
4264 let is_word_char = text.chars().next().is_none_or(|char| {
4265 let classifier = snapshot
4266 .char_classifier_at(start_anchor.to_offset(&snapshot))
4267 .scope_context(Some(CharScopeContext::LinkedEdit));
4268 classifier.is_word(char)
4269 });
4270
4271 if is_word_char {
4272 if let Some(ranges) = self
4273 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4274 {
4275 for (buffer, edits) in ranges {
4276 linked_edits
4277 .entry(buffer.clone())
4278 .or_default()
4279 .extend(edits.into_iter().map(|range| (range, text.clone())));
4280 }
4281 }
4282 } else {
4283 clear_linked_edit_ranges = true;
4284 }
4285 }
4286
4287 new_selections.push((selection.map(|_| anchor), 0));
4288 edits.push((selection.start..selection.end, text.clone()));
4289 }
4290
4291 drop(snapshot);
4292
4293 self.transact(window, cx, |this, window, cx| {
4294 if clear_linked_edit_ranges {
4295 this.linked_edit_ranges.clear();
4296 }
4297 let initial_buffer_versions =
4298 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4299
4300 this.buffer.update(cx, |buffer, cx| {
4301 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4302 });
4303 for (buffer, edits) in linked_edits {
4304 buffer.update(cx, |buffer, cx| {
4305 let snapshot = buffer.snapshot();
4306 let edits = edits
4307 .into_iter()
4308 .map(|(range, text)| {
4309 use text::ToPoint as TP;
4310 let end_point = TP::to_point(&range.end, &snapshot);
4311 let start_point = TP::to_point(&range.start, &snapshot);
4312 (start_point..end_point, text)
4313 })
4314 .sorted_by_key(|(range, _)| range.start);
4315 buffer.edit(edits, None, cx);
4316 })
4317 }
4318 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4319 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4320 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4321 let new_selections =
4322 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4323 .zip(new_selection_deltas)
4324 .map(|(selection, delta)| Selection {
4325 id: selection.id,
4326 start: selection.start + delta,
4327 end: selection.end + delta,
4328 reversed: selection.reversed,
4329 goal: SelectionGoal::None,
4330 })
4331 .collect::<Vec<_>>();
4332
4333 let mut i = 0;
4334 for (position, delta, selection_id, pair) in new_autoclose_regions {
4335 let position = position.to_offset(map.buffer_snapshot()) + delta;
4336 let start = map.buffer_snapshot().anchor_before(position);
4337 let end = map.buffer_snapshot().anchor_after(position);
4338 while let Some(existing_state) = this.autoclose_regions.get(i) {
4339 match existing_state
4340 .range
4341 .start
4342 .cmp(&start, map.buffer_snapshot())
4343 {
4344 Ordering::Less => i += 1,
4345 Ordering::Greater => break,
4346 Ordering::Equal => {
4347 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4348 Ordering::Less => i += 1,
4349 Ordering::Equal => break,
4350 Ordering::Greater => break,
4351 }
4352 }
4353 }
4354 }
4355 this.autoclose_regions.insert(
4356 i,
4357 AutocloseRegion {
4358 selection_id,
4359 range: start..end,
4360 pair,
4361 },
4362 );
4363 }
4364
4365 let had_active_edit_prediction = this.has_active_edit_prediction();
4366 this.change_selections(
4367 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4368 window,
4369 cx,
4370 |s| s.select(new_selections),
4371 );
4372
4373 if !bracket_inserted
4374 && let Some(on_type_format_task) =
4375 this.trigger_on_type_formatting(text.to_string(), window, cx)
4376 {
4377 on_type_format_task.detach_and_log_err(cx);
4378 }
4379
4380 let editor_settings = EditorSettings::get_global(cx);
4381 if bracket_inserted
4382 && (editor_settings.auto_signature_help
4383 || editor_settings.show_signature_help_after_edits)
4384 {
4385 this.show_signature_help(&ShowSignatureHelp, window, cx);
4386 }
4387
4388 let trigger_in_words =
4389 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4390 if this.hard_wrap.is_some() {
4391 let latest: Range<Point> = this.selections.newest(&map).range();
4392 if latest.is_empty()
4393 && this
4394 .buffer()
4395 .read(cx)
4396 .snapshot(cx)
4397 .line_len(MultiBufferRow(latest.start.row))
4398 == latest.start.column
4399 {
4400 this.rewrap_impl(
4401 RewrapOptions {
4402 override_language_settings: true,
4403 preserve_existing_whitespace: true,
4404 },
4405 cx,
4406 )
4407 }
4408 }
4409 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4410 refresh_linked_ranges(this, window, cx);
4411 this.refresh_edit_prediction(true, false, window, cx);
4412 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4413 });
4414 }
4415
4416 fn find_possible_emoji_shortcode_at_position(
4417 snapshot: &MultiBufferSnapshot,
4418 position: Point,
4419 ) -> Option<String> {
4420 let mut chars = Vec::new();
4421 let mut found_colon = false;
4422 for char in snapshot.reversed_chars_at(position).take(100) {
4423 // Found a possible emoji shortcode in the middle of the buffer
4424 if found_colon {
4425 if char.is_whitespace() {
4426 chars.reverse();
4427 return Some(chars.iter().collect());
4428 }
4429 // If the previous character is not a whitespace, we are in the middle of a word
4430 // and we only want to complete the shortcode if the word is made up of other emojis
4431 let mut containing_word = String::new();
4432 for ch in snapshot
4433 .reversed_chars_at(position)
4434 .skip(chars.len() + 1)
4435 .take(100)
4436 {
4437 if ch.is_whitespace() {
4438 break;
4439 }
4440 containing_word.push(ch);
4441 }
4442 let containing_word = containing_word.chars().rev().collect::<String>();
4443 if util::word_consists_of_emojis(containing_word.as_str()) {
4444 chars.reverse();
4445 return Some(chars.iter().collect());
4446 }
4447 }
4448
4449 if char.is_whitespace() || !char.is_ascii() {
4450 return None;
4451 }
4452 if char == ':' {
4453 found_colon = true;
4454 } else {
4455 chars.push(char);
4456 }
4457 }
4458 // Found a possible emoji shortcode at the beginning of the buffer
4459 chars.reverse();
4460 Some(chars.iter().collect())
4461 }
4462
4463 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4465 self.transact(window, cx, |this, window, cx| {
4466 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4467 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4468 let multi_buffer = this.buffer.read(cx);
4469 let buffer = multi_buffer.snapshot(cx);
4470 selections
4471 .iter()
4472 .map(|selection| {
4473 let start_point = selection.start.to_point(&buffer);
4474 let mut existing_indent =
4475 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4476 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4477 let start = selection.start;
4478 let end = selection.end;
4479 let selection_is_empty = start == end;
4480 let language_scope = buffer.language_scope_at(start);
4481 let (
4482 comment_delimiter,
4483 doc_delimiter,
4484 insert_extra_newline,
4485 indent_on_newline,
4486 indent_on_extra_newline,
4487 ) = if let Some(language) = &language_scope {
4488 let mut insert_extra_newline =
4489 insert_extra_newline_brackets(&buffer, start..end, language)
4490 || insert_extra_newline_tree_sitter(&buffer, start..end);
4491
4492 // Comment extension on newline is allowed only for cursor selections
4493 let comment_delimiter = maybe!({
4494 if !selection_is_empty {
4495 return None;
4496 }
4497
4498 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4499 return None;
4500 }
4501
4502 let delimiters = language.line_comment_prefixes();
4503 let max_len_of_delimiter =
4504 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4505 let (snapshot, range) =
4506 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4507
4508 let num_of_whitespaces = snapshot
4509 .chars_for_range(range.clone())
4510 .take_while(|c| c.is_whitespace())
4511 .count();
4512 let comment_candidate = snapshot
4513 .chars_for_range(range.clone())
4514 .skip(num_of_whitespaces)
4515 .take(max_len_of_delimiter)
4516 .collect::<String>();
4517 let (delimiter, trimmed_len) = delimiters
4518 .iter()
4519 .filter_map(|delimiter| {
4520 let prefix = delimiter.trim_end();
4521 if comment_candidate.starts_with(prefix) {
4522 Some((delimiter, prefix.len()))
4523 } else {
4524 None
4525 }
4526 })
4527 .max_by_key(|(_, len)| *len)?;
4528
4529 if let Some(BlockCommentConfig {
4530 start: block_start, ..
4531 }) = language.block_comment()
4532 {
4533 let block_start_trimmed = block_start.trim_end();
4534 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4535 let line_content = snapshot
4536 .chars_for_range(range)
4537 .skip(num_of_whitespaces)
4538 .take(block_start_trimmed.len())
4539 .collect::<String>();
4540
4541 if line_content.starts_with(block_start_trimmed) {
4542 return None;
4543 }
4544 }
4545 }
4546
4547 let cursor_is_placed_after_comment_marker =
4548 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4549 if cursor_is_placed_after_comment_marker {
4550 Some(delimiter.clone())
4551 } else {
4552 None
4553 }
4554 });
4555
4556 let mut indent_on_newline = IndentSize::spaces(0);
4557 let mut indent_on_extra_newline = IndentSize::spaces(0);
4558
4559 let doc_delimiter = maybe!({
4560 if !selection_is_empty {
4561 return None;
4562 }
4563
4564 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4565 return None;
4566 }
4567
4568 let BlockCommentConfig {
4569 start: start_tag,
4570 end: end_tag,
4571 prefix: delimiter,
4572 tab_size: len,
4573 } = language.documentation_comment()?;
4574 let is_within_block_comment = buffer
4575 .language_scope_at(start_point)
4576 .is_some_and(|scope| scope.override_name() == Some("comment"));
4577 if !is_within_block_comment {
4578 return None;
4579 }
4580
4581 let (snapshot, range) =
4582 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4583
4584 let num_of_whitespaces = snapshot
4585 .chars_for_range(range.clone())
4586 .take_while(|c| c.is_whitespace())
4587 .count();
4588
4589 // 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.
4590 let column = start_point.column;
4591 let cursor_is_after_start_tag = {
4592 let start_tag_len = start_tag.len();
4593 let start_tag_line = snapshot
4594 .chars_for_range(range.clone())
4595 .skip(num_of_whitespaces)
4596 .take(start_tag_len)
4597 .collect::<String>();
4598 if start_tag_line.starts_with(start_tag.as_ref()) {
4599 num_of_whitespaces + start_tag_len <= column as usize
4600 } else {
4601 false
4602 }
4603 };
4604
4605 let cursor_is_after_delimiter = {
4606 let delimiter_trim = delimiter.trim_end();
4607 let delimiter_line = snapshot
4608 .chars_for_range(range.clone())
4609 .skip(num_of_whitespaces)
4610 .take(delimiter_trim.len())
4611 .collect::<String>();
4612 if delimiter_line.starts_with(delimiter_trim) {
4613 num_of_whitespaces + delimiter_trim.len() <= column as usize
4614 } else {
4615 false
4616 }
4617 };
4618
4619 let cursor_is_before_end_tag_if_exists = {
4620 let mut char_position = 0u32;
4621 let mut end_tag_offset = None;
4622
4623 'outer: for chunk in snapshot.text_for_range(range) {
4624 if let Some(byte_pos) = chunk.find(&**end_tag) {
4625 let chars_before_match =
4626 chunk[..byte_pos].chars().count() as u32;
4627 end_tag_offset =
4628 Some(char_position + chars_before_match);
4629 break 'outer;
4630 }
4631 char_position += chunk.chars().count() as u32;
4632 }
4633
4634 if let Some(end_tag_offset) = end_tag_offset {
4635 let cursor_is_before_end_tag = column <= end_tag_offset;
4636 if cursor_is_after_start_tag {
4637 if cursor_is_before_end_tag {
4638 insert_extra_newline = true;
4639 }
4640 let cursor_is_at_start_of_end_tag =
4641 column == end_tag_offset;
4642 if cursor_is_at_start_of_end_tag {
4643 indent_on_extra_newline.len = *len;
4644 }
4645 }
4646 cursor_is_before_end_tag
4647 } else {
4648 true
4649 }
4650 };
4651
4652 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4653 && cursor_is_before_end_tag_if_exists
4654 {
4655 if cursor_is_after_start_tag {
4656 indent_on_newline.len = *len;
4657 }
4658 Some(delimiter.clone())
4659 } else {
4660 None
4661 }
4662 });
4663
4664 (
4665 comment_delimiter,
4666 doc_delimiter,
4667 insert_extra_newline,
4668 indent_on_newline,
4669 indent_on_extra_newline,
4670 )
4671 } else {
4672 (
4673 None,
4674 None,
4675 false,
4676 IndentSize::default(),
4677 IndentSize::default(),
4678 )
4679 };
4680
4681 let prevent_auto_indent = doc_delimiter.is_some();
4682 let delimiter = comment_delimiter.or(doc_delimiter);
4683
4684 let capacity_for_delimiter =
4685 delimiter.as_deref().map(str::len).unwrap_or_default();
4686 let mut new_text = String::with_capacity(
4687 1 + capacity_for_delimiter
4688 + existing_indent.len as usize
4689 + indent_on_newline.len as usize
4690 + indent_on_extra_newline.len as usize,
4691 );
4692 new_text.push('\n');
4693 new_text.extend(existing_indent.chars());
4694 new_text.extend(indent_on_newline.chars());
4695
4696 if let Some(delimiter) = &delimiter {
4697 new_text.push_str(delimiter);
4698 }
4699
4700 if insert_extra_newline {
4701 new_text.push('\n');
4702 new_text.extend(existing_indent.chars());
4703 new_text.extend(indent_on_extra_newline.chars());
4704 }
4705
4706 let anchor = buffer.anchor_after(end);
4707 let new_selection = selection.map(|_| anchor);
4708 (
4709 ((start..end, new_text), prevent_auto_indent),
4710 (insert_extra_newline, new_selection),
4711 )
4712 })
4713 .unzip()
4714 };
4715
4716 let mut auto_indent_edits = Vec::new();
4717 let mut edits = Vec::new();
4718 for (edit, prevent_auto_indent) in edits_with_flags {
4719 if prevent_auto_indent {
4720 edits.push(edit);
4721 } else {
4722 auto_indent_edits.push(edit);
4723 }
4724 }
4725 if !edits.is_empty() {
4726 this.edit(edits, cx);
4727 }
4728 if !auto_indent_edits.is_empty() {
4729 this.edit_with_autoindent(auto_indent_edits, cx);
4730 }
4731
4732 let buffer = this.buffer.read(cx).snapshot(cx);
4733 let new_selections = selection_info
4734 .into_iter()
4735 .map(|(extra_newline_inserted, new_selection)| {
4736 let mut cursor = new_selection.end.to_point(&buffer);
4737 if extra_newline_inserted {
4738 cursor.row -= 1;
4739 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4740 }
4741 new_selection.map(|_| cursor)
4742 })
4743 .collect();
4744
4745 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4746 this.refresh_edit_prediction(true, false, window, cx);
4747 });
4748 }
4749
4750 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4751 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4752
4753 let buffer = self.buffer.read(cx);
4754 let snapshot = buffer.snapshot(cx);
4755
4756 let mut edits = Vec::new();
4757 let mut rows = Vec::new();
4758
4759 for (rows_inserted, selection) in self
4760 .selections
4761 .all_adjusted(&self.display_snapshot(cx))
4762 .into_iter()
4763 .enumerate()
4764 {
4765 let cursor = selection.head();
4766 let row = cursor.row;
4767
4768 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4769
4770 let newline = "\n".to_string();
4771 edits.push((start_of_line..start_of_line, newline));
4772
4773 rows.push(row + rows_inserted as u32);
4774 }
4775
4776 self.transact(window, cx, |editor, window, cx| {
4777 editor.edit(edits, cx);
4778
4779 editor.change_selections(Default::default(), window, cx, |s| {
4780 let mut index = 0;
4781 s.move_cursors_with(|map, _, _| {
4782 let row = rows[index];
4783 index += 1;
4784
4785 let point = Point::new(row, 0);
4786 let boundary = map.next_line_boundary(point).1;
4787 let clipped = map.clip_point(boundary, Bias::Left);
4788
4789 (clipped, SelectionGoal::None)
4790 });
4791 });
4792
4793 let mut indent_edits = Vec::new();
4794 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4795 for row in rows {
4796 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4797 for (row, indent) in indents {
4798 if indent.len == 0 {
4799 continue;
4800 }
4801
4802 let text = match indent.kind {
4803 IndentKind::Space => " ".repeat(indent.len as usize),
4804 IndentKind::Tab => "\t".repeat(indent.len as usize),
4805 };
4806 let point = Point::new(row.0, 0);
4807 indent_edits.push((point..point, text));
4808 }
4809 }
4810 editor.edit(indent_edits, cx);
4811 });
4812 }
4813
4814 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4815 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4816
4817 let buffer = self.buffer.read(cx);
4818 let snapshot = buffer.snapshot(cx);
4819
4820 let mut edits = Vec::new();
4821 let mut rows = Vec::new();
4822 let mut rows_inserted = 0;
4823
4824 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4825 let cursor = selection.head();
4826 let row = cursor.row;
4827
4828 let point = Point::new(row + 1, 0);
4829 let start_of_line = snapshot.clip_point(point, Bias::Left);
4830
4831 let newline = "\n".to_string();
4832 edits.push((start_of_line..start_of_line, newline));
4833
4834 rows_inserted += 1;
4835 rows.push(row + rows_inserted);
4836 }
4837
4838 self.transact(window, cx, |editor, window, cx| {
4839 editor.edit(edits, cx);
4840
4841 editor.change_selections(Default::default(), window, cx, |s| {
4842 let mut index = 0;
4843 s.move_cursors_with(|map, _, _| {
4844 let row = rows[index];
4845 index += 1;
4846
4847 let point = Point::new(row, 0);
4848 let boundary = map.next_line_boundary(point).1;
4849 let clipped = map.clip_point(boundary, Bias::Left);
4850
4851 (clipped, SelectionGoal::None)
4852 });
4853 });
4854
4855 let mut indent_edits = Vec::new();
4856 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4857 for row in rows {
4858 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4859 for (row, indent) in indents {
4860 if indent.len == 0 {
4861 continue;
4862 }
4863
4864 let text = match indent.kind {
4865 IndentKind::Space => " ".repeat(indent.len as usize),
4866 IndentKind::Tab => "\t".repeat(indent.len as usize),
4867 };
4868 let point = Point::new(row.0, 0);
4869 indent_edits.push((point..point, text));
4870 }
4871 }
4872 editor.edit(indent_edits, cx);
4873 });
4874 }
4875
4876 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4877 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4878 original_indent_columns: Vec::new(),
4879 });
4880 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4881 }
4882
4883 fn insert_with_autoindent_mode(
4884 &mut self,
4885 text: &str,
4886 autoindent_mode: Option<AutoindentMode>,
4887 window: &mut Window,
4888 cx: &mut Context<Self>,
4889 ) {
4890 if self.read_only(cx) {
4891 return;
4892 }
4893
4894 let text: Arc<str> = text.into();
4895 self.transact(window, cx, |this, window, cx| {
4896 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4897 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4898 let anchors = {
4899 let snapshot = buffer.read(cx);
4900 old_selections
4901 .iter()
4902 .map(|s| {
4903 let anchor = snapshot.anchor_after(s.head());
4904 s.map(|_| anchor)
4905 })
4906 .collect::<Vec<_>>()
4907 };
4908 buffer.edit(
4909 old_selections
4910 .iter()
4911 .map(|s| (s.start..s.end, text.clone())),
4912 autoindent_mode,
4913 cx,
4914 );
4915 anchors
4916 });
4917
4918 this.change_selections(Default::default(), window, cx, |s| {
4919 s.select_anchors(selection_anchors);
4920 });
4921
4922 cx.notify();
4923 });
4924 }
4925
4926 fn trigger_completion_on_input(
4927 &mut self,
4928 text: &str,
4929 trigger_in_words: bool,
4930 window: &mut Window,
4931 cx: &mut Context<Self>,
4932 ) {
4933 let completions_source = self
4934 .context_menu
4935 .borrow()
4936 .as_ref()
4937 .and_then(|menu| match menu {
4938 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4939 CodeContextMenu::CodeActions(_) => None,
4940 });
4941
4942 match completions_source {
4943 Some(CompletionsMenuSource::Words { .. }) => {
4944 self.open_or_update_completions_menu(
4945 Some(CompletionsMenuSource::Words {
4946 ignore_threshold: false,
4947 }),
4948 None,
4949 window,
4950 cx,
4951 );
4952 }
4953 Some(CompletionsMenuSource::Normal)
4954 | Some(CompletionsMenuSource::SnippetChoices)
4955 | None
4956 if self.is_completion_trigger(
4957 text,
4958 trigger_in_words,
4959 completions_source.is_some(),
4960 cx,
4961 ) =>
4962 {
4963 self.show_completions(
4964 &ShowCompletions {
4965 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
4966 },
4967 window,
4968 cx,
4969 )
4970 }
4971 _ => {
4972 self.hide_context_menu(window, cx);
4973 }
4974 }
4975 }
4976
4977 fn is_completion_trigger(
4978 &self,
4979 text: &str,
4980 trigger_in_words: bool,
4981 menu_is_open: bool,
4982 cx: &mut Context<Self>,
4983 ) -> bool {
4984 let position = self.selections.newest_anchor().head();
4985 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
4986 return false;
4987 };
4988
4989 if let Some(completion_provider) = &self.completion_provider {
4990 completion_provider.is_completion_trigger(
4991 &buffer,
4992 position.text_anchor,
4993 text,
4994 trigger_in_words,
4995 menu_is_open,
4996 cx,
4997 )
4998 } else {
4999 false
5000 }
5001 }
5002
5003 /// If any empty selections is touching the start of its innermost containing autoclose
5004 /// region, expand it to select the brackets.
5005 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5006 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5007 let buffer = self.buffer.read(cx).read(cx);
5008 let new_selections = self
5009 .selections_with_autoclose_regions(selections, &buffer)
5010 .map(|(mut selection, region)| {
5011 if !selection.is_empty() {
5012 return selection;
5013 }
5014
5015 if let Some(region) = region {
5016 let mut range = region.range.to_offset(&buffer);
5017 if selection.start == range.start && range.start >= region.pair.start.len() {
5018 range.start -= region.pair.start.len();
5019 if buffer.contains_str_at(range.start, ®ion.pair.start)
5020 && buffer.contains_str_at(range.end, ®ion.pair.end)
5021 {
5022 range.end += region.pair.end.len();
5023 selection.start = range.start;
5024 selection.end = range.end;
5025
5026 return selection;
5027 }
5028 }
5029 }
5030
5031 let always_treat_brackets_as_autoclosed = buffer
5032 .language_settings_at(selection.start, cx)
5033 .always_treat_brackets_as_autoclosed;
5034
5035 if !always_treat_brackets_as_autoclosed {
5036 return selection;
5037 }
5038
5039 if let Some(scope) = buffer.language_scope_at(selection.start) {
5040 for (pair, enabled) in scope.brackets() {
5041 if !enabled || !pair.close {
5042 continue;
5043 }
5044
5045 if buffer.contains_str_at(selection.start, &pair.end) {
5046 let pair_start_len = pair.start.len();
5047 if buffer.contains_str_at(
5048 selection.start.saturating_sub(pair_start_len),
5049 &pair.start,
5050 ) {
5051 selection.start -= pair_start_len;
5052 selection.end += pair.end.len();
5053
5054 return selection;
5055 }
5056 }
5057 }
5058 }
5059
5060 selection
5061 })
5062 .collect();
5063
5064 drop(buffer);
5065 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5066 selections.select(new_selections)
5067 });
5068 }
5069
5070 /// Iterate the given selections, and for each one, find the smallest surrounding
5071 /// autoclose region. This uses the ordering of the selections and the autoclose
5072 /// regions to avoid repeated comparisons.
5073 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5074 &'a self,
5075 selections: impl IntoIterator<Item = Selection<D>>,
5076 buffer: &'a MultiBufferSnapshot,
5077 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5078 let mut i = 0;
5079 let mut regions = self.autoclose_regions.as_slice();
5080 selections.into_iter().map(move |selection| {
5081 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5082
5083 let mut enclosing = None;
5084 while let Some(pair_state) = regions.get(i) {
5085 if pair_state.range.end.to_offset(buffer) < range.start {
5086 regions = ®ions[i + 1..];
5087 i = 0;
5088 } else if pair_state.range.start.to_offset(buffer) > range.end {
5089 break;
5090 } else {
5091 if pair_state.selection_id == selection.id {
5092 enclosing = Some(pair_state);
5093 }
5094 i += 1;
5095 }
5096 }
5097
5098 (selection, enclosing)
5099 })
5100 }
5101
5102 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5103 fn invalidate_autoclose_regions(
5104 &mut self,
5105 mut selections: &[Selection<Anchor>],
5106 buffer: &MultiBufferSnapshot,
5107 ) {
5108 self.autoclose_regions.retain(|state| {
5109 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5110 return false;
5111 }
5112
5113 let mut i = 0;
5114 while let Some(selection) = selections.get(i) {
5115 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5116 selections = &selections[1..];
5117 continue;
5118 }
5119 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5120 break;
5121 }
5122 if selection.id == state.selection_id {
5123 return true;
5124 } else {
5125 i += 1;
5126 }
5127 }
5128 false
5129 });
5130 }
5131
5132 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5133 let offset = position.to_offset(buffer);
5134 let (word_range, kind) =
5135 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5136 if offset > word_range.start && kind == Some(CharKind::Word) {
5137 Some(
5138 buffer
5139 .text_for_range(word_range.start..offset)
5140 .collect::<String>(),
5141 )
5142 } else {
5143 None
5144 }
5145 }
5146
5147 pub fn visible_excerpts(
5148 &self,
5149 cx: &mut Context<Editor>,
5150 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5151 let Some(project) = self.project() else {
5152 return HashMap::default();
5153 };
5154 let project = project.read(cx);
5155 let multi_buffer = self.buffer().read(cx);
5156 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5157 let multi_buffer_visible_start = self
5158 .scroll_manager
5159 .anchor()
5160 .anchor
5161 .to_point(&multi_buffer_snapshot);
5162 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5163 multi_buffer_visible_start
5164 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5165 Bias::Left,
5166 );
5167 multi_buffer_snapshot
5168 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5169 .into_iter()
5170 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5171 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5172 let buffer_file = project::File::from_dyn(buffer.file())?;
5173 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5174 let worktree_entry = buffer_worktree
5175 .read(cx)
5176 .entry_for_id(buffer_file.project_entry_id()?)?;
5177 if worktree_entry.is_ignored {
5178 None
5179 } else {
5180 Some((
5181 excerpt_id,
5182 (
5183 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5184 buffer.version().clone(),
5185 excerpt_visible_range,
5186 ),
5187 ))
5188 }
5189 })
5190 .collect()
5191 }
5192
5193 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5194 TextLayoutDetails {
5195 text_system: window.text_system().clone(),
5196 editor_style: self.style.clone().unwrap(),
5197 rem_size: window.rem_size(),
5198 scroll_anchor: self.scroll_manager.anchor(),
5199 visible_rows: self.visible_line_count(),
5200 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5201 }
5202 }
5203
5204 fn trigger_on_type_formatting(
5205 &self,
5206 input: String,
5207 window: &mut Window,
5208 cx: &mut Context<Self>,
5209 ) -> Option<Task<Result<()>>> {
5210 if input.len() != 1 {
5211 return None;
5212 }
5213
5214 let project = self.project()?;
5215 let position = self.selections.newest_anchor().head();
5216 let (buffer, buffer_position) = self
5217 .buffer
5218 .read(cx)
5219 .text_anchor_for_position(position, cx)?;
5220
5221 let settings = language_settings::language_settings(
5222 buffer
5223 .read(cx)
5224 .language_at(buffer_position)
5225 .map(|l| l.name()),
5226 buffer.read(cx).file(),
5227 cx,
5228 );
5229 if !settings.use_on_type_format {
5230 return None;
5231 }
5232
5233 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5234 // hence we do LSP request & edit on host side only — add formats to host's history.
5235 let push_to_lsp_host_history = true;
5236 // If this is not the host, append its history with new edits.
5237 let push_to_client_history = project.read(cx).is_via_collab();
5238
5239 let on_type_formatting = project.update(cx, |project, cx| {
5240 project.on_type_format(
5241 buffer.clone(),
5242 buffer_position,
5243 input,
5244 push_to_lsp_host_history,
5245 cx,
5246 )
5247 });
5248 Some(cx.spawn_in(window, async move |editor, cx| {
5249 if let Some(transaction) = on_type_formatting.await? {
5250 if push_to_client_history {
5251 buffer
5252 .update(cx, |buffer, _| {
5253 buffer.push_transaction(transaction, Instant::now());
5254 buffer.finalize_last_transaction();
5255 })
5256 .ok();
5257 }
5258 editor.update(cx, |editor, cx| {
5259 editor.refresh_document_highlights(cx);
5260 })?;
5261 }
5262 Ok(())
5263 }))
5264 }
5265
5266 pub fn show_word_completions(
5267 &mut self,
5268 _: &ShowWordCompletions,
5269 window: &mut Window,
5270 cx: &mut Context<Self>,
5271 ) {
5272 self.open_or_update_completions_menu(
5273 Some(CompletionsMenuSource::Words {
5274 ignore_threshold: true,
5275 }),
5276 None,
5277 window,
5278 cx,
5279 );
5280 }
5281
5282 pub fn show_completions(
5283 &mut self,
5284 options: &ShowCompletions,
5285 window: &mut Window,
5286 cx: &mut Context<Self>,
5287 ) {
5288 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5289 }
5290
5291 fn open_or_update_completions_menu(
5292 &mut self,
5293 requested_source: Option<CompletionsMenuSource>,
5294 trigger: Option<&str>,
5295 window: &mut Window,
5296 cx: &mut Context<Self>,
5297 ) {
5298 if self.pending_rename.is_some() {
5299 return;
5300 }
5301
5302 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5303
5304 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5305 // inserted and selected. To handle that case, the start of the selection is used so that
5306 // the menu starts with all choices.
5307 let position = self
5308 .selections
5309 .newest_anchor()
5310 .start
5311 .bias_right(&multibuffer_snapshot);
5312 if position.diff_base_anchor.is_some() {
5313 return;
5314 }
5315 let buffer_position = multibuffer_snapshot.anchor_before(position);
5316 let Some(buffer) = buffer_position
5317 .buffer_id
5318 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5319 else {
5320 return;
5321 };
5322 let buffer_snapshot = buffer.read(cx).snapshot();
5323
5324 let query: Option<Arc<String>> =
5325 Self::completion_query(&multibuffer_snapshot, buffer_position)
5326 .map(|query| query.into());
5327
5328 drop(multibuffer_snapshot);
5329
5330 // Hide the current completions menu when query is empty. Without this, cached
5331 // completions from before the trigger char may be reused (#32774).
5332 if query.is_none() {
5333 let menu_is_open = matches!(
5334 self.context_menu.borrow().as_ref(),
5335 Some(CodeContextMenu::Completions(_))
5336 );
5337 if menu_is_open {
5338 self.hide_context_menu(window, cx);
5339 }
5340 }
5341
5342 let mut ignore_word_threshold = false;
5343 let provider = match requested_source {
5344 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5345 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5346 ignore_word_threshold = ignore_threshold;
5347 None
5348 }
5349 Some(CompletionsMenuSource::SnippetChoices) => {
5350 log::error!("bug: SnippetChoices requested_source is not handled");
5351 None
5352 }
5353 };
5354
5355 let sort_completions = provider
5356 .as_ref()
5357 .is_some_and(|provider| provider.sort_completions());
5358
5359 let filter_completions = provider
5360 .as_ref()
5361 .is_none_or(|provider| provider.filter_completions());
5362
5363 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5364 if filter_completions {
5365 menu.filter(query.clone(), provider.clone(), window, cx);
5366 }
5367 // When `is_incomplete` is false, no need to re-query completions when the current query
5368 // is a suffix of the initial query.
5369 if !menu.is_incomplete {
5370 // If the new query is a suffix of the old query (typing more characters) and
5371 // the previous result was complete, the existing completions can be filtered.
5372 //
5373 // Note that this is always true for snippet completions.
5374 let query_matches = match (&menu.initial_query, &query) {
5375 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5376 (None, _) => true,
5377 _ => false,
5378 };
5379 if query_matches {
5380 let position_matches = if menu.initial_position == position {
5381 true
5382 } else {
5383 let snapshot = self.buffer.read(cx).read(cx);
5384 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5385 };
5386 if position_matches {
5387 return;
5388 }
5389 }
5390 }
5391 };
5392
5393 let trigger_kind = match trigger {
5394 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5395 CompletionTriggerKind::TRIGGER_CHARACTER
5396 }
5397 _ => CompletionTriggerKind::INVOKED,
5398 };
5399 let completion_context = CompletionContext {
5400 trigger_character: trigger.and_then(|trigger| {
5401 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5402 Some(String::from(trigger))
5403 } else {
5404 None
5405 }
5406 }),
5407 trigger_kind,
5408 };
5409
5410 let Anchor {
5411 excerpt_id: buffer_excerpt_id,
5412 text_anchor: buffer_position,
5413 ..
5414 } = buffer_position;
5415
5416 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5417 buffer_snapshot.surrounding_word(buffer_position, None)
5418 {
5419 let word_to_exclude = buffer_snapshot
5420 .text_for_range(word_range.clone())
5421 .collect::<String>();
5422 (
5423 buffer_snapshot.anchor_before(word_range.start)
5424 ..buffer_snapshot.anchor_after(buffer_position),
5425 Some(word_to_exclude),
5426 )
5427 } else {
5428 (buffer_position..buffer_position, None)
5429 };
5430
5431 let language = buffer_snapshot
5432 .language_at(buffer_position)
5433 .map(|language| language.name());
5434
5435 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5436 .completions
5437 .clone();
5438
5439 let show_completion_documentation = buffer_snapshot
5440 .settings_at(buffer_position, cx)
5441 .show_completion_documentation;
5442
5443 // The document can be large, so stay in reasonable bounds when searching for words,
5444 // otherwise completion pop-up might be slow to appear.
5445 const WORD_LOOKUP_ROWS: u32 = 5_000;
5446 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5447 let min_word_search = buffer_snapshot.clip_point(
5448 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5449 Bias::Left,
5450 );
5451 let max_word_search = buffer_snapshot.clip_point(
5452 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5453 Bias::Right,
5454 );
5455 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5456 ..buffer_snapshot.point_to_offset(max_word_search);
5457
5458 let skip_digits = query
5459 .as_ref()
5460 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5461
5462 let omit_word_completions = !self.word_completions_enabled
5463 || (!ignore_word_threshold
5464 && match &query {
5465 Some(query) => query.chars().count() < completion_settings.words_min_length,
5466 None => completion_settings.words_min_length != 0,
5467 });
5468
5469 let (mut words, provider_responses) = match &provider {
5470 Some(provider) => {
5471 let provider_responses = provider.completions(
5472 buffer_excerpt_id,
5473 &buffer,
5474 buffer_position,
5475 completion_context,
5476 window,
5477 cx,
5478 );
5479
5480 let words = match (omit_word_completions, completion_settings.words) {
5481 (true, _) | (_, WordsCompletionMode::Disabled) => {
5482 Task::ready(BTreeMap::default())
5483 }
5484 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5485 .background_spawn(async move {
5486 buffer_snapshot.words_in_range(WordsQuery {
5487 fuzzy_contents: None,
5488 range: word_search_range,
5489 skip_digits,
5490 })
5491 }),
5492 };
5493
5494 (words, provider_responses)
5495 }
5496 None => {
5497 let words = if omit_word_completions {
5498 Task::ready(BTreeMap::default())
5499 } else {
5500 cx.background_spawn(async move {
5501 buffer_snapshot.words_in_range(WordsQuery {
5502 fuzzy_contents: None,
5503 range: word_search_range,
5504 skip_digits,
5505 })
5506 })
5507 };
5508 (words, Task::ready(Ok(Vec::new())))
5509 }
5510 };
5511
5512 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5513
5514 let id = post_inc(&mut self.next_completion_id);
5515 let task = cx.spawn_in(window, async move |editor, cx| {
5516 let Ok(()) = editor.update(cx, |this, _| {
5517 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5518 }) else {
5519 return;
5520 };
5521
5522 // TODO: Ideally completions from different sources would be selectively re-queried, so
5523 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5524 let mut completions = Vec::new();
5525 let mut is_incomplete = false;
5526 let mut display_options: Option<CompletionDisplayOptions> = None;
5527 if let Some(provider_responses) = provider_responses.await.log_err()
5528 && !provider_responses.is_empty()
5529 {
5530 for response in provider_responses {
5531 completions.extend(response.completions);
5532 is_incomplete = is_incomplete || response.is_incomplete;
5533 match display_options.as_mut() {
5534 None => {
5535 display_options = Some(response.display_options);
5536 }
5537 Some(options) => options.merge(&response.display_options),
5538 }
5539 }
5540 if completion_settings.words == WordsCompletionMode::Fallback {
5541 words = Task::ready(BTreeMap::default());
5542 }
5543 }
5544 let display_options = display_options.unwrap_or_default();
5545
5546 let mut words = words.await;
5547 if let Some(word_to_exclude) = &word_to_exclude {
5548 words.remove(word_to_exclude);
5549 }
5550 for lsp_completion in &completions {
5551 words.remove(&lsp_completion.new_text);
5552 }
5553 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5554 replace_range: word_replace_range.clone(),
5555 new_text: word.clone(),
5556 label: CodeLabel::plain(word, None),
5557 icon_path: None,
5558 documentation: None,
5559 source: CompletionSource::BufferWord {
5560 word_range,
5561 resolved: false,
5562 },
5563 insert_text_mode: Some(InsertTextMode::AS_IS),
5564 confirm: None,
5565 }));
5566
5567 let menu = if completions.is_empty() {
5568 None
5569 } else {
5570 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5571 let languages = editor
5572 .workspace
5573 .as_ref()
5574 .and_then(|(workspace, _)| workspace.upgrade())
5575 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5576 let menu = CompletionsMenu::new(
5577 id,
5578 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5579 sort_completions,
5580 show_completion_documentation,
5581 position,
5582 query.clone(),
5583 is_incomplete,
5584 buffer.clone(),
5585 completions.into(),
5586 display_options,
5587 snippet_sort_order,
5588 languages,
5589 language,
5590 cx,
5591 );
5592
5593 let query = if filter_completions { query } else { None };
5594 let matches_task = if let Some(query) = query {
5595 menu.do_async_filtering(query, cx)
5596 } else {
5597 Task::ready(menu.unfiltered_matches())
5598 };
5599 (menu, matches_task)
5600 }) else {
5601 return;
5602 };
5603
5604 let matches = matches_task.await;
5605
5606 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5607 // Newer menu already set, so exit.
5608 if let Some(CodeContextMenu::Completions(prev_menu)) =
5609 editor.context_menu.borrow().as_ref()
5610 && prev_menu.id > id
5611 {
5612 return;
5613 };
5614
5615 // Only valid to take prev_menu because it the new menu is immediately set
5616 // below, or the menu is hidden.
5617 if let Some(CodeContextMenu::Completions(prev_menu)) =
5618 editor.context_menu.borrow_mut().take()
5619 {
5620 let position_matches =
5621 if prev_menu.initial_position == menu.initial_position {
5622 true
5623 } else {
5624 let snapshot = editor.buffer.read(cx).read(cx);
5625 prev_menu.initial_position.to_offset(&snapshot)
5626 == menu.initial_position.to_offset(&snapshot)
5627 };
5628 if position_matches {
5629 // Preserve markdown cache before `set_filter_results` because it will
5630 // try to populate the documentation cache.
5631 menu.preserve_markdown_cache(prev_menu);
5632 }
5633 };
5634
5635 menu.set_filter_results(matches, provider, window, cx);
5636 }) else {
5637 return;
5638 };
5639
5640 menu.visible().then_some(menu)
5641 };
5642
5643 editor
5644 .update_in(cx, |editor, window, cx| {
5645 if editor.focus_handle.is_focused(window)
5646 && let Some(menu) = menu
5647 {
5648 *editor.context_menu.borrow_mut() =
5649 Some(CodeContextMenu::Completions(menu));
5650
5651 crate::hover_popover::hide_hover(editor, cx);
5652 if editor.show_edit_predictions_in_menu() {
5653 editor.update_visible_edit_prediction(window, cx);
5654 } else {
5655 editor.discard_edit_prediction(false, cx);
5656 }
5657
5658 cx.notify();
5659 return;
5660 }
5661
5662 if editor.completion_tasks.len() <= 1 {
5663 // If there are no more completion tasks and the last menu was empty, we should hide it.
5664 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5665 // If it was already hidden and we don't show edit predictions in the menu,
5666 // we should also show the edit prediction when available.
5667 if was_hidden && editor.show_edit_predictions_in_menu() {
5668 editor.update_visible_edit_prediction(window, cx);
5669 }
5670 }
5671 })
5672 .ok();
5673 });
5674
5675 self.completion_tasks.push((id, task));
5676 }
5677
5678 #[cfg(feature = "test-support")]
5679 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5680 let menu = self.context_menu.borrow();
5681 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5682 let completions = menu.completions.borrow();
5683 Some(completions.to_vec())
5684 } else {
5685 None
5686 }
5687 }
5688
5689 pub fn with_completions_menu_matching_id<R>(
5690 &self,
5691 id: CompletionId,
5692 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5693 ) -> R {
5694 let mut context_menu = self.context_menu.borrow_mut();
5695 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5696 return f(None);
5697 };
5698 if completions_menu.id != id {
5699 return f(None);
5700 }
5701 f(Some(completions_menu))
5702 }
5703
5704 pub fn confirm_completion(
5705 &mut self,
5706 action: &ConfirmCompletion,
5707 window: &mut Window,
5708 cx: &mut Context<Self>,
5709 ) -> Option<Task<Result<()>>> {
5710 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5711 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5712 }
5713
5714 pub fn confirm_completion_insert(
5715 &mut self,
5716 _: &ConfirmCompletionInsert,
5717 window: &mut Window,
5718 cx: &mut Context<Self>,
5719 ) -> Option<Task<Result<()>>> {
5720 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5721 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5722 }
5723
5724 pub fn confirm_completion_replace(
5725 &mut self,
5726 _: &ConfirmCompletionReplace,
5727 window: &mut Window,
5728 cx: &mut Context<Self>,
5729 ) -> Option<Task<Result<()>>> {
5730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5731 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5732 }
5733
5734 pub fn compose_completion(
5735 &mut self,
5736 action: &ComposeCompletion,
5737 window: &mut Window,
5738 cx: &mut Context<Self>,
5739 ) -> Option<Task<Result<()>>> {
5740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5741 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5742 }
5743
5744 fn do_completion(
5745 &mut self,
5746 item_ix: Option<usize>,
5747 intent: CompletionIntent,
5748 window: &mut Window,
5749 cx: &mut Context<Editor>,
5750 ) -> Option<Task<Result<()>>> {
5751 use language::ToOffset as _;
5752
5753 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5754 else {
5755 return None;
5756 };
5757
5758 let candidate_id = {
5759 let entries = completions_menu.entries.borrow();
5760 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5761 if self.show_edit_predictions_in_menu() {
5762 self.discard_edit_prediction(true, cx);
5763 }
5764 mat.candidate_id
5765 };
5766
5767 let completion = completions_menu
5768 .completions
5769 .borrow()
5770 .get(candidate_id)?
5771 .clone();
5772 cx.stop_propagation();
5773
5774 let buffer_handle = completions_menu.buffer.clone();
5775
5776 let CompletionEdit {
5777 new_text,
5778 snippet,
5779 replace_range,
5780 } = process_completion_for_edit(
5781 &completion,
5782 intent,
5783 &buffer_handle,
5784 &completions_menu.initial_position.text_anchor,
5785 cx,
5786 );
5787
5788 let buffer = buffer_handle.read(cx);
5789 let snapshot = self.buffer.read(cx).snapshot(cx);
5790 let newest_anchor = self.selections.newest_anchor();
5791 let replace_range_multibuffer = {
5792 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5793 excerpt.map_range_from_buffer(replace_range.clone())
5794 };
5795 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5796 return None;
5797 }
5798
5799 let old_text = buffer
5800 .text_for_range(replace_range.clone())
5801 .collect::<String>();
5802 let lookbehind = newest_anchor
5803 .start
5804 .text_anchor
5805 .to_offset(buffer)
5806 .saturating_sub(replace_range.start);
5807 let lookahead = replace_range
5808 .end
5809 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5810 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5811 let suffix = &old_text[lookbehind.min(old_text.len())..];
5812
5813 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5814 let mut ranges = Vec::new();
5815 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5816
5817 for selection in &selections {
5818 let range = if selection.id == newest_anchor.id {
5819 replace_range_multibuffer.clone()
5820 } else {
5821 let mut range = selection.range();
5822
5823 // if prefix is present, don't duplicate it
5824 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5825 range.start = range.start.saturating_sub(lookbehind);
5826
5827 // if suffix is also present, mimic the newest cursor and replace it
5828 if selection.id != newest_anchor.id
5829 && snapshot.contains_str_at(range.end, suffix)
5830 {
5831 range.end += lookahead;
5832 }
5833 }
5834 range
5835 };
5836
5837 ranges.push(range.clone());
5838
5839 if !self.linked_edit_ranges.is_empty() {
5840 let start_anchor = snapshot.anchor_before(range.start);
5841 let end_anchor = snapshot.anchor_after(range.end);
5842 if let Some(ranges) = self
5843 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5844 {
5845 for (buffer, edits) in ranges {
5846 linked_edits
5847 .entry(buffer.clone())
5848 .or_default()
5849 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5850 }
5851 }
5852 }
5853 }
5854
5855 let common_prefix_len = old_text
5856 .chars()
5857 .zip(new_text.chars())
5858 .take_while(|(a, b)| a == b)
5859 .map(|(a, _)| a.len_utf8())
5860 .sum::<usize>();
5861
5862 cx.emit(EditorEvent::InputHandled {
5863 utf16_range_to_replace: None,
5864 text: new_text[common_prefix_len..].into(),
5865 });
5866
5867 self.transact(window, cx, |editor, window, cx| {
5868 if let Some(mut snippet) = snippet {
5869 snippet.text = new_text.to_string();
5870 editor
5871 .insert_snippet(&ranges, snippet, window, cx)
5872 .log_err();
5873 } else {
5874 editor.buffer.update(cx, |multi_buffer, cx| {
5875 let auto_indent = match completion.insert_text_mode {
5876 Some(InsertTextMode::AS_IS) => None,
5877 _ => editor.autoindent_mode.clone(),
5878 };
5879 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
5880 multi_buffer.edit(edits, auto_indent, cx);
5881 });
5882 }
5883 for (buffer, edits) in linked_edits {
5884 buffer.update(cx, |buffer, cx| {
5885 let snapshot = buffer.snapshot();
5886 let edits = edits
5887 .into_iter()
5888 .map(|(range, text)| {
5889 use text::ToPoint as TP;
5890 let end_point = TP::to_point(&range.end, &snapshot);
5891 let start_point = TP::to_point(&range.start, &snapshot);
5892 (start_point..end_point, text)
5893 })
5894 .sorted_by_key(|(range, _)| range.start);
5895 buffer.edit(edits, None, cx);
5896 })
5897 }
5898
5899 editor.refresh_edit_prediction(true, false, window, cx);
5900 });
5901 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
5902
5903 let show_new_completions_on_confirm = completion
5904 .confirm
5905 .as_ref()
5906 .is_some_and(|confirm| confirm(intent, window, cx));
5907 if show_new_completions_on_confirm {
5908 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
5909 }
5910
5911 let provider = self.completion_provider.as_ref()?;
5912 drop(completion);
5913 let apply_edits = provider.apply_additional_edits_for_completion(
5914 buffer_handle,
5915 completions_menu.completions.clone(),
5916 candidate_id,
5917 true,
5918 cx,
5919 );
5920
5921 let editor_settings = EditorSettings::get_global(cx);
5922 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
5923 // After the code completion is finished, users often want to know what signatures are needed.
5924 // so we should automatically call signature_help
5925 self.show_signature_help(&ShowSignatureHelp, window, cx);
5926 }
5927
5928 Some(cx.foreground_executor().spawn(async move {
5929 apply_edits.await?;
5930 Ok(())
5931 }))
5932 }
5933
5934 pub fn toggle_code_actions(
5935 &mut self,
5936 action: &ToggleCodeActions,
5937 window: &mut Window,
5938 cx: &mut Context<Self>,
5939 ) {
5940 let quick_launch = action.quick_launch;
5941 let mut context_menu = self.context_menu.borrow_mut();
5942 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
5943 if code_actions.deployed_from == action.deployed_from {
5944 // Toggle if we're selecting the same one
5945 *context_menu = None;
5946 cx.notify();
5947 return;
5948 } else {
5949 // Otherwise, clear it and start a new one
5950 *context_menu = None;
5951 cx.notify();
5952 }
5953 }
5954 drop(context_menu);
5955 let snapshot = self.snapshot(window, cx);
5956 let deployed_from = action.deployed_from.clone();
5957 let action = action.clone();
5958 self.completion_tasks.clear();
5959 self.discard_edit_prediction(false, cx);
5960
5961 let multibuffer_point = match &action.deployed_from {
5962 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
5963 DisplayPoint::new(*row, 0).to_point(&snapshot)
5964 }
5965 _ => self
5966 .selections
5967 .newest::<Point>(&snapshot.display_snapshot)
5968 .head(),
5969 };
5970 let Some((buffer, buffer_row)) = snapshot
5971 .buffer_snapshot()
5972 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
5973 .and_then(|(buffer_snapshot, range)| {
5974 self.buffer()
5975 .read(cx)
5976 .buffer(buffer_snapshot.remote_id())
5977 .map(|buffer| (buffer, range.start.row))
5978 })
5979 else {
5980 return;
5981 };
5982 let buffer_id = buffer.read(cx).remote_id();
5983 let tasks = self
5984 .tasks
5985 .get(&(buffer_id, buffer_row))
5986 .map(|t| Arc::new(t.to_owned()));
5987
5988 if !self.focus_handle.is_focused(window) {
5989 return;
5990 }
5991 let project = self.project.clone();
5992
5993 let code_actions_task = match deployed_from {
5994 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
5995 _ => self.code_actions(buffer_row, window, cx),
5996 };
5997
5998 let runnable_task = match deployed_from {
5999 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6000 _ => {
6001 let mut task_context_task = Task::ready(None);
6002 if let Some(tasks) = &tasks
6003 && let Some(project) = project
6004 {
6005 task_context_task =
6006 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6007 }
6008
6009 cx.spawn_in(window, {
6010 let buffer = buffer.clone();
6011 async move |editor, cx| {
6012 let task_context = task_context_task.await;
6013
6014 let resolved_tasks =
6015 tasks
6016 .zip(task_context.clone())
6017 .map(|(tasks, task_context)| ResolvedTasks {
6018 templates: tasks.resolve(&task_context).collect(),
6019 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6020 multibuffer_point.row,
6021 tasks.column,
6022 )),
6023 });
6024 let debug_scenarios = editor
6025 .update(cx, |editor, cx| {
6026 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6027 })?
6028 .await;
6029 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6030 }
6031 })
6032 }
6033 };
6034
6035 cx.spawn_in(window, async move |editor, cx| {
6036 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6037 let code_actions = code_actions_task.await;
6038 let spawn_straight_away = quick_launch
6039 && resolved_tasks
6040 .as_ref()
6041 .is_some_and(|tasks| tasks.templates.len() == 1)
6042 && code_actions
6043 .as_ref()
6044 .is_none_or(|actions| actions.is_empty())
6045 && debug_scenarios.is_empty();
6046
6047 editor.update_in(cx, |editor, window, cx| {
6048 crate::hover_popover::hide_hover(editor, cx);
6049 let actions = CodeActionContents::new(
6050 resolved_tasks,
6051 code_actions,
6052 debug_scenarios,
6053 task_context.unwrap_or_default(),
6054 );
6055
6056 // Don't show the menu if there are no actions available
6057 if actions.is_empty() {
6058 cx.notify();
6059 return Task::ready(Ok(()));
6060 }
6061
6062 *editor.context_menu.borrow_mut() =
6063 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6064 buffer,
6065 actions,
6066 selected_item: Default::default(),
6067 scroll_handle: UniformListScrollHandle::default(),
6068 deployed_from,
6069 }));
6070 cx.notify();
6071 if spawn_straight_away
6072 && let Some(task) = editor.confirm_code_action(
6073 &ConfirmCodeAction { item_ix: Some(0) },
6074 window,
6075 cx,
6076 )
6077 {
6078 return task;
6079 }
6080
6081 Task::ready(Ok(()))
6082 })
6083 })
6084 .detach_and_log_err(cx);
6085 }
6086
6087 fn debug_scenarios(
6088 &mut self,
6089 resolved_tasks: &Option<ResolvedTasks>,
6090 buffer: &Entity<Buffer>,
6091 cx: &mut App,
6092 ) -> Task<Vec<task::DebugScenario>> {
6093 maybe!({
6094 let project = self.project()?;
6095 let dap_store = project.read(cx).dap_store();
6096 let mut scenarios = vec![];
6097 let resolved_tasks = resolved_tasks.as_ref()?;
6098 let buffer = buffer.read(cx);
6099 let language = buffer.language()?;
6100 let file = buffer.file();
6101 let debug_adapter = language_settings(language.name().into(), file, cx)
6102 .debuggers
6103 .first()
6104 .map(SharedString::from)
6105 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6106
6107 dap_store.update(cx, |dap_store, cx| {
6108 for (_, task) in &resolved_tasks.templates {
6109 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6110 task.original_task().clone(),
6111 debug_adapter.clone().into(),
6112 task.display_label().to_owned().into(),
6113 cx,
6114 );
6115 scenarios.push(maybe_scenario);
6116 }
6117 });
6118 Some(cx.background_spawn(async move {
6119 futures::future::join_all(scenarios)
6120 .await
6121 .into_iter()
6122 .flatten()
6123 .collect::<Vec<_>>()
6124 }))
6125 })
6126 .unwrap_or_else(|| Task::ready(vec![]))
6127 }
6128
6129 fn code_actions(
6130 &mut self,
6131 buffer_row: u32,
6132 window: &mut Window,
6133 cx: &mut Context<Self>,
6134 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6135 let mut task = self.code_actions_task.take();
6136 cx.spawn_in(window, async move |editor, cx| {
6137 while let Some(prev_task) = task {
6138 prev_task.await.log_err();
6139 task = editor
6140 .update(cx, |this, _| this.code_actions_task.take())
6141 .ok()?;
6142 }
6143
6144 editor
6145 .update(cx, |editor, cx| {
6146 editor
6147 .available_code_actions
6148 .clone()
6149 .and_then(|(location, code_actions)| {
6150 let snapshot = location.buffer.read(cx).snapshot();
6151 let point_range = location.range.to_point(&snapshot);
6152 let point_range = point_range.start.row..=point_range.end.row;
6153 if point_range.contains(&buffer_row) {
6154 Some(code_actions)
6155 } else {
6156 None
6157 }
6158 })
6159 })
6160 .ok()
6161 .flatten()
6162 })
6163 }
6164
6165 pub fn confirm_code_action(
6166 &mut self,
6167 action: &ConfirmCodeAction,
6168 window: &mut Window,
6169 cx: &mut Context<Self>,
6170 ) -> Option<Task<Result<()>>> {
6171 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6172
6173 let actions_menu =
6174 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6175 menu
6176 } else {
6177 return None;
6178 };
6179
6180 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6181 let action = actions_menu.actions.get(action_ix)?;
6182 let title = action.label();
6183 let buffer = actions_menu.buffer;
6184 let workspace = self.workspace()?;
6185
6186 match action {
6187 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6188 workspace.update(cx, |workspace, cx| {
6189 workspace.schedule_resolved_task(
6190 task_source_kind,
6191 resolved_task,
6192 false,
6193 window,
6194 cx,
6195 );
6196
6197 Some(Task::ready(Ok(())))
6198 })
6199 }
6200 CodeActionsItem::CodeAction {
6201 excerpt_id,
6202 action,
6203 provider,
6204 } => {
6205 let apply_code_action =
6206 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6207 let workspace = workspace.downgrade();
6208 Some(cx.spawn_in(window, async move |editor, cx| {
6209 let project_transaction = apply_code_action.await?;
6210 Self::open_project_transaction(
6211 &editor,
6212 workspace,
6213 project_transaction,
6214 title,
6215 cx,
6216 )
6217 .await
6218 }))
6219 }
6220 CodeActionsItem::DebugScenario(scenario) => {
6221 let context = actions_menu.actions.context;
6222
6223 workspace.update(cx, |workspace, cx| {
6224 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6225 workspace.start_debug_session(
6226 scenario,
6227 context,
6228 Some(buffer),
6229 None,
6230 window,
6231 cx,
6232 );
6233 });
6234 Some(Task::ready(Ok(())))
6235 }
6236 }
6237 }
6238
6239 pub async fn open_project_transaction(
6240 editor: &WeakEntity<Editor>,
6241 workspace: WeakEntity<Workspace>,
6242 transaction: ProjectTransaction,
6243 title: String,
6244 cx: &mut AsyncWindowContext,
6245 ) -> Result<()> {
6246 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6247 cx.update(|_, cx| {
6248 entries.sort_unstable_by_key(|(buffer, _)| {
6249 buffer.read(cx).file().map(|f| f.path().clone())
6250 });
6251 })?;
6252 if entries.is_empty() {
6253 return Ok(());
6254 }
6255
6256 // If the project transaction's edits are all contained within this editor, then
6257 // avoid opening a new editor to display them.
6258
6259 if let [(buffer, transaction)] = &*entries {
6260 let excerpt = editor.update(cx, |editor, cx| {
6261 editor
6262 .buffer()
6263 .read(cx)
6264 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6265 })?;
6266 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6267 && excerpted_buffer == *buffer
6268 {
6269 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6270 let excerpt_range = excerpt_range.to_offset(buffer);
6271 buffer
6272 .edited_ranges_for_transaction::<usize>(transaction)
6273 .all(|range| {
6274 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6275 })
6276 })?;
6277
6278 if all_edits_within_excerpt {
6279 return Ok(());
6280 }
6281 }
6282 }
6283
6284 let mut ranges_to_highlight = Vec::new();
6285 let excerpt_buffer = cx.new(|cx| {
6286 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6287 for (buffer_handle, transaction) in &entries {
6288 let edited_ranges = buffer_handle
6289 .read(cx)
6290 .edited_ranges_for_transaction::<Point>(transaction)
6291 .collect::<Vec<_>>();
6292 let (ranges, _) = multibuffer.set_excerpts_for_path(
6293 PathKey::for_buffer(buffer_handle, cx),
6294 buffer_handle.clone(),
6295 edited_ranges,
6296 multibuffer_context_lines(cx),
6297 cx,
6298 );
6299
6300 ranges_to_highlight.extend(ranges);
6301 }
6302 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6303 multibuffer
6304 })?;
6305
6306 workspace.update_in(cx, |workspace, window, cx| {
6307 let project = workspace.project().clone();
6308 let editor =
6309 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6310 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6311 editor.update(cx, |editor, cx| {
6312 editor.highlight_background::<Self>(
6313 &ranges_to_highlight,
6314 |theme| theme.colors().editor_highlighted_line_background,
6315 cx,
6316 );
6317 });
6318 })?;
6319
6320 Ok(())
6321 }
6322
6323 pub fn clear_code_action_providers(&mut self) {
6324 self.code_action_providers.clear();
6325 self.available_code_actions.take();
6326 }
6327
6328 pub fn add_code_action_provider(
6329 &mut self,
6330 provider: Rc<dyn CodeActionProvider>,
6331 window: &mut Window,
6332 cx: &mut Context<Self>,
6333 ) {
6334 if self
6335 .code_action_providers
6336 .iter()
6337 .any(|existing_provider| existing_provider.id() == provider.id())
6338 {
6339 return;
6340 }
6341
6342 self.code_action_providers.push(provider);
6343 self.refresh_code_actions(window, cx);
6344 }
6345
6346 pub fn remove_code_action_provider(
6347 &mut self,
6348 id: Arc<str>,
6349 window: &mut Window,
6350 cx: &mut Context<Self>,
6351 ) {
6352 self.code_action_providers
6353 .retain(|provider| provider.id() != id);
6354 self.refresh_code_actions(window, cx);
6355 }
6356
6357 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6358 !self.code_action_providers.is_empty()
6359 && EditorSettings::get_global(cx).toolbar.code_actions
6360 }
6361
6362 pub fn has_available_code_actions(&self) -> bool {
6363 self.available_code_actions
6364 .as_ref()
6365 .is_some_and(|(_, actions)| !actions.is_empty())
6366 }
6367
6368 fn render_inline_code_actions(
6369 &self,
6370 icon_size: ui::IconSize,
6371 display_row: DisplayRow,
6372 is_active: bool,
6373 cx: &mut Context<Self>,
6374 ) -> AnyElement {
6375 let show_tooltip = !self.context_menu_visible();
6376 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6377 .icon_size(icon_size)
6378 .shape(ui::IconButtonShape::Square)
6379 .icon_color(ui::Color::Hidden)
6380 .toggle_state(is_active)
6381 .when(show_tooltip, |this| {
6382 this.tooltip({
6383 let focus_handle = self.focus_handle.clone();
6384 move |_window, cx| {
6385 Tooltip::for_action_in(
6386 "Toggle Code Actions",
6387 &ToggleCodeActions {
6388 deployed_from: None,
6389 quick_launch: false,
6390 },
6391 &focus_handle,
6392 cx,
6393 )
6394 }
6395 })
6396 })
6397 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6398 window.focus(&editor.focus_handle(cx));
6399 editor.toggle_code_actions(
6400 &crate::actions::ToggleCodeActions {
6401 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6402 display_row,
6403 )),
6404 quick_launch: false,
6405 },
6406 window,
6407 cx,
6408 );
6409 }))
6410 .into_any_element()
6411 }
6412
6413 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6414 &self.context_menu
6415 }
6416
6417 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6418 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6419 cx.background_executor()
6420 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6421 .await;
6422
6423 let (start_buffer, start, _, end, newest_selection) = this
6424 .update(cx, |this, cx| {
6425 let newest_selection = this.selections.newest_anchor().clone();
6426 if newest_selection.head().diff_base_anchor.is_some() {
6427 return None;
6428 }
6429 let display_snapshot = this.display_snapshot(cx);
6430 let newest_selection_adjusted =
6431 this.selections.newest_adjusted(&display_snapshot);
6432 let buffer = this.buffer.read(cx);
6433
6434 let (start_buffer, start) =
6435 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6436 let (end_buffer, end) =
6437 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6438
6439 Some((start_buffer, start, end_buffer, end, newest_selection))
6440 })?
6441 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6442 .context(
6443 "Expected selection to lie in a single buffer when refreshing code actions",
6444 )?;
6445 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6446 let providers = this.code_action_providers.clone();
6447 let tasks = this
6448 .code_action_providers
6449 .iter()
6450 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6451 .collect::<Vec<_>>();
6452 (providers, tasks)
6453 })?;
6454
6455 let mut actions = Vec::new();
6456 for (provider, provider_actions) in
6457 providers.into_iter().zip(future::join_all(tasks).await)
6458 {
6459 if let Some(provider_actions) = provider_actions.log_err() {
6460 actions.extend(provider_actions.into_iter().map(|action| {
6461 AvailableCodeAction {
6462 excerpt_id: newest_selection.start.excerpt_id,
6463 action,
6464 provider: provider.clone(),
6465 }
6466 }));
6467 }
6468 }
6469
6470 this.update(cx, |this, cx| {
6471 this.available_code_actions = if actions.is_empty() {
6472 None
6473 } else {
6474 Some((
6475 Location {
6476 buffer: start_buffer,
6477 range: start..end,
6478 },
6479 actions.into(),
6480 ))
6481 };
6482 cx.notify();
6483 })
6484 }));
6485 }
6486
6487 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6488 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6489 self.show_git_blame_inline = false;
6490
6491 self.show_git_blame_inline_delay_task =
6492 Some(cx.spawn_in(window, async move |this, cx| {
6493 cx.background_executor().timer(delay).await;
6494
6495 this.update(cx, |this, cx| {
6496 this.show_git_blame_inline = true;
6497 cx.notify();
6498 })
6499 .log_err();
6500 }));
6501 }
6502 }
6503
6504 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6505 let snapshot = self.snapshot(window, cx);
6506 let cursor = self
6507 .selections
6508 .newest::<Point>(&snapshot.display_snapshot)
6509 .head();
6510 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6511 else {
6512 return;
6513 };
6514
6515 let Some(blame) = self.blame.as_ref() else {
6516 return;
6517 };
6518
6519 let row_info = RowInfo {
6520 buffer_id: Some(buffer.remote_id()),
6521 buffer_row: Some(point.row),
6522 ..Default::default()
6523 };
6524 let Some((buffer, blame_entry)) = blame
6525 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6526 .flatten()
6527 else {
6528 return;
6529 };
6530
6531 let anchor = self.selections.newest_anchor().head();
6532 let position = self.to_pixel_point(anchor, &snapshot, window);
6533 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6534 self.show_blame_popover(
6535 buffer,
6536 &blame_entry,
6537 position + last_bounds.origin,
6538 true,
6539 cx,
6540 );
6541 };
6542 }
6543
6544 fn show_blame_popover(
6545 &mut self,
6546 buffer: BufferId,
6547 blame_entry: &BlameEntry,
6548 position: gpui::Point<Pixels>,
6549 ignore_timeout: bool,
6550 cx: &mut Context<Self>,
6551 ) {
6552 if let Some(state) = &mut self.inline_blame_popover {
6553 state.hide_task.take();
6554 } else {
6555 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6556 let blame_entry = blame_entry.clone();
6557 let show_task = cx.spawn(async move |editor, cx| {
6558 if !ignore_timeout {
6559 cx.background_executor()
6560 .timer(std::time::Duration::from_millis(blame_popover_delay))
6561 .await;
6562 }
6563 editor
6564 .update(cx, |editor, cx| {
6565 editor.inline_blame_popover_show_task.take();
6566 let Some(blame) = editor.blame.as_ref() else {
6567 return;
6568 };
6569 let blame = blame.read(cx);
6570 let details = blame.details_for_entry(buffer, &blame_entry);
6571 let markdown = cx.new(|cx| {
6572 Markdown::new(
6573 details
6574 .as_ref()
6575 .map(|message| message.message.clone())
6576 .unwrap_or_default(),
6577 None,
6578 None,
6579 cx,
6580 )
6581 });
6582 editor.inline_blame_popover = Some(InlineBlamePopover {
6583 position,
6584 hide_task: None,
6585 popover_bounds: None,
6586 popover_state: InlineBlamePopoverState {
6587 scroll_handle: ScrollHandle::new(),
6588 commit_message: details,
6589 markdown,
6590 },
6591 keyboard_grace: ignore_timeout,
6592 });
6593 cx.notify();
6594 })
6595 .ok();
6596 });
6597 self.inline_blame_popover_show_task = Some(show_task);
6598 }
6599 }
6600
6601 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6602 self.inline_blame_popover_show_task.take();
6603 if let Some(state) = &mut self.inline_blame_popover {
6604 let hide_task = cx.spawn(async move |editor, cx| {
6605 if !ignore_timeout {
6606 cx.background_executor()
6607 .timer(std::time::Duration::from_millis(100))
6608 .await;
6609 }
6610 editor
6611 .update(cx, |editor, cx| {
6612 editor.inline_blame_popover.take();
6613 cx.notify();
6614 })
6615 .ok();
6616 });
6617 state.hide_task = Some(hide_task);
6618 true
6619 } else {
6620 false
6621 }
6622 }
6623
6624 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6625 if self.pending_rename.is_some() {
6626 return None;
6627 }
6628
6629 let provider = self.semantics_provider.clone()?;
6630 let buffer = self.buffer.read(cx);
6631 let newest_selection = self.selections.newest_anchor().clone();
6632 let cursor_position = newest_selection.head();
6633 let (cursor_buffer, cursor_buffer_position) =
6634 buffer.text_anchor_for_position(cursor_position, cx)?;
6635 let (tail_buffer, tail_buffer_position) =
6636 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6637 if cursor_buffer != tail_buffer {
6638 return None;
6639 }
6640
6641 let snapshot = cursor_buffer.read(cx).snapshot();
6642 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6643 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6644 if start_word_range != end_word_range {
6645 self.document_highlights_task.take();
6646 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6647 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6648 return None;
6649 }
6650
6651 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6652 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6653 cx.background_executor()
6654 .timer(Duration::from_millis(debounce))
6655 .await;
6656
6657 let highlights = if let Some(highlights) = cx
6658 .update(|cx| {
6659 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6660 })
6661 .ok()
6662 .flatten()
6663 {
6664 highlights.await.log_err()
6665 } else {
6666 None
6667 };
6668
6669 if let Some(highlights) = highlights {
6670 this.update(cx, |this, cx| {
6671 if this.pending_rename.is_some() {
6672 return;
6673 }
6674
6675 let buffer = this.buffer.read(cx);
6676 if buffer
6677 .text_anchor_for_position(cursor_position, cx)
6678 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6679 {
6680 return;
6681 }
6682
6683 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6684 let mut write_ranges = Vec::new();
6685 let mut read_ranges = Vec::new();
6686 for highlight in highlights {
6687 let buffer_id = cursor_buffer.read(cx).remote_id();
6688 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6689 {
6690 let start = highlight
6691 .range
6692 .start
6693 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6694 let end = highlight
6695 .range
6696 .end
6697 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6698 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6699 continue;
6700 }
6701
6702 let range =
6703 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6704 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6705 write_ranges.push(range);
6706 } else {
6707 read_ranges.push(range);
6708 }
6709 }
6710 }
6711
6712 this.highlight_background::<DocumentHighlightRead>(
6713 &read_ranges,
6714 |theme| theme.colors().editor_document_highlight_read_background,
6715 cx,
6716 );
6717 this.highlight_background::<DocumentHighlightWrite>(
6718 &write_ranges,
6719 |theme| theme.colors().editor_document_highlight_write_background,
6720 cx,
6721 );
6722 cx.notify();
6723 })
6724 .log_err();
6725 }
6726 }));
6727 None
6728 }
6729
6730 fn prepare_highlight_query_from_selection(
6731 &mut self,
6732 window: &Window,
6733 cx: &mut Context<Editor>,
6734 ) -> Option<(String, Range<Anchor>)> {
6735 if matches!(self.mode, EditorMode::SingleLine) {
6736 return None;
6737 }
6738 if !EditorSettings::get_global(cx).selection_highlight {
6739 return None;
6740 }
6741 if self.selections.count() != 1 || self.selections.line_mode() {
6742 return None;
6743 }
6744 let snapshot = self.snapshot(window, cx);
6745 let selection = self.selections.newest::<Point>(&snapshot);
6746 // If the selection spans multiple rows OR it is empty
6747 if selection.start.row != selection.end.row
6748 || selection.start.column == selection.end.column
6749 {
6750 return None;
6751 }
6752 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6753 let query = snapshot
6754 .buffer_snapshot()
6755 .text_for_range(selection_anchor_range.clone())
6756 .collect::<String>();
6757 if query.trim().is_empty() {
6758 return None;
6759 }
6760 Some((query, selection_anchor_range))
6761 }
6762
6763 fn update_selection_occurrence_highlights(
6764 &mut self,
6765 query_text: String,
6766 query_range: Range<Anchor>,
6767 multi_buffer_range_to_query: Range<Point>,
6768 use_debounce: bool,
6769 window: &mut Window,
6770 cx: &mut Context<Editor>,
6771 ) -> Task<()> {
6772 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6773 cx.spawn_in(window, async move |editor, cx| {
6774 if use_debounce {
6775 cx.background_executor()
6776 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6777 .await;
6778 }
6779 let match_task = cx.background_spawn(async move {
6780 let buffer_ranges = multi_buffer_snapshot
6781 .range_to_buffer_ranges(multi_buffer_range_to_query)
6782 .into_iter()
6783 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6784 let mut match_ranges = Vec::new();
6785 let Ok(regex) = project::search::SearchQuery::text(
6786 query_text.clone(),
6787 false,
6788 false,
6789 false,
6790 Default::default(),
6791 Default::default(),
6792 false,
6793 None,
6794 ) else {
6795 return Vec::default();
6796 };
6797 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6798 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6799 match_ranges.extend(
6800 regex
6801 .search(buffer_snapshot, Some(search_range.clone()))
6802 .await
6803 .into_iter()
6804 .filter_map(|match_range| {
6805 let match_start = buffer_snapshot
6806 .anchor_after(search_range.start + match_range.start);
6807 let match_end = buffer_snapshot
6808 .anchor_before(search_range.start + match_range.end);
6809 let match_anchor_range = Anchor::range_in_buffer(
6810 excerpt_id,
6811 buffer_snapshot.remote_id(),
6812 match_start..match_end,
6813 );
6814 (match_anchor_range != query_range).then_some(match_anchor_range)
6815 }),
6816 );
6817 }
6818 match_ranges
6819 });
6820 let match_ranges = match_task.await;
6821 editor
6822 .update_in(cx, |editor, _, cx| {
6823 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6824 if !match_ranges.is_empty() {
6825 editor.highlight_background::<SelectedTextHighlight>(
6826 &match_ranges,
6827 |theme| theme.colors().editor_document_highlight_bracket_background,
6828 cx,
6829 )
6830 }
6831 })
6832 .log_err();
6833 })
6834 }
6835
6836 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6837 struct NewlineFold;
6838 let type_id = std::any::TypeId::of::<NewlineFold>();
6839 if !self.mode.is_single_line() {
6840 return;
6841 }
6842 let snapshot = self.snapshot(window, cx);
6843 if snapshot.buffer_snapshot().max_point().row == 0 {
6844 return;
6845 }
6846 let task = cx.background_spawn(async move {
6847 let new_newlines = snapshot
6848 .buffer_chars_at(0)
6849 .filter_map(|(c, i)| {
6850 if c == '\n' {
6851 Some(
6852 snapshot.buffer_snapshot().anchor_after(i)
6853 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6854 )
6855 } else {
6856 None
6857 }
6858 })
6859 .collect::<Vec<_>>();
6860 let existing_newlines = snapshot
6861 .folds_in_range(0..snapshot.buffer_snapshot().len())
6862 .filter_map(|fold| {
6863 if fold.placeholder.type_tag == Some(type_id) {
6864 Some(fold.range.start..fold.range.end)
6865 } else {
6866 None
6867 }
6868 })
6869 .collect::<Vec<_>>();
6870
6871 (new_newlines, existing_newlines)
6872 });
6873 self.folding_newlines = cx.spawn(async move |this, cx| {
6874 let (new_newlines, existing_newlines) = task.await;
6875 if new_newlines == existing_newlines {
6876 return;
6877 }
6878 let placeholder = FoldPlaceholder {
6879 render: Arc::new(move |_, _, cx| {
6880 div()
6881 .bg(cx.theme().status().hint_background)
6882 .border_b_1()
6883 .size_full()
6884 .font(ThemeSettings::get_global(cx).buffer_font.clone())
6885 .border_color(cx.theme().status().hint)
6886 .child("\\n")
6887 .into_any()
6888 }),
6889 constrain_width: false,
6890 merge_adjacent: false,
6891 type_tag: Some(type_id),
6892 };
6893 let creases = new_newlines
6894 .into_iter()
6895 .map(|range| Crease::simple(range, placeholder.clone()))
6896 .collect();
6897 this.update(cx, |this, cx| {
6898 this.display_map.update(cx, |display_map, cx| {
6899 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
6900 display_map.fold(creases, cx);
6901 });
6902 })
6903 .ok();
6904 });
6905 }
6906
6907 fn refresh_selected_text_highlights(
6908 &mut self,
6909 on_buffer_edit: bool,
6910 window: &mut Window,
6911 cx: &mut Context<Editor>,
6912 ) {
6913 let Some((query_text, query_range)) =
6914 self.prepare_highlight_query_from_selection(window, cx)
6915 else {
6916 self.clear_background_highlights::<SelectedTextHighlight>(cx);
6917 self.quick_selection_highlight_task.take();
6918 self.debounced_selection_highlight_task.take();
6919 return;
6920 };
6921 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6922 if on_buffer_edit
6923 || self
6924 .quick_selection_highlight_task
6925 .as_ref()
6926 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6927 {
6928 let multi_buffer_visible_start = self
6929 .scroll_manager
6930 .anchor()
6931 .anchor
6932 .to_point(&multi_buffer_snapshot);
6933 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
6934 multi_buffer_visible_start
6935 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
6936 Bias::Left,
6937 );
6938 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
6939 self.quick_selection_highlight_task = Some((
6940 query_range.clone(),
6941 self.update_selection_occurrence_highlights(
6942 query_text.clone(),
6943 query_range.clone(),
6944 multi_buffer_visible_range,
6945 false,
6946 window,
6947 cx,
6948 ),
6949 ));
6950 }
6951 if on_buffer_edit
6952 || self
6953 .debounced_selection_highlight_task
6954 .as_ref()
6955 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
6956 {
6957 let multi_buffer_start = multi_buffer_snapshot
6958 .anchor_before(0)
6959 .to_point(&multi_buffer_snapshot);
6960 let multi_buffer_end = multi_buffer_snapshot
6961 .anchor_after(multi_buffer_snapshot.len())
6962 .to_point(&multi_buffer_snapshot);
6963 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
6964 self.debounced_selection_highlight_task = Some((
6965 query_range.clone(),
6966 self.update_selection_occurrence_highlights(
6967 query_text,
6968 query_range,
6969 multi_buffer_full_range,
6970 true,
6971 window,
6972 cx,
6973 ),
6974 ));
6975 }
6976 }
6977
6978 pub fn refresh_edit_prediction(
6979 &mut self,
6980 debounce: bool,
6981 user_requested: bool,
6982 window: &mut Window,
6983 cx: &mut Context<Self>,
6984 ) -> Option<()> {
6985 if DisableAiSettings::get_global(cx).disable_ai {
6986 return None;
6987 }
6988
6989 let provider = self.edit_prediction_provider()?;
6990 let cursor = self.selections.newest_anchor().head();
6991 let (buffer, cursor_buffer_position) =
6992 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
6993
6994 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
6995 self.discard_edit_prediction(false, cx);
6996 return None;
6997 }
6998
6999 self.update_visible_edit_prediction(window, cx);
7000
7001 if !user_requested
7002 && (!self.should_show_edit_predictions()
7003 || !self.is_focused(window)
7004 || buffer.read(cx).is_empty())
7005 {
7006 self.discard_edit_prediction(false, cx);
7007 return None;
7008 }
7009
7010 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7011 Some(())
7012 }
7013
7014 fn show_edit_predictions_in_menu(&self) -> bool {
7015 match self.edit_prediction_settings {
7016 EditPredictionSettings::Disabled => false,
7017 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7018 }
7019 }
7020
7021 pub fn edit_predictions_enabled(&self) -> bool {
7022 match self.edit_prediction_settings {
7023 EditPredictionSettings::Disabled => false,
7024 EditPredictionSettings::Enabled { .. } => true,
7025 }
7026 }
7027
7028 fn edit_prediction_requires_modifier(&self) -> bool {
7029 match self.edit_prediction_settings {
7030 EditPredictionSettings::Disabled => false,
7031 EditPredictionSettings::Enabled {
7032 preview_requires_modifier,
7033 ..
7034 } => preview_requires_modifier,
7035 }
7036 }
7037
7038 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7039 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7040 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7041 self.discard_edit_prediction(false, cx);
7042 } else {
7043 let selection = self.selections.newest_anchor();
7044 let cursor = selection.head();
7045
7046 if let Some((buffer, cursor_buffer_position)) =
7047 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7048 {
7049 self.edit_prediction_settings =
7050 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7051 }
7052 }
7053 }
7054
7055 fn edit_prediction_settings_at_position(
7056 &self,
7057 buffer: &Entity<Buffer>,
7058 buffer_position: language::Anchor,
7059 cx: &App,
7060 ) -> EditPredictionSettings {
7061 if !self.mode.is_full()
7062 || !self.show_edit_predictions_override.unwrap_or(true)
7063 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7064 {
7065 return EditPredictionSettings::Disabled;
7066 }
7067
7068 let buffer = buffer.read(cx);
7069
7070 let file = buffer.file();
7071
7072 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7073 return EditPredictionSettings::Disabled;
7074 };
7075
7076 let by_provider = matches!(
7077 self.menu_edit_predictions_policy,
7078 MenuEditPredictionsPolicy::ByProvider
7079 );
7080
7081 let show_in_menu = by_provider
7082 && self
7083 .edit_prediction_provider
7084 .as_ref()
7085 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7086
7087 let preview_requires_modifier =
7088 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7089
7090 EditPredictionSettings::Enabled {
7091 show_in_menu,
7092 preview_requires_modifier,
7093 }
7094 }
7095
7096 fn should_show_edit_predictions(&self) -> bool {
7097 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7098 }
7099
7100 pub fn edit_prediction_preview_is_active(&self) -> bool {
7101 matches!(
7102 self.edit_prediction_preview,
7103 EditPredictionPreview::Active { .. }
7104 )
7105 }
7106
7107 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7108 let cursor = self.selections.newest_anchor().head();
7109 if let Some((buffer, cursor_position)) =
7110 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7111 {
7112 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7113 } else {
7114 false
7115 }
7116 }
7117
7118 pub fn supports_minimap(&self, cx: &App) -> bool {
7119 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7120 }
7121
7122 fn edit_predictions_enabled_in_buffer(
7123 &self,
7124 buffer: &Entity<Buffer>,
7125 buffer_position: language::Anchor,
7126 cx: &App,
7127 ) -> bool {
7128 maybe!({
7129 if self.read_only(cx) {
7130 return Some(false);
7131 }
7132 let provider = self.edit_prediction_provider()?;
7133 if !provider.is_enabled(buffer, buffer_position, cx) {
7134 return Some(false);
7135 }
7136 let buffer = buffer.read(cx);
7137 let Some(file) = buffer.file() else {
7138 return Some(true);
7139 };
7140 let settings = all_language_settings(Some(file), cx);
7141 Some(settings.edit_predictions_enabled_for_file(file, cx))
7142 })
7143 .unwrap_or(false)
7144 }
7145
7146 fn cycle_edit_prediction(
7147 &mut self,
7148 direction: Direction,
7149 window: &mut Window,
7150 cx: &mut Context<Self>,
7151 ) -> Option<()> {
7152 let provider = self.edit_prediction_provider()?;
7153 let cursor = self.selections.newest_anchor().head();
7154 let (buffer, cursor_buffer_position) =
7155 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7156 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7157 return None;
7158 }
7159
7160 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7161 self.update_visible_edit_prediction(window, cx);
7162
7163 Some(())
7164 }
7165
7166 pub fn show_edit_prediction(
7167 &mut self,
7168 _: &ShowEditPrediction,
7169 window: &mut Window,
7170 cx: &mut Context<Self>,
7171 ) {
7172 if !self.has_active_edit_prediction() {
7173 self.refresh_edit_prediction(false, true, window, cx);
7174 return;
7175 }
7176
7177 self.update_visible_edit_prediction(window, cx);
7178 }
7179
7180 pub fn display_cursor_names(
7181 &mut self,
7182 _: &DisplayCursorNames,
7183 window: &mut Window,
7184 cx: &mut Context<Self>,
7185 ) {
7186 self.show_cursor_names(window, cx);
7187 }
7188
7189 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7190 self.show_cursor_names = true;
7191 cx.notify();
7192 cx.spawn_in(window, async move |this, cx| {
7193 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7194 this.update(cx, |this, cx| {
7195 this.show_cursor_names = false;
7196 cx.notify()
7197 })
7198 .ok()
7199 })
7200 .detach();
7201 }
7202
7203 pub fn next_edit_prediction(
7204 &mut self,
7205 _: &NextEditPrediction,
7206 window: &mut Window,
7207 cx: &mut Context<Self>,
7208 ) {
7209 if self.has_active_edit_prediction() {
7210 self.cycle_edit_prediction(Direction::Next, window, cx);
7211 } else {
7212 let is_copilot_disabled = self
7213 .refresh_edit_prediction(false, true, window, cx)
7214 .is_none();
7215 if is_copilot_disabled {
7216 cx.propagate();
7217 }
7218 }
7219 }
7220
7221 pub fn previous_edit_prediction(
7222 &mut self,
7223 _: &PreviousEditPrediction,
7224 window: &mut Window,
7225 cx: &mut Context<Self>,
7226 ) {
7227 if self.has_active_edit_prediction() {
7228 self.cycle_edit_prediction(Direction::Prev, window, cx);
7229 } else {
7230 let is_copilot_disabled = self
7231 .refresh_edit_prediction(false, true, window, cx)
7232 .is_none();
7233 if is_copilot_disabled {
7234 cx.propagate();
7235 }
7236 }
7237 }
7238
7239 pub fn accept_edit_prediction(
7240 &mut self,
7241 _: &AcceptEditPrediction,
7242 window: &mut Window,
7243 cx: &mut Context<Self>,
7244 ) {
7245 if self.show_edit_predictions_in_menu() {
7246 self.hide_context_menu(window, cx);
7247 }
7248
7249 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7250 return;
7251 };
7252
7253 match &active_edit_prediction.completion {
7254 EditPrediction::MoveWithin { target, .. } => {
7255 let target = *target;
7256
7257 if let Some(position_map) = &self.last_position_map {
7258 if position_map
7259 .visible_row_range
7260 .contains(&target.to_display_point(&position_map.snapshot).row())
7261 || !self.edit_prediction_requires_modifier()
7262 {
7263 self.unfold_ranges(&[target..target], true, false, cx);
7264 // Note that this is also done in vim's handler of the Tab action.
7265 self.change_selections(
7266 SelectionEffects::scroll(Autoscroll::newest()),
7267 window,
7268 cx,
7269 |selections| {
7270 selections.select_anchor_ranges([target..target]);
7271 },
7272 );
7273 self.clear_row_highlights::<EditPredictionPreview>();
7274
7275 self.edit_prediction_preview
7276 .set_previous_scroll_position(None);
7277 } else {
7278 self.edit_prediction_preview
7279 .set_previous_scroll_position(Some(
7280 position_map.snapshot.scroll_anchor,
7281 ));
7282
7283 self.highlight_rows::<EditPredictionPreview>(
7284 target..target,
7285 cx.theme().colors().editor_highlighted_line_background,
7286 RowHighlightOptions {
7287 autoscroll: true,
7288 ..Default::default()
7289 },
7290 cx,
7291 );
7292 self.request_autoscroll(Autoscroll::fit(), cx);
7293 }
7294 }
7295 }
7296 EditPrediction::MoveOutside { snapshot, target } => {
7297 if let Some(workspace) = self.workspace() {
7298 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7299 .detach_and_log_err(cx);
7300 }
7301 }
7302 EditPrediction::Edit { edits, .. } => {
7303 self.report_edit_prediction_event(
7304 active_edit_prediction.completion_id.clone(),
7305 true,
7306 cx,
7307 );
7308
7309 if let Some(provider) = self.edit_prediction_provider() {
7310 provider.accept(cx);
7311 }
7312
7313 // Store the transaction ID and selections before applying the edit
7314 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7315
7316 let snapshot = self.buffer.read(cx).snapshot(cx);
7317 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7318
7319 self.buffer.update(cx, |buffer, cx| {
7320 buffer.edit(edits.iter().cloned(), None, cx)
7321 });
7322
7323 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7324 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7325 });
7326
7327 let selections = self.selections.disjoint_anchors_arc();
7328 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7329 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7330 if has_new_transaction {
7331 self.selection_history
7332 .insert_transaction(transaction_id_now, selections);
7333 }
7334 }
7335
7336 self.update_visible_edit_prediction(window, cx);
7337 if self.active_edit_prediction.is_none() {
7338 self.refresh_edit_prediction(true, true, window, cx);
7339 }
7340
7341 cx.notify();
7342 }
7343 }
7344
7345 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7346 }
7347
7348 pub fn accept_partial_edit_prediction(
7349 &mut self,
7350 _: &AcceptPartialEditPrediction,
7351 window: &mut Window,
7352 cx: &mut Context<Self>,
7353 ) {
7354 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7355 return;
7356 };
7357 if self.selections.count() != 1 {
7358 return;
7359 }
7360
7361 match &active_edit_prediction.completion {
7362 EditPrediction::MoveWithin { target, .. } => {
7363 let target = *target;
7364 self.change_selections(
7365 SelectionEffects::scroll(Autoscroll::newest()),
7366 window,
7367 cx,
7368 |selections| {
7369 selections.select_anchor_ranges([target..target]);
7370 },
7371 );
7372 }
7373 EditPrediction::MoveOutside { snapshot, target } => {
7374 if let Some(workspace) = self.workspace() {
7375 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7376 .detach_and_log_err(cx);
7377 }
7378 }
7379 EditPrediction::Edit { edits, .. } => {
7380 self.report_edit_prediction_event(
7381 active_edit_prediction.completion_id.clone(),
7382 true,
7383 cx,
7384 );
7385
7386 // Find an insertion that starts at the cursor position.
7387 let snapshot = self.buffer.read(cx).snapshot(cx);
7388 let cursor_offset = self
7389 .selections
7390 .newest::<usize>(&self.display_snapshot(cx))
7391 .head();
7392 let insertion = edits.iter().find_map(|(range, text)| {
7393 let range = range.to_offset(&snapshot);
7394 if range.is_empty() && range.start == cursor_offset {
7395 Some(text)
7396 } else {
7397 None
7398 }
7399 });
7400
7401 if let Some(text) = insertion {
7402 let mut partial_completion = text
7403 .chars()
7404 .by_ref()
7405 .take_while(|c| c.is_alphabetic())
7406 .collect::<String>();
7407 if partial_completion.is_empty() {
7408 partial_completion = text
7409 .chars()
7410 .by_ref()
7411 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7412 .collect::<String>();
7413 }
7414
7415 cx.emit(EditorEvent::InputHandled {
7416 utf16_range_to_replace: None,
7417 text: partial_completion.clone().into(),
7418 });
7419
7420 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7421
7422 self.refresh_edit_prediction(true, true, window, cx);
7423 cx.notify();
7424 } else {
7425 self.accept_edit_prediction(&Default::default(), window, cx);
7426 }
7427 }
7428 }
7429 }
7430
7431 fn discard_edit_prediction(
7432 &mut self,
7433 should_report_edit_prediction_event: bool,
7434 cx: &mut Context<Self>,
7435 ) -> bool {
7436 if should_report_edit_prediction_event {
7437 let completion_id = self
7438 .active_edit_prediction
7439 .as_ref()
7440 .and_then(|active_completion| active_completion.completion_id.clone());
7441
7442 self.report_edit_prediction_event(completion_id, false, cx);
7443 }
7444
7445 if let Some(provider) = self.edit_prediction_provider() {
7446 provider.discard(cx);
7447 }
7448
7449 self.take_active_edit_prediction(cx)
7450 }
7451
7452 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7453 let Some(provider) = self.edit_prediction_provider() else {
7454 return;
7455 };
7456
7457 let Some((_, buffer, _)) = self
7458 .buffer
7459 .read(cx)
7460 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7461 else {
7462 return;
7463 };
7464
7465 let extension = buffer
7466 .read(cx)
7467 .file()
7468 .and_then(|file| Some(file.path().extension()?.to_string()));
7469
7470 let event_type = match accepted {
7471 true => "Edit Prediction Accepted",
7472 false => "Edit Prediction Discarded",
7473 };
7474 telemetry::event!(
7475 event_type,
7476 provider = provider.name(),
7477 prediction_id = id,
7478 suggestion_accepted = accepted,
7479 file_extension = extension,
7480 );
7481 }
7482
7483 fn open_editor_at_anchor(
7484 snapshot: &language::BufferSnapshot,
7485 target: language::Anchor,
7486 workspace: &Entity<Workspace>,
7487 window: &mut Window,
7488 cx: &mut App,
7489 ) -> Task<Result<()>> {
7490 workspace.update(cx, |workspace, cx| {
7491 let path = snapshot.file().map(|file| file.full_path(cx));
7492 let Some(path) =
7493 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7494 else {
7495 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7496 };
7497 let target = text::ToPoint::to_point(&target, snapshot);
7498 let item = workspace.open_path(path, None, true, window, cx);
7499 window.spawn(cx, async move |cx| {
7500 let Some(editor) = item.await?.downcast::<Editor>() else {
7501 return Ok(());
7502 };
7503 editor
7504 .update_in(cx, |editor, window, cx| {
7505 editor.go_to_singleton_buffer_point(target, window, cx);
7506 })
7507 .ok();
7508 anyhow::Ok(())
7509 })
7510 })
7511 }
7512
7513 pub fn has_active_edit_prediction(&self) -> bool {
7514 self.active_edit_prediction.is_some()
7515 }
7516
7517 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7518 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7519 return false;
7520 };
7521
7522 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7523 self.clear_highlights::<EditPredictionHighlight>(cx);
7524 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7525 true
7526 }
7527
7528 /// Returns true when we're displaying the edit prediction popover below the cursor
7529 /// like we are not previewing and the LSP autocomplete menu is visible
7530 /// or we are in `when_holding_modifier` mode.
7531 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7532 if self.edit_prediction_preview_is_active()
7533 || !self.show_edit_predictions_in_menu()
7534 || !self.edit_predictions_enabled()
7535 {
7536 return false;
7537 }
7538
7539 if self.has_visible_completions_menu() {
7540 return true;
7541 }
7542
7543 has_completion && self.edit_prediction_requires_modifier()
7544 }
7545
7546 fn handle_modifiers_changed(
7547 &mut self,
7548 modifiers: Modifiers,
7549 position_map: &PositionMap,
7550 window: &mut Window,
7551 cx: &mut Context<Self>,
7552 ) {
7553 if self.show_edit_predictions_in_menu() {
7554 self.update_edit_prediction_preview(&modifiers, window, cx);
7555 }
7556
7557 self.update_selection_mode(&modifiers, position_map, window, cx);
7558
7559 let mouse_position = window.mouse_position();
7560 if !position_map.text_hitbox.is_hovered(window) {
7561 return;
7562 }
7563
7564 self.update_hovered_link(
7565 position_map.point_for_position(mouse_position),
7566 &position_map.snapshot,
7567 modifiers,
7568 window,
7569 cx,
7570 )
7571 }
7572
7573 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7574 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7575 if invert {
7576 match multi_cursor_setting {
7577 MultiCursorModifier::Alt => modifiers.alt,
7578 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7579 }
7580 } else {
7581 match multi_cursor_setting {
7582 MultiCursorModifier::Alt => modifiers.secondary(),
7583 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7584 }
7585 }
7586 }
7587
7588 fn columnar_selection_mode(
7589 modifiers: &Modifiers,
7590 cx: &mut Context<Self>,
7591 ) -> Option<ColumnarMode> {
7592 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7593 if Self::multi_cursor_modifier(false, modifiers, cx) {
7594 Some(ColumnarMode::FromMouse)
7595 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7596 Some(ColumnarMode::FromSelection)
7597 } else {
7598 None
7599 }
7600 } else {
7601 None
7602 }
7603 }
7604
7605 fn update_selection_mode(
7606 &mut self,
7607 modifiers: &Modifiers,
7608 position_map: &PositionMap,
7609 window: &mut Window,
7610 cx: &mut Context<Self>,
7611 ) {
7612 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7613 return;
7614 };
7615 if self.selections.pending_anchor().is_none() {
7616 return;
7617 }
7618
7619 let mouse_position = window.mouse_position();
7620 let point_for_position = position_map.point_for_position(mouse_position);
7621 let position = point_for_position.previous_valid;
7622
7623 self.select(
7624 SelectPhase::BeginColumnar {
7625 position,
7626 reset: false,
7627 mode,
7628 goal_column: point_for_position.exact_unclipped.column(),
7629 },
7630 window,
7631 cx,
7632 );
7633 }
7634
7635 fn update_edit_prediction_preview(
7636 &mut self,
7637 modifiers: &Modifiers,
7638 window: &mut Window,
7639 cx: &mut Context<Self>,
7640 ) {
7641 let mut modifiers_held = false;
7642 if let Some(accept_keystroke) = self
7643 .accept_edit_prediction_keybind(false, window, cx)
7644 .keystroke()
7645 {
7646 modifiers_held = modifiers_held
7647 || (accept_keystroke.modifiers() == modifiers
7648 && accept_keystroke.modifiers().modified());
7649 };
7650 if let Some(accept_partial_keystroke) = self
7651 .accept_edit_prediction_keybind(true, window, cx)
7652 .keystroke()
7653 {
7654 modifiers_held = modifiers_held
7655 || (accept_partial_keystroke.modifiers() == modifiers
7656 && accept_partial_keystroke.modifiers().modified());
7657 }
7658
7659 if modifiers_held {
7660 if matches!(
7661 self.edit_prediction_preview,
7662 EditPredictionPreview::Inactive { .. }
7663 ) {
7664 self.edit_prediction_preview = EditPredictionPreview::Active {
7665 previous_scroll_position: None,
7666 since: Instant::now(),
7667 };
7668
7669 self.update_visible_edit_prediction(window, cx);
7670 cx.notify();
7671 }
7672 } else if let EditPredictionPreview::Active {
7673 previous_scroll_position,
7674 since,
7675 } = self.edit_prediction_preview
7676 {
7677 if let (Some(previous_scroll_position), Some(position_map)) =
7678 (previous_scroll_position, self.last_position_map.as_ref())
7679 {
7680 self.set_scroll_position(
7681 previous_scroll_position
7682 .scroll_position(&position_map.snapshot.display_snapshot),
7683 window,
7684 cx,
7685 );
7686 }
7687
7688 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7689 released_too_fast: since.elapsed() < Duration::from_millis(200),
7690 };
7691 self.clear_row_highlights::<EditPredictionPreview>();
7692 self.update_visible_edit_prediction(window, cx);
7693 cx.notify();
7694 }
7695 }
7696
7697 fn update_visible_edit_prediction(
7698 &mut self,
7699 _window: &mut Window,
7700 cx: &mut Context<Self>,
7701 ) -> Option<()> {
7702 if DisableAiSettings::get_global(cx).disable_ai {
7703 return None;
7704 }
7705
7706 if self.ime_transaction.is_some() {
7707 self.discard_edit_prediction(false, cx);
7708 return None;
7709 }
7710
7711 let selection = self.selections.newest_anchor();
7712 let cursor = selection.head();
7713 let multibuffer = self.buffer.read(cx).snapshot(cx);
7714 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7715 let excerpt_id = cursor.excerpt_id;
7716
7717 let show_in_menu = self.show_edit_predictions_in_menu();
7718 let completions_menu_has_precedence = !show_in_menu
7719 && (self.context_menu.borrow().is_some()
7720 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7721
7722 if completions_menu_has_precedence
7723 || !offset_selection.is_empty()
7724 || self
7725 .active_edit_prediction
7726 .as_ref()
7727 .is_some_and(|completion| {
7728 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7729 return false;
7730 };
7731 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7732 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7733 !invalidation_range.contains(&offset_selection.head())
7734 })
7735 {
7736 self.discard_edit_prediction(false, cx);
7737 return None;
7738 }
7739
7740 self.take_active_edit_prediction(cx);
7741 let Some(provider) = self.edit_prediction_provider() else {
7742 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7743 return None;
7744 };
7745
7746 let (buffer, cursor_buffer_position) =
7747 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7748
7749 self.edit_prediction_settings =
7750 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7751
7752 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7753
7754 if self.edit_prediction_indent_conflict {
7755 let cursor_point = cursor.to_point(&multibuffer);
7756
7757 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7758
7759 if let Some((_, indent)) = indents.iter().next()
7760 && indent.len == cursor_point.column
7761 {
7762 self.edit_prediction_indent_conflict = false;
7763 }
7764 }
7765
7766 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7767
7768 let (completion_id, edits, edit_preview) = match edit_prediction {
7769 edit_prediction::EditPrediction::Local {
7770 id,
7771 edits,
7772 edit_preview,
7773 } => (id, edits, edit_preview),
7774 edit_prediction::EditPrediction::Jump {
7775 id,
7776 snapshot,
7777 target,
7778 } => {
7779 self.stale_edit_prediction_in_menu = None;
7780 self.active_edit_prediction = Some(EditPredictionState {
7781 inlay_ids: vec![],
7782 completion: EditPrediction::MoveOutside { snapshot, target },
7783 completion_id: id,
7784 invalidation_range: None,
7785 });
7786 cx.notify();
7787 return Some(());
7788 }
7789 };
7790
7791 let edits = edits
7792 .into_iter()
7793 .flat_map(|(range, new_text)| {
7794 Some((
7795 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7796 new_text,
7797 ))
7798 })
7799 .collect::<Vec<_>>();
7800 if edits.is_empty() {
7801 return None;
7802 }
7803
7804 let first_edit_start = edits.first().unwrap().0.start;
7805 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7806 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7807
7808 let last_edit_end = edits.last().unwrap().0.end;
7809 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7810 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7811
7812 let cursor_row = cursor.to_point(&multibuffer).row;
7813
7814 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7815
7816 let mut inlay_ids = Vec::new();
7817 let invalidation_row_range;
7818 let move_invalidation_row_range = if cursor_row < edit_start_row {
7819 Some(cursor_row..edit_end_row)
7820 } else if cursor_row > edit_end_row {
7821 Some(edit_start_row..cursor_row)
7822 } else {
7823 None
7824 };
7825 let supports_jump = self
7826 .edit_prediction_provider
7827 .as_ref()
7828 .map(|provider| provider.provider.supports_jump_to_edit())
7829 .unwrap_or(true);
7830
7831 let is_move = supports_jump
7832 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7833 let completion = if is_move {
7834 invalidation_row_range =
7835 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7836 let target = first_edit_start;
7837 EditPrediction::MoveWithin { target, snapshot }
7838 } else {
7839 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7840 && !self.edit_predictions_hidden_for_vim_mode;
7841
7842 if show_completions_in_buffer {
7843 if edits
7844 .iter()
7845 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7846 {
7847 let mut inlays = Vec::new();
7848 for (range, new_text) in &edits {
7849 let inlay = Inlay::edit_prediction(
7850 post_inc(&mut self.next_inlay_id),
7851 range.start,
7852 new_text.as_str(),
7853 );
7854 inlay_ids.push(inlay.id);
7855 inlays.push(inlay);
7856 }
7857
7858 self.splice_inlays(&[], inlays, cx);
7859 } else {
7860 let background_color = cx.theme().status().deleted_background;
7861 self.highlight_text::<EditPredictionHighlight>(
7862 edits.iter().map(|(range, _)| range.clone()).collect(),
7863 HighlightStyle {
7864 background_color: Some(background_color),
7865 ..Default::default()
7866 },
7867 cx,
7868 );
7869 }
7870 }
7871
7872 invalidation_row_range = edit_start_row..edit_end_row;
7873
7874 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
7875 if provider.show_tab_accept_marker() {
7876 EditDisplayMode::TabAccept
7877 } else {
7878 EditDisplayMode::Inline
7879 }
7880 } else {
7881 EditDisplayMode::DiffPopover
7882 };
7883
7884 EditPrediction::Edit {
7885 edits,
7886 edit_preview,
7887 display_mode,
7888 snapshot,
7889 }
7890 };
7891
7892 let invalidation_range = multibuffer
7893 .anchor_before(Point::new(invalidation_row_range.start, 0))
7894 ..multibuffer.anchor_after(Point::new(
7895 invalidation_row_range.end,
7896 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
7897 ));
7898
7899 self.stale_edit_prediction_in_menu = None;
7900 self.active_edit_prediction = Some(EditPredictionState {
7901 inlay_ids,
7902 completion,
7903 completion_id,
7904 invalidation_range: Some(invalidation_range),
7905 });
7906
7907 cx.notify();
7908
7909 Some(())
7910 }
7911
7912 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
7913 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
7914 }
7915
7916 fn clear_tasks(&mut self) {
7917 self.tasks.clear()
7918 }
7919
7920 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
7921 if self.tasks.insert(key, value).is_some() {
7922 // This case should hopefully be rare, but just in case...
7923 log::error!(
7924 "multiple different run targets found on a single line, only the last target will be rendered"
7925 )
7926 }
7927 }
7928
7929 /// Get all display points of breakpoints that will be rendered within editor
7930 ///
7931 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
7932 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
7933 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
7934 fn active_breakpoints(
7935 &self,
7936 range: Range<DisplayRow>,
7937 window: &mut Window,
7938 cx: &mut Context<Self>,
7939 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
7940 let mut breakpoint_display_points = HashMap::default();
7941
7942 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
7943 return breakpoint_display_points;
7944 };
7945
7946 let snapshot = self.snapshot(window, cx);
7947
7948 let multi_buffer_snapshot = snapshot.buffer_snapshot();
7949 let Some(project) = self.project() else {
7950 return breakpoint_display_points;
7951 };
7952
7953 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
7954 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
7955
7956 for (buffer_snapshot, range, excerpt_id) in
7957 multi_buffer_snapshot.range_to_buffer_ranges(range)
7958 {
7959 let Some(buffer) = project
7960 .read(cx)
7961 .buffer_for_id(buffer_snapshot.remote_id(), cx)
7962 else {
7963 continue;
7964 };
7965 let breakpoints = breakpoint_store.read(cx).breakpoints(
7966 &buffer,
7967 Some(
7968 buffer_snapshot.anchor_before(range.start)
7969 ..buffer_snapshot.anchor_after(range.end),
7970 ),
7971 buffer_snapshot,
7972 cx,
7973 );
7974 for (breakpoint, state) in breakpoints {
7975 let multi_buffer_anchor =
7976 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
7977 let position = multi_buffer_anchor
7978 .to_point(&multi_buffer_snapshot)
7979 .to_display_point(&snapshot);
7980
7981 breakpoint_display_points.insert(
7982 position.row(),
7983 (multi_buffer_anchor, breakpoint.bp.clone(), state),
7984 );
7985 }
7986 }
7987
7988 breakpoint_display_points
7989 }
7990
7991 fn breakpoint_context_menu(
7992 &self,
7993 anchor: Anchor,
7994 window: &mut Window,
7995 cx: &mut Context<Self>,
7996 ) -> Entity<ui::ContextMenu> {
7997 let weak_editor = cx.weak_entity();
7998 let focus_handle = self.focus_handle(cx);
7999
8000 let row = self
8001 .buffer
8002 .read(cx)
8003 .snapshot(cx)
8004 .summary_for_anchor::<Point>(&anchor)
8005 .row;
8006
8007 let breakpoint = self
8008 .breakpoint_at_row(row, window, cx)
8009 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8010
8011 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8012 "Edit Log Breakpoint"
8013 } else {
8014 "Set Log Breakpoint"
8015 };
8016
8017 let condition_breakpoint_msg = if breakpoint
8018 .as_ref()
8019 .is_some_and(|bp| bp.1.condition.is_some())
8020 {
8021 "Edit Condition Breakpoint"
8022 } else {
8023 "Set Condition Breakpoint"
8024 };
8025
8026 let hit_condition_breakpoint_msg = if breakpoint
8027 .as_ref()
8028 .is_some_and(|bp| bp.1.hit_condition.is_some())
8029 {
8030 "Edit Hit Condition Breakpoint"
8031 } else {
8032 "Set Hit Condition Breakpoint"
8033 };
8034
8035 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8036 "Unset Breakpoint"
8037 } else {
8038 "Set Breakpoint"
8039 };
8040
8041 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8042
8043 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8044 BreakpointState::Enabled => Some("Disable"),
8045 BreakpointState::Disabled => Some("Enable"),
8046 });
8047
8048 let (anchor, breakpoint) =
8049 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8050
8051 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8052 menu.on_blur_subscription(Subscription::new(|| {}))
8053 .context(focus_handle)
8054 .when(run_to_cursor, |this| {
8055 let weak_editor = weak_editor.clone();
8056 this.entry("Run to cursor", None, move |window, cx| {
8057 weak_editor
8058 .update(cx, |editor, cx| {
8059 editor.change_selections(
8060 SelectionEffects::no_scroll(),
8061 window,
8062 cx,
8063 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8064 );
8065 })
8066 .ok();
8067
8068 window.dispatch_action(Box::new(RunToCursor), cx);
8069 })
8070 .separator()
8071 })
8072 .when_some(toggle_state_msg, |this, msg| {
8073 this.entry(msg, None, {
8074 let weak_editor = weak_editor.clone();
8075 let breakpoint = breakpoint.clone();
8076 move |_window, cx| {
8077 weak_editor
8078 .update(cx, |this, cx| {
8079 this.edit_breakpoint_at_anchor(
8080 anchor,
8081 breakpoint.as_ref().clone(),
8082 BreakpointEditAction::InvertState,
8083 cx,
8084 );
8085 })
8086 .log_err();
8087 }
8088 })
8089 })
8090 .entry(set_breakpoint_msg, None, {
8091 let weak_editor = weak_editor.clone();
8092 let breakpoint = breakpoint.clone();
8093 move |_window, cx| {
8094 weak_editor
8095 .update(cx, |this, cx| {
8096 this.edit_breakpoint_at_anchor(
8097 anchor,
8098 breakpoint.as_ref().clone(),
8099 BreakpointEditAction::Toggle,
8100 cx,
8101 );
8102 })
8103 .log_err();
8104 }
8105 })
8106 .entry(log_breakpoint_msg, None, {
8107 let breakpoint = breakpoint.clone();
8108 let weak_editor = weak_editor.clone();
8109 move |window, cx| {
8110 weak_editor
8111 .update(cx, |this, cx| {
8112 this.add_edit_breakpoint_block(
8113 anchor,
8114 breakpoint.as_ref(),
8115 BreakpointPromptEditAction::Log,
8116 window,
8117 cx,
8118 );
8119 })
8120 .log_err();
8121 }
8122 })
8123 .entry(condition_breakpoint_msg, None, {
8124 let breakpoint = breakpoint.clone();
8125 let weak_editor = weak_editor.clone();
8126 move |window, cx| {
8127 weak_editor
8128 .update(cx, |this, cx| {
8129 this.add_edit_breakpoint_block(
8130 anchor,
8131 breakpoint.as_ref(),
8132 BreakpointPromptEditAction::Condition,
8133 window,
8134 cx,
8135 );
8136 })
8137 .log_err();
8138 }
8139 })
8140 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8141 weak_editor
8142 .update(cx, |this, cx| {
8143 this.add_edit_breakpoint_block(
8144 anchor,
8145 breakpoint.as_ref(),
8146 BreakpointPromptEditAction::HitCondition,
8147 window,
8148 cx,
8149 );
8150 })
8151 .log_err();
8152 })
8153 })
8154 }
8155
8156 fn render_breakpoint(
8157 &self,
8158 position: Anchor,
8159 row: DisplayRow,
8160 breakpoint: &Breakpoint,
8161 state: Option<BreakpointSessionState>,
8162 cx: &mut Context<Self>,
8163 ) -> IconButton {
8164 let is_rejected = state.is_some_and(|s| !s.verified);
8165 // Is it a breakpoint that shows up when hovering over gutter?
8166 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8167 (false, false),
8168 |PhantomBreakpointIndicator {
8169 is_active,
8170 display_row,
8171 collides_with_existing_breakpoint,
8172 }| {
8173 (
8174 is_active && display_row == row,
8175 collides_with_existing_breakpoint,
8176 )
8177 },
8178 );
8179
8180 let (color, icon) = {
8181 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8182 (false, false) => ui::IconName::DebugBreakpoint,
8183 (true, false) => ui::IconName::DebugLogBreakpoint,
8184 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8185 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8186 };
8187
8188 let color = if is_phantom {
8189 Color::Hint
8190 } else if is_rejected {
8191 Color::Disabled
8192 } else {
8193 Color::Debugger
8194 };
8195
8196 (color, icon)
8197 };
8198
8199 let breakpoint = Arc::from(breakpoint.clone());
8200
8201 let alt_as_text = gpui::Keystroke {
8202 modifiers: Modifiers::secondary_key(),
8203 ..Default::default()
8204 };
8205 let primary_action_text = if breakpoint.is_disabled() {
8206 "Enable breakpoint"
8207 } else if is_phantom && !collides_with_existing {
8208 "Set breakpoint"
8209 } else {
8210 "Unset breakpoint"
8211 };
8212 let focus_handle = self.focus_handle.clone();
8213
8214 let meta = if is_rejected {
8215 SharedString::from("No executable code is associated with this line.")
8216 } else if collides_with_existing && !breakpoint.is_disabled() {
8217 SharedString::from(format!(
8218 "{alt_as_text}-click to disable,\nright-click for more options."
8219 ))
8220 } else {
8221 SharedString::from("Right-click for more options.")
8222 };
8223 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8224 .icon_size(IconSize::XSmall)
8225 .size(ui::ButtonSize::None)
8226 .when(is_rejected, |this| {
8227 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8228 })
8229 .icon_color(color)
8230 .style(ButtonStyle::Transparent)
8231 .on_click(cx.listener({
8232 move |editor, event: &ClickEvent, window, cx| {
8233 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8234 BreakpointEditAction::InvertState
8235 } else {
8236 BreakpointEditAction::Toggle
8237 };
8238
8239 window.focus(&editor.focus_handle(cx));
8240 editor.edit_breakpoint_at_anchor(
8241 position,
8242 breakpoint.as_ref().clone(),
8243 edit_action,
8244 cx,
8245 );
8246 }
8247 }))
8248 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8249 editor.set_breakpoint_context_menu(
8250 row,
8251 Some(position),
8252 event.position(),
8253 window,
8254 cx,
8255 );
8256 }))
8257 .tooltip(move |_window, cx| {
8258 Tooltip::with_meta_in(
8259 primary_action_text,
8260 Some(&ToggleBreakpoint),
8261 meta.clone(),
8262 &focus_handle,
8263 cx,
8264 )
8265 })
8266 }
8267
8268 fn build_tasks_context(
8269 project: &Entity<Project>,
8270 buffer: &Entity<Buffer>,
8271 buffer_row: u32,
8272 tasks: &Arc<RunnableTasks>,
8273 cx: &mut Context<Self>,
8274 ) -> Task<Option<task::TaskContext>> {
8275 let position = Point::new(buffer_row, tasks.column);
8276 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8277 let location = Location {
8278 buffer: buffer.clone(),
8279 range: range_start..range_start,
8280 };
8281 // Fill in the environmental variables from the tree-sitter captures
8282 let mut captured_task_variables = TaskVariables::default();
8283 for (capture_name, value) in tasks.extra_variables.clone() {
8284 captured_task_variables.insert(
8285 task::VariableName::Custom(capture_name.into()),
8286 value.clone(),
8287 );
8288 }
8289 project.update(cx, |project, cx| {
8290 project.task_store().update(cx, |task_store, cx| {
8291 task_store.task_context_for_location(captured_task_variables, location, cx)
8292 })
8293 })
8294 }
8295
8296 pub fn spawn_nearest_task(
8297 &mut self,
8298 action: &SpawnNearestTask,
8299 window: &mut Window,
8300 cx: &mut Context<Self>,
8301 ) {
8302 let Some((workspace, _)) = self.workspace.clone() else {
8303 return;
8304 };
8305 let Some(project) = self.project.clone() else {
8306 return;
8307 };
8308
8309 // Try to find a closest, enclosing node using tree-sitter that has a task
8310 let Some((buffer, buffer_row, tasks)) = self
8311 .find_enclosing_node_task(cx)
8312 // Or find the task that's closest in row-distance.
8313 .or_else(|| self.find_closest_task(cx))
8314 else {
8315 return;
8316 };
8317
8318 let reveal_strategy = action.reveal;
8319 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8320 cx.spawn_in(window, async move |_, cx| {
8321 let context = task_context.await?;
8322 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8323
8324 let resolved = &mut resolved_task.resolved;
8325 resolved.reveal = reveal_strategy;
8326
8327 workspace
8328 .update_in(cx, |workspace, window, cx| {
8329 workspace.schedule_resolved_task(
8330 task_source_kind,
8331 resolved_task,
8332 false,
8333 window,
8334 cx,
8335 );
8336 })
8337 .ok()
8338 })
8339 .detach();
8340 }
8341
8342 fn find_closest_task(
8343 &mut self,
8344 cx: &mut Context<Self>,
8345 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8346 let cursor_row = self
8347 .selections
8348 .newest_adjusted(&self.display_snapshot(cx))
8349 .head()
8350 .row;
8351
8352 let ((buffer_id, row), tasks) = self
8353 .tasks
8354 .iter()
8355 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8356
8357 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8358 let tasks = Arc::new(tasks.to_owned());
8359 Some((buffer, *row, tasks))
8360 }
8361
8362 fn find_enclosing_node_task(
8363 &mut self,
8364 cx: &mut Context<Self>,
8365 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8366 let snapshot = self.buffer.read(cx).snapshot(cx);
8367 let offset = self
8368 .selections
8369 .newest::<usize>(&self.display_snapshot(cx))
8370 .head();
8371 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8372 let buffer_id = excerpt.buffer().remote_id();
8373
8374 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8375 let mut cursor = layer.node().walk();
8376
8377 while cursor.goto_first_child_for_byte(offset).is_some() {
8378 if cursor.node().end_byte() == offset {
8379 cursor.goto_next_sibling();
8380 }
8381 }
8382
8383 // Ascend to the smallest ancestor that contains the range and has a task.
8384 loop {
8385 let node = cursor.node();
8386 let node_range = node.byte_range();
8387 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8388
8389 // Check if this node contains our offset
8390 if node_range.start <= offset && node_range.end >= offset {
8391 // If it contains offset, check for task
8392 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8393 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8394 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8395 }
8396 }
8397
8398 if !cursor.goto_parent() {
8399 break;
8400 }
8401 }
8402 None
8403 }
8404
8405 fn render_run_indicator(
8406 &self,
8407 _style: &EditorStyle,
8408 is_active: bool,
8409 row: DisplayRow,
8410 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8411 cx: &mut Context<Self>,
8412 ) -> IconButton {
8413 let color = Color::Muted;
8414 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8415
8416 IconButton::new(
8417 ("run_indicator", row.0 as usize),
8418 ui::IconName::PlayOutlined,
8419 )
8420 .shape(ui::IconButtonShape::Square)
8421 .icon_size(IconSize::XSmall)
8422 .icon_color(color)
8423 .toggle_state(is_active)
8424 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8425 let quick_launch = match e {
8426 ClickEvent::Keyboard(_) => true,
8427 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8428 };
8429
8430 window.focus(&editor.focus_handle(cx));
8431 editor.toggle_code_actions(
8432 &ToggleCodeActions {
8433 deployed_from: Some(CodeActionSource::RunMenu(row)),
8434 quick_launch,
8435 },
8436 window,
8437 cx,
8438 );
8439 }))
8440 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8441 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8442 }))
8443 }
8444
8445 pub fn context_menu_visible(&self) -> bool {
8446 !self.edit_prediction_preview_is_active()
8447 && self
8448 .context_menu
8449 .borrow()
8450 .as_ref()
8451 .is_some_and(|menu| menu.visible())
8452 }
8453
8454 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8455 self.context_menu
8456 .borrow()
8457 .as_ref()
8458 .map(|menu| menu.origin())
8459 }
8460
8461 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8462 self.context_menu_options = Some(options);
8463 }
8464
8465 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8466 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8467
8468 fn render_edit_prediction_popover(
8469 &mut self,
8470 text_bounds: &Bounds<Pixels>,
8471 content_origin: gpui::Point<Pixels>,
8472 right_margin: Pixels,
8473 editor_snapshot: &EditorSnapshot,
8474 visible_row_range: Range<DisplayRow>,
8475 scroll_top: ScrollOffset,
8476 scroll_bottom: ScrollOffset,
8477 line_layouts: &[LineWithInvisibles],
8478 line_height: Pixels,
8479 scroll_position: gpui::Point<ScrollOffset>,
8480 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8481 newest_selection_head: Option<DisplayPoint>,
8482 editor_width: Pixels,
8483 style: &EditorStyle,
8484 window: &mut Window,
8485 cx: &mut App,
8486 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8487 if self.mode().is_minimap() {
8488 return None;
8489 }
8490 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8491
8492 if self.edit_prediction_visible_in_cursor_popover(true) {
8493 return None;
8494 }
8495
8496 match &active_edit_prediction.completion {
8497 EditPrediction::MoveWithin { target, .. } => {
8498 let target_display_point = target.to_display_point(editor_snapshot);
8499
8500 if self.edit_prediction_requires_modifier() {
8501 if !self.edit_prediction_preview_is_active() {
8502 return None;
8503 }
8504
8505 self.render_edit_prediction_modifier_jump_popover(
8506 text_bounds,
8507 content_origin,
8508 visible_row_range,
8509 line_layouts,
8510 line_height,
8511 scroll_pixel_position,
8512 newest_selection_head,
8513 target_display_point,
8514 window,
8515 cx,
8516 )
8517 } else {
8518 self.render_edit_prediction_eager_jump_popover(
8519 text_bounds,
8520 content_origin,
8521 editor_snapshot,
8522 visible_row_range,
8523 scroll_top,
8524 scroll_bottom,
8525 line_height,
8526 scroll_pixel_position,
8527 target_display_point,
8528 editor_width,
8529 window,
8530 cx,
8531 )
8532 }
8533 }
8534 EditPrediction::Edit {
8535 display_mode: EditDisplayMode::Inline,
8536 ..
8537 } => None,
8538 EditPrediction::Edit {
8539 display_mode: EditDisplayMode::TabAccept,
8540 edits,
8541 ..
8542 } => {
8543 let range = &edits.first()?.0;
8544 let target_display_point = range.end.to_display_point(editor_snapshot);
8545
8546 self.render_edit_prediction_end_of_line_popover(
8547 "Accept",
8548 editor_snapshot,
8549 visible_row_range,
8550 target_display_point,
8551 line_height,
8552 scroll_pixel_position,
8553 content_origin,
8554 editor_width,
8555 window,
8556 cx,
8557 )
8558 }
8559 EditPrediction::Edit {
8560 edits,
8561 edit_preview,
8562 display_mode: EditDisplayMode::DiffPopover,
8563 snapshot,
8564 } => self.render_edit_prediction_diff_popover(
8565 text_bounds,
8566 content_origin,
8567 right_margin,
8568 editor_snapshot,
8569 visible_row_range,
8570 line_layouts,
8571 line_height,
8572 scroll_position,
8573 scroll_pixel_position,
8574 newest_selection_head,
8575 editor_width,
8576 style,
8577 edits,
8578 edit_preview,
8579 snapshot,
8580 window,
8581 cx,
8582 ),
8583 EditPrediction::MoveOutside { snapshot, .. } => {
8584 let file_name = snapshot
8585 .file()
8586 .map(|file| file.file_name(cx))
8587 .unwrap_or("untitled");
8588 let mut element = self
8589 .render_edit_prediction_line_popover(
8590 format!("Jump to {file_name}"),
8591 Some(IconName::ZedPredict),
8592 window,
8593 cx,
8594 )
8595 .into_any();
8596
8597 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8598 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8599 let origin_y = text_bounds.size.height - size.height - px(30.);
8600 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8601 element.prepaint_at(origin, window, cx);
8602
8603 Some((element, origin))
8604 }
8605 }
8606 }
8607
8608 fn render_edit_prediction_modifier_jump_popover(
8609 &mut self,
8610 text_bounds: &Bounds<Pixels>,
8611 content_origin: gpui::Point<Pixels>,
8612 visible_row_range: Range<DisplayRow>,
8613 line_layouts: &[LineWithInvisibles],
8614 line_height: Pixels,
8615 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8616 newest_selection_head: Option<DisplayPoint>,
8617 target_display_point: DisplayPoint,
8618 window: &mut Window,
8619 cx: &mut App,
8620 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8621 let scrolled_content_origin =
8622 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8623
8624 const SCROLL_PADDING_Y: Pixels = px(12.);
8625
8626 if target_display_point.row() < visible_row_range.start {
8627 return self.render_edit_prediction_scroll_popover(
8628 |_| SCROLL_PADDING_Y,
8629 IconName::ArrowUp,
8630 visible_row_range,
8631 line_layouts,
8632 newest_selection_head,
8633 scrolled_content_origin,
8634 window,
8635 cx,
8636 );
8637 } else if target_display_point.row() >= visible_row_range.end {
8638 return self.render_edit_prediction_scroll_popover(
8639 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8640 IconName::ArrowDown,
8641 visible_row_range,
8642 line_layouts,
8643 newest_selection_head,
8644 scrolled_content_origin,
8645 window,
8646 cx,
8647 );
8648 }
8649
8650 const POLE_WIDTH: Pixels = px(2.);
8651
8652 let line_layout =
8653 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8654 let target_column = target_display_point.column() as usize;
8655
8656 let target_x = line_layout.x_for_index(target_column);
8657 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8658 - scroll_pixel_position.y;
8659
8660 let flag_on_right = target_x < text_bounds.size.width / 2.;
8661
8662 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8663 border_color.l += 0.001;
8664
8665 let mut element = v_flex()
8666 .items_end()
8667 .when(flag_on_right, |el| el.items_start())
8668 .child(if flag_on_right {
8669 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8670 .rounded_bl(px(0.))
8671 .rounded_tl(px(0.))
8672 .border_l_2()
8673 .border_color(border_color)
8674 } else {
8675 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8676 .rounded_br(px(0.))
8677 .rounded_tr(px(0.))
8678 .border_r_2()
8679 .border_color(border_color)
8680 })
8681 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8682 .into_any();
8683
8684 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8685
8686 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8687 - point(
8688 if flag_on_right {
8689 POLE_WIDTH
8690 } else {
8691 size.width - POLE_WIDTH
8692 },
8693 size.height - line_height,
8694 );
8695
8696 origin.x = origin.x.max(content_origin.x);
8697
8698 element.prepaint_at(origin, window, cx);
8699
8700 Some((element, origin))
8701 }
8702
8703 fn render_edit_prediction_scroll_popover(
8704 &mut self,
8705 to_y: impl Fn(Size<Pixels>) -> Pixels,
8706 scroll_icon: IconName,
8707 visible_row_range: Range<DisplayRow>,
8708 line_layouts: &[LineWithInvisibles],
8709 newest_selection_head: Option<DisplayPoint>,
8710 scrolled_content_origin: gpui::Point<Pixels>,
8711 window: &mut Window,
8712 cx: &mut App,
8713 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8714 let mut element = self
8715 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8716 .into_any();
8717
8718 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8719
8720 let cursor = newest_selection_head?;
8721 let cursor_row_layout =
8722 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8723 let cursor_column = cursor.column() as usize;
8724
8725 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8726
8727 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8728
8729 element.prepaint_at(origin, window, cx);
8730 Some((element, origin))
8731 }
8732
8733 fn render_edit_prediction_eager_jump_popover(
8734 &mut self,
8735 text_bounds: &Bounds<Pixels>,
8736 content_origin: gpui::Point<Pixels>,
8737 editor_snapshot: &EditorSnapshot,
8738 visible_row_range: Range<DisplayRow>,
8739 scroll_top: ScrollOffset,
8740 scroll_bottom: ScrollOffset,
8741 line_height: Pixels,
8742 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8743 target_display_point: DisplayPoint,
8744 editor_width: Pixels,
8745 window: &mut Window,
8746 cx: &mut App,
8747 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8748 if target_display_point.row().as_f64() < scroll_top {
8749 let mut element = self
8750 .render_edit_prediction_line_popover(
8751 "Jump to Edit",
8752 Some(IconName::ArrowUp),
8753 window,
8754 cx,
8755 )
8756 .into_any();
8757
8758 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8759 let offset = point(
8760 (text_bounds.size.width - size.width) / 2.,
8761 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8762 );
8763
8764 let origin = text_bounds.origin + offset;
8765 element.prepaint_at(origin, window, cx);
8766 Some((element, origin))
8767 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8768 let mut element = self
8769 .render_edit_prediction_line_popover(
8770 "Jump to Edit",
8771 Some(IconName::ArrowDown),
8772 window,
8773 cx,
8774 )
8775 .into_any();
8776
8777 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8778 let offset = point(
8779 (text_bounds.size.width - size.width) / 2.,
8780 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8781 );
8782
8783 let origin = text_bounds.origin + offset;
8784 element.prepaint_at(origin, window, cx);
8785 Some((element, origin))
8786 } else {
8787 self.render_edit_prediction_end_of_line_popover(
8788 "Jump to Edit",
8789 editor_snapshot,
8790 visible_row_range,
8791 target_display_point,
8792 line_height,
8793 scroll_pixel_position,
8794 content_origin,
8795 editor_width,
8796 window,
8797 cx,
8798 )
8799 }
8800 }
8801
8802 fn render_edit_prediction_end_of_line_popover(
8803 self: &mut Editor,
8804 label: &'static str,
8805 editor_snapshot: &EditorSnapshot,
8806 visible_row_range: Range<DisplayRow>,
8807 target_display_point: DisplayPoint,
8808 line_height: Pixels,
8809 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8810 content_origin: gpui::Point<Pixels>,
8811 editor_width: Pixels,
8812 window: &mut Window,
8813 cx: &mut App,
8814 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8815 let target_line_end = DisplayPoint::new(
8816 target_display_point.row(),
8817 editor_snapshot.line_len(target_display_point.row()),
8818 );
8819
8820 let mut element = self
8821 .render_edit_prediction_line_popover(label, None, window, cx)
8822 .into_any();
8823
8824 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8825
8826 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8827
8828 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8829 let mut origin = start_point
8830 + line_origin
8831 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8832 origin.x = origin.x.max(content_origin.x);
8833
8834 let max_x = content_origin.x + editor_width - size.width;
8835
8836 if origin.x > max_x {
8837 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8838
8839 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8840 origin.y += offset;
8841 IconName::ArrowUp
8842 } else {
8843 origin.y -= offset;
8844 IconName::ArrowDown
8845 };
8846
8847 element = self
8848 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8849 .into_any();
8850
8851 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8852
8853 origin.x = content_origin.x + editor_width - size.width - px(2.);
8854 }
8855
8856 element.prepaint_at(origin, window, cx);
8857 Some((element, origin))
8858 }
8859
8860 fn render_edit_prediction_diff_popover(
8861 self: &Editor,
8862 text_bounds: &Bounds<Pixels>,
8863 content_origin: gpui::Point<Pixels>,
8864 right_margin: Pixels,
8865 editor_snapshot: &EditorSnapshot,
8866 visible_row_range: Range<DisplayRow>,
8867 line_layouts: &[LineWithInvisibles],
8868 line_height: Pixels,
8869 scroll_position: gpui::Point<ScrollOffset>,
8870 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8871 newest_selection_head: Option<DisplayPoint>,
8872 editor_width: Pixels,
8873 style: &EditorStyle,
8874 edits: &Vec<(Range<Anchor>, String)>,
8875 edit_preview: &Option<language::EditPreview>,
8876 snapshot: &language::BufferSnapshot,
8877 window: &mut Window,
8878 cx: &mut App,
8879 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8880 let edit_start = edits
8881 .first()
8882 .unwrap()
8883 .0
8884 .start
8885 .to_display_point(editor_snapshot);
8886 let edit_end = edits
8887 .last()
8888 .unwrap()
8889 .0
8890 .end
8891 .to_display_point(editor_snapshot);
8892
8893 let is_visible = visible_row_range.contains(&edit_start.row())
8894 || visible_row_range.contains(&edit_end.row());
8895 if !is_visible {
8896 return None;
8897 }
8898
8899 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
8900 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
8901 } else {
8902 // Fallback for providers without edit_preview
8903 crate::edit_prediction_fallback_text(edits, cx)
8904 };
8905
8906 let styled_text = highlighted_edits.to_styled_text(&style.text);
8907 let line_count = highlighted_edits.text.lines().count();
8908
8909 const BORDER_WIDTH: Pixels = px(1.);
8910
8911 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
8912 let has_keybind = keybind.is_some();
8913
8914 let mut element = h_flex()
8915 .items_start()
8916 .child(
8917 h_flex()
8918 .bg(cx.theme().colors().editor_background)
8919 .border(BORDER_WIDTH)
8920 .shadow_xs()
8921 .border_color(cx.theme().colors().border)
8922 .rounded_l_lg()
8923 .when(line_count > 1, |el| el.rounded_br_lg())
8924 .pr_1()
8925 .child(styled_text),
8926 )
8927 .child(
8928 h_flex()
8929 .h(line_height + BORDER_WIDTH * 2.)
8930 .px_1p5()
8931 .gap_1()
8932 // Workaround: For some reason, there's a gap if we don't do this
8933 .ml(-BORDER_WIDTH)
8934 .shadow(vec![gpui::BoxShadow {
8935 color: gpui::black().opacity(0.05),
8936 offset: point(px(1.), px(1.)),
8937 blur_radius: px(2.),
8938 spread_radius: px(0.),
8939 }])
8940 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
8941 .border(BORDER_WIDTH)
8942 .border_color(cx.theme().colors().border)
8943 .rounded_r_lg()
8944 .id("edit_prediction_diff_popover_keybind")
8945 .when(!has_keybind, |el| {
8946 let status_colors = cx.theme().status();
8947
8948 el.bg(status_colors.error_background)
8949 .border_color(status_colors.error.opacity(0.6))
8950 .child(Icon::new(IconName::Info).color(Color::Error))
8951 .cursor_default()
8952 .hoverable_tooltip(move |_window, cx| {
8953 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
8954 })
8955 })
8956 .children(keybind),
8957 )
8958 .into_any();
8959
8960 let longest_row =
8961 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
8962 let longest_line_width = if visible_row_range.contains(&longest_row) {
8963 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
8964 } else {
8965 layout_line(
8966 longest_row,
8967 editor_snapshot,
8968 style,
8969 editor_width,
8970 |_| false,
8971 window,
8972 cx,
8973 )
8974 .width
8975 };
8976
8977 let viewport_bounds =
8978 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
8979 right: -right_margin,
8980 ..Default::default()
8981 });
8982
8983 let x_after_longest = Pixels::from(
8984 ScrollPixelOffset::from(
8985 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
8986 ) - scroll_pixel_position.x,
8987 );
8988
8989 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8990
8991 // Fully visible if it can be displayed within the window (allow overlapping other
8992 // panes). However, this is only allowed if the popover starts within text_bounds.
8993 let can_position_to_the_right = x_after_longest < text_bounds.right()
8994 && x_after_longest + element_bounds.width < viewport_bounds.right();
8995
8996 let mut origin = if can_position_to_the_right {
8997 point(
8998 x_after_longest,
8999 text_bounds.origin.y
9000 + Pixels::from(
9001 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9002 - scroll_pixel_position.y,
9003 ),
9004 )
9005 } else {
9006 let cursor_row = newest_selection_head.map(|head| head.row());
9007 let above_edit = edit_start
9008 .row()
9009 .0
9010 .checked_sub(line_count as u32)
9011 .map(DisplayRow);
9012 let below_edit = Some(edit_end.row() + 1);
9013 let above_cursor =
9014 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9015 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9016
9017 // Place the edit popover adjacent to the edit if there is a location
9018 // available that is onscreen and does not obscure the cursor. Otherwise,
9019 // place it adjacent to the cursor.
9020 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9021 .into_iter()
9022 .flatten()
9023 .find(|&start_row| {
9024 let end_row = start_row + line_count as u32;
9025 visible_row_range.contains(&start_row)
9026 && visible_row_range.contains(&end_row)
9027 && cursor_row
9028 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9029 })?;
9030
9031 content_origin
9032 + point(
9033 Pixels::from(-scroll_pixel_position.x),
9034 Pixels::from(
9035 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9036 ),
9037 )
9038 };
9039
9040 origin.x -= BORDER_WIDTH;
9041
9042 window.defer_draw(element, origin, 1);
9043
9044 // Do not return an element, since it will already be drawn due to defer_draw.
9045 None
9046 }
9047
9048 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9049 px(30.)
9050 }
9051
9052 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9053 if self.read_only(cx) {
9054 cx.theme().players().read_only()
9055 } else {
9056 self.style.as_ref().unwrap().local_player
9057 }
9058 }
9059
9060 fn render_edit_prediction_accept_keybind(
9061 &self,
9062 window: &mut Window,
9063 cx: &mut App,
9064 ) -> Option<AnyElement> {
9065 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9066 let accept_keystroke = accept_binding.keystroke()?;
9067
9068 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9069
9070 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9071 Color::Accent
9072 } else {
9073 Color::Muted
9074 };
9075
9076 h_flex()
9077 .px_0p5()
9078 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9079 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9080 .text_size(TextSize::XSmall.rems(cx))
9081 .child(h_flex().children(ui::render_modifiers(
9082 accept_keystroke.modifiers(),
9083 PlatformStyle::platform(),
9084 Some(modifiers_color),
9085 Some(IconSize::XSmall.rems().into()),
9086 true,
9087 )))
9088 .when(is_platform_style_mac, |parent| {
9089 parent.child(accept_keystroke.key().to_string())
9090 })
9091 .when(!is_platform_style_mac, |parent| {
9092 parent.child(
9093 Key::new(
9094 util::capitalize(accept_keystroke.key()),
9095 Some(Color::Default),
9096 )
9097 .size(Some(IconSize::XSmall.rems().into())),
9098 )
9099 })
9100 .into_any()
9101 .into()
9102 }
9103
9104 fn render_edit_prediction_line_popover(
9105 &self,
9106 label: impl Into<SharedString>,
9107 icon: Option<IconName>,
9108 window: &mut Window,
9109 cx: &mut App,
9110 ) -> Stateful<Div> {
9111 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9112
9113 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9114 let has_keybind = keybind.is_some();
9115
9116 h_flex()
9117 .id("ep-line-popover")
9118 .py_0p5()
9119 .pl_1()
9120 .pr(padding_right)
9121 .gap_1()
9122 .rounded_md()
9123 .border_1()
9124 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9125 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9126 .shadow_xs()
9127 .when(!has_keybind, |el| {
9128 let status_colors = cx.theme().status();
9129
9130 el.bg(status_colors.error_background)
9131 .border_color(status_colors.error.opacity(0.6))
9132 .pl_2()
9133 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9134 .cursor_default()
9135 .hoverable_tooltip(move |_window, cx| {
9136 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9137 })
9138 })
9139 .children(keybind)
9140 .child(
9141 Label::new(label)
9142 .size(LabelSize::Small)
9143 .when(!has_keybind, |el| {
9144 el.color(cx.theme().status().error.into()).strikethrough()
9145 }),
9146 )
9147 .when(!has_keybind, |el| {
9148 el.child(
9149 h_flex().ml_1().child(
9150 Icon::new(IconName::Info)
9151 .size(IconSize::Small)
9152 .color(cx.theme().status().error.into()),
9153 ),
9154 )
9155 })
9156 .when_some(icon, |element, icon| {
9157 element.child(
9158 div()
9159 .mt(px(1.5))
9160 .child(Icon::new(icon).size(IconSize::Small)),
9161 )
9162 })
9163 }
9164
9165 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9166 let accent_color = cx.theme().colors().text_accent;
9167 let editor_bg_color = cx.theme().colors().editor_background;
9168 editor_bg_color.blend(accent_color.opacity(0.1))
9169 }
9170
9171 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9172 let accent_color = cx.theme().colors().text_accent;
9173 let editor_bg_color = cx.theme().colors().editor_background;
9174 editor_bg_color.blend(accent_color.opacity(0.6))
9175 }
9176 fn get_prediction_provider_icon_name(
9177 provider: &Option<RegisteredEditPredictionProvider>,
9178 ) -> IconName {
9179 match provider {
9180 Some(provider) => match provider.provider.name() {
9181 "copilot" => IconName::Copilot,
9182 "supermaven" => IconName::Supermaven,
9183 _ => IconName::ZedPredict,
9184 },
9185 None => IconName::ZedPredict,
9186 }
9187 }
9188
9189 fn render_edit_prediction_cursor_popover(
9190 &self,
9191 min_width: Pixels,
9192 max_width: Pixels,
9193 cursor_point: Point,
9194 style: &EditorStyle,
9195 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9196 _window: &Window,
9197 cx: &mut Context<Editor>,
9198 ) -> Option<AnyElement> {
9199 let provider = self.edit_prediction_provider.as_ref()?;
9200 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9201
9202 let is_refreshing = provider.provider.is_refreshing(cx);
9203
9204 fn pending_completion_container(icon: IconName) -> Div {
9205 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9206 }
9207
9208 let completion = match &self.active_edit_prediction {
9209 Some(prediction) => {
9210 if !self.has_visible_completions_menu() {
9211 const RADIUS: Pixels = px(6.);
9212 const BORDER_WIDTH: Pixels = px(1.);
9213
9214 return Some(
9215 h_flex()
9216 .elevation_2(cx)
9217 .border(BORDER_WIDTH)
9218 .border_color(cx.theme().colors().border)
9219 .when(accept_keystroke.is_none(), |el| {
9220 el.border_color(cx.theme().status().error)
9221 })
9222 .rounded(RADIUS)
9223 .rounded_tl(px(0.))
9224 .overflow_hidden()
9225 .child(div().px_1p5().child(match &prediction.completion {
9226 EditPrediction::MoveWithin { target, snapshot } => {
9227 use text::ToPoint as _;
9228 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9229 {
9230 Icon::new(IconName::ZedPredictDown)
9231 } else {
9232 Icon::new(IconName::ZedPredictUp)
9233 }
9234 }
9235 EditPrediction::MoveOutside { .. } => {
9236 // TODO [zeta2] custom icon for external jump?
9237 Icon::new(provider_icon)
9238 }
9239 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9240 }))
9241 .child(
9242 h_flex()
9243 .gap_1()
9244 .py_1()
9245 .px_2()
9246 .rounded_r(RADIUS - BORDER_WIDTH)
9247 .border_l_1()
9248 .border_color(cx.theme().colors().border)
9249 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9250 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9251 el.child(
9252 Label::new("Hold")
9253 .size(LabelSize::Small)
9254 .when(accept_keystroke.is_none(), |el| {
9255 el.strikethrough()
9256 })
9257 .line_height_style(LineHeightStyle::UiLabel),
9258 )
9259 })
9260 .id("edit_prediction_cursor_popover_keybind")
9261 .when(accept_keystroke.is_none(), |el| {
9262 let status_colors = cx.theme().status();
9263
9264 el.bg(status_colors.error_background)
9265 .border_color(status_colors.error.opacity(0.6))
9266 .child(Icon::new(IconName::Info).color(Color::Error))
9267 .cursor_default()
9268 .hoverable_tooltip(move |_window, cx| {
9269 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9270 .into()
9271 })
9272 })
9273 .when_some(
9274 accept_keystroke.as_ref(),
9275 |el, accept_keystroke| {
9276 el.child(h_flex().children(ui::render_modifiers(
9277 accept_keystroke.modifiers(),
9278 PlatformStyle::platform(),
9279 Some(Color::Default),
9280 Some(IconSize::XSmall.rems().into()),
9281 false,
9282 )))
9283 },
9284 ),
9285 )
9286 .into_any(),
9287 );
9288 }
9289
9290 self.render_edit_prediction_cursor_popover_preview(
9291 prediction,
9292 cursor_point,
9293 style,
9294 cx,
9295 )?
9296 }
9297
9298 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9299 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9300 stale_completion,
9301 cursor_point,
9302 style,
9303 cx,
9304 )?,
9305
9306 None => pending_completion_container(provider_icon)
9307 .child(Label::new("...").size(LabelSize::Small)),
9308 },
9309
9310 None => pending_completion_container(provider_icon)
9311 .child(Label::new("...").size(LabelSize::Small)),
9312 };
9313
9314 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9315 completion
9316 .with_animation(
9317 "loading-completion",
9318 Animation::new(Duration::from_secs(2))
9319 .repeat()
9320 .with_easing(pulsating_between(0.4, 0.8)),
9321 |label, delta| label.opacity(delta),
9322 )
9323 .into_any_element()
9324 } else {
9325 completion.into_any_element()
9326 };
9327
9328 let has_completion = self.active_edit_prediction.is_some();
9329
9330 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9331 Some(
9332 h_flex()
9333 .min_w(min_width)
9334 .max_w(max_width)
9335 .flex_1()
9336 .elevation_2(cx)
9337 .border_color(cx.theme().colors().border)
9338 .child(
9339 div()
9340 .flex_1()
9341 .py_1()
9342 .px_2()
9343 .overflow_hidden()
9344 .child(completion),
9345 )
9346 .when_some(accept_keystroke, |el, accept_keystroke| {
9347 if !accept_keystroke.modifiers().modified() {
9348 return el;
9349 }
9350
9351 el.child(
9352 h_flex()
9353 .h_full()
9354 .border_l_1()
9355 .rounded_r_lg()
9356 .border_color(cx.theme().colors().border)
9357 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9358 .gap_1()
9359 .py_1()
9360 .px_2()
9361 .child(
9362 h_flex()
9363 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9364 .when(is_platform_style_mac, |parent| parent.gap_1())
9365 .child(h_flex().children(ui::render_modifiers(
9366 accept_keystroke.modifiers(),
9367 PlatformStyle::platform(),
9368 Some(if !has_completion {
9369 Color::Muted
9370 } else {
9371 Color::Default
9372 }),
9373 None,
9374 false,
9375 ))),
9376 )
9377 .child(Label::new("Preview").into_any_element())
9378 .opacity(if has_completion { 1.0 } else { 0.4 }),
9379 )
9380 })
9381 .into_any(),
9382 )
9383 }
9384
9385 fn render_edit_prediction_cursor_popover_preview(
9386 &self,
9387 completion: &EditPredictionState,
9388 cursor_point: Point,
9389 style: &EditorStyle,
9390 cx: &mut Context<Editor>,
9391 ) -> Option<Div> {
9392 use text::ToPoint as _;
9393
9394 fn render_relative_row_jump(
9395 prefix: impl Into<String>,
9396 current_row: u32,
9397 target_row: u32,
9398 ) -> Div {
9399 let (row_diff, arrow) = if target_row < current_row {
9400 (current_row - target_row, IconName::ArrowUp)
9401 } else {
9402 (target_row - current_row, IconName::ArrowDown)
9403 };
9404
9405 h_flex()
9406 .child(
9407 Label::new(format!("{}{}", prefix.into(), row_diff))
9408 .color(Color::Muted)
9409 .size(LabelSize::Small),
9410 )
9411 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9412 }
9413
9414 let supports_jump = self
9415 .edit_prediction_provider
9416 .as_ref()
9417 .map(|provider| provider.provider.supports_jump_to_edit())
9418 .unwrap_or(true);
9419
9420 match &completion.completion {
9421 EditPrediction::MoveWithin {
9422 target, snapshot, ..
9423 } => {
9424 if !supports_jump {
9425 return None;
9426 }
9427
9428 Some(
9429 h_flex()
9430 .px_2()
9431 .gap_2()
9432 .flex_1()
9433 .child(
9434 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9435 Icon::new(IconName::ZedPredictDown)
9436 } else {
9437 Icon::new(IconName::ZedPredictUp)
9438 },
9439 )
9440 .child(Label::new("Jump to Edit")),
9441 )
9442 }
9443 EditPrediction::MoveOutside { snapshot, .. } => {
9444 let file_name = snapshot
9445 .file()
9446 .map(|file| file.file_name(cx))
9447 .unwrap_or("untitled");
9448 Some(
9449 h_flex()
9450 .px_2()
9451 .gap_2()
9452 .flex_1()
9453 .child(Icon::new(IconName::ZedPredict))
9454 .child(Label::new(format!("Jump to {file_name}"))),
9455 )
9456 }
9457 EditPrediction::Edit {
9458 edits,
9459 edit_preview,
9460 snapshot,
9461 display_mode: _,
9462 } => {
9463 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9464
9465 let (highlighted_edits, has_more_lines) =
9466 if let Some(edit_preview) = edit_preview.as_ref() {
9467 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9468 .first_line_preview()
9469 } else {
9470 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9471 };
9472
9473 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9474 .with_default_highlights(&style.text, highlighted_edits.highlights);
9475
9476 let preview = h_flex()
9477 .gap_1()
9478 .min_w_16()
9479 .child(styled_text)
9480 .when(has_more_lines, |parent| parent.child("…"));
9481
9482 let left = if supports_jump && first_edit_row != cursor_point.row {
9483 render_relative_row_jump("", cursor_point.row, first_edit_row)
9484 .into_any_element()
9485 } else {
9486 let icon_name =
9487 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9488 Icon::new(icon_name).into_any_element()
9489 };
9490
9491 Some(
9492 h_flex()
9493 .h_full()
9494 .flex_1()
9495 .gap_2()
9496 .pr_1()
9497 .overflow_x_hidden()
9498 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9499 .child(left)
9500 .child(preview),
9501 )
9502 }
9503 }
9504 }
9505
9506 pub fn render_context_menu(
9507 &self,
9508 style: &EditorStyle,
9509 max_height_in_lines: u32,
9510 window: &mut Window,
9511 cx: &mut Context<Editor>,
9512 ) -> Option<AnyElement> {
9513 let menu = self.context_menu.borrow();
9514 let menu = menu.as_ref()?;
9515 if !menu.visible() {
9516 return None;
9517 };
9518 Some(menu.render(style, max_height_in_lines, window, cx))
9519 }
9520
9521 fn render_context_menu_aside(
9522 &mut self,
9523 max_size: Size<Pixels>,
9524 window: &mut Window,
9525 cx: &mut Context<Editor>,
9526 ) -> Option<AnyElement> {
9527 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9528 if menu.visible() {
9529 menu.render_aside(max_size, window, cx)
9530 } else {
9531 None
9532 }
9533 })
9534 }
9535
9536 fn hide_context_menu(
9537 &mut self,
9538 window: &mut Window,
9539 cx: &mut Context<Self>,
9540 ) -> Option<CodeContextMenu> {
9541 cx.notify();
9542 self.completion_tasks.clear();
9543 let context_menu = self.context_menu.borrow_mut().take();
9544 self.stale_edit_prediction_in_menu.take();
9545 self.update_visible_edit_prediction(window, cx);
9546 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9547 && let Some(completion_provider) = &self.completion_provider
9548 {
9549 completion_provider.selection_changed(None, window, cx);
9550 }
9551 context_menu
9552 }
9553
9554 fn show_snippet_choices(
9555 &mut self,
9556 choices: &Vec<String>,
9557 selection: Range<Anchor>,
9558 cx: &mut Context<Self>,
9559 ) {
9560 let Some((_, buffer, _)) = self
9561 .buffer()
9562 .read(cx)
9563 .excerpt_containing(selection.start, cx)
9564 else {
9565 return;
9566 };
9567 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9568 else {
9569 return;
9570 };
9571 if buffer != end_buffer {
9572 log::error!("expected anchor range to have matching buffer IDs");
9573 return;
9574 }
9575
9576 let id = post_inc(&mut self.next_completion_id);
9577 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9578 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9579 CompletionsMenu::new_snippet_choices(
9580 id,
9581 true,
9582 choices,
9583 selection,
9584 buffer,
9585 snippet_sort_order,
9586 ),
9587 ));
9588 }
9589
9590 pub fn insert_snippet(
9591 &mut self,
9592 insertion_ranges: &[Range<usize>],
9593 snippet: Snippet,
9594 window: &mut Window,
9595 cx: &mut Context<Self>,
9596 ) -> Result<()> {
9597 struct Tabstop<T> {
9598 is_end_tabstop: bool,
9599 ranges: Vec<Range<T>>,
9600 choices: Option<Vec<String>>,
9601 }
9602
9603 let tabstops = self.buffer.update(cx, |buffer, cx| {
9604 let snippet_text: Arc<str> = snippet.text.clone().into();
9605 let edits = insertion_ranges
9606 .iter()
9607 .cloned()
9608 .map(|range| (range, snippet_text.clone()));
9609 let autoindent_mode = AutoindentMode::Block {
9610 original_indent_columns: Vec::new(),
9611 };
9612 buffer.edit(edits, Some(autoindent_mode), cx);
9613
9614 let snapshot = &*buffer.read(cx);
9615 let snippet = &snippet;
9616 snippet
9617 .tabstops
9618 .iter()
9619 .map(|tabstop| {
9620 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9621 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9622 });
9623 let mut tabstop_ranges = tabstop
9624 .ranges
9625 .iter()
9626 .flat_map(|tabstop_range| {
9627 let mut delta = 0_isize;
9628 insertion_ranges.iter().map(move |insertion_range| {
9629 let insertion_start = insertion_range.start as isize + delta;
9630 delta +=
9631 snippet.text.len() as isize - insertion_range.len() as isize;
9632
9633 let start = ((insertion_start + tabstop_range.start) as usize)
9634 .min(snapshot.len());
9635 let end = ((insertion_start + tabstop_range.end) as usize)
9636 .min(snapshot.len());
9637 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9638 })
9639 })
9640 .collect::<Vec<_>>();
9641 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9642
9643 Tabstop {
9644 is_end_tabstop,
9645 ranges: tabstop_ranges,
9646 choices: tabstop.choices.clone(),
9647 }
9648 })
9649 .collect::<Vec<_>>()
9650 });
9651 if let Some(tabstop) = tabstops.first() {
9652 self.change_selections(Default::default(), window, cx, |s| {
9653 // Reverse order so that the first range is the newest created selection.
9654 // Completions will use it and autoscroll will prioritize it.
9655 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9656 });
9657
9658 if let Some(choices) = &tabstop.choices
9659 && let Some(selection) = tabstop.ranges.first()
9660 {
9661 self.show_snippet_choices(choices, selection.clone(), cx)
9662 }
9663
9664 // If we're already at the last tabstop and it's at the end of the snippet,
9665 // we're done, we don't need to keep the state around.
9666 if !tabstop.is_end_tabstop {
9667 let choices = tabstops
9668 .iter()
9669 .map(|tabstop| tabstop.choices.clone())
9670 .collect();
9671
9672 let ranges = tabstops
9673 .into_iter()
9674 .map(|tabstop| tabstop.ranges)
9675 .collect::<Vec<_>>();
9676
9677 self.snippet_stack.push(SnippetState {
9678 active_index: 0,
9679 ranges,
9680 choices,
9681 });
9682 }
9683
9684 // Check whether the just-entered snippet ends with an auto-closable bracket.
9685 if self.autoclose_regions.is_empty() {
9686 let snapshot = self.buffer.read(cx).snapshot(cx);
9687 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9688 let selection_head = selection.head();
9689 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9690 continue;
9691 };
9692
9693 let mut bracket_pair = None;
9694 let max_lookup_length = scope
9695 .brackets()
9696 .map(|(pair, _)| {
9697 pair.start
9698 .as_str()
9699 .chars()
9700 .count()
9701 .max(pair.end.as_str().chars().count())
9702 })
9703 .max();
9704 if let Some(max_lookup_length) = max_lookup_length {
9705 let next_text = snapshot
9706 .chars_at(selection_head)
9707 .take(max_lookup_length)
9708 .collect::<String>();
9709 let prev_text = snapshot
9710 .reversed_chars_at(selection_head)
9711 .take(max_lookup_length)
9712 .collect::<String>();
9713
9714 for (pair, enabled) in scope.brackets() {
9715 if enabled
9716 && pair.close
9717 && prev_text.starts_with(pair.start.as_str())
9718 && next_text.starts_with(pair.end.as_str())
9719 {
9720 bracket_pair = Some(pair.clone());
9721 break;
9722 }
9723 }
9724 }
9725
9726 if let Some(pair) = bracket_pair {
9727 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9728 let autoclose_enabled =
9729 self.use_autoclose && snapshot_settings.use_autoclose;
9730 if autoclose_enabled {
9731 let start = snapshot.anchor_after(selection_head);
9732 let end = snapshot.anchor_after(selection_head);
9733 self.autoclose_regions.push(AutocloseRegion {
9734 selection_id: selection.id,
9735 range: start..end,
9736 pair,
9737 });
9738 }
9739 }
9740 }
9741 }
9742 }
9743 Ok(())
9744 }
9745
9746 pub fn move_to_next_snippet_tabstop(
9747 &mut self,
9748 window: &mut Window,
9749 cx: &mut Context<Self>,
9750 ) -> bool {
9751 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9752 }
9753
9754 pub fn move_to_prev_snippet_tabstop(
9755 &mut self,
9756 window: &mut Window,
9757 cx: &mut Context<Self>,
9758 ) -> bool {
9759 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9760 }
9761
9762 pub fn move_to_snippet_tabstop(
9763 &mut self,
9764 bias: Bias,
9765 window: &mut Window,
9766 cx: &mut Context<Self>,
9767 ) -> bool {
9768 if let Some(mut snippet) = self.snippet_stack.pop() {
9769 match bias {
9770 Bias::Left => {
9771 if snippet.active_index > 0 {
9772 snippet.active_index -= 1;
9773 } else {
9774 self.snippet_stack.push(snippet);
9775 return false;
9776 }
9777 }
9778 Bias::Right => {
9779 if snippet.active_index + 1 < snippet.ranges.len() {
9780 snippet.active_index += 1;
9781 } else {
9782 self.snippet_stack.push(snippet);
9783 return false;
9784 }
9785 }
9786 }
9787 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9788 self.change_selections(Default::default(), window, cx, |s| {
9789 // Reverse order so that the first range is the newest created selection.
9790 // Completions will use it and autoscroll will prioritize it.
9791 s.select_ranges(current_ranges.iter().rev().cloned())
9792 });
9793
9794 if let Some(choices) = &snippet.choices[snippet.active_index]
9795 && let Some(selection) = current_ranges.first()
9796 {
9797 self.show_snippet_choices(choices, selection.clone(), cx);
9798 }
9799
9800 // If snippet state is not at the last tabstop, push it back on the stack
9801 if snippet.active_index + 1 < snippet.ranges.len() {
9802 self.snippet_stack.push(snippet);
9803 }
9804 return true;
9805 }
9806 }
9807
9808 false
9809 }
9810
9811 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9812 self.transact(window, cx, |this, window, cx| {
9813 this.select_all(&SelectAll, window, cx);
9814 this.insert("", window, cx);
9815 });
9816 }
9817
9818 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9819 if self.read_only(cx) {
9820 return;
9821 }
9822 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9823 self.transact(window, cx, |this, window, cx| {
9824 this.select_autoclose_pair(window, cx);
9825
9826 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9827
9828 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9829 if !this.linked_edit_ranges.is_empty() {
9830 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9831 let snapshot = this.buffer.read(cx).snapshot(cx);
9832
9833 for selection in selections.iter() {
9834 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9835 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9836 if selection_start.buffer_id != selection_end.buffer_id {
9837 continue;
9838 }
9839 if let Some(ranges) =
9840 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9841 {
9842 for (buffer, entries) in ranges {
9843 linked_ranges.entry(buffer).or_default().extend(entries);
9844 }
9845 }
9846 }
9847 }
9848
9849 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9850 for selection in &mut selections {
9851 if selection.is_empty() {
9852 let old_head = selection.head();
9853 let mut new_head =
9854 movement::left(&display_map, old_head.to_display_point(&display_map))
9855 .to_point(&display_map);
9856 if let Some((buffer, line_buffer_range)) = display_map
9857 .buffer_snapshot()
9858 .buffer_line_for_row(MultiBufferRow(old_head.row))
9859 {
9860 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9861 let indent_len = match indent_size.kind {
9862 IndentKind::Space => {
9863 buffer.settings_at(line_buffer_range.start, cx).tab_size
9864 }
9865 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9866 };
9867 if old_head.column <= indent_size.len && old_head.column > 0 {
9868 let indent_len = indent_len.get();
9869 new_head = cmp::min(
9870 new_head,
9871 MultiBufferPoint::new(
9872 old_head.row,
9873 ((old_head.column - 1) / indent_len) * indent_len,
9874 ),
9875 );
9876 }
9877 }
9878
9879 selection.set_head(new_head, SelectionGoal::None);
9880 }
9881 }
9882
9883 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
9884 this.insert("", window, cx);
9885 let empty_str: Arc<str> = Arc::from("");
9886 for (buffer, edits) in linked_ranges {
9887 let snapshot = buffer.read(cx).snapshot();
9888 use text::ToPoint as TP;
9889
9890 let edits = edits
9891 .into_iter()
9892 .map(|range| {
9893 let end_point = TP::to_point(&range.end, &snapshot);
9894 let mut start_point = TP::to_point(&range.start, &snapshot);
9895
9896 if end_point == start_point {
9897 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
9898 .saturating_sub(1);
9899 start_point =
9900 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
9901 };
9902
9903 (start_point..end_point, empty_str.clone())
9904 })
9905 .sorted_by_key(|(range, _)| range.start)
9906 .collect::<Vec<_>>();
9907 buffer.update(cx, |this, cx| {
9908 this.edit(edits, None, cx);
9909 })
9910 }
9911 this.refresh_edit_prediction(true, false, window, cx);
9912 refresh_linked_ranges(this, window, cx);
9913 });
9914 }
9915
9916 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
9917 if self.read_only(cx) {
9918 return;
9919 }
9920 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9921 self.transact(window, cx, |this, window, cx| {
9922 this.change_selections(Default::default(), window, cx, |s| {
9923 s.move_with(|map, selection| {
9924 if selection.is_empty() {
9925 let cursor = movement::right(map, selection.head());
9926 selection.end = cursor;
9927 selection.reversed = true;
9928 selection.goal = SelectionGoal::None;
9929 }
9930 })
9931 });
9932 this.insert("", window, cx);
9933 this.refresh_edit_prediction(true, false, window, cx);
9934 });
9935 }
9936
9937 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
9938 if self.mode.is_single_line() {
9939 cx.propagate();
9940 return;
9941 }
9942
9943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9944 if self.move_to_prev_snippet_tabstop(window, cx) {
9945 return;
9946 }
9947 self.outdent(&Outdent, window, cx);
9948 }
9949
9950 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
9951 if self.mode.is_single_line() {
9952 cx.propagate();
9953 return;
9954 }
9955
9956 if self.move_to_next_snippet_tabstop(window, cx) {
9957 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9958 return;
9959 }
9960 if self.read_only(cx) {
9961 return;
9962 }
9963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9964 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
9965 let buffer = self.buffer.read(cx);
9966 let snapshot = buffer.snapshot(cx);
9967 let rows_iter = selections.iter().map(|s| s.head().row);
9968 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
9969
9970 let has_some_cursor_in_whitespace = selections
9971 .iter()
9972 .filter(|selection| selection.is_empty())
9973 .any(|selection| {
9974 let cursor = selection.head();
9975 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9976 cursor.column < current_indent.len
9977 });
9978
9979 let mut edits = Vec::new();
9980 let mut prev_edited_row = 0;
9981 let mut row_delta = 0;
9982 for selection in &mut selections {
9983 if selection.start.row != prev_edited_row {
9984 row_delta = 0;
9985 }
9986 prev_edited_row = selection.end.row;
9987
9988 // If the selection is non-empty, then increase the indentation of the selected lines.
9989 if !selection.is_empty() {
9990 row_delta =
9991 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
9992 continue;
9993 }
9994
9995 let cursor = selection.head();
9996 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
9997 if let Some(suggested_indent) =
9998 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
9999 {
10000 // Don't do anything if already at suggested indent
10001 // and there is any other cursor which is not
10002 if has_some_cursor_in_whitespace
10003 && cursor.column == current_indent.len
10004 && current_indent.len == suggested_indent.len
10005 {
10006 continue;
10007 }
10008
10009 // Adjust line and move cursor to suggested indent
10010 // if cursor is not at suggested indent
10011 if cursor.column < suggested_indent.len
10012 && cursor.column <= current_indent.len
10013 && current_indent.len <= suggested_indent.len
10014 {
10015 selection.start = Point::new(cursor.row, suggested_indent.len);
10016 selection.end = selection.start;
10017 if row_delta == 0 {
10018 edits.extend(Buffer::edit_for_indent_size_adjustment(
10019 cursor.row,
10020 current_indent,
10021 suggested_indent,
10022 ));
10023 row_delta = suggested_indent.len - current_indent.len;
10024 }
10025 continue;
10026 }
10027
10028 // If current indent is more than suggested indent
10029 // only move cursor to current indent and skip indent
10030 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10031 selection.start = Point::new(cursor.row, current_indent.len);
10032 selection.end = selection.start;
10033 continue;
10034 }
10035 }
10036
10037 // Otherwise, insert a hard or soft tab.
10038 let settings = buffer.language_settings_at(cursor, cx);
10039 let tab_size = if settings.hard_tabs {
10040 IndentSize::tab()
10041 } else {
10042 let tab_size = settings.tab_size.get();
10043 let indent_remainder = snapshot
10044 .text_for_range(Point::new(cursor.row, 0)..cursor)
10045 .flat_map(str::chars)
10046 .fold(row_delta % tab_size, |counter: u32, c| {
10047 if c == '\t' {
10048 0
10049 } else {
10050 (counter + 1) % tab_size
10051 }
10052 });
10053
10054 let chars_to_next_tab_stop = tab_size - indent_remainder;
10055 IndentSize::spaces(chars_to_next_tab_stop)
10056 };
10057 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10058 selection.end = selection.start;
10059 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10060 row_delta += tab_size.len;
10061 }
10062
10063 self.transact(window, cx, |this, window, cx| {
10064 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10065 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10066 this.refresh_edit_prediction(true, false, window, cx);
10067 });
10068 }
10069
10070 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10071 if self.read_only(cx) {
10072 return;
10073 }
10074 if self.mode.is_single_line() {
10075 cx.propagate();
10076 return;
10077 }
10078
10079 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10080 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10081 let mut prev_edited_row = 0;
10082 let mut row_delta = 0;
10083 let mut edits = Vec::new();
10084 let buffer = self.buffer.read(cx);
10085 let snapshot = buffer.snapshot(cx);
10086 for selection in &mut selections {
10087 if selection.start.row != prev_edited_row {
10088 row_delta = 0;
10089 }
10090 prev_edited_row = selection.end.row;
10091
10092 row_delta =
10093 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10094 }
10095
10096 self.transact(window, cx, |this, window, cx| {
10097 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10098 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10099 });
10100 }
10101
10102 fn indent_selection(
10103 buffer: &MultiBuffer,
10104 snapshot: &MultiBufferSnapshot,
10105 selection: &mut Selection<Point>,
10106 edits: &mut Vec<(Range<Point>, String)>,
10107 delta_for_start_row: u32,
10108 cx: &App,
10109 ) -> u32 {
10110 let settings = buffer.language_settings_at(selection.start, cx);
10111 let tab_size = settings.tab_size.get();
10112 let indent_kind = if settings.hard_tabs {
10113 IndentKind::Tab
10114 } else {
10115 IndentKind::Space
10116 };
10117 let mut start_row = selection.start.row;
10118 let mut end_row = selection.end.row + 1;
10119
10120 // If a selection ends at the beginning of a line, don't indent
10121 // that last line.
10122 if selection.end.column == 0 && selection.end.row > selection.start.row {
10123 end_row -= 1;
10124 }
10125
10126 // Avoid re-indenting a row that has already been indented by a
10127 // previous selection, but still update this selection's column
10128 // to reflect that indentation.
10129 if delta_for_start_row > 0 {
10130 start_row += 1;
10131 selection.start.column += delta_for_start_row;
10132 if selection.end.row == selection.start.row {
10133 selection.end.column += delta_for_start_row;
10134 }
10135 }
10136
10137 let mut delta_for_end_row = 0;
10138 let has_multiple_rows = start_row + 1 != end_row;
10139 for row in start_row..end_row {
10140 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10141 let indent_delta = match (current_indent.kind, indent_kind) {
10142 (IndentKind::Space, IndentKind::Space) => {
10143 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10144 IndentSize::spaces(columns_to_next_tab_stop)
10145 }
10146 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10147 (_, IndentKind::Tab) => IndentSize::tab(),
10148 };
10149
10150 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10151 0
10152 } else {
10153 selection.start.column
10154 };
10155 let row_start = Point::new(row, start);
10156 edits.push((
10157 row_start..row_start,
10158 indent_delta.chars().collect::<String>(),
10159 ));
10160
10161 // Update this selection's endpoints to reflect the indentation.
10162 if row == selection.start.row {
10163 selection.start.column += indent_delta.len;
10164 }
10165 if row == selection.end.row {
10166 selection.end.column += indent_delta.len;
10167 delta_for_end_row = indent_delta.len;
10168 }
10169 }
10170
10171 if selection.start.row == selection.end.row {
10172 delta_for_start_row + delta_for_end_row
10173 } else {
10174 delta_for_end_row
10175 }
10176 }
10177
10178 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10179 if self.read_only(cx) {
10180 return;
10181 }
10182 if self.mode.is_single_line() {
10183 cx.propagate();
10184 return;
10185 }
10186
10187 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10188 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10189 let selections = self.selections.all::<Point>(&display_map);
10190 let mut deletion_ranges = Vec::new();
10191 let mut last_outdent = None;
10192 {
10193 let buffer = self.buffer.read(cx);
10194 let snapshot = buffer.snapshot(cx);
10195 for selection in &selections {
10196 let settings = buffer.language_settings_at(selection.start, cx);
10197 let tab_size = settings.tab_size.get();
10198 let mut rows = selection.spanned_rows(false, &display_map);
10199
10200 // Avoid re-outdenting a row that has already been outdented by a
10201 // previous selection.
10202 if let Some(last_row) = last_outdent
10203 && last_row == rows.start
10204 {
10205 rows.start = rows.start.next_row();
10206 }
10207 let has_multiple_rows = rows.len() > 1;
10208 for row in rows.iter_rows() {
10209 let indent_size = snapshot.indent_size_for_line(row);
10210 if indent_size.len > 0 {
10211 let deletion_len = match indent_size.kind {
10212 IndentKind::Space => {
10213 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10214 if columns_to_prev_tab_stop == 0 {
10215 tab_size
10216 } else {
10217 columns_to_prev_tab_stop
10218 }
10219 }
10220 IndentKind::Tab => 1,
10221 };
10222 let start = if has_multiple_rows
10223 || deletion_len > selection.start.column
10224 || indent_size.len < selection.start.column
10225 {
10226 0
10227 } else {
10228 selection.start.column - deletion_len
10229 };
10230 deletion_ranges.push(
10231 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10232 );
10233 last_outdent = Some(row);
10234 }
10235 }
10236 }
10237 }
10238
10239 self.transact(window, cx, |this, window, cx| {
10240 this.buffer.update(cx, |buffer, cx| {
10241 let empty_str: Arc<str> = Arc::default();
10242 buffer.edit(
10243 deletion_ranges
10244 .into_iter()
10245 .map(|range| (range, empty_str.clone())),
10246 None,
10247 cx,
10248 );
10249 });
10250 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10251 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10252 });
10253 }
10254
10255 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10256 if self.read_only(cx) {
10257 return;
10258 }
10259 if self.mode.is_single_line() {
10260 cx.propagate();
10261 return;
10262 }
10263
10264 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10265 let selections = self
10266 .selections
10267 .all::<usize>(&self.display_snapshot(cx))
10268 .into_iter()
10269 .map(|s| s.range());
10270
10271 self.transact(window, cx, |this, window, cx| {
10272 this.buffer.update(cx, |buffer, cx| {
10273 buffer.autoindent_ranges(selections, cx);
10274 });
10275 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10276 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10277 });
10278 }
10279
10280 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10282 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10283 let selections = self.selections.all::<Point>(&display_map);
10284
10285 let mut new_cursors = Vec::new();
10286 let mut edit_ranges = Vec::new();
10287 let mut selections = selections.iter().peekable();
10288 while let Some(selection) = selections.next() {
10289 let mut rows = selection.spanned_rows(false, &display_map);
10290
10291 // Accumulate contiguous regions of rows that we want to delete.
10292 while let Some(next_selection) = selections.peek() {
10293 let next_rows = next_selection.spanned_rows(false, &display_map);
10294 if next_rows.start <= rows.end {
10295 rows.end = next_rows.end;
10296 selections.next().unwrap();
10297 } else {
10298 break;
10299 }
10300 }
10301
10302 let buffer = display_map.buffer_snapshot();
10303 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10304 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10305 // If there's a line after the range, delete the \n from the end of the row range
10306 (
10307 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10308 rows.end,
10309 )
10310 } else {
10311 // If there isn't a line after the range, delete the \n from the line before the
10312 // start of the row range
10313 edit_start = edit_start.saturating_sub(1);
10314 (buffer.len(), rows.start.previous_row())
10315 };
10316
10317 let text_layout_details = self.text_layout_details(window);
10318 let x = display_map.x_for_display_point(
10319 selection.head().to_display_point(&display_map),
10320 &text_layout_details,
10321 );
10322 let row = Point::new(target_row.0, 0)
10323 .to_display_point(&display_map)
10324 .row();
10325 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10326
10327 new_cursors.push((
10328 selection.id,
10329 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10330 SelectionGoal::None,
10331 ));
10332 edit_ranges.push(edit_start..edit_end);
10333 }
10334
10335 self.transact(window, cx, |this, window, cx| {
10336 let buffer = this.buffer.update(cx, |buffer, cx| {
10337 let empty_str: Arc<str> = Arc::default();
10338 buffer.edit(
10339 edit_ranges
10340 .into_iter()
10341 .map(|range| (range, empty_str.clone())),
10342 None,
10343 cx,
10344 );
10345 buffer.snapshot(cx)
10346 });
10347 let new_selections = new_cursors
10348 .into_iter()
10349 .map(|(id, cursor, goal)| {
10350 let cursor = cursor.to_point(&buffer);
10351 Selection {
10352 id,
10353 start: cursor,
10354 end: cursor,
10355 reversed: false,
10356 goal,
10357 }
10358 })
10359 .collect();
10360
10361 this.change_selections(Default::default(), window, cx, |s| {
10362 s.select(new_selections);
10363 });
10364 });
10365 }
10366
10367 pub fn join_lines_impl(
10368 &mut self,
10369 insert_whitespace: bool,
10370 window: &mut Window,
10371 cx: &mut Context<Self>,
10372 ) {
10373 if self.read_only(cx) {
10374 return;
10375 }
10376 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10377 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10378 let start = MultiBufferRow(selection.start.row);
10379 // Treat single line selections as if they include the next line. Otherwise this action
10380 // would do nothing for single line selections individual cursors.
10381 let end = if selection.start.row == selection.end.row {
10382 MultiBufferRow(selection.start.row + 1)
10383 } else {
10384 MultiBufferRow(selection.end.row)
10385 };
10386
10387 if let Some(last_row_range) = row_ranges.last_mut()
10388 && start <= last_row_range.end
10389 {
10390 last_row_range.end = end;
10391 continue;
10392 }
10393 row_ranges.push(start..end);
10394 }
10395
10396 let snapshot = self.buffer.read(cx).snapshot(cx);
10397 let mut cursor_positions = Vec::new();
10398 for row_range in &row_ranges {
10399 let anchor = snapshot.anchor_before(Point::new(
10400 row_range.end.previous_row().0,
10401 snapshot.line_len(row_range.end.previous_row()),
10402 ));
10403 cursor_positions.push(anchor..anchor);
10404 }
10405
10406 self.transact(window, cx, |this, window, cx| {
10407 for row_range in row_ranges.into_iter().rev() {
10408 for row in row_range.iter_rows().rev() {
10409 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10410 let next_line_row = row.next_row();
10411 let indent = snapshot.indent_size_for_line(next_line_row);
10412 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10413
10414 let replace =
10415 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10416 " "
10417 } else {
10418 ""
10419 };
10420
10421 this.buffer.update(cx, |buffer, cx| {
10422 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10423 });
10424 }
10425 }
10426
10427 this.change_selections(Default::default(), window, cx, |s| {
10428 s.select_anchor_ranges(cursor_positions)
10429 });
10430 });
10431 }
10432
10433 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10434 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10435 self.join_lines_impl(true, window, cx);
10436 }
10437
10438 pub fn sort_lines_case_sensitive(
10439 &mut self,
10440 _: &SortLinesCaseSensitive,
10441 window: &mut Window,
10442 cx: &mut Context<Self>,
10443 ) {
10444 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10445 }
10446
10447 pub fn sort_lines_by_length(
10448 &mut self,
10449 _: &SortLinesByLength,
10450 window: &mut Window,
10451 cx: &mut Context<Self>,
10452 ) {
10453 self.manipulate_immutable_lines(window, cx, |lines| {
10454 lines.sort_by_key(|&line| line.chars().count())
10455 })
10456 }
10457
10458 pub fn sort_lines_case_insensitive(
10459 &mut self,
10460 _: &SortLinesCaseInsensitive,
10461 window: &mut Window,
10462 cx: &mut Context<Self>,
10463 ) {
10464 self.manipulate_immutable_lines(window, cx, |lines| {
10465 lines.sort_by_key(|line| line.to_lowercase())
10466 })
10467 }
10468
10469 pub fn unique_lines_case_insensitive(
10470 &mut self,
10471 _: &UniqueLinesCaseInsensitive,
10472 window: &mut Window,
10473 cx: &mut Context<Self>,
10474 ) {
10475 self.manipulate_immutable_lines(window, cx, |lines| {
10476 let mut seen = HashSet::default();
10477 lines.retain(|line| seen.insert(line.to_lowercase()));
10478 })
10479 }
10480
10481 pub fn unique_lines_case_sensitive(
10482 &mut self,
10483 _: &UniqueLinesCaseSensitive,
10484 window: &mut Window,
10485 cx: &mut Context<Self>,
10486 ) {
10487 self.manipulate_immutable_lines(window, cx, |lines| {
10488 let mut seen = HashSet::default();
10489 lines.retain(|line| seen.insert(*line));
10490 })
10491 }
10492
10493 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10494 let snapshot = self.buffer.read(cx).snapshot(cx);
10495 for selection in self.selections.disjoint_anchors_arc().iter() {
10496 if snapshot
10497 .language_at(selection.start)
10498 .and_then(|lang| lang.config().wrap_characters.as_ref())
10499 .is_some()
10500 {
10501 return true;
10502 }
10503 }
10504 false
10505 }
10506
10507 fn wrap_selections_in_tag(
10508 &mut self,
10509 _: &WrapSelectionsInTag,
10510 window: &mut Window,
10511 cx: &mut Context<Self>,
10512 ) {
10513 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10514
10515 let snapshot = self.buffer.read(cx).snapshot(cx);
10516
10517 let mut edits = Vec::new();
10518 let mut boundaries = Vec::new();
10519
10520 for selection in self
10521 .selections
10522 .all::<Point>(&self.display_snapshot(cx))
10523 .iter()
10524 {
10525 let Some(wrap_config) = snapshot
10526 .language_at(selection.start)
10527 .and_then(|lang| lang.config().wrap_characters.clone())
10528 else {
10529 continue;
10530 };
10531
10532 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10533 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10534
10535 let start_before = snapshot.anchor_before(selection.start);
10536 let end_after = snapshot.anchor_after(selection.end);
10537
10538 edits.push((start_before..start_before, open_tag));
10539 edits.push((end_after..end_after, close_tag));
10540
10541 boundaries.push((
10542 start_before,
10543 end_after,
10544 wrap_config.start_prefix.len(),
10545 wrap_config.end_suffix.len(),
10546 ));
10547 }
10548
10549 if edits.is_empty() {
10550 return;
10551 }
10552
10553 self.transact(window, cx, |this, window, cx| {
10554 let buffer = this.buffer.update(cx, |buffer, cx| {
10555 buffer.edit(edits, None, cx);
10556 buffer.snapshot(cx)
10557 });
10558
10559 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10560 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10561 boundaries.into_iter()
10562 {
10563 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10564 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10565 new_selections.push(open_offset..open_offset);
10566 new_selections.push(close_offset..close_offset);
10567 }
10568
10569 this.change_selections(Default::default(), window, cx, |s| {
10570 s.select_ranges(new_selections);
10571 });
10572
10573 this.request_autoscroll(Autoscroll::fit(), cx);
10574 });
10575 }
10576
10577 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10578 let Some(project) = self.project.clone() else {
10579 return;
10580 };
10581 self.reload(project, window, cx)
10582 .detach_and_notify_err(window, cx);
10583 }
10584
10585 pub fn restore_file(
10586 &mut self,
10587 _: &::git::RestoreFile,
10588 window: &mut Window,
10589 cx: &mut Context<Self>,
10590 ) {
10591 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10592 let mut buffer_ids = HashSet::default();
10593 let snapshot = self.buffer().read(cx).snapshot(cx);
10594 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10595 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10596 }
10597
10598 let buffer = self.buffer().read(cx);
10599 let ranges = buffer_ids
10600 .into_iter()
10601 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10602 .collect::<Vec<_>>();
10603
10604 self.restore_hunks_in_ranges(ranges, window, cx);
10605 }
10606
10607 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10609 let selections = self
10610 .selections
10611 .all(&self.display_snapshot(cx))
10612 .into_iter()
10613 .map(|s| s.range())
10614 .collect();
10615 self.restore_hunks_in_ranges(selections, window, cx);
10616 }
10617
10618 pub fn restore_hunks_in_ranges(
10619 &mut self,
10620 ranges: Vec<Range<Point>>,
10621 window: &mut Window,
10622 cx: &mut Context<Editor>,
10623 ) {
10624 let mut revert_changes = HashMap::default();
10625 let chunk_by = self
10626 .snapshot(window, cx)
10627 .hunks_for_ranges(ranges)
10628 .into_iter()
10629 .chunk_by(|hunk| hunk.buffer_id);
10630 for (buffer_id, hunks) in &chunk_by {
10631 let hunks = hunks.collect::<Vec<_>>();
10632 for hunk in &hunks {
10633 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10634 }
10635 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10636 }
10637 drop(chunk_by);
10638 if !revert_changes.is_empty() {
10639 self.transact(window, cx, |editor, window, cx| {
10640 editor.restore(revert_changes, window, cx);
10641 });
10642 }
10643 }
10644
10645 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10646 if let Some(status) = self
10647 .addons
10648 .iter()
10649 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10650 {
10651 return Some(status);
10652 }
10653 self.project
10654 .as_ref()?
10655 .read(cx)
10656 .status_for_buffer_id(buffer_id, cx)
10657 }
10658
10659 pub fn open_active_item_in_terminal(
10660 &mut self,
10661 _: &OpenInTerminal,
10662 window: &mut Window,
10663 cx: &mut Context<Self>,
10664 ) {
10665 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10666 let project_path = buffer.read(cx).project_path(cx)?;
10667 let project = self.project()?.read(cx);
10668 let entry = project.entry_for_path(&project_path, cx)?;
10669 let parent = match &entry.canonical_path {
10670 Some(canonical_path) => canonical_path.to_path_buf(),
10671 None => project.absolute_path(&project_path, cx)?,
10672 }
10673 .parent()?
10674 .to_path_buf();
10675 Some(parent)
10676 }) {
10677 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10678 }
10679 }
10680
10681 fn set_breakpoint_context_menu(
10682 &mut self,
10683 display_row: DisplayRow,
10684 position: Option<Anchor>,
10685 clicked_point: gpui::Point<Pixels>,
10686 window: &mut Window,
10687 cx: &mut Context<Self>,
10688 ) {
10689 let source = self
10690 .buffer
10691 .read(cx)
10692 .snapshot(cx)
10693 .anchor_before(Point::new(display_row.0, 0u32));
10694
10695 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10696
10697 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10698 self,
10699 source,
10700 clicked_point,
10701 context_menu,
10702 window,
10703 cx,
10704 );
10705 }
10706
10707 fn add_edit_breakpoint_block(
10708 &mut self,
10709 anchor: Anchor,
10710 breakpoint: &Breakpoint,
10711 edit_action: BreakpointPromptEditAction,
10712 window: &mut Window,
10713 cx: &mut Context<Self>,
10714 ) {
10715 let weak_editor = cx.weak_entity();
10716 let bp_prompt = cx.new(|cx| {
10717 BreakpointPromptEditor::new(
10718 weak_editor,
10719 anchor,
10720 breakpoint.clone(),
10721 edit_action,
10722 window,
10723 cx,
10724 )
10725 });
10726
10727 let height = bp_prompt.update(cx, |this, cx| {
10728 this.prompt
10729 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10730 });
10731 let cloned_prompt = bp_prompt.clone();
10732 let blocks = vec![BlockProperties {
10733 style: BlockStyle::Sticky,
10734 placement: BlockPlacement::Above(anchor),
10735 height: Some(height),
10736 render: Arc::new(move |cx| {
10737 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10738 cloned_prompt.clone().into_any_element()
10739 }),
10740 priority: 0,
10741 }];
10742
10743 let focus_handle = bp_prompt.focus_handle(cx);
10744 window.focus(&focus_handle);
10745
10746 let block_ids = self.insert_blocks(blocks, None, cx);
10747 bp_prompt.update(cx, |prompt, _| {
10748 prompt.add_block_ids(block_ids);
10749 });
10750 }
10751
10752 pub(crate) fn breakpoint_at_row(
10753 &self,
10754 row: u32,
10755 window: &mut Window,
10756 cx: &mut Context<Self>,
10757 ) -> Option<(Anchor, Breakpoint)> {
10758 let snapshot = self.snapshot(window, cx);
10759 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10760
10761 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10762 }
10763
10764 pub(crate) fn breakpoint_at_anchor(
10765 &self,
10766 breakpoint_position: Anchor,
10767 snapshot: &EditorSnapshot,
10768 cx: &mut Context<Self>,
10769 ) -> Option<(Anchor, Breakpoint)> {
10770 let buffer = self
10771 .buffer
10772 .read(cx)
10773 .buffer_for_anchor(breakpoint_position, cx)?;
10774
10775 let enclosing_excerpt = breakpoint_position.excerpt_id;
10776 let buffer_snapshot = buffer.read(cx).snapshot();
10777
10778 let row = buffer_snapshot
10779 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10780 .row;
10781
10782 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10783 let anchor_end = snapshot
10784 .buffer_snapshot()
10785 .anchor_after(Point::new(row, line_len));
10786
10787 self.breakpoint_store
10788 .as_ref()?
10789 .read_with(cx, |breakpoint_store, cx| {
10790 breakpoint_store
10791 .breakpoints(
10792 &buffer,
10793 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10794 &buffer_snapshot,
10795 cx,
10796 )
10797 .next()
10798 .and_then(|(bp, _)| {
10799 let breakpoint_row = buffer_snapshot
10800 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10801 .row;
10802
10803 if breakpoint_row == row {
10804 snapshot
10805 .buffer_snapshot()
10806 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10807 .map(|position| (position, bp.bp.clone()))
10808 } else {
10809 None
10810 }
10811 })
10812 })
10813 }
10814
10815 pub fn edit_log_breakpoint(
10816 &mut self,
10817 _: &EditLogBreakpoint,
10818 window: &mut Window,
10819 cx: &mut Context<Self>,
10820 ) {
10821 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10822 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10823 message: None,
10824 state: BreakpointState::Enabled,
10825 condition: None,
10826 hit_condition: None,
10827 });
10828
10829 self.add_edit_breakpoint_block(
10830 anchor,
10831 &breakpoint,
10832 BreakpointPromptEditAction::Log,
10833 window,
10834 cx,
10835 );
10836 }
10837 }
10838
10839 fn breakpoints_at_cursors(
10840 &self,
10841 window: &mut Window,
10842 cx: &mut Context<Self>,
10843 ) -> Vec<(Anchor, Option<Breakpoint>)> {
10844 let snapshot = self.snapshot(window, cx);
10845 let cursors = self
10846 .selections
10847 .disjoint_anchors_arc()
10848 .iter()
10849 .map(|selection| {
10850 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
10851
10852 let breakpoint_position = self
10853 .breakpoint_at_row(cursor_position.row, window, cx)
10854 .map(|bp| bp.0)
10855 .unwrap_or_else(|| {
10856 snapshot
10857 .display_snapshot
10858 .buffer_snapshot()
10859 .anchor_after(Point::new(cursor_position.row, 0))
10860 });
10861
10862 let breakpoint = self
10863 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10864 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
10865
10866 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
10867 })
10868 // 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.
10869 .collect::<HashMap<Anchor, _>>();
10870
10871 cursors.into_iter().collect()
10872 }
10873
10874 pub fn enable_breakpoint(
10875 &mut self,
10876 _: &crate::actions::EnableBreakpoint,
10877 window: &mut Window,
10878 cx: &mut Context<Self>,
10879 ) {
10880 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10881 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
10882 continue;
10883 };
10884 self.edit_breakpoint_at_anchor(
10885 anchor,
10886 breakpoint,
10887 BreakpointEditAction::InvertState,
10888 cx,
10889 );
10890 }
10891 }
10892
10893 pub fn disable_breakpoint(
10894 &mut self,
10895 _: &crate::actions::DisableBreakpoint,
10896 window: &mut Window,
10897 cx: &mut Context<Self>,
10898 ) {
10899 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10900 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
10901 continue;
10902 };
10903 self.edit_breakpoint_at_anchor(
10904 anchor,
10905 breakpoint,
10906 BreakpointEditAction::InvertState,
10907 cx,
10908 );
10909 }
10910 }
10911
10912 pub fn toggle_breakpoint(
10913 &mut self,
10914 _: &crate::actions::ToggleBreakpoint,
10915 window: &mut Window,
10916 cx: &mut Context<Self>,
10917 ) {
10918 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10919 if let Some(breakpoint) = breakpoint {
10920 self.edit_breakpoint_at_anchor(
10921 anchor,
10922 breakpoint,
10923 BreakpointEditAction::Toggle,
10924 cx,
10925 );
10926 } else {
10927 self.edit_breakpoint_at_anchor(
10928 anchor,
10929 Breakpoint::new_standard(),
10930 BreakpointEditAction::Toggle,
10931 cx,
10932 );
10933 }
10934 }
10935 }
10936
10937 pub fn edit_breakpoint_at_anchor(
10938 &mut self,
10939 breakpoint_position: Anchor,
10940 breakpoint: Breakpoint,
10941 edit_action: BreakpointEditAction,
10942 cx: &mut Context<Self>,
10943 ) {
10944 let Some(breakpoint_store) = &self.breakpoint_store else {
10945 return;
10946 };
10947
10948 let Some(buffer) = self
10949 .buffer
10950 .read(cx)
10951 .buffer_for_anchor(breakpoint_position, cx)
10952 else {
10953 return;
10954 };
10955
10956 breakpoint_store.update(cx, |breakpoint_store, cx| {
10957 breakpoint_store.toggle_breakpoint(
10958 buffer,
10959 BreakpointWithPosition {
10960 position: breakpoint_position.text_anchor,
10961 bp: breakpoint,
10962 },
10963 edit_action,
10964 cx,
10965 );
10966 });
10967
10968 cx.notify();
10969 }
10970
10971 #[cfg(any(test, feature = "test-support"))]
10972 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
10973 self.breakpoint_store.clone()
10974 }
10975
10976 pub fn prepare_restore_change(
10977 &self,
10978 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
10979 hunk: &MultiBufferDiffHunk,
10980 cx: &mut App,
10981 ) -> Option<()> {
10982 if hunk.is_created_file() {
10983 return None;
10984 }
10985 let buffer = self.buffer.read(cx);
10986 let diff = buffer.diff_for(hunk.buffer_id)?;
10987 let buffer = buffer.buffer(hunk.buffer_id)?;
10988 let buffer = buffer.read(cx);
10989 let original_text = diff
10990 .read(cx)
10991 .base_text()
10992 .as_rope()
10993 .slice(hunk.diff_base_byte_range.clone());
10994 let buffer_snapshot = buffer.snapshot();
10995 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
10996 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
10997 probe
10998 .0
10999 .start
11000 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11001 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11002 }) {
11003 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11004 Some(())
11005 } else {
11006 None
11007 }
11008 }
11009
11010 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11011 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11012 }
11013
11014 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11015 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11016 }
11017
11018 fn manipulate_lines<M>(
11019 &mut self,
11020 window: &mut Window,
11021 cx: &mut Context<Self>,
11022 mut manipulate: M,
11023 ) where
11024 M: FnMut(&str) -> LineManipulationResult,
11025 {
11026 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11027
11028 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11029 let buffer = self.buffer.read(cx).snapshot(cx);
11030
11031 let mut edits = Vec::new();
11032
11033 let selections = self.selections.all::<Point>(&display_map);
11034 let mut selections = selections.iter().peekable();
11035 let mut contiguous_row_selections = Vec::new();
11036 let mut new_selections = Vec::new();
11037 let mut added_lines = 0;
11038 let mut removed_lines = 0;
11039
11040 while let Some(selection) = selections.next() {
11041 let (start_row, end_row) = consume_contiguous_rows(
11042 &mut contiguous_row_selections,
11043 selection,
11044 &display_map,
11045 &mut selections,
11046 );
11047
11048 let start_point = Point::new(start_row.0, 0);
11049 let end_point = Point::new(
11050 end_row.previous_row().0,
11051 buffer.line_len(end_row.previous_row()),
11052 );
11053 let text = buffer
11054 .text_for_range(start_point..end_point)
11055 .collect::<String>();
11056
11057 let LineManipulationResult {
11058 new_text,
11059 line_count_before,
11060 line_count_after,
11061 } = manipulate(&text);
11062
11063 edits.push((start_point..end_point, new_text));
11064
11065 // Selections must change based on added and removed line count
11066 let start_row =
11067 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11068 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11069 new_selections.push(Selection {
11070 id: selection.id,
11071 start: start_row,
11072 end: end_row,
11073 goal: SelectionGoal::None,
11074 reversed: selection.reversed,
11075 });
11076
11077 if line_count_after > line_count_before {
11078 added_lines += line_count_after - line_count_before;
11079 } else if line_count_before > line_count_after {
11080 removed_lines += line_count_before - line_count_after;
11081 }
11082 }
11083
11084 self.transact(window, cx, |this, window, cx| {
11085 let buffer = this.buffer.update(cx, |buffer, cx| {
11086 buffer.edit(edits, None, cx);
11087 buffer.snapshot(cx)
11088 });
11089
11090 // Recalculate offsets on newly edited buffer
11091 let new_selections = new_selections
11092 .iter()
11093 .map(|s| {
11094 let start_point = Point::new(s.start.0, 0);
11095 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11096 Selection {
11097 id: s.id,
11098 start: buffer.point_to_offset(start_point),
11099 end: buffer.point_to_offset(end_point),
11100 goal: s.goal,
11101 reversed: s.reversed,
11102 }
11103 })
11104 .collect();
11105
11106 this.change_selections(Default::default(), window, cx, |s| {
11107 s.select(new_selections);
11108 });
11109
11110 this.request_autoscroll(Autoscroll::fit(), cx);
11111 });
11112 }
11113
11114 fn manipulate_immutable_lines<Fn>(
11115 &mut self,
11116 window: &mut Window,
11117 cx: &mut Context<Self>,
11118 mut callback: Fn,
11119 ) where
11120 Fn: FnMut(&mut Vec<&str>),
11121 {
11122 self.manipulate_lines(window, cx, |text| {
11123 let mut lines: Vec<&str> = text.split('\n').collect();
11124 let line_count_before = lines.len();
11125
11126 callback(&mut lines);
11127
11128 LineManipulationResult {
11129 new_text: lines.join("\n"),
11130 line_count_before,
11131 line_count_after: lines.len(),
11132 }
11133 });
11134 }
11135
11136 fn manipulate_mutable_lines<Fn>(
11137 &mut self,
11138 window: &mut Window,
11139 cx: &mut Context<Self>,
11140 mut callback: Fn,
11141 ) where
11142 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11143 {
11144 self.manipulate_lines(window, cx, |text| {
11145 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11146 let line_count_before = lines.len();
11147
11148 callback(&mut lines);
11149
11150 LineManipulationResult {
11151 new_text: lines.join("\n"),
11152 line_count_before,
11153 line_count_after: lines.len(),
11154 }
11155 });
11156 }
11157
11158 pub fn convert_indentation_to_spaces(
11159 &mut self,
11160 _: &ConvertIndentationToSpaces,
11161 window: &mut Window,
11162 cx: &mut Context<Self>,
11163 ) {
11164 let settings = self.buffer.read(cx).language_settings(cx);
11165 let tab_size = settings.tab_size.get() as usize;
11166
11167 self.manipulate_mutable_lines(window, cx, |lines| {
11168 // Allocates a reasonably sized scratch buffer once for the whole loop
11169 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11170 // Avoids recomputing spaces that could be inserted many times
11171 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11172 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11173 .collect();
11174
11175 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11176 let mut chars = line.as_ref().chars();
11177 let mut col = 0;
11178 let mut changed = false;
11179
11180 for ch in chars.by_ref() {
11181 match ch {
11182 ' ' => {
11183 reindented_line.push(' ');
11184 col += 1;
11185 }
11186 '\t' => {
11187 // \t are converted to spaces depending on the current column
11188 let spaces_len = tab_size - (col % tab_size);
11189 reindented_line.extend(&space_cache[spaces_len - 1]);
11190 col += spaces_len;
11191 changed = true;
11192 }
11193 _ => {
11194 // If we dont append before break, the character is consumed
11195 reindented_line.push(ch);
11196 break;
11197 }
11198 }
11199 }
11200
11201 if !changed {
11202 reindented_line.clear();
11203 continue;
11204 }
11205 // Append the rest of the line and replace old reference with new one
11206 reindented_line.extend(chars);
11207 *line = Cow::Owned(reindented_line.clone());
11208 reindented_line.clear();
11209 }
11210 });
11211 }
11212
11213 pub fn convert_indentation_to_tabs(
11214 &mut self,
11215 _: &ConvertIndentationToTabs,
11216 window: &mut Window,
11217 cx: &mut Context<Self>,
11218 ) {
11219 let settings = self.buffer.read(cx).language_settings(cx);
11220 let tab_size = settings.tab_size.get() as usize;
11221
11222 self.manipulate_mutable_lines(window, cx, |lines| {
11223 // Allocates a reasonably sized buffer once for the whole loop
11224 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11225 // Avoids recomputing spaces that could be inserted many times
11226 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11227 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11228 .collect();
11229
11230 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11231 let mut chars = line.chars();
11232 let mut spaces_count = 0;
11233 let mut first_non_indent_char = None;
11234 let mut changed = false;
11235
11236 for ch in chars.by_ref() {
11237 match ch {
11238 ' ' => {
11239 // Keep track of spaces. Append \t when we reach tab_size
11240 spaces_count += 1;
11241 changed = true;
11242 if spaces_count == tab_size {
11243 reindented_line.push('\t');
11244 spaces_count = 0;
11245 }
11246 }
11247 '\t' => {
11248 reindented_line.push('\t');
11249 spaces_count = 0;
11250 }
11251 _ => {
11252 // Dont append it yet, we might have remaining spaces
11253 first_non_indent_char = Some(ch);
11254 break;
11255 }
11256 }
11257 }
11258
11259 if !changed {
11260 reindented_line.clear();
11261 continue;
11262 }
11263 // Remaining spaces that didn't make a full tab stop
11264 if spaces_count > 0 {
11265 reindented_line.extend(&space_cache[spaces_count - 1]);
11266 }
11267 // If we consume an extra character that was not indentation, add it back
11268 if let Some(extra_char) = first_non_indent_char {
11269 reindented_line.push(extra_char);
11270 }
11271 // Append the rest of the line and replace old reference with new one
11272 reindented_line.extend(chars);
11273 *line = Cow::Owned(reindented_line.clone());
11274 reindented_line.clear();
11275 }
11276 });
11277 }
11278
11279 pub fn convert_to_upper_case(
11280 &mut self,
11281 _: &ConvertToUpperCase,
11282 window: &mut Window,
11283 cx: &mut Context<Self>,
11284 ) {
11285 self.manipulate_text(window, cx, |text| text.to_uppercase())
11286 }
11287
11288 pub fn convert_to_lower_case(
11289 &mut self,
11290 _: &ConvertToLowerCase,
11291 window: &mut Window,
11292 cx: &mut Context<Self>,
11293 ) {
11294 self.manipulate_text(window, cx, |text| text.to_lowercase())
11295 }
11296
11297 pub fn convert_to_title_case(
11298 &mut self,
11299 _: &ConvertToTitleCase,
11300 window: &mut Window,
11301 cx: &mut Context<Self>,
11302 ) {
11303 self.manipulate_text(window, cx, |text| {
11304 text.split('\n')
11305 .map(|line| line.to_case(Case::Title))
11306 .join("\n")
11307 })
11308 }
11309
11310 pub fn convert_to_snake_case(
11311 &mut self,
11312 _: &ConvertToSnakeCase,
11313 window: &mut Window,
11314 cx: &mut Context<Self>,
11315 ) {
11316 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11317 }
11318
11319 pub fn convert_to_kebab_case(
11320 &mut self,
11321 _: &ConvertToKebabCase,
11322 window: &mut Window,
11323 cx: &mut Context<Self>,
11324 ) {
11325 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11326 }
11327
11328 pub fn convert_to_upper_camel_case(
11329 &mut self,
11330 _: &ConvertToUpperCamelCase,
11331 window: &mut Window,
11332 cx: &mut Context<Self>,
11333 ) {
11334 self.manipulate_text(window, cx, |text| {
11335 text.split('\n')
11336 .map(|line| line.to_case(Case::UpperCamel))
11337 .join("\n")
11338 })
11339 }
11340
11341 pub fn convert_to_lower_camel_case(
11342 &mut self,
11343 _: &ConvertToLowerCamelCase,
11344 window: &mut Window,
11345 cx: &mut Context<Self>,
11346 ) {
11347 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11348 }
11349
11350 pub fn convert_to_opposite_case(
11351 &mut self,
11352 _: &ConvertToOppositeCase,
11353 window: &mut Window,
11354 cx: &mut Context<Self>,
11355 ) {
11356 self.manipulate_text(window, cx, |text| {
11357 text.chars()
11358 .fold(String::with_capacity(text.len()), |mut t, c| {
11359 if c.is_uppercase() {
11360 t.extend(c.to_lowercase());
11361 } else {
11362 t.extend(c.to_uppercase());
11363 }
11364 t
11365 })
11366 })
11367 }
11368
11369 pub fn convert_to_sentence_case(
11370 &mut self,
11371 _: &ConvertToSentenceCase,
11372 window: &mut Window,
11373 cx: &mut Context<Self>,
11374 ) {
11375 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11376 }
11377
11378 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11379 self.manipulate_text(window, cx, |text| {
11380 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11381 if has_upper_case_characters {
11382 text.to_lowercase()
11383 } else {
11384 text.to_uppercase()
11385 }
11386 })
11387 }
11388
11389 pub fn convert_to_rot13(
11390 &mut self,
11391 _: &ConvertToRot13,
11392 window: &mut Window,
11393 cx: &mut Context<Self>,
11394 ) {
11395 self.manipulate_text(window, cx, |text| {
11396 text.chars()
11397 .map(|c| match c {
11398 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11399 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11400 _ => c,
11401 })
11402 .collect()
11403 })
11404 }
11405
11406 pub fn convert_to_rot47(
11407 &mut self,
11408 _: &ConvertToRot47,
11409 window: &mut Window,
11410 cx: &mut Context<Self>,
11411 ) {
11412 self.manipulate_text(window, cx, |text| {
11413 text.chars()
11414 .map(|c| {
11415 let code_point = c as u32;
11416 if code_point >= 33 && code_point <= 126 {
11417 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11418 }
11419 c
11420 })
11421 .collect()
11422 })
11423 }
11424
11425 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11426 where
11427 Fn: FnMut(&str) -> String,
11428 {
11429 let buffer = self.buffer.read(cx).snapshot(cx);
11430
11431 let mut new_selections = Vec::new();
11432 let mut edits = Vec::new();
11433 let mut selection_adjustment = 0i32;
11434
11435 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11436 let selection_is_empty = selection.is_empty();
11437
11438 let (start, end) = if selection_is_empty {
11439 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11440 (word_range.start, word_range.end)
11441 } else {
11442 (
11443 buffer.point_to_offset(selection.start),
11444 buffer.point_to_offset(selection.end),
11445 )
11446 };
11447
11448 let text = buffer.text_for_range(start..end).collect::<String>();
11449 let old_length = text.len() as i32;
11450 let text = callback(&text);
11451
11452 new_selections.push(Selection {
11453 start: (start as i32 - selection_adjustment) as usize,
11454 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11455 goal: SelectionGoal::None,
11456 id: selection.id,
11457 reversed: selection.reversed,
11458 });
11459
11460 selection_adjustment += old_length - text.len() as i32;
11461
11462 edits.push((start..end, text));
11463 }
11464
11465 self.transact(window, cx, |this, window, cx| {
11466 this.buffer.update(cx, |buffer, cx| {
11467 buffer.edit(edits, None, cx);
11468 });
11469
11470 this.change_selections(Default::default(), window, cx, |s| {
11471 s.select(new_selections);
11472 });
11473
11474 this.request_autoscroll(Autoscroll::fit(), cx);
11475 });
11476 }
11477
11478 pub fn move_selection_on_drop(
11479 &mut self,
11480 selection: &Selection<Anchor>,
11481 target: DisplayPoint,
11482 is_cut: bool,
11483 window: &mut Window,
11484 cx: &mut Context<Self>,
11485 ) {
11486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11487 let buffer = display_map.buffer_snapshot();
11488 let mut edits = Vec::new();
11489 let insert_point = display_map
11490 .clip_point(target, Bias::Left)
11491 .to_point(&display_map);
11492 let text = buffer
11493 .text_for_range(selection.start..selection.end)
11494 .collect::<String>();
11495 if is_cut {
11496 edits.push(((selection.start..selection.end), String::new()));
11497 }
11498 let insert_anchor = buffer.anchor_before(insert_point);
11499 edits.push(((insert_anchor..insert_anchor), text));
11500 let last_edit_start = insert_anchor.bias_left(buffer);
11501 let last_edit_end = insert_anchor.bias_right(buffer);
11502 self.transact(window, cx, |this, window, cx| {
11503 this.buffer.update(cx, |buffer, cx| {
11504 buffer.edit(edits, None, cx);
11505 });
11506 this.change_selections(Default::default(), window, cx, |s| {
11507 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11508 });
11509 });
11510 }
11511
11512 pub fn clear_selection_drag_state(&mut self) {
11513 self.selection_drag_state = SelectionDragState::None;
11514 }
11515
11516 pub fn duplicate(
11517 &mut self,
11518 upwards: bool,
11519 whole_lines: bool,
11520 window: &mut Window,
11521 cx: &mut Context<Self>,
11522 ) {
11523 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11524
11525 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11526 let buffer = display_map.buffer_snapshot();
11527 let selections = self.selections.all::<Point>(&display_map);
11528
11529 let mut edits = Vec::new();
11530 let mut selections_iter = selections.iter().peekable();
11531 while let Some(selection) = selections_iter.next() {
11532 let mut rows = selection.spanned_rows(false, &display_map);
11533 // duplicate line-wise
11534 if whole_lines || selection.start == selection.end {
11535 // Avoid duplicating the same lines twice.
11536 while let Some(next_selection) = selections_iter.peek() {
11537 let next_rows = next_selection.spanned_rows(false, &display_map);
11538 if next_rows.start < rows.end {
11539 rows.end = next_rows.end;
11540 selections_iter.next().unwrap();
11541 } else {
11542 break;
11543 }
11544 }
11545
11546 // Copy the text from the selected row region and splice it either at the start
11547 // or end of the region.
11548 let start = Point::new(rows.start.0, 0);
11549 let end = Point::new(
11550 rows.end.previous_row().0,
11551 buffer.line_len(rows.end.previous_row()),
11552 );
11553
11554 let mut text = buffer.text_for_range(start..end).collect::<String>();
11555
11556 let insert_location = if upwards {
11557 // When duplicating upward, we need to insert before the current line.
11558 // If we're on the last line and it doesn't end with a newline,
11559 // we need to add a newline before the duplicated content.
11560 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11561 && buffer.max_point().column > 0
11562 && !text.ends_with('\n');
11563
11564 if needs_leading_newline {
11565 text.insert(0, '\n');
11566 end
11567 } else {
11568 text.push('\n');
11569 Point::new(rows.start.0, 0)
11570 }
11571 } else {
11572 text.push('\n');
11573 start
11574 };
11575 edits.push((insert_location..insert_location, text));
11576 } else {
11577 // duplicate character-wise
11578 let start = selection.start;
11579 let end = selection.end;
11580 let text = buffer.text_for_range(start..end).collect::<String>();
11581 edits.push((selection.end..selection.end, text));
11582 }
11583 }
11584
11585 self.transact(window, cx, |this, window, cx| {
11586 this.buffer.update(cx, |buffer, cx| {
11587 buffer.edit(edits, None, cx);
11588 });
11589
11590 // When duplicating upward with whole lines, move the cursor to the duplicated line
11591 if upwards && whole_lines {
11592 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11593
11594 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11595 let mut new_ranges = Vec::new();
11596 let selections = s.all::<Point>(&display_map);
11597 let mut selections_iter = selections.iter().peekable();
11598
11599 while let Some(first_selection) = selections_iter.next() {
11600 // Group contiguous selections together to find the total row span
11601 let mut group_selections = vec![first_selection];
11602 let mut rows = first_selection.spanned_rows(false, &display_map);
11603
11604 while let Some(next_selection) = selections_iter.peek() {
11605 let next_rows = next_selection.spanned_rows(false, &display_map);
11606 if next_rows.start < rows.end {
11607 rows.end = next_rows.end;
11608 group_selections.push(selections_iter.next().unwrap());
11609 } else {
11610 break;
11611 }
11612 }
11613
11614 let row_count = rows.end.0 - rows.start.0;
11615
11616 // Move all selections in this group up by the total number of duplicated rows
11617 for selection in group_selections {
11618 let new_start = Point::new(
11619 selection.start.row.saturating_sub(row_count),
11620 selection.start.column,
11621 );
11622
11623 let new_end = Point::new(
11624 selection.end.row.saturating_sub(row_count),
11625 selection.end.column,
11626 );
11627
11628 new_ranges.push(new_start..new_end);
11629 }
11630 }
11631
11632 s.select_ranges(new_ranges);
11633 });
11634 }
11635
11636 this.request_autoscroll(Autoscroll::fit(), cx);
11637 });
11638 }
11639
11640 pub fn duplicate_line_up(
11641 &mut self,
11642 _: &DuplicateLineUp,
11643 window: &mut Window,
11644 cx: &mut Context<Self>,
11645 ) {
11646 self.duplicate(true, true, window, cx);
11647 }
11648
11649 pub fn duplicate_line_down(
11650 &mut self,
11651 _: &DuplicateLineDown,
11652 window: &mut Window,
11653 cx: &mut Context<Self>,
11654 ) {
11655 self.duplicate(false, true, window, cx);
11656 }
11657
11658 pub fn duplicate_selection(
11659 &mut self,
11660 _: &DuplicateSelection,
11661 window: &mut Window,
11662 cx: &mut Context<Self>,
11663 ) {
11664 self.duplicate(false, false, window, cx);
11665 }
11666
11667 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11669 if self.mode.is_single_line() {
11670 cx.propagate();
11671 return;
11672 }
11673
11674 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11675 let buffer = self.buffer.read(cx).snapshot(cx);
11676
11677 let mut edits = Vec::new();
11678 let mut unfold_ranges = Vec::new();
11679 let mut refold_creases = Vec::new();
11680
11681 let selections = self.selections.all::<Point>(&display_map);
11682 let mut selections = selections.iter().peekable();
11683 let mut contiguous_row_selections = Vec::new();
11684 let mut new_selections = Vec::new();
11685
11686 while let Some(selection) = selections.next() {
11687 // Find all the selections that span a contiguous row range
11688 let (start_row, end_row) = consume_contiguous_rows(
11689 &mut contiguous_row_selections,
11690 selection,
11691 &display_map,
11692 &mut selections,
11693 );
11694
11695 // Move the text spanned by the row range to be before the line preceding the row range
11696 if start_row.0 > 0 {
11697 let range_to_move = Point::new(
11698 start_row.previous_row().0,
11699 buffer.line_len(start_row.previous_row()),
11700 )
11701 ..Point::new(
11702 end_row.previous_row().0,
11703 buffer.line_len(end_row.previous_row()),
11704 );
11705 let insertion_point = display_map
11706 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11707 .0;
11708
11709 // Don't move lines across excerpts
11710 if buffer
11711 .excerpt_containing(insertion_point..range_to_move.end)
11712 .is_some()
11713 {
11714 let text = buffer
11715 .text_for_range(range_to_move.clone())
11716 .flat_map(|s| s.chars())
11717 .skip(1)
11718 .chain(['\n'])
11719 .collect::<String>();
11720
11721 edits.push((
11722 buffer.anchor_after(range_to_move.start)
11723 ..buffer.anchor_before(range_to_move.end),
11724 String::new(),
11725 ));
11726 let insertion_anchor = buffer.anchor_after(insertion_point);
11727 edits.push((insertion_anchor..insertion_anchor, text));
11728
11729 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11730
11731 // Move selections up
11732 new_selections.extend(contiguous_row_selections.drain(..).map(
11733 |mut selection| {
11734 selection.start.row -= row_delta;
11735 selection.end.row -= row_delta;
11736 selection
11737 },
11738 ));
11739
11740 // Move folds up
11741 unfold_ranges.push(range_to_move.clone());
11742 for fold in display_map.folds_in_range(
11743 buffer.anchor_before(range_to_move.start)
11744 ..buffer.anchor_after(range_to_move.end),
11745 ) {
11746 let mut start = fold.range.start.to_point(&buffer);
11747 let mut end = fold.range.end.to_point(&buffer);
11748 start.row -= row_delta;
11749 end.row -= row_delta;
11750 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11751 }
11752 }
11753 }
11754
11755 // If we didn't move line(s), preserve the existing selections
11756 new_selections.append(&mut contiguous_row_selections);
11757 }
11758
11759 self.transact(window, cx, |this, window, cx| {
11760 this.unfold_ranges(&unfold_ranges, true, true, cx);
11761 this.buffer.update(cx, |buffer, cx| {
11762 for (range, text) in edits {
11763 buffer.edit([(range, text)], None, cx);
11764 }
11765 });
11766 this.fold_creases(refold_creases, true, window, cx);
11767 this.change_selections(Default::default(), window, cx, |s| {
11768 s.select(new_selections);
11769 })
11770 });
11771 }
11772
11773 pub fn move_line_down(
11774 &mut self,
11775 _: &MoveLineDown,
11776 window: &mut Window,
11777 cx: &mut Context<Self>,
11778 ) {
11779 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11780 if self.mode.is_single_line() {
11781 cx.propagate();
11782 return;
11783 }
11784
11785 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11786 let buffer = self.buffer.read(cx).snapshot(cx);
11787
11788 let mut edits = Vec::new();
11789 let mut unfold_ranges = Vec::new();
11790 let mut refold_creases = Vec::new();
11791
11792 let selections = self.selections.all::<Point>(&display_map);
11793 let mut selections = selections.iter().peekable();
11794 let mut contiguous_row_selections = Vec::new();
11795 let mut new_selections = Vec::new();
11796
11797 while let Some(selection) = selections.next() {
11798 // Find all the selections that span a contiguous row range
11799 let (start_row, end_row) = consume_contiguous_rows(
11800 &mut contiguous_row_selections,
11801 selection,
11802 &display_map,
11803 &mut selections,
11804 );
11805
11806 // Move the text spanned by the row range to be after the last line of the row range
11807 if end_row.0 <= buffer.max_point().row {
11808 let range_to_move =
11809 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11810 let insertion_point = display_map
11811 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11812 .0;
11813
11814 // Don't move lines across excerpt boundaries
11815 if buffer
11816 .excerpt_containing(range_to_move.start..insertion_point)
11817 .is_some()
11818 {
11819 let mut text = String::from("\n");
11820 text.extend(buffer.text_for_range(range_to_move.clone()));
11821 text.pop(); // Drop trailing newline
11822 edits.push((
11823 buffer.anchor_after(range_to_move.start)
11824 ..buffer.anchor_before(range_to_move.end),
11825 String::new(),
11826 ));
11827 let insertion_anchor = buffer.anchor_after(insertion_point);
11828 edits.push((insertion_anchor..insertion_anchor, text));
11829
11830 let row_delta = insertion_point.row - range_to_move.end.row + 1;
11831
11832 // Move selections down
11833 new_selections.extend(contiguous_row_selections.drain(..).map(
11834 |mut selection| {
11835 selection.start.row += row_delta;
11836 selection.end.row += row_delta;
11837 selection
11838 },
11839 ));
11840
11841 // Move folds down
11842 unfold_ranges.push(range_to_move.clone());
11843 for fold in display_map.folds_in_range(
11844 buffer.anchor_before(range_to_move.start)
11845 ..buffer.anchor_after(range_to_move.end),
11846 ) {
11847 let mut start = fold.range.start.to_point(&buffer);
11848 let mut end = fold.range.end.to_point(&buffer);
11849 start.row += row_delta;
11850 end.row += row_delta;
11851 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11852 }
11853 }
11854 }
11855
11856 // If we didn't move line(s), preserve the existing selections
11857 new_selections.append(&mut contiguous_row_selections);
11858 }
11859
11860 self.transact(window, cx, |this, window, cx| {
11861 this.unfold_ranges(&unfold_ranges, true, true, cx);
11862 this.buffer.update(cx, |buffer, cx| {
11863 for (range, text) in edits {
11864 buffer.edit([(range, text)], None, cx);
11865 }
11866 });
11867 this.fold_creases(refold_creases, true, window, cx);
11868 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
11869 });
11870 }
11871
11872 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
11873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11874 let text_layout_details = &self.text_layout_details(window);
11875 self.transact(window, cx, |this, window, cx| {
11876 let edits = this.change_selections(Default::default(), window, cx, |s| {
11877 let mut edits: Vec<(Range<usize>, String)> = Default::default();
11878 s.move_with(|display_map, selection| {
11879 if !selection.is_empty() {
11880 return;
11881 }
11882
11883 let mut head = selection.head();
11884 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
11885 if head.column() == display_map.line_len(head.row()) {
11886 transpose_offset = display_map
11887 .buffer_snapshot()
11888 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11889 }
11890
11891 if transpose_offset == 0 {
11892 return;
11893 }
11894
11895 *head.column_mut() += 1;
11896 head = display_map.clip_point(head, Bias::Right);
11897 let goal = SelectionGoal::HorizontalPosition(
11898 display_map
11899 .x_for_display_point(head, text_layout_details)
11900 .into(),
11901 );
11902 selection.collapse_to(head, goal);
11903
11904 let transpose_start = display_map
11905 .buffer_snapshot()
11906 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
11907 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
11908 let transpose_end = display_map
11909 .buffer_snapshot()
11910 .clip_offset(transpose_offset + 1, Bias::Right);
11911 if let Some(ch) = display_map
11912 .buffer_snapshot()
11913 .chars_at(transpose_start)
11914 .next()
11915 {
11916 edits.push((transpose_start..transpose_offset, String::new()));
11917 edits.push((transpose_end..transpose_end, ch.to_string()));
11918 }
11919 }
11920 });
11921 edits
11922 });
11923 this.buffer
11924 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
11925 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
11926 this.change_selections(Default::default(), window, cx, |s| {
11927 s.select(selections);
11928 });
11929 });
11930 }
11931
11932 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
11933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11934 if self.mode.is_single_line() {
11935 cx.propagate();
11936 return;
11937 }
11938
11939 self.rewrap_impl(RewrapOptions::default(), cx)
11940 }
11941
11942 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
11943 let buffer = self.buffer.read(cx).snapshot(cx);
11944 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
11945
11946 #[derive(Clone, Debug, PartialEq)]
11947 enum CommentFormat {
11948 /// single line comment, with prefix for line
11949 Line(String),
11950 /// single line within a block comment, with prefix for line
11951 BlockLine(String),
11952 /// a single line of a block comment that includes the initial delimiter
11953 BlockCommentWithStart(BlockCommentConfig),
11954 /// a single line of a block comment that includes the ending delimiter
11955 BlockCommentWithEnd(BlockCommentConfig),
11956 }
11957
11958 // Split selections to respect paragraph, indent, and comment prefix boundaries.
11959 let wrap_ranges = selections.into_iter().flat_map(|selection| {
11960 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
11961 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
11962 .peekable();
11963
11964 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
11965 row
11966 } else {
11967 return Vec::new();
11968 };
11969
11970 let language_settings = buffer.language_settings_at(selection.head(), cx);
11971 let language_scope = buffer.language_scope_at(selection.head());
11972
11973 let indent_and_prefix_for_row =
11974 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
11975 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
11976 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
11977 &language_scope
11978 {
11979 let indent_end = Point::new(row, indent.len);
11980 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11981 let line_text_after_indent = buffer
11982 .text_for_range(indent_end..line_end)
11983 .collect::<String>();
11984
11985 let is_within_comment_override = buffer
11986 .language_scope_at(indent_end)
11987 .is_some_and(|scope| scope.override_name() == Some("comment"));
11988 let comment_delimiters = if is_within_comment_override {
11989 // we are within a comment syntax node, but we don't
11990 // yet know what kind of comment: block, doc or line
11991 match (
11992 language_scope.documentation_comment(),
11993 language_scope.block_comment(),
11994 ) {
11995 (Some(config), _) | (_, Some(config))
11996 if buffer.contains_str_at(indent_end, &config.start) =>
11997 {
11998 Some(CommentFormat::BlockCommentWithStart(config.clone()))
11999 }
12000 (Some(config), _) | (_, Some(config))
12001 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12002 {
12003 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12004 }
12005 (Some(config), _) | (_, Some(config))
12006 if buffer.contains_str_at(indent_end, &config.prefix) =>
12007 {
12008 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12009 }
12010 (_, _) => language_scope
12011 .line_comment_prefixes()
12012 .iter()
12013 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12014 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12015 }
12016 } else {
12017 // we not in an overridden comment node, but we may
12018 // be within a non-overridden line comment node
12019 language_scope
12020 .line_comment_prefixes()
12021 .iter()
12022 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12023 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12024 };
12025
12026 let rewrap_prefix = language_scope
12027 .rewrap_prefixes()
12028 .iter()
12029 .find_map(|prefix_regex| {
12030 prefix_regex.find(&line_text_after_indent).map(|mat| {
12031 if mat.start() == 0 {
12032 Some(mat.as_str().to_string())
12033 } else {
12034 None
12035 }
12036 })
12037 })
12038 .flatten();
12039 (comment_delimiters, rewrap_prefix)
12040 } else {
12041 (None, None)
12042 };
12043 (indent, comment_prefix, rewrap_prefix)
12044 };
12045
12046 let mut ranges = Vec::new();
12047 let from_empty_selection = selection.is_empty();
12048
12049 let mut current_range_start = first_row;
12050 let mut prev_row = first_row;
12051 let (
12052 mut current_range_indent,
12053 mut current_range_comment_delimiters,
12054 mut current_range_rewrap_prefix,
12055 ) = indent_and_prefix_for_row(first_row);
12056
12057 for row in non_blank_rows_iter.skip(1) {
12058 let has_paragraph_break = row > prev_row + 1;
12059
12060 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12061 indent_and_prefix_for_row(row);
12062
12063 let has_indent_change = row_indent != current_range_indent;
12064 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12065
12066 let has_boundary_change = has_comment_change
12067 || row_rewrap_prefix.is_some()
12068 || (has_indent_change && current_range_comment_delimiters.is_some());
12069
12070 if has_paragraph_break || has_boundary_change {
12071 ranges.push((
12072 language_settings.clone(),
12073 Point::new(current_range_start, 0)
12074 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12075 current_range_indent,
12076 current_range_comment_delimiters.clone(),
12077 current_range_rewrap_prefix.clone(),
12078 from_empty_selection,
12079 ));
12080 current_range_start = row;
12081 current_range_indent = row_indent;
12082 current_range_comment_delimiters = row_comment_delimiters;
12083 current_range_rewrap_prefix = row_rewrap_prefix;
12084 }
12085 prev_row = row;
12086 }
12087
12088 ranges.push((
12089 language_settings.clone(),
12090 Point::new(current_range_start, 0)
12091 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12092 current_range_indent,
12093 current_range_comment_delimiters,
12094 current_range_rewrap_prefix,
12095 from_empty_selection,
12096 ));
12097
12098 ranges
12099 });
12100
12101 let mut edits = Vec::new();
12102 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12103
12104 for (
12105 language_settings,
12106 wrap_range,
12107 mut indent_size,
12108 comment_prefix,
12109 rewrap_prefix,
12110 from_empty_selection,
12111 ) in wrap_ranges
12112 {
12113 let mut start_row = wrap_range.start.row;
12114 let mut end_row = wrap_range.end.row;
12115
12116 // Skip selections that overlap with a range that has already been rewrapped.
12117 let selection_range = start_row..end_row;
12118 if rewrapped_row_ranges
12119 .iter()
12120 .any(|range| range.overlaps(&selection_range))
12121 {
12122 continue;
12123 }
12124
12125 let tab_size = language_settings.tab_size;
12126
12127 let (line_prefix, inside_comment) = match &comment_prefix {
12128 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12129 (Some(prefix.as_str()), true)
12130 }
12131 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12132 (Some(prefix.as_ref()), true)
12133 }
12134 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12135 start: _,
12136 end: _,
12137 prefix,
12138 tab_size,
12139 })) => {
12140 indent_size.len += tab_size;
12141 (Some(prefix.as_ref()), true)
12142 }
12143 None => (None, false),
12144 };
12145 let indent_prefix = indent_size.chars().collect::<String>();
12146 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12147
12148 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12149 RewrapBehavior::InComments => inside_comment,
12150 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12151 RewrapBehavior::Anywhere => true,
12152 };
12153
12154 let should_rewrap = options.override_language_settings
12155 || allow_rewrap_based_on_language
12156 || self.hard_wrap.is_some();
12157 if !should_rewrap {
12158 continue;
12159 }
12160
12161 if from_empty_selection {
12162 'expand_upwards: while start_row > 0 {
12163 let prev_row = start_row - 1;
12164 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12165 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12166 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12167 {
12168 start_row = prev_row;
12169 } else {
12170 break 'expand_upwards;
12171 }
12172 }
12173
12174 'expand_downwards: while end_row < buffer.max_point().row {
12175 let next_row = end_row + 1;
12176 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12177 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12178 && !buffer.is_line_blank(MultiBufferRow(next_row))
12179 {
12180 end_row = next_row;
12181 } else {
12182 break 'expand_downwards;
12183 }
12184 }
12185 }
12186
12187 let start = Point::new(start_row, 0);
12188 let start_offset = ToOffset::to_offset(&start, &buffer);
12189 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12190 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12191 let mut first_line_delimiter = None;
12192 let mut last_line_delimiter = None;
12193 let Some(lines_without_prefixes) = selection_text
12194 .lines()
12195 .enumerate()
12196 .map(|(ix, line)| {
12197 let line_trimmed = line.trim_start();
12198 if rewrap_prefix.is_some() && ix > 0 {
12199 Ok(line_trimmed)
12200 } else if let Some(
12201 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12202 start,
12203 prefix,
12204 end,
12205 tab_size,
12206 })
12207 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12208 start,
12209 prefix,
12210 end,
12211 tab_size,
12212 }),
12213 ) = &comment_prefix
12214 {
12215 let line_trimmed = line_trimmed
12216 .strip_prefix(start.as_ref())
12217 .map(|s| {
12218 let mut indent_size = indent_size;
12219 indent_size.len -= tab_size;
12220 let indent_prefix: String = indent_size.chars().collect();
12221 first_line_delimiter = Some((indent_prefix, start));
12222 s.trim_start()
12223 })
12224 .unwrap_or(line_trimmed);
12225 let line_trimmed = line_trimmed
12226 .strip_suffix(end.as_ref())
12227 .map(|s| {
12228 last_line_delimiter = Some(end);
12229 s.trim_end()
12230 })
12231 .unwrap_or(line_trimmed);
12232 let line_trimmed = line_trimmed
12233 .strip_prefix(prefix.as_ref())
12234 .unwrap_or(line_trimmed);
12235 Ok(line_trimmed)
12236 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12237 line_trimmed.strip_prefix(prefix).with_context(|| {
12238 format!("line did not start with prefix {prefix:?}: {line:?}")
12239 })
12240 } else {
12241 line_trimmed
12242 .strip_prefix(&line_prefix.trim_start())
12243 .with_context(|| {
12244 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12245 })
12246 }
12247 })
12248 .collect::<Result<Vec<_>, _>>()
12249 .log_err()
12250 else {
12251 continue;
12252 };
12253
12254 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12255 buffer
12256 .language_settings_at(Point::new(start_row, 0), cx)
12257 .preferred_line_length as usize
12258 });
12259
12260 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12261 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12262 } else {
12263 line_prefix.clone()
12264 };
12265
12266 let wrapped_text = {
12267 let mut wrapped_text = wrap_with_prefix(
12268 line_prefix,
12269 subsequent_lines_prefix,
12270 lines_without_prefixes.join("\n"),
12271 wrap_column,
12272 tab_size,
12273 options.preserve_existing_whitespace,
12274 );
12275
12276 if let Some((indent, delimiter)) = first_line_delimiter {
12277 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12278 }
12279 if let Some(last_line) = last_line_delimiter {
12280 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12281 }
12282
12283 wrapped_text
12284 };
12285
12286 // TODO: should always use char-based diff while still supporting cursor behavior that
12287 // matches vim.
12288 let mut diff_options = DiffOptions::default();
12289 if options.override_language_settings {
12290 diff_options.max_word_diff_len = 0;
12291 diff_options.max_word_diff_line_count = 0;
12292 } else {
12293 diff_options.max_word_diff_len = usize::MAX;
12294 diff_options.max_word_diff_line_count = usize::MAX;
12295 }
12296
12297 for (old_range, new_text) in
12298 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12299 {
12300 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12301 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12302 edits.push((edit_start..edit_end, new_text));
12303 }
12304
12305 rewrapped_row_ranges.push(start_row..=end_row);
12306 }
12307
12308 self.buffer
12309 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12310 }
12311
12312 pub fn cut_common(
12313 &mut self,
12314 cut_no_selection_line: bool,
12315 window: &mut Window,
12316 cx: &mut Context<Self>,
12317 ) -> ClipboardItem {
12318 let mut text = String::new();
12319 let buffer = self.buffer.read(cx).snapshot(cx);
12320 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12321 let mut clipboard_selections = Vec::with_capacity(selections.len());
12322 {
12323 let max_point = buffer.max_point();
12324 let mut is_first = true;
12325 for selection in &mut selections {
12326 let is_entire_line =
12327 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12328 if is_entire_line {
12329 selection.start = Point::new(selection.start.row, 0);
12330 if !selection.is_empty() && selection.end.column == 0 {
12331 selection.end = cmp::min(max_point, selection.end);
12332 } else {
12333 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12334 }
12335 selection.goal = SelectionGoal::None;
12336 }
12337 if is_first {
12338 is_first = false;
12339 } else {
12340 text += "\n";
12341 }
12342 let mut len = 0;
12343 for chunk in buffer.text_for_range(selection.start..selection.end) {
12344 text.push_str(chunk);
12345 len += chunk.len();
12346 }
12347 clipboard_selections.push(ClipboardSelection {
12348 len,
12349 is_entire_line,
12350 first_line_indent: buffer
12351 .indent_size_for_line(MultiBufferRow(selection.start.row))
12352 .len,
12353 });
12354 }
12355 }
12356
12357 self.transact(window, cx, |this, window, cx| {
12358 this.change_selections(Default::default(), window, cx, |s| {
12359 s.select(selections);
12360 });
12361 this.insert("", window, cx);
12362 });
12363 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12364 }
12365
12366 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12367 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12368 let item = self.cut_common(true, window, cx);
12369 cx.write_to_clipboard(item);
12370 }
12371
12372 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12373 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12374 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12375 s.move_with(|snapshot, sel| {
12376 if sel.is_empty() {
12377 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12378 }
12379 if sel.is_empty() {
12380 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12381 }
12382 });
12383 });
12384 let item = self.cut_common(false, window, cx);
12385 cx.set_global(KillRing(item))
12386 }
12387
12388 pub fn kill_ring_yank(
12389 &mut self,
12390 _: &KillRingYank,
12391 window: &mut Window,
12392 cx: &mut Context<Self>,
12393 ) {
12394 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12395 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12396 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12397 (kill_ring.text().to_string(), kill_ring.metadata_json())
12398 } else {
12399 return;
12400 }
12401 } else {
12402 return;
12403 };
12404 self.do_paste(&text, metadata, false, window, cx);
12405 }
12406
12407 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12408 self.do_copy(true, cx);
12409 }
12410
12411 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12412 self.do_copy(false, cx);
12413 }
12414
12415 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12416 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12417 let buffer = self.buffer.read(cx).read(cx);
12418 let mut text = String::new();
12419
12420 let mut clipboard_selections = Vec::with_capacity(selections.len());
12421 {
12422 let max_point = buffer.max_point();
12423 let mut is_first = true;
12424 for selection in &selections {
12425 let mut start = selection.start;
12426 let mut end = selection.end;
12427 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12428 let mut add_trailing_newline = false;
12429 if is_entire_line {
12430 start = Point::new(start.row, 0);
12431 let next_line_start = Point::new(end.row + 1, 0);
12432 if next_line_start <= max_point {
12433 end = next_line_start;
12434 } else {
12435 // We're on the last line without a trailing newline.
12436 // Copy to the end of the line and add a newline afterwards.
12437 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12438 add_trailing_newline = true;
12439 }
12440 }
12441
12442 let mut trimmed_selections = Vec::new();
12443 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12444 let row = MultiBufferRow(start.row);
12445 let first_indent = buffer.indent_size_for_line(row);
12446 if first_indent.len == 0 || start.column > first_indent.len {
12447 trimmed_selections.push(start..end);
12448 } else {
12449 trimmed_selections.push(
12450 Point::new(row.0, first_indent.len)
12451 ..Point::new(row.0, buffer.line_len(row)),
12452 );
12453 for row in start.row + 1..=end.row {
12454 let mut line_len = buffer.line_len(MultiBufferRow(row));
12455 if row == end.row {
12456 line_len = end.column;
12457 }
12458 if line_len == 0 {
12459 trimmed_selections
12460 .push(Point::new(row, 0)..Point::new(row, line_len));
12461 continue;
12462 }
12463 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12464 if row_indent_size.len >= first_indent.len {
12465 trimmed_selections.push(
12466 Point::new(row, first_indent.len)..Point::new(row, line_len),
12467 );
12468 } else {
12469 trimmed_selections.clear();
12470 trimmed_selections.push(start..end);
12471 break;
12472 }
12473 }
12474 }
12475 } else {
12476 trimmed_selections.push(start..end);
12477 }
12478
12479 for trimmed_range in trimmed_selections {
12480 if is_first {
12481 is_first = false;
12482 } else {
12483 text += "\n";
12484 }
12485 let mut len = 0;
12486 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12487 text.push_str(chunk);
12488 len += chunk.len();
12489 }
12490 if add_trailing_newline {
12491 text.push('\n');
12492 len += 1;
12493 }
12494 clipboard_selections.push(ClipboardSelection {
12495 len,
12496 is_entire_line,
12497 first_line_indent: buffer
12498 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12499 .len,
12500 });
12501 }
12502 }
12503 }
12504
12505 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12506 text,
12507 clipboard_selections,
12508 ));
12509 }
12510
12511 pub fn do_paste(
12512 &mut self,
12513 text: &String,
12514 clipboard_selections: Option<Vec<ClipboardSelection>>,
12515 handle_entire_lines: bool,
12516 window: &mut Window,
12517 cx: &mut Context<Self>,
12518 ) {
12519 if self.read_only(cx) {
12520 return;
12521 }
12522
12523 let clipboard_text = Cow::Borrowed(text.as_str());
12524
12525 self.transact(window, cx, |this, window, cx| {
12526 let had_active_edit_prediction = this.has_active_edit_prediction();
12527 let display_map = this.display_snapshot(cx);
12528 let old_selections = this.selections.all::<usize>(&display_map);
12529 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12530
12531 if let Some(mut clipboard_selections) = clipboard_selections {
12532 let all_selections_were_entire_line =
12533 clipboard_selections.iter().all(|s| s.is_entire_line);
12534 let first_selection_indent_column =
12535 clipboard_selections.first().map(|s| s.first_line_indent);
12536 if clipboard_selections.len() != old_selections.len() {
12537 clipboard_selections.drain(..);
12538 }
12539 let mut auto_indent_on_paste = true;
12540
12541 this.buffer.update(cx, |buffer, cx| {
12542 let snapshot = buffer.read(cx);
12543 auto_indent_on_paste = snapshot
12544 .language_settings_at(cursor_offset, cx)
12545 .auto_indent_on_paste;
12546
12547 let mut start_offset = 0;
12548 let mut edits = Vec::new();
12549 let mut original_indent_columns = Vec::new();
12550 for (ix, selection) in old_selections.iter().enumerate() {
12551 let to_insert;
12552 let entire_line;
12553 let original_indent_column;
12554 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12555 let end_offset = start_offset + clipboard_selection.len;
12556 to_insert = &clipboard_text[start_offset..end_offset];
12557 entire_line = clipboard_selection.is_entire_line;
12558 start_offset = end_offset + 1;
12559 original_indent_column = Some(clipboard_selection.first_line_indent);
12560 } else {
12561 to_insert = &*clipboard_text;
12562 entire_line = all_selections_were_entire_line;
12563 original_indent_column = first_selection_indent_column
12564 }
12565
12566 let (range, to_insert) =
12567 if selection.is_empty() && handle_entire_lines && entire_line {
12568 // If the corresponding selection was empty when this slice of the
12569 // clipboard text was written, then the entire line containing the
12570 // selection was copied. If this selection is also currently empty,
12571 // then paste the line before the current line of the buffer.
12572 let column = selection.start.to_point(&snapshot).column as usize;
12573 let line_start = selection.start - column;
12574 (line_start..line_start, Cow::Borrowed(to_insert))
12575 } else {
12576 let language = snapshot.language_at(selection.head());
12577 let range = selection.range();
12578 if let Some(language) = language
12579 && language.name() == "Markdown".into()
12580 {
12581 edit_for_markdown_paste(
12582 &snapshot,
12583 range,
12584 to_insert,
12585 url::Url::parse(to_insert).ok(),
12586 )
12587 } else {
12588 (range, Cow::Borrowed(to_insert))
12589 }
12590 };
12591
12592 edits.push((range, to_insert));
12593 original_indent_columns.push(original_indent_column);
12594 }
12595 drop(snapshot);
12596
12597 buffer.edit(
12598 edits,
12599 if auto_indent_on_paste {
12600 Some(AutoindentMode::Block {
12601 original_indent_columns,
12602 })
12603 } else {
12604 None
12605 },
12606 cx,
12607 );
12608 });
12609
12610 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12611 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12612 } else {
12613 let url = url::Url::parse(&clipboard_text).ok();
12614
12615 let auto_indent_mode = if !clipboard_text.is_empty() {
12616 Some(AutoindentMode::Block {
12617 original_indent_columns: Vec::new(),
12618 })
12619 } else {
12620 None
12621 };
12622
12623 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12624 let snapshot = buffer.snapshot(cx);
12625
12626 let anchors = old_selections
12627 .iter()
12628 .map(|s| {
12629 let anchor = snapshot.anchor_after(s.head());
12630 s.map(|_| anchor)
12631 })
12632 .collect::<Vec<_>>();
12633
12634 let mut edits = Vec::new();
12635
12636 for selection in old_selections.iter() {
12637 let language = snapshot.language_at(selection.head());
12638 let range = selection.range();
12639
12640 let (edit_range, edit_text) = if let Some(language) = language
12641 && language.name() == "Markdown".into()
12642 {
12643 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12644 } else {
12645 (range, clipboard_text.clone())
12646 };
12647
12648 edits.push((edit_range, edit_text));
12649 }
12650
12651 drop(snapshot);
12652 buffer.edit(edits, auto_indent_mode, cx);
12653
12654 anchors
12655 });
12656
12657 this.change_selections(Default::default(), window, cx, |s| {
12658 s.select_anchors(selection_anchors);
12659 });
12660 }
12661
12662 let trigger_in_words =
12663 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12664
12665 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12666 });
12667 }
12668
12669 pub fn diff_clipboard_with_selection(
12670 &mut self,
12671 _: &DiffClipboardWithSelection,
12672 window: &mut Window,
12673 cx: &mut Context<Self>,
12674 ) {
12675 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12676
12677 if selections.is_empty() {
12678 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12679 return;
12680 };
12681
12682 let clipboard_text = match cx.read_from_clipboard() {
12683 Some(item) => match item.entries().first() {
12684 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12685 _ => None,
12686 },
12687 None => None,
12688 };
12689
12690 let Some(clipboard_text) = clipboard_text else {
12691 log::warn!("Clipboard doesn't contain text.");
12692 return;
12693 };
12694
12695 window.dispatch_action(
12696 Box::new(DiffClipboardWithSelectionData {
12697 clipboard_text,
12698 editor: cx.entity(),
12699 }),
12700 cx,
12701 );
12702 }
12703
12704 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12705 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12706 if let Some(item) = cx.read_from_clipboard() {
12707 let entries = item.entries();
12708
12709 match entries.first() {
12710 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12711 // of all the pasted entries.
12712 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12713 .do_paste(
12714 clipboard_string.text(),
12715 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12716 true,
12717 window,
12718 cx,
12719 ),
12720 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12721 }
12722 }
12723 }
12724
12725 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12726 if self.read_only(cx) {
12727 return;
12728 }
12729
12730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12731
12732 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12733 if let Some((selections, _)) =
12734 self.selection_history.transaction(transaction_id).cloned()
12735 {
12736 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12737 s.select_anchors(selections.to_vec());
12738 });
12739 } else {
12740 log::error!(
12741 "No entry in selection_history found for undo. \
12742 This may correspond to a bug where undo does not update the selection. \
12743 If this is occurring, please add details to \
12744 https://github.com/zed-industries/zed/issues/22692"
12745 );
12746 }
12747 self.request_autoscroll(Autoscroll::fit(), cx);
12748 self.unmark_text(window, cx);
12749 self.refresh_edit_prediction(true, false, window, cx);
12750 cx.emit(EditorEvent::Edited { transaction_id });
12751 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12752 }
12753 }
12754
12755 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12756 if self.read_only(cx) {
12757 return;
12758 }
12759
12760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12761
12762 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12763 if let Some((_, Some(selections))) =
12764 self.selection_history.transaction(transaction_id).cloned()
12765 {
12766 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12767 s.select_anchors(selections.to_vec());
12768 });
12769 } else {
12770 log::error!(
12771 "No entry in selection_history found for redo. \
12772 This may correspond to a bug where undo does not update the selection. \
12773 If this is occurring, please add details to \
12774 https://github.com/zed-industries/zed/issues/22692"
12775 );
12776 }
12777 self.request_autoscroll(Autoscroll::fit(), cx);
12778 self.unmark_text(window, cx);
12779 self.refresh_edit_prediction(true, false, window, cx);
12780 cx.emit(EditorEvent::Edited { transaction_id });
12781 }
12782 }
12783
12784 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12785 self.buffer
12786 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12787 }
12788
12789 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12790 self.buffer
12791 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12792 }
12793
12794 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12795 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12796 self.change_selections(Default::default(), window, cx, |s| {
12797 s.move_with(|map, selection| {
12798 let cursor = if selection.is_empty() {
12799 movement::left(map, selection.start)
12800 } else {
12801 selection.start
12802 };
12803 selection.collapse_to(cursor, SelectionGoal::None);
12804 });
12805 })
12806 }
12807
12808 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12809 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12810 self.change_selections(Default::default(), window, cx, |s| {
12811 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12812 })
12813 }
12814
12815 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12816 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12817 self.change_selections(Default::default(), window, cx, |s| {
12818 s.move_with(|map, selection| {
12819 let cursor = if selection.is_empty() {
12820 movement::right(map, selection.end)
12821 } else {
12822 selection.end
12823 };
12824 selection.collapse_to(cursor, SelectionGoal::None)
12825 });
12826 })
12827 }
12828
12829 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12831 self.change_selections(Default::default(), window, cx, |s| {
12832 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
12833 });
12834 }
12835
12836 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
12837 if self.take_rename(true, window, cx).is_some() {
12838 return;
12839 }
12840
12841 if self.mode.is_single_line() {
12842 cx.propagate();
12843 return;
12844 }
12845
12846 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12847
12848 let text_layout_details = &self.text_layout_details(window);
12849 let selection_count = self.selections.count();
12850 let first_selection = self.selections.first_anchor();
12851
12852 self.change_selections(Default::default(), window, cx, |s| {
12853 s.move_with(|map, selection| {
12854 if !selection.is_empty() {
12855 selection.goal = SelectionGoal::None;
12856 }
12857 let (cursor, goal) = movement::up(
12858 map,
12859 selection.start,
12860 selection.goal,
12861 false,
12862 text_layout_details,
12863 );
12864 selection.collapse_to(cursor, goal);
12865 });
12866 });
12867
12868 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
12869 {
12870 cx.propagate();
12871 }
12872 }
12873
12874 pub fn move_up_by_lines(
12875 &mut self,
12876 action: &MoveUpByLines,
12877 window: &mut Window,
12878 cx: &mut Context<Self>,
12879 ) {
12880 if self.take_rename(true, window, cx).is_some() {
12881 return;
12882 }
12883
12884 if self.mode.is_single_line() {
12885 cx.propagate();
12886 return;
12887 }
12888
12889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12890
12891 let text_layout_details = &self.text_layout_details(window);
12892
12893 self.change_selections(Default::default(), window, cx, |s| {
12894 s.move_with(|map, selection| {
12895 if !selection.is_empty() {
12896 selection.goal = SelectionGoal::None;
12897 }
12898 let (cursor, goal) = movement::up_by_rows(
12899 map,
12900 selection.start,
12901 action.lines,
12902 selection.goal,
12903 false,
12904 text_layout_details,
12905 );
12906 selection.collapse_to(cursor, goal);
12907 });
12908 })
12909 }
12910
12911 pub fn move_down_by_lines(
12912 &mut self,
12913 action: &MoveDownByLines,
12914 window: &mut Window,
12915 cx: &mut Context<Self>,
12916 ) {
12917 if self.take_rename(true, window, cx).is_some() {
12918 return;
12919 }
12920
12921 if self.mode.is_single_line() {
12922 cx.propagate();
12923 return;
12924 }
12925
12926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12927
12928 let text_layout_details = &self.text_layout_details(window);
12929
12930 self.change_selections(Default::default(), window, cx, |s| {
12931 s.move_with(|map, selection| {
12932 if !selection.is_empty() {
12933 selection.goal = SelectionGoal::None;
12934 }
12935 let (cursor, goal) = movement::down_by_rows(
12936 map,
12937 selection.start,
12938 action.lines,
12939 selection.goal,
12940 false,
12941 text_layout_details,
12942 );
12943 selection.collapse_to(cursor, goal);
12944 });
12945 })
12946 }
12947
12948 pub fn select_down_by_lines(
12949 &mut self,
12950 action: &SelectDownByLines,
12951 window: &mut Window,
12952 cx: &mut Context<Self>,
12953 ) {
12954 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12955 let text_layout_details = &self.text_layout_details(window);
12956 self.change_selections(Default::default(), window, cx, |s| {
12957 s.move_heads_with(|map, head, goal| {
12958 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
12959 })
12960 })
12961 }
12962
12963 pub fn select_up_by_lines(
12964 &mut self,
12965 action: &SelectUpByLines,
12966 window: &mut Window,
12967 cx: &mut Context<Self>,
12968 ) {
12969 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12970 let text_layout_details = &self.text_layout_details(window);
12971 self.change_selections(Default::default(), window, cx, |s| {
12972 s.move_heads_with(|map, head, goal| {
12973 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
12974 })
12975 })
12976 }
12977
12978 pub fn select_page_up(
12979 &mut self,
12980 _: &SelectPageUp,
12981 window: &mut Window,
12982 cx: &mut Context<Self>,
12983 ) {
12984 let Some(row_count) = self.visible_row_count() else {
12985 return;
12986 };
12987
12988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12989
12990 let text_layout_details = &self.text_layout_details(window);
12991
12992 self.change_selections(Default::default(), window, cx, |s| {
12993 s.move_heads_with(|map, head, goal| {
12994 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
12995 })
12996 })
12997 }
12998
12999 pub fn move_page_up(
13000 &mut self,
13001 action: &MovePageUp,
13002 window: &mut Window,
13003 cx: &mut Context<Self>,
13004 ) {
13005 if self.take_rename(true, window, cx).is_some() {
13006 return;
13007 }
13008
13009 if self
13010 .context_menu
13011 .borrow_mut()
13012 .as_mut()
13013 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13014 .unwrap_or(false)
13015 {
13016 return;
13017 }
13018
13019 if matches!(self.mode, EditorMode::SingleLine) {
13020 cx.propagate();
13021 return;
13022 }
13023
13024 let Some(row_count) = self.visible_row_count() else {
13025 return;
13026 };
13027
13028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13029
13030 let effects = if action.center_cursor {
13031 SelectionEffects::scroll(Autoscroll::center())
13032 } else {
13033 SelectionEffects::default()
13034 };
13035
13036 let text_layout_details = &self.text_layout_details(window);
13037
13038 self.change_selections(effects, window, cx, |s| {
13039 s.move_with(|map, selection| {
13040 if !selection.is_empty() {
13041 selection.goal = SelectionGoal::None;
13042 }
13043 let (cursor, goal) = movement::up_by_rows(
13044 map,
13045 selection.end,
13046 row_count,
13047 selection.goal,
13048 false,
13049 text_layout_details,
13050 );
13051 selection.collapse_to(cursor, goal);
13052 });
13053 });
13054 }
13055
13056 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13057 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13058 let text_layout_details = &self.text_layout_details(window);
13059 self.change_selections(Default::default(), window, cx, |s| {
13060 s.move_heads_with(|map, head, goal| {
13061 movement::up(map, head, goal, false, text_layout_details)
13062 })
13063 })
13064 }
13065
13066 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13067 self.take_rename(true, window, cx);
13068
13069 if self.mode.is_single_line() {
13070 cx.propagate();
13071 return;
13072 }
13073
13074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13075
13076 let text_layout_details = &self.text_layout_details(window);
13077 let selection_count = self.selections.count();
13078 let first_selection = self.selections.first_anchor();
13079
13080 self.change_selections(Default::default(), window, cx, |s| {
13081 s.move_with(|map, selection| {
13082 if !selection.is_empty() {
13083 selection.goal = SelectionGoal::None;
13084 }
13085 let (cursor, goal) = movement::down(
13086 map,
13087 selection.end,
13088 selection.goal,
13089 false,
13090 text_layout_details,
13091 );
13092 selection.collapse_to(cursor, goal);
13093 });
13094 });
13095
13096 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13097 {
13098 cx.propagate();
13099 }
13100 }
13101
13102 pub fn select_page_down(
13103 &mut self,
13104 _: &SelectPageDown,
13105 window: &mut Window,
13106 cx: &mut Context<Self>,
13107 ) {
13108 let Some(row_count) = self.visible_row_count() else {
13109 return;
13110 };
13111
13112 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13113
13114 let text_layout_details = &self.text_layout_details(window);
13115
13116 self.change_selections(Default::default(), window, cx, |s| {
13117 s.move_heads_with(|map, head, goal| {
13118 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13119 })
13120 })
13121 }
13122
13123 pub fn move_page_down(
13124 &mut self,
13125 action: &MovePageDown,
13126 window: &mut Window,
13127 cx: &mut Context<Self>,
13128 ) {
13129 if self.take_rename(true, window, cx).is_some() {
13130 return;
13131 }
13132
13133 if self
13134 .context_menu
13135 .borrow_mut()
13136 .as_mut()
13137 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13138 .unwrap_or(false)
13139 {
13140 return;
13141 }
13142
13143 if matches!(self.mode, EditorMode::SingleLine) {
13144 cx.propagate();
13145 return;
13146 }
13147
13148 let Some(row_count) = self.visible_row_count() else {
13149 return;
13150 };
13151
13152 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13153
13154 let effects = if action.center_cursor {
13155 SelectionEffects::scroll(Autoscroll::center())
13156 } else {
13157 SelectionEffects::default()
13158 };
13159
13160 let text_layout_details = &self.text_layout_details(window);
13161 self.change_selections(effects, window, cx, |s| {
13162 s.move_with(|map, selection| {
13163 if !selection.is_empty() {
13164 selection.goal = SelectionGoal::None;
13165 }
13166 let (cursor, goal) = movement::down_by_rows(
13167 map,
13168 selection.end,
13169 row_count,
13170 selection.goal,
13171 false,
13172 text_layout_details,
13173 );
13174 selection.collapse_to(cursor, goal);
13175 });
13176 });
13177 }
13178
13179 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13181 let text_layout_details = &self.text_layout_details(window);
13182 self.change_selections(Default::default(), window, cx, |s| {
13183 s.move_heads_with(|map, head, goal| {
13184 movement::down(map, head, goal, false, text_layout_details)
13185 })
13186 });
13187 }
13188
13189 pub fn context_menu_first(
13190 &mut self,
13191 _: &ContextMenuFirst,
13192 window: &mut Window,
13193 cx: &mut Context<Self>,
13194 ) {
13195 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13196 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13197 }
13198 }
13199
13200 pub fn context_menu_prev(
13201 &mut self,
13202 _: &ContextMenuPrevious,
13203 window: &mut Window,
13204 cx: &mut Context<Self>,
13205 ) {
13206 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13207 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13208 }
13209 }
13210
13211 pub fn context_menu_next(
13212 &mut self,
13213 _: &ContextMenuNext,
13214 window: &mut Window,
13215 cx: &mut Context<Self>,
13216 ) {
13217 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13218 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13219 }
13220 }
13221
13222 pub fn context_menu_last(
13223 &mut self,
13224 _: &ContextMenuLast,
13225 window: &mut Window,
13226 cx: &mut Context<Self>,
13227 ) {
13228 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13229 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13230 }
13231 }
13232
13233 pub fn signature_help_prev(
13234 &mut self,
13235 _: &SignatureHelpPrevious,
13236 _: &mut Window,
13237 cx: &mut Context<Self>,
13238 ) {
13239 if let Some(popover) = self.signature_help_state.popover_mut() {
13240 if popover.current_signature == 0 {
13241 popover.current_signature = popover.signatures.len() - 1;
13242 } else {
13243 popover.current_signature -= 1;
13244 }
13245 cx.notify();
13246 }
13247 }
13248
13249 pub fn signature_help_next(
13250 &mut self,
13251 _: &SignatureHelpNext,
13252 _: &mut Window,
13253 cx: &mut Context<Self>,
13254 ) {
13255 if let Some(popover) = self.signature_help_state.popover_mut() {
13256 if popover.current_signature + 1 == popover.signatures.len() {
13257 popover.current_signature = 0;
13258 } else {
13259 popover.current_signature += 1;
13260 }
13261 cx.notify();
13262 }
13263 }
13264
13265 pub fn move_to_previous_word_start(
13266 &mut self,
13267 _: &MoveToPreviousWordStart,
13268 window: &mut Window,
13269 cx: &mut Context<Self>,
13270 ) {
13271 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13272 self.change_selections(Default::default(), window, cx, |s| {
13273 s.move_cursors_with(|map, head, _| {
13274 (
13275 movement::previous_word_start(map, head),
13276 SelectionGoal::None,
13277 )
13278 });
13279 })
13280 }
13281
13282 pub fn move_to_previous_subword_start(
13283 &mut self,
13284 _: &MoveToPreviousSubwordStart,
13285 window: &mut Window,
13286 cx: &mut Context<Self>,
13287 ) {
13288 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13289 self.change_selections(Default::default(), window, cx, |s| {
13290 s.move_cursors_with(|map, head, _| {
13291 (
13292 movement::previous_subword_start(map, head),
13293 SelectionGoal::None,
13294 )
13295 });
13296 })
13297 }
13298
13299 pub fn select_to_previous_word_start(
13300 &mut self,
13301 _: &SelectToPreviousWordStart,
13302 window: &mut Window,
13303 cx: &mut Context<Self>,
13304 ) {
13305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13306 self.change_selections(Default::default(), window, cx, |s| {
13307 s.move_heads_with(|map, head, _| {
13308 (
13309 movement::previous_word_start(map, head),
13310 SelectionGoal::None,
13311 )
13312 });
13313 })
13314 }
13315
13316 pub fn select_to_previous_subword_start(
13317 &mut self,
13318 _: &SelectToPreviousSubwordStart,
13319 window: &mut Window,
13320 cx: &mut Context<Self>,
13321 ) {
13322 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13323 self.change_selections(Default::default(), window, cx, |s| {
13324 s.move_heads_with(|map, head, _| {
13325 (
13326 movement::previous_subword_start(map, head),
13327 SelectionGoal::None,
13328 )
13329 });
13330 })
13331 }
13332
13333 pub fn delete_to_previous_word_start(
13334 &mut self,
13335 action: &DeleteToPreviousWordStart,
13336 window: &mut Window,
13337 cx: &mut Context<Self>,
13338 ) {
13339 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13340 self.transact(window, cx, |this, window, cx| {
13341 this.select_autoclose_pair(window, cx);
13342 this.change_selections(Default::default(), window, cx, |s| {
13343 s.move_with(|map, selection| {
13344 if selection.is_empty() {
13345 let mut cursor = if action.ignore_newlines {
13346 movement::previous_word_start(map, selection.head())
13347 } else {
13348 movement::previous_word_start_or_newline(map, selection.head())
13349 };
13350 cursor = movement::adjust_greedy_deletion(
13351 map,
13352 selection.head(),
13353 cursor,
13354 action.ignore_brackets,
13355 );
13356 selection.set_head(cursor, SelectionGoal::None);
13357 }
13358 });
13359 });
13360 this.insert("", window, cx);
13361 });
13362 }
13363
13364 pub fn delete_to_previous_subword_start(
13365 &mut self,
13366 _: &DeleteToPreviousSubwordStart,
13367 window: &mut Window,
13368 cx: &mut Context<Self>,
13369 ) {
13370 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13371 self.transact(window, cx, |this, window, cx| {
13372 this.select_autoclose_pair(window, cx);
13373 this.change_selections(Default::default(), window, cx, |s| {
13374 s.move_with(|map, selection| {
13375 if selection.is_empty() {
13376 let mut cursor = movement::previous_subword_start(map, selection.head());
13377 cursor =
13378 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13379 selection.set_head(cursor, SelectionGoal::None);
13380 }
13381 });
13382 });
13383 this.insert("", window, cx);
13384 });
13385 }
13386
13387 pub fn move_to_next_word_end(
13388 &mut self,
13389 _: &MoveToNextWordEnd,
13390 window: &mut Window,
13391 cx: &mut Context<Self>,
13392 ) {
13393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13394 self.change_selections(Default::default(), window, cx, |s| {
13395 s.move_cursors_with(|map, head, _| {
13396 (movement::next_word_end(map, head), SelectionGoal::None)
13397 });
13398 })
13399 }
13400
13401 pub fn move_to_next_subword_end(
13402 &mut self,
13403 _: &MoveToNextSubwordEnd,
13404 window: &mut Window,
13405 cx: &mut Context<Self>,
13406 ) {
13407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13408 self.change_selections(Default::default(), window, cx, |s| {
13409 s.move_cursors_with(|map, head, _| {
13410 (movement::next_subword_end(map, head), SelectionGoal::None)
13411 });
13412 })
13413 }
13414
13415 pub fn select_to_next_word_end(
13416 &mut self,
13417 _: &SelectToNextWordEnd,
13418 window: &mut Window,
13419 cx: &mut Context<Self>,
13420 ) {
13421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13422 self.change_selections(Default::default(), window, cx, |s| {
13423 s.move_heads_with(|map, head, _| {
13424 (movement::next_word_end(map, head), SelectionGoal::None)
13425 });
13426 })
13427 }
13428
13429 pub fn select_to_next_subword_end(
13430 &mut self,
13431 _: &SelectToNextSubwordEnd,
13432 window: &mut Window,
13433 cx: &mut Context<Self>,
13434 ) {
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13436 self.change_selections(Default::default(), window, cx, |s| {
13437 s.move_heads_with(|map, head, _| {
13438 (movement::next_subword_end(map, head), SelectionGoal::None)
13439 });
13440 })
13441 }
13442
13443 pub fn delete_to_next_word_end(
13444 &mut self,
13445 action: &DeleteToNextWordEnd,
13446 window: &mut Window,
13447 cx: &mut Context<Self>,
13448 ) {
13449 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13450 self.transact(window, cx, |this, window, cx| {
13451 this.change_selections(Default::default(), window, cx, |s| {
13452 s.move_with(|map, selection| {
13453 if selection.is_empty() {
13454 let mut cursor = if action.ignore_newlines {
13455 movement::next_word_end(map, selection.head())
13456 } else {
13457 movement::next_word_end_or_newline(map, selection.head())
13458 };
13459 cursor = movement::adjust_greedy_deletion(
13460 map,
13461 selection.head(),
13462 cursor,
13463 action.ignore_brackets,
13464 );
13465 selection.set_head(cursor, SelectionGoal::None);
13466 }
13467 });
13468 });
13469 this.insert("", window, cx);
13470 });
13471 }
13472
13473 pub fn delete_to_next_subword_end(
13474 &mut self,
13475 _: &DeleteToNextSubwordEnd,
13476 window: &mut Window,
13477 cx: &mut Context<Self>,
13478 ) {
13479 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13480 self.transact(window, cx, |this, window, cx| {
13481 this.change_selections(Default::default(), window, cx, |s| {
13482 s.move_with(|map, selection| {
13483 if selection.is_empty() {
13484 let mut cursor = movement::next_subword_end(map, selection.head());
13485 cursor =
13486 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13487 selection.set_head(cursor, SelectionGoal::None);
13488 }
13489 });
13490 });
13491 this.insert("", window, cx);
13492 });
13493 }
13494
13495 pub fn move_to_beginning_of_line(
13496 &mut self,
13497 action: &MoveToBeginningOfLine,
13498 window: &mut Window,
13499 cx: &mut Context<Self>,
13500 ) {
13501 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13502 self.change_selections(Default::default(), window, cx, |s| {
13503 s.move_cursors_with(|map, head, _| {
13504 (
13505 movement::indented_line_beginning(
13506 map,
13507 head,
13508 action.stop_at_soft_wraps,
13509 action.stop_at_indent,
13510 ),
13511 SelectionGoal::None,
13512 )
13513 });
13514 })
13515 }
13516
13517 pub fn select_to_beginning_of_line(
13518 &mut self,
13519 action: &SelectToBeginningOfLine,
13520 window: &mut Window,
13521 cx: &mut Context<Self>,
13522 ) {
13523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13524 self.change_selections(Default::default(), window, cx, |s| {
13525 s.move_heads_with(|map, head, _| {
13526 (
13527 movement::indented_line_beginning(
13528 map,
13529 head,
13530 action.stop_at_soft_wraps,
13531 action.stop_at_indent,
13532 ),
13533 SelectionGoal::None,
13534 )
13535 });
13536 });
13537 }
13538
13539 pub fn delete_to_beginning_of_line(
13540 &mut self,
13541 action: &DeleteToBeginningOfLine,
13542 window: &mut Window,
13543 cx: &mut Context<Self>,
13544 ) {
13545 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13546 self.transact(window, cx, |this, window, cx| {
13547 this.change_selections(Default::default(), window, cx, |s| {
13548 s.move_with(|_, selection| {
13549 selection.reversed = true;
13550 });
13551 });
13552
13553 this.select_to_beginning_of_line(
13554 &SelectToBeginningOfLine {
13555 stop_at_soft_wraps: false,
13556 stop_at_indent: action.stop_at_indent,
13557 },
13558 window,
13559 cx,
13560 );
13561 this.backspace(&Backspace, window, cx);
13562 });
13563 }
13564
13565 pub fn move_to_end_of_line(
13566 &mut self,
13567 action: &MoveToEndOfLine,
13568 window: &mut Window,
13569 cx: &mut Context<Self>,
13570 ) {
13571 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13572 self.change_selections(Default::default(), window, cx, |s| {
13573 s.move_cursors_with(|map, head, _| {
13574 (
13575 movement::line_end(map, head, action.stop_at_soft_wraps),
13576 SelectionGoal::None,
13577 )
13578 });
13579 })
13580 }
13581
13582 pub fn select_to_end_of_line(
13583 &mut self,
13584 action: &SelectToEndOfLine,
13585 window: &mut Window,
13586 cx: &mut Context<Self>,
13587 ) {
13588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13589 self.change_selections(Default::default(), window, cx, |s| {
13590 s.move_heads_with(|map, head, _| {
13591 (
13592 movement::line_end(map, head, action.stop_at_soft_wraps),
13593 SelectionGoal::None,
13594 )
13595 });
13596 })
13597 }
13598
13599 pub fn delete_to_end_of_line(
13600 &mut self,
13601 _: &DeleteToEndOfLine,
13602 window: &mut Window,
13603 cx: &mut Context<Self>,
13604 ) {
13605 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13606 self.transact(window, cx, |this, window, cx| {
13607 this.select_to_end_of_line(
13608 &SelectToEndOfLine {
13609 stop_at_soft_wraps: false,
13610 },
13611 window,
13612 cx,
13613 );
13614 this.delete(&Delete, window, cx);
13615 });
13616 }
13617
13618 pub fn cut_to_end_of_line(
13619 &mut self,
13620 action: &CutToEndOfLine,
13621 window: &mut Window,
13622 cx: &mut Context<Self>,
13623 ) {
13624 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13625 self.transact(window, cx, |this, window, cx| {
13626 this.select_to_end_of_line(
13627 &SelectToEndOfLine {
13628 stop_at_soft_wraps: false,
13629 },
13630 window,
13631 cx,
13632 );
13633 if !action.stop_at_newlines {
13634 this.change_selections(Default::default(), window, cx, |s| {
13635 s.move_with(|_, sel| {
13636 if sel.is_empty() {
13637 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13638 }
13639 });
13640 });
13641 }
13642 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13643 let item = this.cut_common(false, window, cx);
13644 cx.write_to_clipboard(item);
13645 });
13646 }
13647
13648 pub fn move_to_start_of_paragraph(
13649 &mut self,
13650 _: &MoveToStartOfParagraph,
13651 window: &mut Window,
13652 cx: &mut Context<Self>,
13653 ) {
13654 if matches!(self.mode, EditorMode::SingleLine) {
13655 cx.propagate();
13656 return;
13657 }
13658 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13659 self.change_selections(Default::default(), window, cx, |s| {
13660 s.move_with(|map, selection| {
13661 selection.collapse_to(
13662 movement::start_of_paragraph(map, selection.head(), 1),
13663 SelectionGoal::None,
13664 )
13665 });
13666 })
13667 }
13668
13669 pub fn move_to_end_of_paragraph(
13670 &mut self,
13671 _: &MoveToEndOfParagraph,
13672 window: &mut Window,
13673 cx: &mut Context<Self>,
13674 ) {
13675 if matches!(self.mode, EditorMode::SingleLine) {
13676 cx.propagate();
13677 return;
13678 }
13679 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13680 self.change_selections(Default::default(), window, cx, |s| {
13681 s.move_with(|map, selection| {
13682 selection.collapse_to(
13683 movement::end_of_paragraph(map, selection.head(), 1),
13684 SelectionGoal::None,
13685 )
13686 });
13687 })
13688 }
13689
13690 pub fn select_to_start_of_paragraph(
13691 &mut self,
13692 _: &SelectToStartOfParagraph,
13693 window: &mut Window,
13694 cx: &mut Context<Self>,
13695 ) {
13696 if matches!(self.mode, EditorMode::SingleLine) {
13697 cx.propagate();
13698 return;
13699 }
13700 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13701 self.change_selections(Default::default(), window, cx, |s| {
13702 s.move_heads_with(|map, head, _| {
13703 (
13704 movement::start_of_paragraph(map, head, 1),
13705 SelectionGoal::None,
13706 )
13707 });
13708 })
13709 }
13710
13711 pub fn select_to_end_of_paragraph(
13712 &mut self,
13713 _: &SelectToEndOfParagraph,
13714 window: &mut Window,
13715 cx: &mut Context<Self>,
13716 ) {
13717 if matches!(self.mode, EditorMode::SingleLine) {
13718 cx.propagate();
13719 return;
13720 }
13721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13722 self.change_selections(Default::default(), window, cx, |s| {
13723 s.move_heads_with(|map, head, _| {
13724 (
13725 movement::end_of_paragraph(map, head, 1),
13726 SelectionGoal::None,
13727 )
13728 });
13729 })
13730 }
13731
13732 pub fn move_to_start_of_excerpt(
13733 &mut self,
13734 _: &MoveToStartOfExcerpt,
13735 window: &mut Window,
13736 cx: &mut Context<Self>,
13737 ) {
13738 if matches!(self.mode, EditorMode::SingleLine) {
13739 cx.propagate();
13740 return;
13741 }
13742 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13743 self.change_selections(Default::default(), window, cx, |s| {
13744 s.move_with(|map, selection| {
13745 selection.collapse_to(
13746 movement::start_of_excerpt(
13747 map,
13748 selection.head(),
13749 workspace::searchable::Direction::Prev,
13750 ),
13751 SelectionGoal::None,
13752 )
13753 });
13754 })
13755 }
13756
13757 pub fn move_to_start_of_next_excerpt(
13758 &mut self,
13759 _: &MoveToStartOfNextExcerpt,
13760 window: &mut Window,
13761 cx: &mut Context<Self>,
13762 ) {
13763 if matches!(self.mode, EditorMode::SingleLine) {
13764 cx.propagate();
13765 return;
13766 }
13767
13768 self.change_selections(Default::default(), window, cx, |s| {
13769 s.move_with(|map, selection| {
13770 selection.collapse_to(
13771 movement::start_of_excerpt(
13772 map,
13773 selection.head(),
13774 workspace::searchable::Direction::Next,
13775 ),
13776 SelectionGoal::None,
13777 )
13778 });
13779 })
13780 }
13781
13782 pub fn move_to_end_of_excerpt(
13783 &mut self,
13784 _: &MoveToEndOfExcerpt,
13785 window: &mut Window,
13786 cx: &mut Context<Self>,
13787 ) {
13788 if matches!(self.mode, EditorMode::SingleLine) {
13789 cx.propagate();
13790 return;
13791 }
13792 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13793 self.change_selections(Default::default(), window, cx, |s| {
13794 s.move_with(|map, selection| {
13795 selection.collapse_to(
13796 movement::end_of_excerpt(
13797 map,
13798 selection.head(),
13799 workspace::searchable::Direction::Next,
13800 ),
13801 SelectionGoal::None,
13802 )
13803 });
13804 })
13805 }
13806
13807 pub fn move_to_end_of_previous_excerpt(
13808 &mut self,
13809 _: &MoveToEndOfPreviousExcerpt,
13810 window: &mut Window,
13811 cx: &mut Context<Self>,
13812 ) {
13813 if matches!(self.mode, EditorMode::SingleLine) {
13814 cx.propagate();
13815 return;
13816 }
13817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13818 self.change_selections(Default::default(), window, cx, |s| {
13819 s.move_with(|map, selection| {
13820 selection.collapse_to(
13821 movement::end_of_excerpt(
13822 map,
13823 selection.head(),
13824 workspace::searchable::Direction::Prev,
13825 ),
13826 SelectionGoal::None,
13827 )
13828 });
13829 })
13830 }
13831
13832 pub fn select_to_start_of_excerpt(
13833 &mut self,
13834 _: &SelectToStartOfExcerpt,
13835 window: &mut Window,
13836 cx: &mut Context<Self>,
13837 ) {
13838 if matches!(self.mode, EditorMode::SingleLine) {
13839 cx.propagate();
13840 return;
13841 }
13842 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13843 self.change_selections(Default::default(), window, cx, |s| {
13844 s.move_heads_with(|map, head, _| {
13845 (
13846 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13847 SelectionGoal::None,
13848 )
13849 });
13850 })
13851 }
13852
13853 pub fn select_to_start_of_next_excerpt(
13854 &mut self,
13855 _: &SelectToStartOfNextExcerpt,
13856 window: &mut Window,
13857 cx: &mut Context<Self>,
13858 ) {
13859 if matches!(self.mode, EditorMode::SingleLine) {
13860 cx.propagate();
13861 return;
13862 }
13863 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13864 self.change_selections(Default::default(), window, cx, |s| {
13865 s.move_heads_with(|map, head, _| {
13866 (
13867 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
13868 SelectionGoal::None,
13869 )
13870 });
13871 })
13872 }
13873
13874 pub fn select_to_end_of_excerpt(
13875 &mut self,
13876 _: &SelectToEndOfExcerpt,
13877 window: &mut Window,
13878 cx: &mut Context<Self>,
13879 ) {
13880 if matches!(self.mode, EditorMode::SingleLine) {
13881 cx.propagate();
13882 return;
13883 }
13884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13885 self.change_selections(Default::default(), window, cx, |s| {
13886 s.move_heads_with(|map, head, _| {
13887 (
13888 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
13889 SelectionGoal::None,
13890 )
13891 });
13892 })
13893 }
13894
13895 pub fn select_to_end_of_previous_excerpt(
13896 &mut self,
13897 _: &SelectToEndOfPreviousExcerpt,
13898 window: &mut Window,
13899 cx: &mut Context<Self>,
13900 ) {
13901 if matches!(self.mode, EditorMode::SingleLine) {
13902 cx.propagate();
13903 return;
13904 }
13905 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13906 self.change_selections(Default::default(), window, cx, |s| {
13907 s.move_heads_with(|map, head, _| {
13908 (
13909 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
13910 SelectionGoal::None,
13911 )
13912 });
13913 })
13914 }
13915
13916 pub fn move_to_beginning(
13917 &mut self,
13918 _: &MoveToBeginning,
13919 window: &mut Window,
13920 cx: &mut Context<Self>,
13921 ) {
13922 if matches!(self.mode, EditorMode::SingleLine) {
13923 cx.propagate();
13924 return;
13925 }
13926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13927 self.change_selections(Default::default(), window, cx, |s| {
13928 s.select_ranges(vec![0..0]);
13929 });
13930 }
13931
13932 pub fn select_to_beginning(
13933 &mut self,
13934 _: &SelectToBeginning,
13935 window: &mut Window,
13936 cx: &mut Context<Self>,
13937 ) {
13938 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
13939 selection.set_head(Point::zero(), SelectionGoal::None);
13940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13941 self.change_selections(Default::default(), window, cx, |s| {
13942 s.select(vec![selection]);
13943 });
13944 }
13945
13946 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
13947 if matches!(self.mode, EditorMode::SingleLine) {
13948 cx.propagate();
13949 return;
13950 }
13951 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13952 let cursor = self.buffer.read(cx).read(cx).len();
13953 self.change_selections(Default::default(), window, cx, |s| {
13954 s.select_ranges(vec![cursor..cursor])
13955 });
13956 }
13957
13958 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
13959 self.nav_history = nav_history;
13960 }
13961
13962 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
13963 self.nav_history.as_ref()
13964 }
13965
13966 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
13967 self.push_to_nav_history(
13968 self.selections.newest_anchor().head(),
13969 None,
13970 false,
13971 true,
13972 cx,
13973 );
13974 }
13975
13976 fn push_to_nav_history(
13977 &mut self,
13978 cursor_anchor: Anchor,
13979 new_position: Option<Point>,
13980 is_deactivate: bool,
13981 always: bool,
13982 cx: &mut Context<Self>,
13983 ) {
13984 if let Some(nav_history) = self.nav_history.as_mut() {
13985 let buffer = self.buffer.read(cx).read(cx);
13986 let cursor_position = cursor_anchor.to_point(&buffer);
13987 let scroll_state = self.scroll_manager.anchor();
13988 let scroll_top_row = scroll_state.top_row(&buffer);
13989 drop(buffer);
13990
13991 if let Some(new_position) = new_position {
13992 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
13993 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
13994 return;
13995 }
13996 }
13997
13998 nav_history.push(
13999 Some(NavigationData {
14000 cursor_anchor,
14001 cursor_position,
14002 scroll_anchor: scroll_state,
14003 scroll_top_row,
14004 }),
14005 cx,
14006 );
14007 cx.emit(EditorEvent::PushedToNavHistory {
14008 anchor: cursor_anchor,
14009 is_deactivate,
14010 })
14011 }
14012 }
14013
14014 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14016 let buffer = self.buffer.read(cx).snapshot(cx);
14017 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14018 selection.set_head(buffer.len(), SelectionGoal::None);
14019 self.change_selections(Default::default(), window, cx, |s| {
14020 s.select(vec![selection]);
14021 });
14022 }
14023
14024 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14025 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14026 let end = self.buffer.read(cx).read(cx).len();
14027 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14028 s.select_ranges(vec![0..end]);
14029 });
14030 }
14031
14032 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14033 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14034 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14035 let mut selections = self.selections.all::<Point>(&display_map);
14036 let max_point = display_map.buffer_snapshot().max_point();
14037 for selection in &mut selections {
14038 let rows = selection.spanned_rows(true, &display_map);
14039 selection.start = Point::new(rows.start.0, 0);
14040 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14041 selection.reversed = false;
14042 }
14043 self.change_selections(Default::default(), window, cx, |s| {
14044 s.select(selections);
14045 });
14046 }
14047
14048 pub fn split_selection_into_lines(
14049 &mut self,
14050 action: &SplitSelectionIntoLines,
14051 window: &mut Window,
14052 cx: &mut Context<Self>,
14053 ) {
14054 let selections = self
14055 .selections
14056 .all::<Point>(&self.display_snapshot(cx))
14057 .into_iter()
14058 .map(|selection| selection.start..selection.end)
14059 .collect::<Vec<_>>();
14060 self.unfold_ranges(&selections, true, true, cx);
14061
14062 let mut new_selection_ranges = Vec::new();
14063 {
14064 let buffer = self.buffer.read(cx).read(cx);
14065 for selection in selections {
14066 for row in selection.start.row..selection.end.row {
14067 let line_start = Point::new(row, 0);
14068 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14069
14070 if action.keep_selections {
14071 // Keep the selection range for each line
14072 let selection_start = if row == selection.start.row {
14073 selection.start
14074 } else {
14075 line_start
14076 };
14077 new_selection_ranges.push(selection_start..line_end);
14078 } else {
14079 // Collapse to cursor at end of line
14080 new_selection_ranges.push(line_end..line_end);
14081 }
14082 }
14083
14084 let is_multiline_selection = selection.start.row != selection.end.row;
14085 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14086 // so this action feels more ergonomic when paired with other selection operations
14087 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14088 if !should_skip_last {
14089 if action.keep_selections {
14090 if is_multiline_selection {
14091 let line_start = Point::new(selection.end.row, 0);
14092 new_selection_ranges.push(line_start..selection.end);
14093 } else {
14094 new_selection_ranges.push(selection.start..selection.end);
14095 }
14096 } else {
14097 new_selection_ranges.push(selection.end..selection.end);
14098 }
14099 }
14100 }
14101 }
14102 self.change_selections(Default::default(), window, cx, |s| {
14103 s.select_ranges(new_selection_ranges);
14104 });
14105 }
14106
14107 pub fn add_selection_above(
14108 &mut self,
14109 action: &AddSelectionAbove,
14110 window: &mut Window,
14111 cx: &mut Context<Self>,
14112 ) {
14113 self.add_selection(true, action.skip_soft_wrap, window, cx);
14114 }
14115
14116 pub fn add_selection_below(
14117 &mut self,
14118 action: &AddSelectionBelow,
14119 window: &mut Window,
14120 cx: &mut Context<Self>,
14121 ) {
14122 self.add_selection(false, action.skip_soft_wrap, window, cx);
14123 }
14124
14125 fn add_selection(
14126 &mut self,
14127 above: bool,
14128 skip_soft_wrap: bool,
14129 window: &mut Window,
14130 cx: &mut Context<Self>,
14131 ) {
14132 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14133
14134 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14135 let all_selections = self.selections.all::<Point>(&display_map);
14136 let text_layout_details = self.text_layout_details(window);
14137
14138 let (mut columnar_selections, new_selections_to_columnarize) = {
14139 if let Some(state) = self.add_selections_state.as_ref() {
14140 let columnar_selection_ids: HashSet<_> = state
14141 .groups
14142 .iter()
14143 .flat_map(|group| group.stack.iter())
14144 .copied()
14145 .collect();
14146
14147 all_selections
14148 .into_iter()
14149 .partition(|s| columnar_selection_ids.contains(&s.id))
14150 } else {
14151 (Vec::new(), all_selections)
14152 }
14153 };
14154
14155 let mut state = self
14156 .add_selections_state
14157 .take()
14158 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14159
14160 for selection in new_selections_to_columnarize {
14161 let range = selection.display_range(&display_map).sorted();
14162 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14163 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14164 let positions = start_x.min(end_x)..start_x.max(end_x);
14165 let mut stack = Vec::new();
14166 for row in range.start.row().0..=range.end.row().0 {
14167 if let Some(selection) = self.selections.build_columnar_selection(
14168 &display_map,
14169 DisplayRow(row),
14170 &positions,
14171 selection.reversed,
14172 &text_layout_details,
14173 ) {
14174 stack.push(selection.id);
14175 columnar_selections.push(selection);
14176 }
14177 }
14178 if !stack.is_empty() {
14179 if above {
14180 stack.reverse();
14181 }
14182 state.groups.push(AddSelectionsGroup { above, stack });
14183 }
14184 }
14185
14186 let mut final_selections = Vec::new();
14187 let end_row = if above {
14188 DisplayRow(0)
14189 } else {
14190 display_map.max_point().row()
14191 };
14192
14193 let mut last_added_item_per_group = HashMap::default();
14194 for group in state.groups.iter_mut() {
14195 if let Some(last_id) = group.stack.last() {
14196 last_added_item_per_group.insert(*last_id, group);
14197 }
14198 }
14199
14200 for selection in columnar_selections {
14201 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14202 if above == group.above {
14203 let range = selection.display_range(&display_map).sorted();
14204 debug_assert_eq!(range.start.row(), range.end.row());
14205 let mut row = range.start.row();
14206 let positions =
14207 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14208 Pixels::from(start)..Pixels::from(end)
14209 } else {
14210 let start_x =
14211 display_map.x_for_display_point(range.start, &text_layout_details);
14212 let end_x =
14213 display_map.x_for_display_point(range.end, &text_layout_details);
14214 start_x.min(end_x)..start_x.max(end_x)
14215 };
14216
14217 let mut maybe_new_selection = None;
14218 let direction = if above { -1 } else { 1 };
14219
14220 while row != end_row {
14221 if skip_soft_wrap {
14222 row = display_map
14223 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14224 .row();
14225 } else if above {
14226 row.0 -= 1;
14227 } else {
14228 row.0 += 1;
14229 }
14230
14231 if let Some(new_selection) = self.selections.build_columnar_selection(
14232 &display_map,
14233 row,
14234 &positions,
14235 selection.reversed,
14236 &text_layout_details,
14237 ) {
14238 maybe_new_selection = Some(new_selection);
14239 break;
14240 }
14241 }
14242
14243 if let Some(new_selection) = maybe_new_selection {
14244 group.stack.push(new_selection.id);
14245 if above {
14246 final_selections.push(new_selection);
14247 final_selections.push(selection);
14248 } else {
14249 final_selections.push(selection);
14250 final_selections.push(new_selection);
14251 }
14252 } else {
14253 final_selections.push(selection);
14254 }
14255 } else {
14256 group.stack.pop();
14257 }
14258 } else {
14259 final_selections.push(selection);
14260 }
14261 }
14262
14263 self.change_selections(Default::default(), window, cx, |s| {
14264 s.select(final_selections);
14265 });
14266
14267 let final_selection_ids: HashSet<_> = self
14268 .selections
14269 .all::<Point>(&display_map)
14270 .iter()
14271 .map(|s| s.id)
14272 .collect();
14273 state.groups.retain_mut(|group| {
14274 // selections might get merged above so we remove invalid items from stacks
14275 group.stack.retain(|id| final_selection_ids.contains(id));
14276
14277 // single selection in stack can be treated as initial state
14278 group.stack.len() > 1
14279 });
14280
14281 if !state.groups.is_empty() {
14282 self.add_selections_state = Some(state);
14283 }
14284 }
14285
14286 fn select_match_ranges(
14287 &mut self,
14288 range: Range<usize>,
14289 reversed: bool,
14290 replace_newest: bool,
14291 auto_scroll: Option<Autoscroll>,
14292 window: &mut Window,
14293 cx: &mut Context<Editor>,
14294 ) {
14295 self.unfold_ranges(
14296 std::slice::from_ref(&range),
14297 false,
14298 auto_scroll.is_some(),
14299 cx,
14300 );
14301 let effects = if let Some(scroll) = auto_scroll {
14302 SelectionEffects::scroll(scroll)
14303 } else {
14304 SelectionEffects::no_scroll()
14305 };
14306 self.change_selections(effects, window, cx, |s| {
14307 if replace_newest {
14308 s.delete(s.newest_anchor().id);
14309 }
14310 if reversed {
14311 s.insert_range(range.end..range.start);
14312 } else {
14313 s.insert_range(range);
14314 }
14315 });
14316 }
14317
14318 pub fn select_next_match_internal(
14319 &mut self,
14320 display_map: &DisplaySnapshot,
14321 replace_newest: bool,
14322 autoscroll: Option<Autoscroll>,
14323 window: &mut Window,
14324 cx: &mut Context<Self>,
14325 ) -> Result<()> {
14326 let buffer = display_map.buffer_snapshot();
14327 let mut selections = self.selections.all::<usize>(&display_map);
14328 if let Some(mut select_next_state) = self.select_next_state.take() {
14329 let query = &select_next_state.query;
14330 if !select_next_state.done {
14331 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14332 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14333 let mut next_selected_range = None;
14334
14335 // Collect and sort selection ranges for efficient overlap checking
14336 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14337 selection_ranges.sort_by_key(|r| r.start);
14338
14339 let bytes_after_last_selection =
14340 buffer.bytes_in_range(last_selection.end..buffer.len());
14341 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14342 let query_matches = query
14343 .stream_find_iter(bytes_after_last_selection)
14344 .map(|result| (last_selection.end, result))
14345 .chain(
14346 query
14347 .stream_find_iter(bytes_before_first_selection)
14348 .map(|result| (0, result)),
14349 );
14350
14351 for (start_offset, query_match) in query_matches {
14352 let query_match = query_match.unwrap(); // can only fail due to I/O
14353 let offset_range =
14354 start_offset + query_match.start()..start_offset + query_match.end();
14355
14356 if !select_next_state.wordwise
14357 || (!buffer.is_inside_word(offset_range.start, None)
14358 && !buffer.is_inside_word(offset_range.end, None))
14359 {
14360 // Use binary search to check for overlap (O(log n))
14361 let overlaps = selection_ranges
14362 .binary_search_by(|range| {
14363 if range.end <= offset_range.start {
14364 std::cmp::Ordering::Less
14365 } else if range.start >= offset_range.end {
14366 std::cmp::Ordering::Greater
14367 } else {
14368 std::cmp::Ordering::Equal
14369 }
14370 })
14371 .is_ok();
14372
14373 if !overlaps {
14374 next_selected_range = Some(offset_range);
14375 break;
14376 }
14377 }
14378 }
14379
14380 if let Some(next_selected_range) = next_selected_range {
14381 self.select_match_ranges(
14382 next_selected_range,
14383 last_selection.reversed,
14384 replace_newest,
14385 autoscroll,
14386 window,
14387 cx,
14388 );
14389 } else {
14390 select_next_state.done = true;
14391 }
14392 }
14393
14394 self.select_next_state = Some(select_next_state);
14395 } else {
14396 let mut only_carets = true;
14397 let mut same_text_selected = true;
14398 let mut selected_text = None;
14399
14400 let mut selections_iter = selections.iter().peekable();
14401 while let Some(selection) = selections_iter.next() {
14402 if selection.start != selection.end {
14403 only_carets = false;
14404 }
14405
14406 if same_text_selected {
14407 if selected_text.is_none() {
14408 selected_text =
14409 Some(buffer.text_for_range(selection.range()).collect::<String>());
14410 }
14411
14412 if let Some(next_selection) = selections_iter.peek() {
14413 if next_selection.range().len() == selection.range().len() {
14414 let next_selected_text = buffer
14415 .text_for_range(next_selection.range())
14416 .collect::<String>();
14417 if Some(next_selected_text) != selected_text {
14418 same_text_selected = false;
14419 selected_text = None;
14420 }
14421 } else {
14422 same_text_selected = false;
14423 selected_text = None;
14424 }
14425 }
14426 }
14427 }
14428
14429 if only_carets {
14430 for selection in &mut selections {
14431 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14432 selection.start = word_range.start;
14433 selection.end = word_range.end;
14434 selection.goal = SelectionGoal::None;
14435 selection.reversed = false;
14436 self.select_match_ranges(
14437 selection.start..selection.end,
14438 selection.reversed,
14439 replace_newest,
14440 autoscroll,
14441 window,
14442 cx,
14443 );
14444 }
14445
14446 if selections.len() == 1 {
14447 let selection = selections
14448 .last()
14449 .expect("ensured that there's only one selection");
14450 let query = buffer
14451 .text_for_range(selection.start..selection.end)
14452 .collect::<String>();
14453 let is_empty = query.is_empty();
14454 let select_state = SelectNextState {
14455 query: AhoCorasick::new(&[query])?,
14456 wordwise: true,
14457 done: is_empty,
14458 };
14459 self.select_next_state = Some(select_state);
14460 } else {
14461 self.select_next_state = None;
14462 }
14463 } else if let Some(selected_text) = selected_text {
14464 self.select_next_state = Some(SelectNextState {
14465 query: AhoCorasick::new(&[selected_text])?,
14466 wordwise: false,
14467 done: false,
14468 });
14469 self.select_next_match_internal(
14470 display_map,
14471 replace_newest,
14472 autoscroll,
14473 window,
14474 cx,
14475 )?;
14476 }
14477 }
14478 Ok(())
14479 }
14480
14481 pub fn select_all_matches(
14482 &mut self,
14483 _action: &SelectAllMatches,
14484 window: &mut Window,
14485 cx: &mut Context<Self>,
14486 ) -> Result<()> {
14487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14488
14489 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14490
14491 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14492 let Some(select_next_state) = self.select_next_state.as_mut() else {
14493 return Ok(());
14494 };
14495 if select_next_state.done {
14496 return Ok(());
14497 }
14498
14499 let mut new_selections = Vec::new();
14500
14501 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14502 let buffer = display_map.buffer_snapshot();
14503 let query_matches = select_next_state
14504 .query
14505 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14506
14507 for query_match in query_matches.into_iter() {
14508 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14509 let offset_range = if reversed {
14510 query_match.end()..query_match.start()
14511 } else {
14512 query_match.start()..query_match.end()
14513 };
14514
14515 if !select_next_state.wordwise
14516 || (!buffer.is_inside_word(offset_range.start, None)
14517 && !buffer.is_inside_word(offset_range.end, None))
14518 {
14519 new_selections.push(offset_range.start..offset_range.end);
14520 }
14521 }
14522
14523 select_next_state.done = true;
14524
14525 if new_selections.is_empty() {
14526 log::error!("bug: new_selections is empty in select_all_matches");
14527 return Ok(());
14528 }
14529
14530 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14531 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14532 selections.select_ranges(new_selections)
14533 });
14534
14535 Ok(())
14536 }
14537
14538 pub fn select_next(
14539 &mut self,
14540 action: &SelectNext,
14541 window: &mut Window,
14542 cx: &mut Context<Self>,
14543 ) -> Result<()> {
14544 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14545 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14546 self.select_next_match_internal(
14547 &display_map,
14548 action.replace_newest,
14549 Some(Autoscroll::newest()),
14550 window,
14551 cx,
14552 )?;
14553 Ok(())
14554 }
14555
14556 pub fn select_previous(
14557 &mut self,
14558 action: &SelectPrevious,
14559 window: &mut Window,
14560 cx: &mut Context<Self>,
14561 ) -> Result<()> {
14562 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14563 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14564 let buffer = display_map.buffer_snapshot();
14565 let mut selections = self.selections.all::<usize>(&display_map);
14566 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14567 let query = &select_prev_state.query;
14568 if !select_prev_state.done {
14569 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14570 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14571 let mut next_selected_range = None;
14572 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14573 let bytes_before_last_selection =
14574 buffer.reversed_bytes_in_range(0..last_selection.start);
14575 let bytes_after_first_selection =
14576 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14577 let query_matches = query
14578 .stream_find_iter(bytes_before_last_selection)
14579 .map(|result| (last_selection.start, result))
14580 .chain(
14581 query
14582 .stream_find_iter(bytes_after_first_selection)
14583 .map(|result| (buffer.len(), result)),
14584 );
14585 for (end_offset, query_match) in query_matches {
14586 let query_match = query_match.unwrap(); // can only fail due to I/O
14587 let offset_range =
14588 end_offset - query_match.end()..end_offset - query_match.start();
14589
14590 if !select_prev_state.wordwise
14591 || (!buffer.is_inside_word(offset_range.start, None)
14592 && !buffer.is_inside_word(offset_range.end, None))
14593 {
14594 next_selected_range = Some(offset_range);
14595 break;
14596 }
14597 }
14598
14599 if let Some(next_selected_range) = next_selected_range {
14600 self.select_match_ranges(
14601 next_selected_range,
14602 last_selection.reversed,
14603 action.replace_newest,
14604 Some(Autoscroll::newest()),
14605 window,
14606 cx,
14607 );
14608 } else {
14609 select_prev_state.done = true;
14610 }
14611 }
14612
14613 self.select_prev_state = Some(select_prev_state);
14614 } else {
14615 let mut only_carets = true;
14616 let mut same_text_selected = true;
14617 let mut selected_text = None;
14618
14619 let mut selections_iter = selections.iter().peekable();
14620 while let Some(selection) = selections_iter.next() {
14621 if selection.start != selection.end {
14622 only_carets = false;
14623 }
14624
14625 if same_text_selected {
14626 if selected_text.is_none() {
14627 selected_text =
14628 Some(buffer.text_for_range(selection.range()).collect::<String>());
14629 }
14630
14631 if let Some(next_selection) = selections_iter.peek() {
14632 if next_selection.range().len() == selection.range().len() {
14633 let next_selected_text = buffer
14634 .text_for_range(next_selection.range())
14635 .collect::<String>();
14636 if Some(next_selected_text) != selected_text {
14637 same_text_selected = false;
14638 selected_text = None;
14639 }
14640 } else {
14641 same_text_selected = false;
14642 selected_text = None;
14643 }
14644 }
14645 }
14646 }
14647
14648 if only_carets {
14649 for selection in &mut selections {
14650 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14651 selection.start = word_range.start;
14652 selection.end = word_range.end;
14653 selection.goal = SelectionGoal::None;
14654 selection.reversed = false;
14655 self.select_match_ranges(
14656 selection.start..selection.end,
14657 selection.reversed,
14658 action.replace_newest,
14659 Some(Autoscroll::newest()),
14660 window,
14661 cx,
14662 );
14663 }
14664 if selections.len() == 1 {
14665 let selection = selections
14666 .last()
14667 .expect("ensured that there's only one selection");
14668 let query = buffer
14669 .text_for_range(selection.start..selection.end)
14670 .collect::<String>();
14671 let is_empty = query.is_empty();
14672 let select_state = SelectNextState {
14673 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14674 wordwise: true,
14675 done: is_empty,
14676 };
14677 self.select_prev_state = Some(select_state);
14678 } else {
14679 self.select_prev_state = None;
14680 }
14681 } else if let Some(selected_text) = selected_text {
14682 self.select_prev_state = Some(SelectNextState {
14683 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14684 wordwise: false,
14685 done: false,
14686 });
14687 self.select_previous(action, window, cx)?;
14688 }
14689 }
14690 Ok(())
14691 }
14692
14693 pub fn find_next_match(
14694 &mut self,
14695 _: &FindNextMatch,
14696 window: &mut Window,
14697 cx: &mut Context<Self>,
14698 ) -> Result<()> {
14699 let selections = self.selections.disjoint_anchors_arc();
14700 match selections.first() {
14701 Some(first) if selections.len() >= 2 => {
14702 self.change_selections(Default::default(), window, cx, |s| {
14703 s.select_ranges([first.range()]);
14704 });
14705 }
14706 _ => self.select_next(
14707 &SelectNext {
14708 replace_newest: true,
14709 },
14710 window,
14711 cx,
14712 )?,
14713 }
14714 Ok(())
14715 }
14716
14717 pub fn find_previous_match(
14718 &mut self,
14719 _: &FindPreviousMatch,
14720 window: &mut Window,
14721 cx: &mut Context<Self>,
14722 ) -> Result<()> {
14723 let selections = self.selections.disjoint_anchors_arc();
14724 match selections.last() {
14725 Some(last) if selections.len() >= 2 => {
14726 self.change_selections(Default::default(), window, cx, |s| {
14727 s.select_ranges([last.range()]);
14728 });
14729 }
14730 _ => self.select_previous(
14731 &SelectPrevious {
14732 replace_newest: true,
14733 },
14734 window,
14735 cx,
14736 )?,
14737 }
14738 Ok(())
14739 }
14740
14741 pub fn toggle_comments(
14742 &mut self,
14743 action: &ToggleComments,
14744 window: &mut Window,
14745 cx: &mut Context<Self>,
14746 ) {
14747 if self.read_only(cx) {
14748 return;
14749 }
14750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14751 let text_layout_details = &self.text_layout_details(window);
14752 self.transact(window, cx, |this, window, cx| {
14753 let mut selections = this
14754 .selections
14755 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14756 let mut edits = Vec::new();
14757 let mut selection_edit_ranges = Vec::new();
14758 let mut last_toggled_row = None;
14759 let snapshot = this.buffer.read(cx).read(cx);
14760 let empty_str: Arc<str> = Arc::default();
14761 let mut suffixes_inserted = Vec::new();
14762 let ignore_indent = action.ignore_indent;
14763
14764 fn comment_prefix_range(
14765 snapshot: &MultiBufferSnapshot,
14766 row: MultiBufferRow,
14767 comment_prefix: &str,
14768 comment_prefix_whitespace: &str,
14769 ignore_indent: bool,
14770 ) -> Range<Point> {
14771 let indent_size = if ignore_indent {
14772 0
14773 } else {
14774 snapshot.indent_size_for_line(row).len
14775 };
14776
14777 let start = Point::new(row.0, indent_size);
14778
14779 let mut line_bytes = snapshot
14780 .bytes_in_range(start..snapshot.max_point())
14781 .flatten()
14782 .copied();
14783
14784 // If this line currently begins with the line comment prefix, then record
14785 // the range containing the prefix.
14786 if line_bytes
14787 .by_ref()
14788 .take(comment_prefix.len())
14789 .eq(comment_prefix.bytes())
14790 {
14791 // Include any whitespace that matches the comment prefix.
14792 let matching_whitespace_len = line_bytes
14793 .zip(comment_prefix_whitespace.bytes())
14794 .take_while(|(a, b)| a == b)
14795 .count() as u32;
14796 let end = Point::new(
14797 start.row,
14798 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14799 );
14800 start..end
14801 } else {
14802 start..start
14803 }
14804 }
14805
14806 fn comment_suffix_range(
14807 snapshot: &MultiBufferSnapshot,
14808 row: MultiBufferRow,
14809 comment_suffix: &str,
14810 comment_suffix_has_leading_space: bool,
14811 ) -> Range<Point> {
14812 let end = Point::new(row.0, snapshot.line_len(row));
14813 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14814
14815 let mut line_end_bytes = snapshot
14816 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14817 .flatten()
14818 .copied();
14819
14820 let leading_space_len = if suffix_start_column > 0
14821 && line_end_bytes.next() == Some(b' ')
14822 && comment_suffix_has_leading_space
14823 {
14824 1
14825 } else {
14826 0
14827 };
14828
14829 // If this line currently begins with the line comment prefix, then record
14830 // the range containing the prefix.
14831 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14832 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14833 start..end
14834 } else {
14835 end..end
14836 }
14837 }
14838
14839 // TODO: Handle selections that cross excerpts
14840 for selection in &mut selections {
14841 let start_column = snapshot
14842 .indent_size_for_line(MultiBufferRow(selection.start.row))
14843 .len;
14844 let language = if let Some(language) =
14845 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
14846 {
14847 language
14848 } else {
14849 continue;
14850 };
14851
14852 selection_edit_ranges.clear();
14853
14854 // If multiple selections contain a given row, avoid processing that
14855 // row more than once.
14856 let mut start_row = MultiBufferRow(selection.start.row);
14857 if last_toggled_row == Some(start_row) {
14858 start_row = start_row.next_row();
14859 }
14860 let end_row =
14861 if selection.end.row > selection.start.row && selection.end.column == 0 {
14862 MultiBufferRow(selection.end.row - 1)
14863 } else {
14864 MultiBufferRow(selection.end.row)
14865 };
14866 last_toggled_row = Some(end_row);
14867
14868 if start_row > end_row {
14869 continue;
14870 }
14871
14872 // If the language has line comments, toggle those.
14873 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
14874
14875 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
14876 if ignore_indent {
14877 full_comment_prefixes = full_comment_prefixes
14878 .into_iter()
14879 .map(|s| Arc::from(s.trim_end()))
14880 .collect();
14881 }
14882
14883 if !full_comment_prefixes.is_empty() {
14884 let first_prefix = full_comment_prefixes
14885 .first()
14886 .expect("prefixes is non-empty");
14887 let prefix_trimmed_lengths = full_comment_prefixes
14888 .iter()
14889 .map(|p| p.trim_end_matches(' ').len())
14890 .collect::<SmallVec<[usize; 4]>>();
14891
14892 let mut all_selection_lines_are_comments = true;
14893
14894 for row in start_row.0..=end_row.0 {
14895 let row = MultiBufferRow(row);
14896 if start_row < end_row && snapshot.is_line_blank(row) {
14897 continue;
14898 }
14899
14900 let prefix_range = full_comment_prefixes
14901 .iter()
14902 .zip(prefix_trimmed_lengths.iter().copied())
14903 .map(|(prefix, trimmed_prefix_len)| {
14904 comment_prefix_range(
14905 snapshot.deref(),
14906 row,
14907 &prefix[..trimmed_prefix_len],
14908 &prefix[trimmed_prefix_len..],
14909 ignore_indent,
14910 )
14911 })
14912 .max_by_key(|range| range.end.column - range.start.column)
14913 .expect("prefixes is non-empty");
14914
14915 if prefix_range.is_empty() {
14916 all_selection_lines_are_comments = false;
14917 }
14918
14919 selection_edit_ranges.push(prefix_range);
14920 }
14921
14922 if all_selection_lines_are_comments {
14923 edits.extend(
14924 selection_edit_ranges
14925 .iter()
14926 .cloned()
14927 .map(|range| (range, empty_str.clone())),
14928 );
14929 } else {
14930 let min_column = selection_edit_ranges
14931 .iter()
14932 .map(|range| range.start.column)
14933 .min()
14934 .unwrap_or(0);
14935 edits.extend(selection_edit_ranges.iter().map(|range| {
14936 let position = Point::new(range.start.row, min_column);
14937 (position..position, first_prefix.clone())
14938 }));
14939 }
14940 } else if let Some(BlockCommentConfig {
14941 start: full_comment_prefix,
14942 end: comment_suffix,
14943 ..
14944 }) = language.block_comment()
14945 {
14946 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
14947 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
14948 let prefix_range = comment_prefix_range(
14949 snapshot.deref(),
14950 start_row,
14951 comment_prefix,
14952 comment_prefix_whitespace,
14953 ignore_indent,
14954 );
14955 let suffix_range = comment_suffix_range(
14956 snapshot.deref(),
14957 end_row,
14958 comment_suffix.trim_start_matches(' '),
14959 comment_suffix.starts_with(' '),
14960 );
14961
14962 if prefix_range.is_empty() || suffix_range.is_empty() {
14963 edits.push((
14964 prefix_range.start..prefix_range.start,
14965 full_comment_prefix.clone(),
14966 ));
14967 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
14968 suffixes_inserted.push((end_row, comment_suffix.len()));
14969 } else {
14970 edits.push((prefix_range, empty_str.clone()));
14971 edits.push((suffix_range, empty_str.clone()));
14972 }
14973 } else {
14974 continue;
14975 }
14976 }
14977
14978 drop(snapshot);
14979 this.buffer.update(cx, |buffer, cx| {
14980 buffer.edit(edits, None, cx);
14981 });
14982
14983 // Adjust selections so that they end before any comment suffixes that
14984 // were inserted.
14985 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
14986 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
14987 let snapshot = this.buffer.read(cx).read(cx);
14988 for selection in &mut selections {
14989 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
14990 match row.cmp(&MultiBufferRow(selection.end.row)) {
14991 Ordering::Less => {
14992 suffixes_inserted.next();
14993 continue;
14994 }
14995 Ordering::Greater => break,
14996 Ordering::Equal => {
14997 if selection.end.column == snapshot.line_len(row) {
14998 if selection.is_empty() {
14999 selection.start.column -= suffix_len as u32;
15000 }
15001 selection.end.column -= suffix_len as u32;
15002 }
15003 break;
15004 }
15005 }
15006 }
15007 }
15008
15009 drop(snapshot);
15010 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15011
15012 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15013 let selections_on_single_row = selections.windows(2).all(|selections| {
15014 selections[0].start.row == selections[1].start.row
15015 && selections[0].end.row == selections[1].end.row
15016 && selections[0].start.row == selections[0].end.row
15017 });
15018 let selections_selecting = selections
15019 .iter()
15020 .any(|selection| selection.start != selection.end);
15021 let advance_downwards = action.advance_downwards
15022 && selections_on_single_row
15023 && !selections_selecting
15024 && !matches!(this.mode, EditorMode::SingleLine);
15025
15026 if advance_downwards {
15027 let snapshot = this.buffer.read(cx).snapshot(cx);
15028
15029 this.change_selections(Default::default(), window, cx, |s| {
15030 s.move_cursors_with(|display_snapshot, display_point, _| {
15031 let mut point = display_point.to_point(display_snapshot);
15032 point.row += 1;
15033 point = snapshot.clip_point(point, Bias::Left);
15034 let display_point = point.to_display_point(display_snapshot);
15035 let goal = SelectionGoal::HorizontalPosition(
15036 display_snapshot
15037 .x_for_display_point(display_point, text_layout_details)
15038 .into(),
15039 );
15040 (display_point, goal)
15041 })
15042 });
15043 }
15044 });
15045 }
15046
15047 pub fn select_enclosing_symbol(
15048 &mut self,
15049 _: &SelectEnclosingSymbol,
15050 window: &mut Window,
15051 cx: &mut Context<Self>,
15052 ) {
15053 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15054
15055 let buffer = self.buffer.read(cx).snapshot(cx);
15056 let old_selections = self
15057 .selections
15058 .all::<usize>(&self.display_snapshot(cx))
15059 .into_boxed_slice();
15060
15061 fn update_selection(
15062 selection: &Selection<usize>,
15063 buffer_snap: &MultiBufferSnapshot,
15064 ) -> Option<Selection<usize>> {
15065 let cursor = selection.head();
15066 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15067 for symbol in symbols.iter().rev() {
15068 let start = symbol.range.start.to_offset(buffer_snap);
15069 let end = symbol.range.end.to_offset(buffer_snap);
15070 let new_range = start..end;
15071 if start < selection.start || end > selection.end {
15072 return Some(Selection {
15073 id: selection.id,
15074 start: new_range.start,
15075 end: new_range.end,
15076 goal: SelectionGoal::None,
15077 reversed: selection.reversed,
15078 });
15079 }
15080 }
15081 None
15082 }
15083
15084 let mut selected_larger_symbol = false;
15085 let new_selections = old_selections
15086 .iter()
15087 .map(|selection| match update_selection(selection, &buffer) {
15088 Some(new_selection) => {
15089 if new_selection.range() != selection.range() {
15090 selected_larger_symbol = true;
15091 }
15092 new_selection
15093 }
15094 None => selection.clone(),
15095 })
15096 .collect::<Vec<_>>();
15097
15098 if selected_larger_symbol {
15099 self.change_selections(Default::default(), window, cx, |s| {
15100 s.select(new_selections);
15101 });
15102 }
15103 }
15104
15105 pub fn select_larger_syntax_node(
15106 &mut self,
15107 _: &SelectLargerSyntaxNode,
15108 window: &mut Window,
15109 cx: &mut Context<Self>,
15110 ) {
15111 let Some(visible_row_count) = self.visible_row_count() else {
15112 return;
15113 };
15114 let old_selections: Box<[_]> = self
15115 .selections
15116 .all::<usize>(&self.display_snapshot(cx))
15117 .into();
15118 if old_selections.is_empty() {
15119 return;
15120 }
15121
15122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15123
15124 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15125 let buffer = self.buffer.read(cx).snapshot(cx);
15126
15127 let mut selected_larger_node = false;
15128 let mut new_selections = old_selections
15129 .iter()
15130 .map(|selection| {
15131 let old_range = selection.start..selection.end;
15132
15133 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15134 // manually select word at selection
15135 if ["string_content", "inline"].contains(&node.kind()) {
15136 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15137 // ignore if word is already selected
15138 if !word_range.is_empty() && old_range != word_range {
15139 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15140 // only select word if start and end point belongs to same word
15141 if word_range == last_word_range {
15142 selected_larger_node = true;
15143 return Selection {
15144 id: selection.id,
15145 start: word_range.start,
15146 end: word_range.end,
15147 goal: SelectionGoal::None,
15148 reversed: selection.reversed,
15149 };
15150 }
15151 }
15152 }
15153 }
15154
15155 let mut new_range = old_range.clone();
15156 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15157 new_range = range;
15158 if !node.is_named() {
15159 continue;
15160 }
15161 if !display_map.intersects_fold(new_range.start)
15162 && !display_map.intersects_fold(new_range.end)
15163 {
15164 break;
15165 }
15166 }
15167
15168 selected_larger_node |= new_range != old_range;
15169 Selection {
15170 id: selection.id,
15171 start: new_range.start,
15172 end: new_range.end,
15173 goal: SelectionGoal::None,
15174 reversed: selection.reversed,
15175 }
15176 })
15177 .collect::<Vec<_>>();
15178
15179 if !selected_larger_node {
15180 return; // don't put this call in the history
15181 }
15182
15183 // scroll based on transformation done to the last selection created by the user
15184 let (last_old, last_new) = old_selections
15185 .last()
15186 .zip(new_selections.last().cloned())
15187 .expect("old_selections isn't empty");
15188
15189 // revert selection
15190 let is_selection_reversed = {
15191 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15192 new_selections.last_mut().expect("checked above").reversed =
15193 should_newest_selection_be_reversed;
15194 should_newest_selection_be_reversed
15195 };
15196
15197 if selected_larger_node {
15198 self.select_syntax_node_history.disable_clearing = true;
15199 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15200 s.select(new_selections.clone());
15201 });
15202 self.select_syntax_node_history.disable_clearing = false;
15203 }
15204
15205 let start_row = last_new.start.to_display_point(&display_map).row().0;
15206 let end_row = last_new.end.to_display_point(&display_map).row().0;
15207 let selection_height = end_row - start_row + 1;
15208 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15209
15210 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15211 let scroll_behavior = if fits_on_the_screen {
15212 self.request_autoscroll(Autoscroll::fit(), cx);
15213 SelectSyntaxNodeScrollBehavior::FitSelection
15214 } else if is_selection_reversed {
15215 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15216 SelectSyntaxNodeScrollBehavior::CursorTop
15217 } else {
15218 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15219 SelectSyntaxNodeScrollBehavior::CursorBottom
15220 };
15221
15222 self.select_syntax_node_history.push((
15223 old_selections,
15224 scroll_behavior,
15225 is_selection_reversed,
15226 ));
15227 }
15228
15229 pub fn select_smaller_syntax_node(
15230 &mut self,
15231 _: &SelectSmallerSyntaxNode,
15232 window: &mut Window,
15233 cx: &mut Context<Self>,
15234 ) {
15235 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15236
15237 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15238 self.select_syntax_node_history.pop()
15239 {
15240 if let Some(selection) = selections.last_mut() {
15241 selection.reversed = is_selection_reversed;
15242 }
15243
15244 self.select_syntax_node_history.disable_clearing = true;
15245 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15246 s.select(selections.to_vec());
15247 });
15248 self.select_syntax_node_history.disable_clearing = false;
15249
15250 match scroll_behavior {
15251 SelectSyntaxNodeScrollBehavior::CursorTop => {
15252 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15253 }
15254 SelectSyntaxNodeScrollBehavior::FitSelection => {
15255 self.request_autoscroll(Autoscroll::fit(), cx);
15256 }
15257 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15258 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15259 }
15260 }
15261 }
15262 }
15263
15264 pub fn unwrap_syntax_node(
15265 &mut self,
15266 _: &UnwrapSyntaxNode,
15267 window: &mut Window,
15268 cx: &mut Context<Self>,
15269 ) {
15270 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15271
15272 let buffer = self.buffer.read(cx).snapshot(cx);
15273 let selections = self
15274 .selections
15275 .all::<usize>(&self.display_snapshot(cx))
15276 .into_iter()
15277 // subtracting the offset requires sorting
15278 .sorted_by_key(|i| i.start);
15279
15280 let full_edits = selections
15281 .into_iter()
15282 .filter_map(|selection| {
15283 let child = if selection.is_empty()
15284 && let Some((_, ancestor_range)) =
15285 buffer.syntax_ancestor(selection.start..selection.end)
15286 {
15287 ancestor_range
15288 } else {
15289 selection.range()
15290 };
15291
15292 let mut parent = child.clone();
15293 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15294 parent = ancestor_range;
15295 if parent.start < child.start || parent.end > child.end {
15296 break;
15297 }
15298 }
15299
15300 if parent == child {
15301 return None;
15302 }
15303 let text = buffer.text_for_range(child).collect::<String>();
15304 Some((selection.id, parent, text))
15305 })
15306 .collect::<Vec<_>>();
15307 if full_edits.is_empty() {
15308 return;
15309 }
15310
15311 self.transact(window, cx, |this, window, cx| {
15312 this.buffer.update(cx, |buffer, cx| {
15313 buffer.edit(
15314 full_edits
15315 .iter()
15316 .map(|(_, p, t)| (p.clone(), t.clone()))
15317 .collect::<Vec<_>>(),
15318 None,
15319 cx,
15320 );
15321 });
15322 this.change_selections(Default::default(), window, cx, |s| {
15323 let mut offset = 0;
15324 let mut selections = vec![];
15325 for (id, parent, text) in full_edits {
15326 let start = parent.start - offset;
15327 offset += parent.len() - text.len();
15328 selections.push(Selection {
15329 id,
15330 start,
15331 end: start + text.len(),
15332 reversed: false,
15333 goal: Default::default(),
15334 });
15335 }
15336 s.select(selections);
15337 });
15338 });
15339 }
15340
15341 pub fn select_next_syntax_node(
15342 &mut self,
15343 _: &SelectNextSyntaxNode,
15344 window: &mut Window,
15345 cx: &mut Context<Self>,
15346 ) {
15347 let old_selections: Box<[_]> = self
15348 .selections
15349 .all::<usize>(&self.display_snapshot(cx))
15350 .into();
15351 if old_selections.is_empty() {
15352 return;
15353 }
15354
15355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15356
15357 let buffer = self.buffer.read(cx).snapshot(cx);
15358 let mut selected_sibling = false;
15359
15360 let new_selections = old_selections
15361 .iter()
15362 .map(|selection| {
15363 let old_range = selection.start..selection.end;
15364
15365 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15366 let new_range = node.byte_range();
15367 selected_sibling = true;
15368 Selection {
15369 id: selection.id,
15370 start: new_range.start,
15371 end: new_range.end,
15372 goal: SelectionGoal::None,
15373 reversed: selection.reversed,
15374 }
15375 } else {
15376 selection.clone()
15377 }
15378 })
15379 .collect::<Vec<_>>();
15380
15381 if selected_sibling {
15382 self.change_selections(
15383 SelectionEffects::scroll(Autoscroll::fit()),
15384 window,
15385 cx,
15386 |s| {
15387 s.select(new_selections);
15388 },
15389 );
15390 }
15391 }
15392
15393 pub fn select_prev_syntax_node(
15394 &mut self,
15395 _: &SelectPreviousSyntaxNode,
15396 window: &mut Window,
15397 cx: &mut Context<Self>,
15398 ) {
15399 let old_selections: Box<[_]> = self
15400 .selections
15401 .all::<usize>(&self.display_snapshot(cx))
15402 .into();
15403 if old_selections.is_empty() {
15404 return;
15405 }
15406
15407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15408
15409 let buffer = self.buffer.read(cx).snapshot(cx);
15410 let mut selected_sibling = false;
15411
15412 let new_selections = old_selections
15413 .iter()
15414 .map(|selection| {
15415 let old_range = selection.start..selection.end;
15416
15417 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15418 let new_range = node.byte_range();
15419 selected_sibling = true;
15420 Selection {
15421 id: selection.id,
15422 start: new_range.start,
15423 end: new_range.end,
15424 goal: SelectionGoal::None,
15425 reversed: selection.reversed,
15426 }
15427 } else {
15428 selection.clone()
15429 }
15430 })
15431 .collect::<Vec<_>>();
15432
15433 if selected_sibling {
15434 self.change_selections(
15435 SelectionEffects::scroll(Autoscroll::fit()),
15436 window,
15437 cx,
15438 |s| {
15439 s.select(new_selections);
15440 },
15441 );
15442 }
15443 }
15444
15445 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15446 if !EditorSettings::get_global(cx).gutter.runnables {
15447 self.clear_tasks();
15448 return Task::ready(());
15449 }
15450 let project = self.project().map(Entity::downgrade);
15451 let task_sources = self.lsp_task_sources(cx);
15452 let multi_buffer = self.buffer.downgrade();
15453 cx.spawn_in(window, async move |editor, cx| {
15454 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15455 let Some(project) = project.and_then(|p| p.upgrade()) else {
15456 return;
15457 };
15458 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15459 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15460 }) else {
15461 return;
15462 };
15463
15464 let hide_runnables = project
15465 .update(cx, |project, _| project.is_via_collab())
15466 .unwrap_or(true);
15467 if hide_runnables {
15468 return;
15469 }
15470 let new_rows =
15471 cx.background_spawn({
15472 let snapshot = display_snapshot.clone();
15473 async move {
15474 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15475 }
15476 })
15477 .await;
15478 let Ok(lsp_tasks) =
15479 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15480 else {
15481 return;
15482 };
15483 let lsp_tasks = lsp_tasks.await;
15484
15485 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15486 lsp_tasks
15487 .into_iter()
15488 .flat_map(|(kind, tasks)| {
15489 tasks.into_iter().filter_map(move |(location, task)| {
15490 Some((kind.clone(), location?, task))
15491 })
15492 })
15493 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15494 let buffer = location.target.buffer;
15495 let buffer_snapshot = buffer.read(cx).snapshot();
15496 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15497 |(excerpt_id, snapshot, _)| {
15498 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15499 display_snapshot
15500 .buffer_snapshot()
15501 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15502 } else {
15503 None
15504 }
15505 },
15506 );
15507 if let Some(offset) = offset {
15508 let task_buffer_range =
15509 location.target.range.to_point(&buffer_snapshot);
15510 let context_buffer_range =
15511 task_buffer_range.to_offset(&buffer_snapshot);
15512 let context_range = BufferOffset(context_buffer_range.start)
15513 ..BufferOffset(context_buffer_range.end);
15514
15515 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15516 .or_insert_with(|| RunnableTasks {
15517 templates: Vec::new(),
15518 offset,
15519 column: task_buffer_range.start.column,
15520 extra_variables: HashMap::default(),
15521 context_range,
15522 })
15523 .templates
15524 .push((kind, task.original_task().clone()));
15525 }
15526
15527 acc
15528 })
15529 }) else {
15530 return;
15531 };
15532
15533 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15534 buffer.language_settings(cx).tasks.prefer_lsp
15535 }) else {
15536 return;
15537 };
15538
15539 let rows = Self::runnable_rows(
15540 project,
15541 display_snapshot,
15542 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15543 new_rows,
15544 cx.clone(),
15545 )
15546 .await;
15547 editor
15548 .update(cx, |editor, _| {
15549 editor.clear_tasks();
15550 for (key, mut value) in rows {
15551 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15552 value.templates.extend(lsp_tasks.templates);
15553 }
15554
15555 editor.insert_tasks(key, value);
15556 }
15557 for (key, value) in lsp_tasks_by_rows {
15558 editor.insert_tasks(key, value);
15559 }
15560 })
15561 .ok();
15562 })
15563 }
15564 fn fetch_runnable_ranges(
15565 snapshot: &DisplaySnapshot,
15566 range: Range<Anchor>,
15567 ) -> Vec<language::RunnableRange> {
15568 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15569 }
15570
15571 fn runnable_rows(
15572 project: Entity<Project>,
15573 snapshot: DisplaySnapshot,
15574 prefer_lsp: bool,
15575 runnable_ranges: Vec<RunnableRange>,
15576 cx: AsyncWindowContext,
15577 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15578 cx.spawn(async move |cx| {
15579 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15580 for mut runnable in runnable_ranges {
15581 let Some(tasks) = cx
15582 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15583 .ok()
15584 else {
15585 continue;
15586 };
15587 let mut tasks = tasks.await;
15588
15589 if prefer_lsp {
15590 tasks.retain(|(task_kind, _)| {
15591 !matches!(task_kind, TaskSourceKind::Language { .. })
15592 });
15593 }
15594 if tasks.is_empty() {
15595 continue;
15596 }
15597
15598 let point = runnable
15599 .run_range
15600 .start
15601 .to_point(&snapshot.buffer_snapshot());
15602 let Some(row) = snapshot
15603 .buffer_snapshot()
15604 .buffer_line_for_row(MultiBufferRow(point.row))
15605 .map(|(_, range)| range.start.row)
15606 else {
15607 continue;
15608 };
15609
15610 let context_range =
15611 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15612 runnable_rows.push((
15613 (runnable.buffer_id, row),
15614 RunnableTasks {
15615 templates: tasks,
15616 offset: snapshot
15617 .buffer_snapshot()
15618 .anchor_before(runnable.run_range.start),
15619 context_range,
15620 column: point.column,
15621 extra_variables: runnable.extra_captures,
15622 },
15623 ));
15624 }
15625 runnable_rows
15626 })
15627 }
15628
15629 fn templates_with_tags(
15630 project: &Entity<Project>,
15631 runnable: &mut Runnable,
15632 cx: &mut App,
15633 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15634 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15635 let (worktree_id, file) = project
15636 .buffer_for_id(runnable.buffer, cx)
15637 .and_then(|buffer| buffer.read(cx).file())
15638 .map(|file| (file.worktree_id(cx), file.clone()))
15639 .unzip();
15640
15641 (
15642 project.task_store().read(cx).task_inventory().cloned(),
15643 worktree_id,
15644 file,
15645 )
15646 });
15647
15648 let tags = mem::take(&mut runnable.tags);
15649 let language = runnable.language.clone();
15650 cx.spawn(async move |cx| {
15651 let mut templates_with_tags = Vec::new();
15652 if let Some(inventory) = inventory {
15653 for RunnableTag(tag) in tags {
15654 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15655 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15656 }) else {
15657 return templates_with_tags;
15658 };
15659 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15660 move |(_, template)| {
15661 template.tags.iter().any(|source_tag| source_tag == &tag)
15662 },
15663 ));
15664 }
15665 }
15666 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15667
15668 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15669 // Strongest source wins; if we have worktree tag binding, prefer that to
15670 // global and language bindings;
15671 // if we have a global binding, prefer that to language binding.
15672 let first_mismatch = templates_with_tags
15673 .iter()
15674 .position(|(tag_source, _)| tag_source != leading_tag_source);
15675 if let Some(index) = first_mismatch {
15676 templates_with_tags.truncate(index);
15677 }
15678 }
15679
15680 templates_with_tags
15681 })
15682 }
15683
15684 pub fn move_to_enclosing_bracket(
15685 &mut self,
15686 _: &MoveToEnclosingBracket,
15687 window: &mut Window,
15688 cx: &mut Context<Self>,
15689 ) {
15690 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15691 self.change_selections(Default::default(), window, cx, |s| {
15692 s.move_offsets_with(|snapshot, selection| {
15693 let Some(enclosing_bracket_ranges) =
15694 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15695 else {
15696 return;
15697 };
15698
15699 let mut best_length = usize::MAX;
15700 let mut best_inside = false;
15701 let mut best_in_bracket_range = false;
15702 let mut best_destination = None;
15703 for (open, close) in enclosing_bracket_ranges {
15704 let close = close.to_inclusive();
15705 let length = close.end() - open.start;
15706 let inside = selection.start >= open.end && selection.end <= *close.start();
15707 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15708 || close.contains(&selection.head());
15709
15710 // If best is next to a bracket and current isn't, skip
15711 if !in_bracket_range && best_in_bracket_range {
15712 continue;
15713 }
15714
15715 // Prefer smaller lengths unless best is inside and current isn't
15716 if length > best_length && (best_inside || !inside) {
15717 continue;
15718 }
15719
15720 best_length = length;
15721 best_inside = inside;
15722 best_in_bracket_range = in_bracket_range;
15723 best_destination = Some(
15724 if close.contains(&selection.start) && close.contains(&selection.end) {
15725 if inside { open.end } else { open.start }
15726 } else if inside {
15727 *close.start()
15728 } else {
15729 *close.end()
15730 },
15731 );
15732 }
15733
15734 if let Some(destination) = best_destination {
15735 selection.collapse_to(destination, SelectionGoal::None);
15736 }
15737 })
15738 });
15739 }
15740
15741 pub fn undo_selection(
15742 &mut self,
15743 _: &UndoSelection,
15744 window: &mut Window,
15745 cx: &mut Context<Self>,
15746 ) {
15747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15748 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15749 self.selection_history.mode = SelectionHistoryMode::Undoing;
15750 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15751 this.end_selection(window, cx);
15752 this.change_selections(
15753 SelectionEffects::scroll(Autoscroll::newest()),
15754 window,
15755 cx,
15756 |s| s.select_anchors(entry.selections.to_vec()),
15757 );
15758 });
15759 self.selection_history.mode = SelectionHistoryMode::Normal;
15760
15761 self.select_next_state = entry.select_next_state;
15762 self.select_prev_state = entry.select_prev_state;
15763 self.add_selections_state = entry.add_selections_state;
15764 }
15765 }
15766
15767 pub fn redo_selection(
15768 &mut self,
15769 _: &RedoSelection,
15770 window: &mut Window,
15771 cx: &mut Context<Self>,
15772 ) {
15773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15774 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15775 self.selection_history.mode = SelectionHistoryMode::Redoing;
15776 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15777 this.end_selection(window, cx);
15778 this.change_selections(
15779 SelectionEffects::scroll(Autoscroll::newest()),
15780 window,
15781 cx,
15782 |s| s.select_anchors(entry.selections.to_vec()),
15783 );
15784 });
15785 self.selection_history.mode = SelectionHistoryMode::Normal;
15786
15787 self.select_next_state = entry.select_next_state;
15788 self.select_prev_state = entry.select_prev_state;
15789 self.add_selections_state = entry.add_selections_state;
15790 }
15791 }
15792
15793 pub fn expand_excerpts(
15794 &mut self,
15795 action: &ExpandExcerpts,
15796 _: &mut Window,
15797 cx: &mut Context<Self>,
15798 ) {
15799 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15800 }
15801
15802 pub fn expand_excerpts_down(
15803 &mut self,
15804 action: &ExpandExcerptsDown,
15805 _: &mut Window,
15806 cx: &mut Context<Self>,
15807 ) {
15808 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15809 }
15810
15811 pub fn expand_excerpts_up(
15812 &mut self,
15813 action: &ExpandExcerptsUp,
15814 _: &mut Window,
15815 cx: &mut Context<Self>,
15816 ) {
15817 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15818 }
15819
15820 pub fn expand_excerpts_for_direction(
15821 &mut self,
15822 lines: u32,
15823 direction: ExpandExcerptDirection,
15824
15825 cx: &mut Context<Self>,
15826 ) {
15827 let selections = self.selections.disjoint_anchors_arc();
15828
15829 let lines = if lines == 0 {
15830 EditorSettings::get_global(cx).expand_excerpt_lines
15831 } else {
15832 lines
15833 };
15834
15835 self.buffer.update(cx, |buffer, cx| {
15836 let snapshot = buffer.snapshot(cx);
15837 let mut excerpt_ids = selections
15838 .iter()
15839 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15840 .collect::<Vec<_>>();
15841 excerpt_ids.sort();
15842 excerpt_ids.dedup();
15843 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
15844 })
15845 }
15846
15847 pub fn expand_excerpt(
15848 &mut self,
15849 excerpt: ExcerptId,
15850 direction: ExpandExcerptDirection,
15851 window: &mut Window,
15852 cx: &mut Context<Self>,
15853 ) {
15854 let current_scroll_position = self.scroll_position(cx);
15855 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
15856 let mut should_scroll_up = false;
15857
15858 if direction == ExpandExcerptDirection::Down {
15859 let multi_buffer = self.buffer.read(cx);
15860 let snapshot = multi_buffer.snapshot(cx);
15861 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
15862 && let Some(buffer) = multi_buffer.buffer(buffer_id)
15863 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
15864 {
15865 let buffer_snapshot = buffer.read(cx).snapshot();
15866 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
15867 let last_row = buffer_snapshot.max_point().row;
15868 let lines_below = last_row.saturating_sub(excerpt_end_row);
15869 should_scroll_up = lines_below >= lines_to_expand;
15870 }
15871 }
15872
15873 self.buffer.update(cx, |buffer, cx| {
15874 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
15875 });
15876
15877 if should_scroll_up {
15878 let new_scroll_position =
15879 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
15880 self.set_scroll_position(new_scroll_position, window, cx);
15881 }
15882 }
15883
15884 pub fn go_to_singleton_buffer_point(
15885 &mut self,
15886 point: Point,
15887 window: &mut Window,
15888 cx: &mut Context<Self>,
15889 ) {
15890 self.go_to_singleton_buffer_range(point..point, window, cx);
15891 }
15892
15893 pub fn go_to_singleton_buffer_range(
15894 &mut self,
15895 range: Range<Point>,
15896 window: &mut Window,
15897 cx: &mut Context<Self>,
15898 ) {
15899 let multibuffer = self.buffer().read(cx);
15900 let Some(buffer) = multibuffer.as_singleton() else {
15901 return;
15902 };
15903 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
15904 return;
15905 };
15906 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
15907 return;
15908 };
15909 self.change_selections(
15910 SelectionEffects::default().nav_history(true),
15911 window,
15912 cx,
15913 |s| s.select_anchor_ranges([start..end]),
15914 );
15915 }
15916
15917 pub fn go_to_diagnostic(
15918 &mut self,
15919 action: &GoToDiagnostic,
15920 window: &mut Window,
15921 cx: &mut Context<Self>,
15922 ) {
15923 if !self.diagnostics_enabled() {
15924 return;
15925 }
15926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15927 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
15928 }
15929
15930 pub fn go_to_prev_diagnostic(
15931 &mut self,
15932 action: &GoToPreviousDiagnostic,
15933 window: &mut Window,
15934 cx: &mut Context<Self>,
15935 ) {
15936 if !self.diagnostics_enabled() {
15937 return;
15938 }
15939 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15940 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
15941 }
15942
15943 pub fn go_to_diagnostic_impl(
15944 &mut self,
15945 direction: Direction,
15946 severity: GoToDiagnosticSeverityFilter,
15947 window: &mut Window,
15948 cx: &mut Context<Self>,
15949 ) {
15950 let buffer = self.buffer.read(cx).snapshot(cx);
15951 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
15952
15953 let mut active_group_id = None;
15954 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
15955 && active_group.active_range.start.to_offset(&buffer) == selection.start
15956 {
15957 active_group_id = Some(active_group.group_id);
15958 }
15959
15960 fn filtered<'a>(
15961 snapshot: EditorSnapshot,
15962 severity: GoToDiagnosticSeverityFilter,
15963 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
15964 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
15965 diagnostics
15966 .filter(move |entry| severity.matches(entry.diagnostic.severity))
15967 .filter(|entry| entry.range.start != entry.range.end)
15968 .filter(|entry| !entry.diagnostic.is_unnecessary)
15969 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
15970 }
15971
15972 let snapshot = self.snapshot(window, cx);
15973 let before = filtered(
15974 snapshot.clone(),
15975 severity,
15976 buffer
15977 .diagnostics_in_range(0..selection.start)
15978 .filter(|entry| entry.range.start <= selection.start),
15979 );
15980 let after = filtered(
15981 snapshot,
15982 severity,
15983 buffer
15984 .diagnostics_in_range(selection.start..buffer.len())
15985 .filter(|entry| entry.range.start >= selection.start),
15986 );
15987
15988 let mut found: Option<DiagnosticEntryRef<usize>> = None;
15989 if direction == Direction::Prev {
15990 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
15991 {
15992 for diagnostic in prev_diagnostics.into_iter().rev() {
15993 if diagnostic.range.start != selection.start
15994 || active_group_id
15995 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
15996 {
15997 found = Some(diagnostic);
15998 break 'outer;
15999 }
16000 }
16001 }
16002 } else {
16003 for diagnostic in after.chain(before) {
16004 if diagnostic.range.start != selection.start
16005 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16006 {
16007 found = Some(diagnostic);
16008 break;
16009 }
16010 }
16011 }
16012 let Some(next_diagnostic) = found else {
16013 return;
16014 };
16015
16016 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16017 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16018 return;
16019 };
16020 self.change_selections(Default::default(), window, cx, |s| {
16021 s.select_ranges(vec![
16022 next_diagnostic.range.start..next_diagnostic.range.start,
16023 ])
16024 });
16025 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16026 self.refresh_edit_prediction(false, true, window, cx);
16027 }
16028
16029 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16031 let snapshot = self.snapshot(window, cx);
16032 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16033 self.go_to_hunk_before_or_after_position(
16034 &snapshot,
16035 selection.head(),
16036 Direction::Next,
16037 window,
16038 cx,
16039 );
16040 }
16041
16042 pub fn go_to_hunk_before_or_after_position(
16043 &mut self,
16044 snapshot: &EditorSnapshot,
16045 position: Point,
16046 direction: Direction,
16047 window: &mut Window,
16048 cx: &mut Context<Editor>,
16049 ) {
16050 let row = if direction == Direction::Next {
16051 self.hunk_after_position(snapshot, position)
16052 .map(|hunk| hunk.row_range.start)
16053 } else {
16054 self.hunk_before_position(snapshot, position)
16055 };
16056
16057 if let Some(row) = row {
16058 let destination = Point::new(row.0, 0);
16059 let autoscroll = Autoscroll::center();
16060
16061 self.unfold_ranges(&[destination..destination], false, false, cx);
16062 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16063 s.select_ranges([destination..destination]);
16064 });
16065 }
16066 }
16067
16068 fn hunk_after_position(
16069 &mut self,
16070 snapshot: &EditorSnapshot,
16071 position: Point,
16072 ) -> Option<MultiBufferDiffHunk> {
16073 snapshot
16074 .buffer_snapshot()
16075 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16076 .find(|hunk| hunk.row_range.start.0 > position.row)
16077 .or_else(|| {
16078 snapshot
16079 .buffer_snapshot()
16080 .diff_hunks_in_range(Point::zero()..position)
16081 .find(|hunk| hunk.row_range.end.0 < position.row)
16082 })
16083 }
16084
16085 fn go_to_prev_hunk(
16086 &mut self,
16087 _: &GoToPreviousHunk,
16088 window: &mut Window,
16089 cx: &mut Context<Self>,
16090 ) {
16091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16092 let snapshot = self.snapshot(window, cx);
16093 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16094 self.go_to_hunk_before_or_after_position(
16095 &snapshot,
16096 selection.head(),
16097 Direction::Prev,
16098 window,
16099 cx,
16100 );
16101 }
16102
16103 fn hunk_before_position(
16104 &mut self,
16105 snapshot: &EditorSnapshot,
16106 position: Point,
16107 ) -> Option<MultiBufferRow> {
16108 snapshot
16109 .buffer_snapshot()
16110 .diff_hunk_before(position)
16111 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16112 }
16113
16114 fn go_to_next_change(
16115 &mut self,
16116 _: &GoToNextChange,
16117 window: &mut Window,
16118 cx: &mut Context<Self>,
16119 ) {
16120 if let Some(selections) = self
16121 .change_list
16122 .next_change(1, Direction::Next)
16123 .map(|s| s.to_vec())
16124 {
16125 self.change_selections(Default::default(), window, cx, |s| {
16126 let map = s.display_map();
16127 s.select_display_ranges(selections.iter().map(|a| {
16128 let point = a.to_display_point(&map);
16129 point..point
16130 }))
16131 })
16132 }
16133 }
16134
16135 fn go_to_previous_change(
16136 &mut self,
16137 _: &GoToPreviousChange,
16138 window: &mut Window,
16139 cx: &mut Context<Self>,
16140 ) {
16141 if let Some(selections) = self
16142 .change_list
16143 .next_change(1, Direction::Prev)
16144 .map(|s| s.to_vec())
16145 {
16146 self.change_selections(Default::default(), window, cx, |s| {
16147 let map = s.display_map();
16148 s.select_display_ranges(selections.iter().map(|a| {
16149 let point = a.to_display_point(&map);
16150 point..point
16151 }))
16152 })
16153 }
16154 }
16155
16156 pub fn go_to_next_document_highlight(
16157 &mut self,
16158 _: &GoToNextDocumentHighlight,
16159 window: &mut Window,
16160 cx: &mut Context<Self>,
16161 ) {
16162 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16163 }
16164
16165 pub fn go_to_prev_document_highlight(
16166 &mut self,
16167 _: &GoToPreviousDocumentHighlight,
16168 window: &mut Window,
16169 cx: &mut Context<Self>,
16170 ) {
16171 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16172 }
16173
16174 pub fn go_to_document_highlight_before_or_after_position(
16175 &mut self,
16176 direction: Direction,
16177 window: &mut Window,
16178 cx: &mut Context<Editor>,
16179 ) {
16180 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16181 let snapshot = self.snapshot(window, cx);
16182 let buffer = &snapshot.buffer_snapshot();
16183 let position = self
16184 .selections
16185 .newest::<Point>(&snapshot.display_snapshot)
16186 .head();
16187 let anchor_position = buffer.anchor_after(position);
16188
16189 // Get all document highlights (both read and write)
16190 let mut all_highlights = Vec::new();
16191
16192 if let Some((_, read_highlights)) = self
16193 .background_highlights
16194 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16195 {
16196 all_highlights.extend(read_highlights.iter());
16197 }
16198
16199 if let Some((_, write_highlights)) = self
16200 .background_highlights
16201 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16202 {
16203 all_highlights.extend(write_highlights.iter());
16204 }
16205
16206 if all_highlights.is_empty() {
16207 return;
16208 }
16209
16210 // Sort highlights by position
16211 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16212
16213 let target_highlight = match direction {
16214 Direction::Next => {
16215 // Find the first highlight after the current position
16216 all_highlights
16217 .iter()
16218 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16219 }
16220 Direction::Prev => {
16221 // Find the last highlight before the current position
16222 all_highlights
16223 .iter()
16224 .rev()
16225 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16226 }
16227 };
16228
16229 if let Some(highlight) = target_highlight {
16230 let destination = highlight.start.to_point(buffer);
16231 let autoscroll = Autoscroll::center();
16232
16233 self.unfold_ranges(&[destination..destination], false, false, cx);
16234 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16235 s.select_ranges([destination..destination]);
16236 });
16237 }
16238 }
16239
16240 fn go_to_line<T: 'static>(
16241 &mut self,
16242 position: Anchor,
16243 highlight_color: Option<Hsla>,
16244 window: &mut Window,
16245 cx: &mut Context<Self>,
16246 ) {
16247 let snapshot = self.snapshot(window, cx).display_snapshot;
16248 let position = position.to_point(&snapshot.buffer_snapshot());
16249 let start = snapshot
16250 .buffer_snapshot()
16251 .clip_point(Point::new(position.row, 0), Bias::Left);
16252 let end = start + Point::new(1, 0);
16253 let start = snapshot.buffer_snapshot().anchor_before(start);
16254 let end = snapshot.buffer_snapshot().anchor_before(end);
16255
16256 self.highlight_rows::<T>(
16257 start..end,
16258 highlight_color
16259 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16260 Default::default(),
16261 cx,
16262 );
16263
16264 if self.buffer.read(cx).is_singleton() {
16265 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16266 }
16267 }
16268
16269 pub fn go_to_definition(
16270 &mut self,
16271 _: &GoToDefinition,
16272 window: &mut Window,
16273 cx: &mut Context<Self>,
16274 ) -> Task<Result<Navigated>> {
16275 let definition =
16276 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16277 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16278 cx.spawn_in(window, async move |editor, cx| {
16279 if definition.await? == Navigated::Yes {
16280 return Ok(Navigated::Yes);
16281 }
16282 match fallback_strategy {
16283 GoToDefinitionFallback::None => Ok(Navigated::No),
16284 GoToDefinitionFallback::FindAllReferences => {
16285 match editor.update_in(cx, |editor, window, cx| {
16286 editor.find_all_references(&FindAllReferences, window, cx)
16287 })? {
16288 Some(references) => references.await,
16289 None => Ok(Navigated::No),
16290 }
16291 }
16292 }
16293 })
16294 }
16295
16296 pub fn go_to_declaration(
16297 &mut self,
16298 _: &GoToDeclaration,
16299 window: &mut Window,
16300 cx: &mut Context<Self>,
16301 ) -> Task<Result<Navigated>> {
16302 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16303 }
16304
16305 pub fn go_to_declaration_split(
16306 &mut self,
16307 _: &GoToDeclaration,
16308 window: &mut Window,
16309 cx: &mut Context<Self>,
16310 ) -> Task<Result<Navigated>> {
16311 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16312 }
16313
16314 pub fn go_to_implementation(
16315 &mut self,
16316 _: &GoToImplementation,
16317 window: &mut Window,
16318 cx: &mut Context<Self>,
16319 ) -> Task<Result<Navigated>> {
16320 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16321 }
16322
16323 pub fn go_to_implementation_split(
16324 &mut self,
16325 _: &GoToImplementationSplit,
16326 window: &mut Window,
16327 cx: &mut Context<Self>,
16328 ) -> Task<Result<Navigated>> {
16329 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16330 }
16331
16332 pub fn go_to_type_definition(
16333 &mut self,
16334 _: &GoToTypeDefinition,
16335 window: &mut Window,
16336 cx: &mut Context<Self>,
16337 ) -> Task<Result<Navigated>> {
16338 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16339 }
16340
16341 pub fn go_to_definition_split(
16342 &mut self,
16343 _: &GoToDefinitionSplit,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) -> Task<Result<Navigated>> {
16347 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16348 }
16349
16350 pub fn go_to_type_definition_split(
16351 &mut self,
16352 _: &GoToTypeDefinitionSplit,
16353 window: &mut Window,
16354 cx: &mut Context<Self>,
16355 ) -> Task<Result<Navigated>> {
16356 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16357 }
16358
16359 fn go_to_definition_of_kind(
16360 &mut self,
16361 kind: GotoDefinitionKind,
16362 split: bool,
16363 window: &mut Window,
16364 cx: &mut Context<Self>,
16365 ) -> Task<Result<Navigated>> {
16366 let Some(provider) = self.semantics_provider.clone() else {
16367 return Task::ready(Ok(Navigated::No));
16368 };
16369 let head = self
16370 .selections
16371 .newest::<usize>(&self.display_snapshot(cx))
16372 .head();
16373 let buffer = self.buffer.read(cx);
16374 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16375 return Task::ready(Ok(Navigated::No));
16376 };
16377 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16378 return Task::ready(Ok(Navigated::No));
16379 };
16380
16381 cx.spawn_in(window, async move |editor, cx| {
16382 let Some(definitions) = definitions.await? else {
16383 return Ok(Navigated::No);
16384 };
16385 let navigated = editor
16386 .update_in(cx, |editor, window, cx| {
16387 editor.navigate_to_hover_links(
16388 Some(kind),
16389 definitions
16390 .into_iter()
16391 .filter(|location| {
16392 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16393 })
16394 .map(HoverLink::Text)
16395 .collect::<Vec<_>>(),
16396 split,
16397 window,
16398 cx,
16399 )
16400 })?
16401 .await?;
16402 anyhow::Ok(navigated)
16403 })
16404 }
16405
16406 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16407 let selection = self.selections.newest_anchor();
16408 let head = selection.head();
16409 let tail = selection.tail();
16410
16411 let Some((buffer, start_position)) =
16412 self.buffer.read(cx).text_anchor_for_position(head, cx)
16413 else {
16414 return;
16415 };
16416
16417 let end_position = if head != tail {
16418 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16419 return;
16420 };
16421 Some(pos)
16422 } else {
16423 None
16424 };
16425
16426 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16427 let url = if let Some(end_pos) = end_position {
16428 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16429 } else {
16430 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16431 };
16432
16433 if let Some(url) = url {
16434 cx.update(|window, cx| {
16435 if parse_zed_link(&url, cx).is_some() {
16436 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16437 } else {
16438 cx.open_url(&url);
16439 }
16440 })?;
16441 }
16442
16443 anyhow::Ok(())
16444 });
16445
16446 url_finder.detach();
16447 }
16448
16449 pub fn open_selected_filename(
16450 &mut self,
16451 _: &OpenSelectedFilename,
16452 window: &mut Window,
16453 cx: &mut Context<Self>,
16454 ) {
16455 let Some(workspace) = self.workspace() else {
16456 return;
16457 };
16458
16459 let position = self.selections.newest_anchor().head();
16460
16461 let Some((buffer, buffer_position)) =
16462 self.buffer.read(cx).text_anchor_for_position(position, cx)
16463 else {
16464 return;
16465 };
16466
16467 let project = self.project.clone();
16468
16469 cx.spawn_in(window, async move |_, cx| {
16470 let result = find_file(&buffer, project, buffer_position, cx).await;
16471
16472 if let Some((_, path)) = result {
16473 workspace
16474 .update_in(cx, |workspace, window, cx| {
16475 workspace.open_resolved_path(path, window, cx)
16476 })?
16477 .await?;
16478 }
16479 anyhow::Ok(())
16480 })
16481 .detach();
16482 }
16483
16484 pub(crate) fn navigate_to_hover_links(
16485 &mut self,
16486 kind: Option<GotoDefinitionKind>,
16487 definitions: Vec<HoverLink>,
16488 split: bool,
16489 window: &mut Window,
16490 cx: &mut Context<Editor>,
16491 ) -> Task<Result<Navigated>> {
16492 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16493 let mut first_url_or_file = None;
16494 let definitions: Vec<_> = definitions
16495 .into_iter()
16496 .filter_map(|def| match def {
16497 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16498 HoverLink::InlayHint(lsp_location, server_id) => {
16499 let computation =
16500 self.compute_target_location(lsp_location, server_id, window, cx);
16501 Some(cx.background_spawn(computation))
16502 }
16503 HoverLink::Url(url) => {
16504 first_url_or_file = Some(Either::Left(url));
16505 None
16506 }
16507 HoverLink::File(path) => {
16508 first_url_or_file = Some(Either::Right(path));
16509 None
16510 }
16511 })
16512 .collect();
16513
16514 let workspace = self.workspace();
16515
16516 cx.spawn_in(window, async move |editor, cx| {
16517 let locations: Vec<Location> = future::join_all(definitions)
16518 .await
16519 .into_iter()
16520 .filter_map(|location| location.transpose())
16521 .collect::<Result<_>>()
16522 .context("location tasks")?;
16523 let mut locations = cx.update(|_, cx| {
16524 locations
16525 .into_iter()
16526 .map(|location| {
16527 let buffer = location.buffer.read(cx);
16528 (location.buffer, location.range.to_point(buffer))
16529 })
16530 .into_group_map()
16531 })?;
16532 let mut num_locations = 0;
16533 for ranges in locations.values_mut() {
16534 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16535 ranges.dedup();
16536 num_locations += ranges.len();
16537 }
16538
16539 if num_locations > 1 {
16540 let Some(workspace) = workspace else {
16541 return Ok(Navigated::No);
16542 };
16543
16544 let tab_kind = match kind {
16545 Some(GotoDefinitionKind::Implementation) => "Implementations",
16546 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16547 Some(GotoDefinitionKind::Declaration) => "Declarations",
16548 Some(GotoDefinitionKind::Type) => "Types",
16549 };
16550 let title = editor
16551 .update_in(cx, |_, _, cx| {
16552 let target = locations
16553 .iter()
16554 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16555 .map(|(buffer, location)| {
16556 buffer
16557 .read(cx)
16558 .text_for_range(location.clone())
16559 .collect::<String>()
16560 })
16561 .filter(|text| !text.contains('\n'))
16562 .unique()
16563 .take(3)
16564 .join(", ");
16565 if target.is_empty() {
16566 tab_kind.to_owned()
16567 } else {
16568 format!("{tab_kind} for {target}")
16569 }
16570 })
16571 .context("buffer title")?;
16572
16573 let opened = workspace
16574 .update_in(cx, |workspace, window, cx| {
16575 Self::open_locations_in_multibuffer(
16576 workspace,
16577 locations,
16578 title,
16579 split,
16580 MultibufferSelectionMode::First,
16581 window,
16582 cx,
16583 )
16584 })
16585 .is_ok();
16586
16587 anyhow::Ok(Navigated::from_bool(opened))
16588 } else if num_locations == 0 {
16589 // If there is one url or file, open it directly
16590 match first_url_or_file {
16591 Some(Either::Left(url)) => {
16592 cx.update(|_, cx| cx.open_url(&url))?;
16593 Ok(Navigated::Yes)
16594 }
16595 Some(Either::Right(path)) => {
16596 let Some(workspace) = workspace else {
16597 return Ok(Navigated::No);
16598 };
16599
16600 workspace
16601 .update_in(cx, |workspace, window, cx| {
16602 workspace.open_resolved_path(path, window, cx)
16603 })?
16604 .await?;
16605 Ok(Navigated::Yes)
16606 }
16607 None => Ok(Navigated::No),
16608 }
16609 } else {
16610 let Some(workspace) = workspace else {
16611 return Ok(Navigated::No);
16612 };
16613
16614 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16615 let target_range = target_ranges.first().unwrap().clone();
16616
16617 editor.update_in(cx, |editor, window, cx| {
16618 let range = target_range.to_point(target_buffer.read(cx));
16619 let range = editor.range_for_match(&range);
16620 let range = collapse_multiline_range(range);
16621
16622 if !split
16623 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16624 {
16625 editor.go_to_singleton_buffer_range(range, window, cx);
16626 } else {
16627 let pane = workspace.read(cx).active_pane().clone();
16628 window.defer(cx, move |window, cx| {
16629 let target_editor: Entity<Self> =
16630 workspace.update(cx, |workspace, cx| {
16631 let pane = if split {
16632 workspace.adjacent_pane(window, cx)
16633 } else {
16634 workspace.active_pane().clone()
16635 };
16636
16637 workspace.open_project_item(
16638 pane,
16639 target_buffer.clone(),
16640 true,
16641 true,
16642 window,
16643 cx,
16644 )
16645 });
16646 target_editor.update(cx, |target_editor, cx| {
16647 // When selecting a definition in a different buffer, disable the nav history
16648 // to avoid creating a history entry at the previous cursor location.
16649 pane.update(cx, |pane, _| pane.disable_history());
16650 target_editor.go_to_singleton_buffer_range(range, window, cx);
16651 pane.update(cx, |pane, _| pane.enable_history());
16652 });
16653 });
16654 }
16655 Navigated::Yes
16656 })
16657 }
16658 })
16659 }
16660
16661 fn compute_target_location(
16662 &self,
16663 lsp_location: lsp::Location,
16664 server_id: LanguageServerId,
16665 window: &mut Window,
16666 cx: &mut Context<Self>,
16667 ) -> Task<anyhow::Result<Option<Location>>> {
16668 let Some(project) = self.project.clone() else {
16669 return Task::ready(Ok(None));
16670 };
16671
16672 cx.spawn_in(window, async move |editor, cx| {
16673 let location_task = editor.update(cx, |_, cx| {
16674 project.update(cx, |project, cx| {
16675 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16676 })
16677 })?;
16678 let location = Some({
16679 let target_buffer_handle = location_task.await.context("open local buffer")?;
16680 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16681 let target_start = target_buffer
16682 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16683 let target_end = target_buffer
16684 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16685 target_buffer.anchor_after(target_start)
16686 ..target_buffer.anchor_before(target_end)
16687 })?;
16688 Location {
16689 buffer: target_buffer_handle,
16690 range,
16691 }
16692 });
16693 Ok(location)
16694 })
16695 }
16696
16697 pub fn find_all_references(
16698 &mut self,
16699 _: &FindAllReferences,
16700 window: &mut Window,
16701 cx: &mut Context<Self>,
16702 ) -> Option<Task<Result<Navigated>>> {
16703 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16704 let multi_buffer = self.buffer.read(cx);
16705 let head = selection.head();
16706
16707 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16708 let head_anchor = multi_buffer_snapshot.anchor_at(
16709 head,
16710 if head < selection.tail() {
16711 Bias::Right
16712 } else {
16713 Bias::Left
16714 },
16715 );
16716
16717 match self
16718 .find_all_references_task_sources
16719 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16720 {
16721 Ok(_) => {
16722 log::info!(
16723 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16724 );
16725 return None;
16726 }
16727 Err(i) => {
16728 self.find_all_references_task_sources.insert(i, head_anchor);
16729 }
16730 }
16731
16732 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16733 let workspace = self.workspace()?;
16734 let project = workspace.read(cx).project().clone();
16735 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16736 Some(cx.spawn_in(window, async move |editor, cx| {
16737 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16738 if let Ok(i) = editor
16739 .find_all_references_task_sources
16740 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16741 {
16742 editor.find_all_references_task_sources.remove(i);
16743 }
16744 });
16745
16746 let Some(locations) = references.await? else {
16747 return anyhow::Ok(Navigated::No);
16748 };
16749 let mut locations = cx.update(|_, cx| {
16750 locations
16751 .into_iter()
16752 .map(|location| {
16753 let buffer = location.buffer.read(cx);
16754 (location.buffer, location.range.to_point(buffer))
16755 })
16756 .into_group_map()
16757 })?;
16758 if locations.is_empty() {
16759 return anyhow::Ok(Navigated::No);
16760 }
16761 for ranges in locations.values_mut() {
16762 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16763 ranges.dedup();
16764 }
16765
16766 workspace.update_in(cx, |workspace, window, cx| {
16767 let target = locations
16768 .iter()
16769 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16770 .map(|(buffer, location)| {
16771 buffer
16772 .read(cx)
16773 .text_for_range(location.clone())
16774 .collect::<String>()
16775 })
16776 .filter(|text| !text.contains('\n'))
16777 .unique()
16778 .take(3)
16779 .join(", ");
16780 let title = if target.is_empty() {
16781 "References".to_owned()
16782 } else {
16783 format!("References to {target}")
16784 };
16785 Self::open_locations_in_multibuffer(
16786 workspace,
16787 locations,
16788 title,
16789 false,
16790 MultibufferSelectionMode::First,
16791 window,
16792 cx,
16793 );
16794 Navigated::Yes
16795 })
16796 }))
16797 }
16798
16799 /// Opens a multibuffer with the given project locations in it
16800 pub fn open_locations_in_multibuffer(
16801 workspace: &mut Workspace,
16802 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16803 title: String,
16804 split: bool,
16805 multibuffer_selection_mode: MultibufferSelectionMode,
16806 window: &mut Window,
16807 cx: &mut Context<Workspace>,
16808 ) {
16809 if locations.is_empty() {
16810 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16811 return;
16812 }
16813
16814 let capability = workspace.project().read(cx).capability();
16815 let mut ranges = <Vec<Range<Anchor>>>::new();
16816
16817 // a key to find existing multibuffer editors with the same set of locations
16818 // to prevent us from opening more and more multibuffer tabs for searches and the like
16819 let mut key = (title.clone(), vec![]);
16820 let excerpt_buffer = cx.new(|cx| {
16821 let key = &mut key.1;
16822 let mut multibuffer = MultiBuffer::new(capability);
16823 for (buffer, mut ranges_for_buffer) in locations {
16824 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
16825 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
16826 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
16827 PathKey::for_buffer(&buffer, cx),
16828 buffer.clone(),
16829 ranges_for_buffer,
16830 multibuffer_context_lines(cx),
16831 cx,
16832 );
16833 ranges.extend(new_ranges)
16834 }
16835
16836 multibuffer.with_title(title)
16837 });
16838 let existing = workspace.active_pane().update(cx, |pane, cx| {
16839 pane.items()
16840 .filter_map(|item| item.downcast::<Editor>())
16841 .find(|editor| {
16842 editor
16843 .read(cx)
16844 .lookup_key
16845 .as_ref()
16846 .and_then(|it| {
16847 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
16848 })
16849 .is_some_and(|it| *it == key)
16850 })
16851 });
16852 let editor = existing.unwrap_or_else(|| {
16853 cx.new(|cx| {
16854 let mut editor = Editor::for_multibuffer(
16855 excerpt_buffer,
16856 Some(workspace.project().clone()),
16857 window,
16858 cx,
16859 );
16860 editor.lookup_key = Some(Box::new(key));
16861 editor
16862 })
16863 });
16864 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
16865 MultibufferSelectionMode::First => {
16866 if let Some(first_range) = ranges.first() {
16867 editor.change_selections(
16868 SelectionEffects::no_scroll(),
16869 window,
16870 cx,
16871 |selections| {
16872 selections.clear_disjoint();
16873 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
16874 },
16875 );
16876 }
16877 editor.highlight_background::<Self>(
16878 &ranges,
16879 |theme| theme.colors().editor_highlighted_line_background,
16880 cx,
16881 );
16882 }
16883 MultibufferSelectionMode::All => {
16884 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
16885 selections.clear_disjoint();
16886 selections.select_anchor_ranges(ranges);
16887 });
16888 }
16889 });
16890
16891 let item = Box::new(editor);
16892 let item_id = item.item_id();
16893
16894 if split {
16895 let pane = workspace.adjacent_pane(window, cx);
16896 workspace.add_item(pane, item, None, true, true, window, cx);
16897 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
16898 let (preview_item_id, preview_item_idx) =
16899 workspace.active_pane().read_with(cx, |pane, _| {
16900 (pane.preview_item_id(), pane.preview_item_idx())
16901 });
16902
16903 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
16904
16905 if let Some(preview_item_id) = preview_item_id {
16906 workspace.active_pane().update(cx, |pane, cx| {
16907 pane.remove_item(preview_item_id, false, false, window, cx);
16908 });
16909 }
16910 } else {
16911 workspace.add_item_to_active_pane(item, None, true, window, cx);
16912 }
16913 workspace.active_pane().update(cx, |pane, cx| {
16914 pane.set_preview_item_id(Some(item_id), cx);
16915 });
16916 }
16917
16918 pub fn rename(
16919 &mut self,
16920 _: &Rename,
16921 window: &mut Window,
16922 cx: &mut Context<Self>,
16923 ) -> Option<Task<Result<()>>> {
16924 use language::ToOffset as _;
16925
16926 let provider = self.semantics_provider.clone()?;
16927 let selection = self.selections.newest_anchor().clone();
16928 let (cursor_buffer, cursor_buffer_position) = self
16929 .buffer
16930 .read(cx)
16931 .text_anchor_for_position(selection.head(), cx)?;
16932 let (tail_buffer, cursor_buffer_position_end) = self
16933 .buffer
16934 .read(cx)
16935 .text_anchor_for_position(selection.tail(), cx)?;
16936 if tail_buffer != cursor_buffer {
16937 return None;
16938 }
16939
16940 let snapshot = cursor_buffer.read(cx).snapshot();
16941 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
16942 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
16943 let prepare_rename = provider
16944 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
16945 .unwrap_or_else(|| Task::ready(Ok(None)));
16946 drop(snapshot);
16947
16948 Some(cx.spawn_in(window, async move |this, cx| {
16949 let rename_range = if let Some(range) = prepare_rename.await? {
16950 Some(range)
16951 } else {
16952 this.update(cx, |this, cx| {
16953 let buffer = this.buffer.read(cx).snapshot(cx);
16954 let mut buffer_highlights = this
16955 .document_highlights_for_position(selection.head(), &buffer)
16956 .filter(|highlight| {
16957 highlight.start.excerpt_id == selection.head().excerpt_id
16958 && highlight.end.excerpt_id == selection.head().excerpt_id
16959 });
16960 buffer_highlights
16961 .next()
16962 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
16963 })?
16964 };
16965 if let Some(rename_range) = rename_range {
16966 this.update_in(cx, |this, window, cx| {
16967 let snapshot = cursor_buffer.read(cx).snapshot();
16968 let rename_buffer_range = rename_range.to_offset(&snapshot);
16969 let cursor_offset_in_rename_range =
16970 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
16971 let cursor_offset_in_rename_range_end =
16972 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
16973
16974 this.take_rename(false, window, cx);
16975 let buffer = this.buffer.read(cx).read(cx);
16976 let cursor_offset = selection.head().to_offset(&buffer);
16977 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
16978 let rename_end = rename_start + rename_buffer_range.len();
16979 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
16980 let mut old_highlight_id = None;
16981 let old_name: Arc<str> = buffer
16982 .chunks(rename_start..rename_end, true)
16983 .map(|chunk| {
16984 if old_highlight_id.is_none() {
16985 old_highlight_id = chunk.syntax_highlight_id;
16986 }
16987 chunk.text
16988 })
16989 .collect::<String>()
16990 .into();
16991
16992 drop(buffer);
16993
16994 // Position the selection in the rename editor so that it matches the current selection.
16995 this.show_local_selections = false;
16996 let rename_editor = cx.new(|cx| {
16997 let mut editor = Editor::single_line(window, cx);
16998 editor.buffer.update(cx, |buffer, cx| {
16999 buffer.edit([(0..0, old_name.clone())], None, cx)
17000 });
17001 let rename_selection_range = match cursor_offset_in_rename_range
17002 .cmp(&cursor_offset_in_rename_range_end)
17003 {
17004 Ordering::Equal => {
17005 editor.select_all(&SelectAll, window, cx);
17006 return editor;
17007 }
17008 Ordering::Less => {
17009 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17010 }
17011 Ordering::Greater => {
17012 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17013 }
17014 };
17015 if rename_selection_range.end > old_name.len() {
17016 editor.select_all(&SelectAll, window, cx);
17017 } else {
17018 editor.change_selections(Default::default(), window, cx, |s| {
17019 s.select_ranges([rename_selection_range]);
17020 });
17021 }
17022 editor
17023 });
17024 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17025 if e == &EditorEvent::Focused {
17026 cx.emit(EditorEvent::FocusedIn)
17027 }
17028 })
17029 .detach();
17030
17031 let write_highlights =
17032 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17033 let read_highlights =
17034 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17035 let ranges = write_highlights
17036 .iter()
17037 .flat_map(|(_, ranges)| ranges.iter())
17038 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17039 .cloned()
17040 .collect();
17041
17042 this.highlight_text::<Rename>(
17043 ranges,
17044 HighlightStyle {
17045 fade_out: Some(0.6),
17046 ..Default::default()
17047 },
17048 cx,
17049 );
17050 let rename_focus_handle = rename_editor.focus_handle(cx);
17051 window.focus(&rename_focus_handle);
17052 let block_id = this.insert_blocks(
17053 [BlockProperties {
17054 style: BlockStyle::Flex,
17055 placement: BlockPlacement::Below(range.start),
17056 height: Some(1),
17057 render: Arc::new({
17058 let rename_editor = rename_editor.clone();
17059 move |cx: &mut BlockContext| {
17060 let mut text_style = cx.editor_style.text.clone();
17061 if let Some(highlight_style) = old_highlight_id
17062 .and_then(|h| h.style(&cx.editor_style.syntax))
17063 {
17064 text_style = text_style.highlight(highlight_style);
17065 }
17066 div()
17067 .block_mouse_except_scroll()
17068 .pl(cx.anchor_x)
17069 .child(EditorElement::new(
17070 &rename_editor,
17071 EditorStyle {
17072 background: cx.theme().system().transparent,
17073 local_player: cx.editor_style.local_player,
17074 text: text_style,
17075 scrollbar_width: cx.editor_style.scrollbar_width,
17076 syntax: cx.editor_style.syntax.clone(),
17077 status: cx.editor_style.status.clone(),
17078 inlay_hints_style: HighlightStyle {
17079 font_weight: Some(FontWeight::BOLD),
17080 ..make_inlay_hints_style(cx.app)
17081 },
17082 edit_prediction_styles: make_suggestion_styles(
17083 cx.app,
17084 ),
17085 ..EditorStyle::default()
17086 },
17087 ))
17088 .into_any_element()
17089 }
17090 }),
17091 priority: 0,
17092 }],
17093 Some(Autoscroll::fit()),
17094 cx,
17095 )[0];
17096 this.pending_rename = Some(RenameState {
17097 range,
17098 old_name,
17099 editor: rename_editor,
17100 block_id,
17101 });
17102 })?;
17103 }
17104
17105 Ok(())
17106 }))
17107 }
17108
17109 pub fn confirm_rename(
17110 &mut self,
17111 _: &ConfirmRename,
17112 window: &mut Window,
17113 cx: &mut Context<Self>,
17114 ) -> Option<Task<Result<()>>> {
17115 let rename = self.take_rename(false, window, cx)?;
17116 let workspace = self.workspace()?.downgrade();
17117 let (buffer, start) = self
17118 .buffer
17119 .read(cx)
17120 .text_anchor_for_position(rename.range.start, cx)?;
17121 let (end_buffer, _) = self
17122 .buffer
17123 .read(cx)
17124 .text_anchor_for_position(rename.range.end, cx)?;
17125 if buffer != end_buffer {
17126 return None;
17127 }
17128
17129 let old_name = rename.old_name;
17130 let new_name = rename.editor.read(cx).text(cx);
17131
17132 let rename = self.semantics_provider.as_ref()?.perform_rename(
17133 &buffer,
17134 start,
17135 new_name.clone(),
17136 cx,
17137 )?;
17138
17139 Some(cx.spawn_in(window, async move |editor, cx| {
17140 let project_transaction = rename.await?;
17141 Self::open_project_transaction(
17142 &editor,
17143 workspace,
17144 project_transaction,
17145 format!("Rename: {} → {}", old_name, new_name),
17146 cx,
17147 )
17148 .await?;
17149
17150 editor.update(cx, |editor, cx| {
17151 editor.refresh_document_highlights(cx);
17152 })?;
17153 Ok(())
17154 }))
17155 }
17156
17157 fn take_rename(
17158 &mut self,
17159 moving_cursor: bool,
17160 window: &mut Window,
17161 cx: &mut Context<Self>,
17162 ) -> Option<RenameState> {
17163 let rename = self.pending_rename.take()?;
17164 if rename.editor.focus_handle(cx).is_focused(window) {
17165 window.focus(&self.focus_handle);
17166 }
17167
17168 self.remove_blocks(
17169 [rename.block_id].into_iter().collect(),
17170 Some(Autoscroll::fit()),
17171 cx,
17172 );
17173 self.clear_highlights::<Rename>(cx);
17174 self.show_local_selections = true;
17175
17176 if moving_cursor {
17177 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17178 editor
17179 .selections
17180 .newest::<usize>(&editor.display_snapshot(cx))
17181 .head()
17182 });
17183
17184 // Update the selection to match the position of the selection inside
17185 // the rename editor.
17186 let snapshot = self.buffer.read(cx).read(cx);
17187 let rename_range = rename.range.to_offset(&snapshot);
17188 let cursor_in_editor = snapshot
17189 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17190 .min(rename_range.end);
17191 drop(snapshot);
17192
17193 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17194 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17195 });
17196 } else {
17197 self.refresh_document_highlights(cx);
17198 }
17199
17200 Some(rename)
17201 }
17202
17203 pub fn pending_rename(&self) -> Option<&RenameState> {
17204 self.pending_rename.as_ref()
17205 }
17206
17207 fn format(
17208 &mut self,
17209 _: &Format,
17210 window: &mut Window,
17211 cx: &mut Context<Self>,
17212 ) -> Option<Task<Result<()>>> {
17213 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17214
17215 let project = match &self.project {
17216 Some(project) => project.clone(),
17217 None => return None,
17218 };
17219
17220 Some(self.perform_format(
17221 project,
17222 FormatTrigger::Manual,
17223 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17224 window,
17225 cx,
17226 ))
17227 }
17228
17229 fn format_selections(
17230 &mut self,
17231 _: &FormatSelections,
17232 window: &mut Window,
17233 cx: &mut Context<Self>,
17234 ) -> Option<Task<Result<()>>> {
17235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17236
17237 let project = match &self.project {
17238 Some(project) => project.clone(),
17239 None => return None,
17240 };
17241
17242 let ranges = self
17243 .selections
17244 .all_adjusted(&self.display_snapshot(cx))
17245 .into_iter()
17246 .map(|selection| selection.range())
17247 .collect_vec();
17248
17249 Some(self.perform_format(
17250 project,
17251 FormatTrigger::Manual,
17252 FormatTarget::Ranges(ranges),
17253 window,
17254 cx,
17255 ))
17256 }
17257
17258 fn perform_format(
17259 &mut self,
17260 project: Entity<Project>,
17261 trigger: FormatTrigger,
17262 target: FormatTarget,
17263 window: &mut Window,
17264 cx: &mut Context<Self>,
17265 ) -> Task<Result<()>> {
17266 let buffer = self.buffer.clone();
17267 let (buffers, target) = match target {
17268 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17269 FormatTarget::Ranges(selection_ranges) => {
17270 let multi_buffer = buffer.read(cx);
17271 let snapshot = multi_buffer.read(cx);
17272 let mut buffers = HashSet::default();
17273 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17274 BTreeMap::new();
17275 for selection_range in selection_ranges {
17276 for (buffer, buffer_range, _) in
17277 snapshot.range_to_buffer_ranges(selection_range)
17278 {
17279 let buffer_id = buffer.remote_id();
17280 let start = buffer.anchor_before(buffer_range.start);
17281 let end = buffer.anchor_after(buffer_range.end);
17282 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17283 buffer_id_to_ranges
17284 .entry(buffer_id)
17285 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17286 .or_insert_with(|| vec![start..end]);
17287 }
17288 }
17289 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17290 }
17291 };
17292
17293 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17294 let selections_prev = transaction_id_prev
17295 .and_then(|transaction_id_prev| {
17296 // default to selections as they were after the last edit, if we have them,
17297 // instead of how they are now.
17298 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17299 // will take you back to where you made the last edit, instead of staying where you scrolled
17300 self.selection_history
17301 .transaction(transaction_id_prev)
17302 .map(|t| t.0.clone())
17303 })
17304 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17305
17306 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17307 let format = project.update(cx, |project, cx| {
17308 project.format(buffers, target, true, trigger, cx)
17309 });
17310
17311 cx.spawn_in(window, async move |editor, cx| {
17312 let transaction = futures::select_biased! {
17313 transaction = format.log_err().fuse() => transaction,
17314 () = timeout => {
17315 log::warn!("timed out waiting for formatting");
17316 None
17317 }
17318 };
17319
17320 buffer
17321 .update(cx, |buffer, cx| {
17322 if let Some(transaction) = transaction
17323 && !buffer.is_singleton()
17324 {
17325 buffer.push_transaction(&transaction.0, cx);
17326 }
17327 cx.notify();
17328 })
17329 .ok();
17330
17331 if let Some(transaction_id_now) =
17332 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17333 {
17334 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17335 if has_new_transaction {
17336 _ = editor.update(cx, |editor, _| {
17337 editor
17338 .selection_history
17339 .insert_transaction(transaction_id_now, selections_prev);
17340 });
17341 }
17342 }
17343
17344 Ok(())
17345 })
17346 }
17347
17348 fn organize_imports(
17349 &mut self,
17350 _: &OrganizeImports,
17351 window: &mut Window,
17352 cx: &mut Context<Self>,
17353 ) -> Option<Task<Result<()>>> {
17354 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17355 let project = match &self.project {
17356 Some(project) => project.clone(),
17357 None => return None,
17358 };
17359 Some(self.perform_code_action_kind(
17360 project,
17361 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17362 window,
17363 cx,
17364 ))
17365 }
17366
17367 fn perform_code_action_kind(
17368 &mut self,
17369 project: Entity<Project>,
17370 kind: CodeActionKind,
17371 window: &mut Window,
17372 cx: &mut Context<Self>,
17373 ) -> Task<Result<()>> {
17374 let buffer = self.buffer.clone();
17375 let buffers = buffer.read(cx).all_buffers();
17376 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17377 let apply_action = project.update(cx, |project, cx| {
17378 project.apply_code_action_kind(buffers, kind, true, cx)
17379 });
17380 cx.spawn_in(window, async move |_, cx| {
17381 let transaction = futures::select_biased! {
17382 () = timeout => {
17383 log::warn!("timed out waiting for executing code action");
17384 None
17385 }
17386 transaction = apply_action.log_err().fuse() => transaction,
17387 };
17388 buffer
17389 .update(cx, |buffer, cx| {
17390 // check if we need this
17391 if let Some(transaction) = transaction
17392 && !buffer.is_singleton()
17393 {
17394 buffer.push_transaction(&transaction.0, cx);
17395 }
17396 cx.notify();
17397 })
17398 .ok();
17399 Ok(())
17400 })
17401 }
17402
17403 pub fn restart_language_server(
17404 &mut self,
17405 _: &RestartLanguageServer,
17406 _: &mut Window,
17407 cx: &mut Context<Self>,
17408 ) {
17409 if let Some(project) = self.project.clone() {
17410 self.buffer.update(cx, |multi_buffer, cx| {
17411 project.update(cx, |project, cx| {
17412 project.restart_language_servers_for_buffers(
17413 multi_buffer.all_buffers().into_iter().collect(),
17414 HashSet::default(),
17415 cx,
17416 );
17417 });
17418 })
17419 }
17420 }
17421
17422 pub fn stop_language_server(
17423 &mut self,
17424 _: &StopLanguageServer,
17425 _: &mut Window,
17426 cx: &mut Context<Self>,
17427 ) {
17428 if let Some(project) = self.project.clone() {
17429 self.buffer.update(cx, |multi_buffer, cx| {
17430 project.update(cx, |project, cx| {
17431 project.stop_language_servers_for_buffers(
17432 multi_buffer.all_buffers().into_iter().collect(),
17433 HashSet::default(),
17434 cx,
17435 );
17436 });
17437 });
17438 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17439 }
17440 }
17441
17442 fn cancel_language_server_work(
17443 workspace: &mut Workspace,
17444 _: &actions::CancelLanguageServerWork,
17445 _: &mut Window,
17446 cx: &mut Context<Workspace>,
17447 ) {
17448 let project = workspace.project();
17449 let buffers = workspace
17450 .active_item(cx)
17451 .and_then(|item| item.act_as::<Editor>(cx))
17452 .map_or(HashSet::default(), |editor| {
17453 editor.read(cx).buffer.read(cx).all_buffers()
17454 });
17455 project.update(cx, |project, cx| {
17456 project.cancel_language_server_work_for_buffers(buffers, cx);
17457 });
17458 }
17459
17460 fn show_character_palette(
17461 &mut self,
17462 _: &ShowCharacterPalette,
17463 window: &mut Window,
17464 _: &mut Context<Self>,
17465 ) {
17466 window.show_character_palette();
17467 }
17468
17469 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17470 if !self.diagnostics_enabled() {
17471 return;
17472 }
17473
17474 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17475 let buffer = self.buffer.read(cx).snapshot(cx);
17476 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17477 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17478 let is_valid = buffer
17479 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17480 .any(|entry| {
17481 entry.diagnostic.is_primary
17482 && !entry.range.is_empty()
17483 && entry.range.start == primary_range_start
17484 && entry.diagnostic.message == active_diagnostics.active_message
17485 });
17486
17487 if !is_valid {
17488 self.dismiss_diagnostics(cx);
17489 }
17490 }
17491 }
17492
17493 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17494 match &self.active_diagnostics {
17495 ActiveDiagnostic::Group(group) => Some(group),
17496 _ => None,
17497 }
17498 }
17499
17500 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17501 if !self.diagnostics_enabled() {
17502 return;
17503 }
17504 self.dismiss_diagnostics(cx);
17505 self.active_diagnostics = ActiveDiagnostic::All;
17506 }
17507
17508 fn activate_diagnostics(
17509 &mut self,
17510 buffer_id: BufferId,
17511 diagnostic: DiagnosticEntryRef<'_, usize>,
17512 window: &mut Window,
17513 cx: &mut Context<Self>,
17514 ) {
17515 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17516 return;
17517 }
17518 self.dismiss_diagnostics(cx);
17519 let snapshot = self.snapshot(window, cx);
17520 let buffer = self.buffer.read(cx).snapshot(cx);
17521 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17522 return;
17523 };
17524
17525 let diagnostic_group = buffer
17526 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17527 .collect::<Vec<_>>();
17528
17529 let blocks =
17530 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17531
17532 let blocks = self.display_map.update(cx, |display_map, cx| {
17533 display_map.insert_blocks(blocks, cx).into_iter().collect()
17534 });
17535 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17536 active_range: buffer.anchor_before(diagnostic.range.start)
17537 ..buffer.anchor_after(diagnostic.range.end),
17538 active_message: diagnostic.diagnostic.message.clone(),
17539 group_id: diagnostic.diagnostic.group_id,
17540 blocks,
17541 });
17542 cx.notify();
17543 }
17544
17545 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17546 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17547 return;
17548 };
17549
17550 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17551 if let ActiveDiagnostic::Group(group) = prev {
17552 self.display_map.update(cx, |display_map, cx| {
17553 display_map.remove_blocks(group.blocks, cx);
17554 });
17555 cx.notify();
17556 }
17557 }
17558
17559 /// Disable inline diagnostics rendering for this editor.
17560 pub fn disable_inline_diagnostics(&mut self) {
17561 self.inline_diagnostics_enabled = false;
17562 self.inline_diagnostics_update = Task::ready(());
17563 self.inline_diagnostics.clear();
17564 }
17565
17566 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17567 self.diagnostics_enabled = false;
17568 self.dismiss_diagnostics(cx);
17569 self.inline_diagnostics_update = Task::ready(());
17570 self.inline_diagnostics.clear();
17571 }
17572
17573 pub fn disable_word_completions(&mut self) {
17574 self.word_completions_enabled = false;
17575 }
17576
17577 pub fn diagnostics_enabled(&self) -> bool {
17578 self.diagnostics_enabled && self.mode.is_full()
17579 }
17580
17581 pub fn inline_diagnostics_enabled(&self) -> bool {
17582 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17583 }
17584
17585 pub fn show_inline_diagnostics(&self) -> bool {
17586 self.show_inline_diagnostics
17587 }
17588
17589 pub fn toggle_inline_diagnostics(
17590 &mut self,
17591 _: &ToggleInlineDiagnostics,
17592 window: &mut Window,
17593 cx: &mut Context<Editor>,
17594 ) {
17595 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17596 self.refresh_inline_diagnostics(false, window, cx);
17597 }
17598
17599 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17600 self.diagnostics_max_severity = severity;
17601 self.display_map.update(cx, |display_map, _| {
17602 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17603 });
17604 }
17605
17606 pub fn toggle_diagnostics(
17607 &mut self,
17608 _: &ToggleDiagnostics,
17609 window: &mut Window,
17610 cx: &mut Context<Editor>,
17611 ) {
17612 if !self.diagnostics_enabled() {
17613 return;
17614 }
17615
17616 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17617 EditorSettings::get_global(cx)
17618 .diagnostics_max_severity
17619 .filter(|severity| severity != &DiagnosticSeverity::Off)
17620 .unwrap_or(DiagnosticSeverity::Hint)
17621 } else {
17622 DiagnosticSeverity::Off
17623 };
17624 self.set_max_diagnostics_severity(new_severity, cx);
17625 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17626 self.active_diagnostics = ActiveDiagnostic::None;
17627 self.inline_diagnostics_update = Task::ready(());
17628 self.inline_diagnostics.clear();
17629 } else {
17630 self.refresh_inline_diagnostics(false, window, cx);
17631 }
17632
17633 cx.notify();
17634 }
17635
17636 pub fn toggle_minimap(
17637 &mut self,
17638 _: &ToggleMinimap,
17639 window: &mut Window,
17640 cx: &mut Context<Editor>,
17641 ) {
17642 if self.supports_minimap(cx) {
17643 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17644 }
17645 }
17646
17647 fn refresh_inline_diagnostics(
17648 &mut self,
17649 debounce: bool,
17650 window: &mut Window,
17651 cx: &mut Context<Self>,
17652 ) {
17653 let max_severity = ProjectSettings::get_global(cx)
17654 .diagnostics
17655 .inline
17656 .max_severity
17657 .unwrap_or(self.diagnostics_max_severity);
17658
17659 if !self.inline_diagnostics_enabled()
17660 || !self.show_inline_diagnostics
17661 || max_severity == DiagnosticSeverity::Off
17662 {
17663 self.inline_diagnostics_update = Task::ready(());
17664 self.inline_diagnostics.clear();
17665 return;
17666 }
17667
17668 let debounce_ms = ProjectSettings::get_global(cx)
17669 .diagnostics
17670 .inline
17671 .update_debounce_ms;
17672 let debounce = if debounce && debounce_ms > 0 {
17673 Some(Duration::from_millis(debounce_ms))
17674 } else {
17675 None
17676 };
17677 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17678 if let Some(debounce) = debounce {
17679 cx.background_executor().timer(debounce).await;
17680 }
17681 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17682 editor
17683 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17684 .ok()
17685 }) else {
17686 return;
17687 };
17688
17689 let new_inline_diagnostics = cx
17690 .background_spawn(async move {
17691 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17692 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17693 let message = diagnostic_entry
17694 .diagnostic
17695 .message
17696 .split_once('\n')
17697 .map(|(line, _)| line)
17698 .map(SharedString::new)
17699 .unwrap_or_else(|| {
17700 SharedString::new(&*diagnostic_entry.diagnostic.message)
17701 });
17702 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17703 let (Ok(i) | Err(i)) = inline_diagnostics
17704 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17705 inline_diagnostics.insert(
17706 i,
17707 (
17708 start_anchor,
17709 InlineDiagnostic {
17710 message,
17711 group_id: diagnostic_entry.diagnostic.group_id,
17712 start: diagnostic_entry.range.start.to_point(&snapshot),
17713 is_primary: diagnostic_entry.diagnostic.is_primary,
17714 severity: diagnostic_entry.diagnostic.severity,
17715 },
17716 ),
17717 );
17718 }
17719 inline_diagnostics
17720 })
17721 .await;
17722
17723 editor
17724 .update(cx, |editor, cx| {
17725 editor.inline_diagnostics = new_inline_diagnostics;
17726 cx.notify();
17727 })
17728 .ok();
17729 });
17730 }
17731
17732 fn pull_diagnostics(
17733 &mut self,
17734 buffer_id: Option<BufferId>,
17735 window: &Window,
17736 cx: &mut Context<Self>,
17737 ) -> Option<()> {
17738 if self.ignore_lsp_data() {
17739 return None;
17740 }
17741 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17742 .diagnostics
17743 .lsp_pull_diagnostics;
17744 if !pull_diagnostics_settings.enabled {
17745 return None;
17746 }
17747 let project = self.project()?.downgrade();
17748 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17749 let mut buffers = self.buffer.read(cx).all_buffers();
17750 buffers.retain(|buffer| {
17751 let buffer_id_to_retain = buffer.read(cx).remote_id();
17752 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17753 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17754 });
17755 if buffers.is_empty() {
17756 self.pull_diagnostics_task = Task::ready(());
17757 return None;
17758 }
17759
17760 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17761 cx.background_executor().timer(debounce).await;
17762
17763 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17764 buffers
17765 .into_iter()
17766 .filter_map(|buffer| {
17767 project
17768 .update(cx, |project, cx| {
17769 project.lsp_store().update(cx, |lsp_store, cx| {
17770 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17771 })
17772 })
17773 .ok()
17774 })
17775 .collect::<FuturesUnordered<_>>()
17776 }) else {
17777 return;
17778 };
17779
17780 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17781 match pull_task {
17782 Ok(()) => {
17783 if editor
17784 .update_in(cx, |editor, window, cx| {
17785 editor.update_diagnostics_state(window, cx);
17786 })
17787 .is_err()
17788 {
17789 return;
17790 }
17791 }
17792 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17793 }
17794 }
17795 });
17796
17797 Some(())
17798 }
17799
17800 pub fn set_selections_from_remote(
17801 &mut self,
17802 selections: Vec<Selection<Anchor>>,
17803 pending_selection: Option<Selection<Anchor>>,
17804 window: &mut Window,
17805 cx: &mut Context<Self>,
17806 ) {
17807 let old_cursor_position = self.selections.newest_anchor().head();
17808 self.selections.change_with(cx, |s| {
17809 s.select_anchors(selections);
17810 if let Some(pending_selection) = pending_selection {
17811 s.set_pending(pending_selection, SelectMode::Character);
17812 } else {
17813 s.clear_pending();
17814 }
17815 });
17816 self.selections_did_change(
17817 false,
17818 &old_cursor_position,
17819 SelectionEffects::default(),
17820 window,
17821 cx,
17822 );
17823 }
17824
17825 pub fn transact(
17826 &mut self,
17827 window: &mut Window,
17828 cx: &mut Context<Self>,
17829 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
17830 ) -> Option<TransactionId> {
17831 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
17832 this.start_transaction_at(Instant::now(), window, cx);
17833 update(this, window, cx);
17834 this.end_transaction_at(Instant::now(), cx)
17835 })
17836 }
17837
17838 pub fn start_transaction_at(
17839 &mut self,
17840 now: Instant,
17841 window: &mut Window,
17842 cx: &mut Context<Self>,
17843 ) -> Option<TransactionId> {
17844 self.end_selection(window, cx);
17845 if let Some(tx_id) = self
17846 .buffer
17847 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
17848 {
17849 self.selection_history
17850 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
17851 cx.emit(EditorEvent::TransactionBegun {
17852 transaction_id: tx_id,
17853 });
17854 Some(tx_id)
17855 } else {
17856 None
17857 }
17858 }
17859
17860 pub fn end_transaction_at(
17861 &mut self,
17862 now: Instant,
17863 cx: &mut Context<Self>,
17864 ) -> Option<TransactionId> {
17865 if let Some(transaction_id) = self
17866 .buffer
17867 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
17868 {
17869 if let Some((_, end_selections)) =
17870 self.selection_history.transaction_mut(transaction_id)
17871 {
17872 *end_selections = Some(self.selections.disjoint_anchors_arc());
17873 } else {
17874 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
17875 }
17876
17877 cx.emit(EditorEvent::Edited { transaction_id });
17878 Some(transaction_id)
17879 } else {
17880 None
17881 }
17882 }
17883
17884 pub fn modify_transaction_selection_history(
17885 &mut self,
17886 transaction_id: TransactionId,
17887 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
17888 ) -> bool {
17889 self.selection_history
17890 .transaction_mut(transaction_id)
17891 .map(modify)
17892 .is_some()
17893 }
17894
17895 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
17896 if self.selection_mark_mode {
17897 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17898 s.move_with(|_, sel| {
17899 sel.collapse_to(sel.head(), SelectionGoal::None);
17900 });
17901 })
17902 }
17903 self.selection_mark_mode = true;
17904 cx.notify();
17905 }
17906
17907 pub fn swap_selection_ends(
17908 &mut self,
17909 _: &actions::SwapSelectionEnds,
17910 window: &mut Window,
17911 cx: &mut Context<Self>,
17912 ) {
17913 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17914 s.move_with(|_, sel| {
17915 if sel.start != sel.end {
17916 sel.reversed = !sel.reversed
17917 }
17918 });
17919 });
17920 self.request_autoscroll(Autoscroll::newest(), cx);
17921 cx.notify();
17922 }
17923
17924 pub fn toggle_focus(
17925 workspace: &mut Workspace,
17926 _: &actions::ToggleFocus,
17927 window: &mut Window,
17928 cx: &mut Context<Workspace>,
17929 ) {
17930 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
17931 return;
17932 };
17933 workspace.activate_item(&item, true, true, window, cx);
17934 }
17935
17936 pub fn toggle_fold(
17937 &mut self,
17938 _: &actions::ToggleFold,
17939 window: &mut Window,
17940 cx: &mut Context<Self>,
17941 ) {
17942 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
17943 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17944 let selection = self.selections.newest::<Point>(&display_map);
17945
17946 let range = if selection.is_empty() {
17947 let point = selection.head().to_display_point(&display_map);
17948 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17949 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17950 .to_point(&display_map);
17951 start..end
17952 } else {
17953 selection.range()
17954 };
17955 if display_map.folds_in_range(range).next().is_some() {
17956 self.unfold_lines(&Default::default(), window, cx)
17957 } else {
17958 self.fold(&Default::default(), window, cx)
17959 }
17960 } else {
17961 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
17962 let buffer_ids: HashSet<_> = self
17963 .selections
17964 .disjoint_anchor_ranges()
17965 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
17966 .collect();
17967
17968 let should_unfold = buffer_ids
17969 .iter()
17970 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
17971
17972 for buffer_id in buffer_ids {
17973 if should_unfold {
17974 self.unfold_buffer(buffer_id, cx);
17975 } else {
17976 self.fold_buffer(buffer_id, cx);
17977 }
17978 }
17979 }
17980 }
17981
17982 pub fn toggle_fold_recursive(
17983 &mut self,
17984 _: &actions::ToggleFoldRecursive,
17985 window: &mut Window,
17986 cx: &mut Context<Self>,
17987 ) {
17988 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
17989
17990 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
17991 let range = if selection.is_empty() {
17992 let point = selection.head().to_display_point(&display_map);
17993 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
17994 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
17995 .to_point(&display_map);
17996 start..end
17997 } else {
17998 selection.range()
17999 };
18000 if display_map.folds_in_range(range).next().is_some() {
18001 self.unfold_recursive(&Default::default(), window, cx)
18002 } else {
18003 self.fold_recursive(&Default::default(), window, cx)
18004 }
18005 }
18006
18007 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18008 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18009 let mut to_fold = Vec::new();
18010 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18011 let selections = self.selections.all_adjusted(&display_map);
18012
18013 for selection in selections {
18014 let range = selection.range().sorted();
18015 let buffer_start_row = range.start.row;
18016
18017 if range.start.row != range.end.row {
18018 let mut found = false;
18019 let mut row = range.start.row;
18020 while row <= range.end.row {
18021 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18022 {
18023 found = true;
18024 row = crease.range().end.row + 1;
18025 to_fold.push(crease);
18026 } else {
18027 row += 1
18028 }
18029 }
18030 if found {
18031 continue;
18032 }
18033 }
18034
18035 for row in (0..=range.start.row).rev() {
18036 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18037 && crease.range().end.row >= buffer_start_row
18038 {
18039 to_fold.push(crease);
18040 if row <= range.start.row {
18041 break;
18042 }
18043 }
18044 }
18045 }
18046
18047 self.fold_creases(to_fold, true, window, cx);
18048 } else {
18049 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18050 let buffer_ids = self
18051 .selections
18052 .disjoint_anchor_ranges()
18053 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18054 .collect::<HashSet<_>>();
18055 for buffer_id in buffer_ids {
18056 self.fold_buffer(buffer_id, cx);
18057 }
18058 }
18059 }
18060
18061 pub fn toggle_fold_all(
18062 &mut self,
18063 _: &actions::ToggleFoldAll,
18064 window: &mut Window,
18065 cx: &mut Context<Self>,
18066 ) {
18067 if self.buffer.read(cx).is_singleton() {
18068 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18069 let has_folds = display_map
18070 .folds_in_range(0..display_map.buffer_snapshot().len())
18071 .next()
18072 .is_some();
18073
18074 if has_folds {
18075 self.unfold_all(&actions::UnfoldAll, window, cx);
18076 } else {
18077 self.fold_all(&actions::FoldAll, window, cx);
18078 }
18079 } else {
18080 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18081 let should_unfold = buffer_ids
18082 .iter()
18083 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18084
18085 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18086 editor
18087 .update_in(cx, |editor, _, cx| {
18088 for buffer_id in buffer_ids {
18089 if should_unfold {
18090 editor.unfold_buffer(buffer_id, cx);
18091 } else {
18092 editor.fold_buffer(buffer_id, cx);
18093 }
18094 }
18095 })
18096 .ok();
18097 });
18098 }
18099 }
18100
18101 fn fold_at_level(
18102 &mut self,
18103 fold_at: &FoldAtLevel,
18104 window: &mut Window,
18105 cx: &mut Context<Self>,
18106 ) {
18107 if !self.buffer.read(cx).is_singleton() {
18108 return;
18109 }
18110
18111 let fold_at_level = fold_at.0;
18112 let snapshot = self.buffer.read(cx).snapshot(cx);
18113 let mut to_fold = Vec::new();
18114 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18115
18116 let row_ranges_to_keep: Vec<Range<u32>> = self
18117 .selections
18118 .all::<Point>(&self.display_snapshot(cx))
18119 .into_iter()
18120 .map(|sel| sel.start.row..sel.end.row)
18121 .collect();
18122
18123 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18124 while start_row < end_row {
18125 match self
18126 .snapshot(window, cx)
18127 .crease_for_buffer_row(MultiBufferRow(start_row))
18128 {
18129 Some(crease) => {
18130 let nested_start_row = crease.range().start.row + 1;
18131 let nested_end_row = crease.range().end.row;
18132
18133 if current_level < fold_at_level {
18134 stack.push((nested_start_row, nested_end_row, current_level + 1));
18135 } else if current_level == fold_at_level {
18136 // Fold iff there is no selection completely contained within the fold region
18137 if !row_ranges_to_keep.iter().any(|selection| {
18138 selection.end >= nested_start_row
18139 && selection.start <= nested_end_row
18140 }) {
18141 to_fold.push(crease);
18142 }
18143 }
18144
18145 start_row = nested_end_row + 1;
18146 }
18147 None => start_row += 1,
18148 }
18149 }
18150 }
18151
18152 self.fold_creases(to_fold, true, window, cx);
18153 }
18154
18155 pub fn fold_at_level_1(
18156 &mut self,
18157 _: &actions::FoldAtLevel1,
18158 window: &mut Window,
18159 cx: &mut Context<Self>,
18160 ) {
18161 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18162 }
18163
18164 pub fn fold_at_level_2(
18165 &mut self,
18166 _: &actions::FoldAtLevel2,
18167 window: &mut Window,
18168 cx: &mut Context<Self>,
18169 ) {
18170 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18171 }
18172
18173 pub fn fold_at_level_3(
18174 &mut self,
18175 _: &actions::FoldAtLevel3,
18176 window: &mut Window,
18177 cx: &mut Context<Self>,
18178 ) {
18179 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18180 }
18181
18182 pub fn fold_at_level_4(
18183 &mut self,
18184 _: &actions::FoldAtLevel4,
18185 window: &mut Window,
18186 cx: &mut Context<Self>,
18187 ) {
18188 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18189 }
18190
18191 pub fn fold_at_level_5(
18192 &mut self,
18193 _: &actions::FoldAtLevel5,
18194 window: &mut Window,
18195 cx: &mut Context<Self>,
18196 ) {
18197 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18198 }
18199
18200 pub fn fold_at_level_6(
18201 &mut self,
18202 _: &actions::FoldAtLevel6,
18203 window: &mut Window,
18204 cx: &mut Context<Self>,
18205 ) {
18206 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18207 }
18208
18209 pub fn fold_at_level_7(
18210 &mut self,
18211 _: &actions::FoldAtLevel7,
18212 window: &mut Window,
18213 cx: &mut Context<Self>,
18214 ) {
18215 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18216 }
18217
18218 pub fn fold_at_level_8(
18219 &mut self,
18220 _: &actions::FoldAtLevel8,
18221 window: &mut Window,
18222 cx: &mut Context<Self>,
18223 ) {
18224 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18225 }
18226
18227 pub fn fold_at_level_9(
18228 &mut self,
18229 _: &actions::FoldAtLevel9,
18230 window: &mut Window,
18231 cx: &mut Context<Self>,
18232 ) {
18233 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18234 }
18235
18236 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18237 if self.buffer.read(cx).is_singleton() {
18238 let mut fold_ranges = Vec::new();
18239 let snapshot = self.buffer.read(cx).snapshot(cx);
18240
18241 for row in 0..snapshot.max_row().0 {
18242 if let Some(foldable_range) = self
18243 .snapshot(window, cx)
18244 .crease_for_buffer_row(MultiBufferRow(row))
18245 {
18246 fold_ranges.push(foldable_range);
18247 }
18248 }
18249
18250 self.fold_creases(fold_ranges, true, window, cx);
18251 } else {
18252 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18253 editor
18254 .update_in(cx, |editor, _, cx| {
18255 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18256 editor.fold_buffer(buffer_id, cx);
18257 }
18258 })
18259 .ok();
18260 });
18261 }
18262 }
18263
18264 pub fn fold_function_bodies(
18265 &mut self,
18266 _: &actions::FoldFunctionBodies,
18267 window: &mut Window,
18268 cx: &mut Context<Self>,
18269 ) {
18270 let snapshot = self.buffer.read(cx).snapshot(cx);
18271
18272 let ranges = snapshot
18273 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18274 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18275 .collect::<Vec<_>>();
18276
18277 let creases = ranges
18278 .into_iter()
18279 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18280 .collect();
18281
18282 self.fold_creases(creases, true, window, cx);
18283 }
18284
18285 pub fn fold_recursive(
18286 &mut self,
18287 _: &actions::FoldRecursive,
18288 window: &mut Window,
18289 cx: &mut Context<Self>,
18290 ) {
18291 let mut to_fold = Vec::new();
18292 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18293 let selections = self.selections.all_adjusted(&display_map);
18294
18295 for selection in selections {
18296 let range = selection.range().sorted();
18297 let buffer_start_row = range.start.row;
18298
18299 if range.start.row != range.end.row {
18300 let mut found = false;
18301 for row in range.start.row..=range.end.row {
18302 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18303 found = true;
18304 to_fold.push(crease);
18305 }
18306 }
18307 if found {
18308 continue;
18309 }
18310 }
18311
18312 for row in (0..=range.start.row).rev() {
18313 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18314 if crease.range().end.row >= buffer_start_row {
18315 to_fold.push(crease);
18316 } else {
18317 break;
18318 }
18319 }
18320 }
18321 }
18322
18323 self.fold_creases(to_fold, true, window, cx);
18324 }
18325
18326 pub fn fold_at(
18327 &mut self,
18328 buffer_row: MultiBufferRow,
18329 window: &mut Window,
18330 cx: &mut Context<Self>,
18331 ) {
18332 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18333
18334 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18335 let autoscroll = self
18336 .selections
18337 .all::<Point>(&display_map)
18338 .iter()
18339 .any(|selection| crease.range().overlaps(&selection.range()));
18340
18341 self.fold_creases(vec![crease], autoscroll, window, cx);
18342 }
18343 }
18344
18345 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18346 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18348 let buffer = display_map.buffer_snapshot();
18349 let selections = self.selections.all::<Point>(&display_map);
18350 let ranges = selections
18351 .iter()
18352 .map(|s| {
18353 let range = s.display_range(&display_map).sorted();
18354 let mut start = range.start.to_point(&display_map);
18355 let mut end = range.end.to_point(&display_map);
18356 start.column = 0;
18357 end.column = buffer.line_len(MultiBufferRow(end.row));
18358 start..end
18359 })
18360 .collect::<Vec<_>>();
18361
18362 self.unfold_ranges(&ranges, true, true, cx);
18363 } else {
18364 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18365 let buffer_ids = self
18366 .selections
18367 .disjoint_anchor_ranges()
18368 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18369 .collect::<HashSet<_>>();
18370 for buffer_id in buffer_ids {
18371 self.unfold_buffer(buffer_id, cx);
18372 }
18373 }
18374 }
18375
18376 pub fn unfold_recursive(
18377 &mut self,
18378 _: &UnfoldRecursive,
18379 _window: &mut Window,
18380 cx: &mut Context<Self>,
18381 ) {
18382 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18383 let selections = self.selections.all::<Point>(&display_map);
18384 let ranges = selections
18385 .iter()
18386 .map(|s| {
18387 let mut range = s.display_range(&display_map).sorted();
18388 *range.start.column_mut() = 0;
18389 *range.end.column_mut() = display_map.line_len(range.end.row());
18390 let start = range.start.to_point(&display_map);
18391 let end = range.end.to_point(&display_map);
18392 start..end
18393 })
18394 .collect::<Vec<_>>();
18395
18396 self.unfold_ranges(&ranges, true, true, cx);
18397 }
18398
18399 pub fn unfold_at(
18400 &mut self,
18401 buffer_row: MultiBufferRow,
18402 _window: &mut Window,
18403 cx: &mut Context<Self>,
18404 ) {
18405 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18406
18407 let intersection_range = Point::new(buffer_row.0, 0)
18408 ..Point::new(
18409 buffer_row.0,
18410 display_map.buffer_snapshot().line_len(buffer_row),
18411 );
18412
18413 let autoscroll = self
18414 .selections
18415 .all::<Point>(&display_map)
18416 .iter()
18417 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18418
18419 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18420 }
18421
18422 pub fn unfold_all(
18423 &mut self,
18424 _: &actions::UnfoldAll,
18425 _window: &mut Window,
18426 cx: &mut Context<Self>,
18427 ) {
18428 if self.buffer.read(cx).is_singleton() {
18429 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18430 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18431 } else {
18432 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18433 editor
18434 .update(cx, |editor, cx| {
18435 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18436 editor.unfold_buffer(buffer_id, cx);
18437 }
18438 })
18439 .ok();
18440 });
18441 }
18442 }
18443
18444 pub fn fold_selected_ranges(
18445 &mut self,
18446 _: &FoldSelectedRanges,
18447 window: &mut Window,
18448 cx: &mut Context<Self>,
18449 ) {
18450 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18451 let selections = self.selections.all_adjusted(&display_map);
18452 let ranges = selections
18453 .into_iter()
18454 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18455 .collect::<Vec<_>>();
18456 self.fold_creases(ranges, true, window, cx);
18457 }
18458
18459 pub fn fold_ranges<T: ToOffset + Clone>(
18460 &mut self,
18461 ranges: Vec<Range<T>>,
18462 auto_scroll: bool,
18463 window: &mut Window,
18464 cx: &mut Context<Self>,
18465 ) {
18466 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18467 let ranges = ranges
18468 .into_iter()
18469 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18470 .collect::<Vec<_>>();
18471 self.fold_creases(ranges, auto_scroll, window, cx);
18472 }
18473
18474 pub fn fold_creases<T: ToOffset + Clone>(
18475 &mut self,
18476 creases: Vec<Crease<T>>,
18477 auto_scroll: bool,
18478 _window: &mut Window,
18479 cx: &mut Context<Self>,
18480 ) {
18481 if creases.is_empty() {
18482 return;
18483 }
18484
18485 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18486
18487 if auto_scroll {
18488 self.request_autoscroll(Autoscroll::fit(), cx);
18489 }
18490
18491 cx.notify();
18492
18493 self.scrollbar_marker_state.dirty = true;
18494 self.folds_did_change(cx);
18495 }
18496
18497 /// Removes any folds whose ranges intersect any of the given ranges.
18498 pub fn unfold_ranges<T: ToOffset + Clone>(
18499 &mut self,
18500 ranges: &[Range<T>],
18501 inclusive: bool,
18502 auto_scroll: bool,
18503 cx: &mut Context<Self>,
18504 ) {
18505 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18506 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18507 });
18508 self.folds_did_change(cx);
18509 }
18510
18511 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18512 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18513 return;
18514 }
18515 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18516 self.display_map.update(cx, |display_map, cx| {
18517 display_map.fold_buffers([buffer_id], cx)
18518 });
18519 cx.emit(EditorEvent::BufferFoldToggled {
18520 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18521 folded: true,
18522 });
18523 cx.notify();
18524 }
18525
18526 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18527 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18528 return;
18529 }
18530 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18531 self.display_map.update(cx, |display_map, cx| {
18532 display_map.unfold_buffers([buffer_id], cx);
18533 });
18534 cx.emit(EditorEvent::BufferFoldToggled {
18535 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18536 folded: false,
18537 });
18538 cx.notify();
18539 }
18540
18541 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18542 self.display_map.read(cx).is_buffer_folded(buffer)
18543 }
18544
18545 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18546 self.display_map.read(cx).folded_buffers()
18547 }
18548
18549 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18550 self.display_map.update(cx, |display_map, cx| {
18551 display_map.disable_header_for_buffer(buffer_id, cx);
18552 });
18553 cx.notify();
18554 }
18555
18556 /// Removes any folds with the given ranges.
18557 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18558 &mut self,
18559 ranges: &[Range<T>],
18560 type_id: TypeId,
18561 auto_scroll: bool,
18562 cx: &mut Context<Self>,
18563 ) {
18564 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18565 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18566 });
18567 self.folds_did_change(cx);
18568 }
18569
18570 fn remove_folds_with<T: ToOffset + Clone>(
18571 &mut self,
18572 ranges: &[Range<T>],
18573 auto_scroll: bool,
18574 cx: &mut Context<Self>,
18575 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18576 ) {
18577 if ranges.is_empty() {
18578 return;
18579 }
18580
18581 let mut buffers_affected = HashSet::default();
18582 let multi_buffer = self.buffer().read(cx);
18583 for range in ranges {
18584 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18585 buffers_affected.insert(buffer.read(cx).remote_id());
18586 };
18587 }
18588
18589 self.display_map.update(cx, update);
18590
18591 if auto_scroll {
18592 self.request_autoscroll(Autoscroll::fit(), cx);
18593 }
18594
18595 cx.notify();
18596 self.scrollbar_marker_state.dirty = true;
18597 self.active_indent_guides_state.dirty = true;
18598 }
18599
18600 pub fn update_renderer_widths(
18601 &mut self,
18602 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18603 cx: &mut Context<Self>,
18604 ) -> bool {
18605 self.display_map
18606 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18607 }
18608
18609 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18610 self.display_map.read(cx).fold_placeholder.clone()
18611 }
18612
18613 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18614 self.buffer.update(cx, |buffer, cx| {
18615 buffer.set_all_diff_hunks_expanded(cx);
18616 });
18617 }
18618
18619 pub fn expand_all_diff_hunks(
18620 &mut self,
18621 _: &ExpandAllDiffHunks,
18622 _window: &mut Window,
18623 cx: &mut Context<Self>,
18624 ) {
18625 self.buffer.update(cx, |buffer, cx| {
18626 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18627 });
18628 }
18629
18630 pub fn collapse_all_diff_hunks(
18631 &mut self,
18632 _: &CollapseAllDiffHunks,
18633 _window: &mut Window,
18634 cx: &mut Context<Self>,
18635 ) {
18636 self.buffer.update(cx, |buffer, cx| {
18637 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18638 });
18639 }
18640
18641 pub fn toggle_selected_diff_hunks(
18642 &mut self,
18643 _: &ToggleSelectedDiffHunks,
18644 _window: &mut Window,
18645 cx: &mut Context<Self>,
18646 ) {
18647 let ranges: Vec<_> = self
18648 .selections
18649 .disjoint_anchors()
18650 .iter()
18651 .map(|s| s.range())
18652 .collect();
18653 self.toggle_diff_hunks_in_ranges(ranges, cx);
18654 }
18655
18656 pub fn diff_hunks_in_ranges<'a>(
18657 &'a self,
18658 ranges: &'a [Range<Anchor>],
18659 buffer: &'a MultiBufferSnapshot,
18660 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18661 ranges.iter().flat_map(move |range| {
18662 let end_excerpt_id = range.end.excerpt_id;
18663 let range = range.to_point(buffer);
18664 let mut peek_end = range.end;
18665 if range.end.row < buffer.max_row().0 {
18666 peek_end = Point::new(range.end.row + 1, 0);
18667 }
18668 buffer
18669 .diff_hunks_in_range(range.start..peek_end)
18670 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18671 })
18672 }
18673
18674 pub fn has_stageable_diff_hunks_in_ranges(
18675 &self,
18676 ranges: &[Range<Anchor>],
18677 snapshot: &MultiBufferSnapshot,
18678 ) -> bool {
18679 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18680 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18681 }
18682
18683 pub fn toggle_staged_selected_diff_hunks(
18684 &mut self,
18685 _: &::git::ToggleStaged,
18686 _: &mut Window,
18687 cx: &mut Context<Self>,
18688 ) {
18689 let snapshot = self.buffer.read(cx).snapshot(cx);
18690 let ranges: Vec<_> = self
18691 .selections
18692 .disjoint_anchors()
18693 .iter()
18694 .map(|s| s.range())
18695 .collect();
18696 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18697 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18698 }
18699
18700 pub fn set_render_diff_hunk_controls(
18701 &mut self,
18702 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18703 cx: &mut Context<Self>,
18704 ) {
18705 self.render_diff_hunk_controls = render_diff_hunk_controls;
18706 cx.notify();
18707 }
18708
18709 pub fn stage_and_next(
18710 &mut self,
18711 _: &::git::StageAndNext,
18712 window: &mut Window,
18713 cx: &mut Context<Self>,
18714 ) {
18715 self.do_stage_or_unstage_and_next(true, window, cx);
18716 }
18717
18718 pub fn unstage_and_next(
18719 &mut self,
18720 _: &::git::UnstageAndNext,
18721 window: &mut Window,
18722 cx: &mut Context<Self>,
18723 ) {
18724 self.do_stage_or_unstage_and_next(false, window, cx);
18725 }
18726
18727 pub fn stage_or_unstage_diff_hunks(
18728 &mut self,
18729 stage: bool,
18730 ranges: Vec<Range<Anchor>>,
18731 cx: &mut Context<Self>,
18732 ) {
18733 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18734 cx.spawn(async move |this, cx| {
18735 task.await?;
18736 this.update(cx, |this, cx| {
18737 let snapshot = this.buffer.read(cx).snapshot(cx);
18738 let chunk_by = this
18739 .diff_hunks_in_ranges(&ranges, &snapshot)
18740 .chunk_by(|hunk| hunk.buffer_id);
18741 for (buffer_id, hunks) in &chunk_by {
18742 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18743 }
18744 })
18745 })
18746 .detach_and_log_err(cx);
18747 }
18748
18749 fn save_buffers_for_ranges_if_needed(
18750 &mut self,
18751 ranges: &[Range<Anchor>],
18752 cx: &mut Context<Editor>,
18753 ) -> Task<Result<()>> {
18754 let multibuffer = self.buffer.read(cx);
18755 let snapshot = multibuffer.read(cx);
18756 let buffer_ids: HashSet<_> = ranges
18757 .iter()
18758 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18759 .collect();
18760 drop(snapshot);
18761
18762 let mut buffers = HashSet::default();
18763 for buffer_id in buffer_ids {
18764 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18765 let buffer = buffer_entity.read(cx);
18766 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18767 {
18768 buffers.insert(buffer_entity);
18769 }
18770 }
18771 }
18772
18773 if let Some(project) = &self.project {
18774 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18775 } else {
18776 Task::ready(Ok(()))
18777 }
18778 }
18779
18780 fn do_stage_or_unstage_and_next(
18781 &mut self,
18782 stage: bool,
18783 window: &mut Window,
18784 cx: &mut Context<Self>,
18785 ) {
18786 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18787
18788 if ranges.iter().any(|range| range.start != range.end) {
18789 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18790 return;
18791 }
18792
18793 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18794 let snapshot = self.snapshot(window, cx);
18795 let position = self
18796 .selections
18797 .newest::<Point>(&snapshot.display_snapshot)
18798 .head();
18799 let mut row = snapshot
18800 .buffer_snapshot()
18801 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18802 .find(|hunk| hunk.row_range.start.0 > position.row)
18803 .map(|hunk| hunk.row_range.start);
18804
18805 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18806 // Outside of the project diff editor, wrap around to the beginning.
18807 if !all_diff_hunks_expanded {
18808 row = row.or_else(|| {
18809 snapshot
18810 .buffer_snapshot()
18811 .diff_hunks_in_range(Point::zero()..position)
18812 .find(|hunk| hunk.row_range.end.0 < position.row)
18813 .map(|hunk| hunk.row_range.start)
18814 });
18815 }
18816
18817 if let Some(row) = row {
18818 let destination = Point::new(row.0, 0);
18819 let autoscroll = Autoscroll::center();
18820
18821 self.unfold_ranges(&[destination..destination], false, false, cx);
18822 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
18823 s.select_ranges([destination..destination]);
18824 });
18825 }
18826 }
18827
18828 fn do_stage_or_unstage(
18829 &self,
18830 stage: bool,
18831 buffer_id: BufferId,
18832 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
18833 cx: &mut App,
18834 ) -> Option<()> {
18835 let project = self.project()?;
18836 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
18837 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
18838 let buffer_snapshot = buffer.read(cx).snapshot();
18839 let file_exists = buffer_snapshot
18840 .file()
18841 .is_some_and(|file| file.disk_state().exists());
18842 diff.update(cx, |diff, cx| {
18843 diff.stage_or_unstage_hunks(
18844 stage,
18845 &hunks
18846 .map(|hunk| buffer_diff::DiffHunk {
18847 buffer_range: hunk.buffer_range,
18848 diff_base_byte_range: hunk.diff_base_byte_range,
18849 secondary_status: hunk.secondary_status,
18850 range: Point::zero()..Point::zero(), // unused
18851 })
18852 .collect::<Vec<_>>(),
18853 &buffer_snapshot,
18854 file_exists,
18855 cx,
18856 )
18857 });
18858 None
18859 }
18860
18861 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
18862 let ranges: Vec<_> = self
18863 .selections
18864 .disjoint_anchors()
18865 .iter()
18866 .map(|s| s.range())
18867 .collect();
18868 self.buffer
18869 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
18870 }
18871
18872 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
18873 self.buffer.update(cx, |buffer, cx| {
18874 let ranges = vec![Anchor::min()..Anchor::max()];
18875 if !buffer.all_diff_hunks_expanded()
18876 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
18877 {
18878 buffer.collapse_diff_hunks(ranges, cx);
18879 true
18880 } else {
18881 false
18882 }
18883 })
18884 }
18885
18886 fn toggle_diff_hunks_in_ranges(
18887 &mut self,
18888 ranges: Vec<Range<Anchor>>,
18889 cx: &mut Context<Editor>,
18890 ) {
18891 self.buffer.update(cx, |buffer, cx| {
18892 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
18893 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
18894 })
18895 }
18896
18897 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
18898 self.buffer.update(cx, |buffer, cx| {
18899 let snapshot = buffer.snapshot(cx);
18900 let excerpt_id = range.end.excerpt_id;
18901 let point_range = range.to_point(&snapshot);
18902 let expand = !buffer.single_hunk_is_expanded(range, cx);
18903 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
18904 })
18905 }
18906
18907 pub(crate) fn apply_all_diff_hunks(
18908 &mut self,
18909 _: &ApplyAllDiffHunks,
18910 window: &mut Window,
18911 cx: &mut Context<Self>,
18912 ) {
18913 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18914
18915 let buffers = self.buffer.read(cx).all_buffers();
18916 for branch_buffer in buffers {
18917 branch_buffer.update(cx, |branch_buffer, cx| {
18918 branch_buffer.merge_into_base(Vec::new(), cx);
18919 });
18920 }
18921
18922 if let Some(project) = self.project.clone() {
18923 self.save(
18924 SaveOptions {
18925 format: true,
18926 autosave: false,
18927 },
18928 project,
18929 window,
18930 cx,
18931 )
18932 .detach_and_log_err(cx);
18933 }
18934 }
18935
18936 pub(crate) fn apply_selected_diff_hunks(
18937 &mut self,
18938 _: &ApplyDiffHunk,
18939 window: &mut Window,
18940 cx: &mut Context<Self>,
18941 ) {
18942 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18943 let snapshot = self.snapshot(window, cx);
18944 let hunks = snapshot.hunks_for_ranges(
18945 self.selections
18946 .all(&snapshot.display_snapshot)
18947 .into_iter()
18948 .map(|selection| selection.range()),
18949 );
18950 let mut ranges_by_buffer = HashMap::default();
18951 self.transact(window, cx, |editor, _window, cx| {
18952 for hunk in hunks {
18953 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
18954 ranges_by_buffer
18955 .entry(buffer.clone())
18956 .or_insert_with(Vec::new)
18957 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
18958 }
18959 }
18960
18961 for (buffer, ranges) in ranges_by_buffer {
18962 buffer.update(cx, |buffer, cx| {
18963 buffer.merge_into_base(ranges, cx);
18964 });
18965 }
18966 });
18967
18968 if let Some(project) = self.project.clone() {
18969 self.save(
18970 SaveOptions {
18971 format: true,
18972 autosave: false,
18973 },
18974 project,
18975 window,
18976 cx,
18977 )
18978 .detach_and_log_err(cx);
18979 }
18980 }
18981
18982 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
18983 if hovered != self.gutter_hovered {
18984 self.gutter_hovered = hovered;
18985 cx.notify();
18986 }
18987 }
18988
18989 pub fn insert_blocks(
18990 &mut self,
18991 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
18992 autoscroll: Option<Autoscroll>,
18993 cx: &mut Context<Self>,
18994 ) -> Vec<CustomBlockId> {
18995 let blocks = self
18996 .display_map
18997 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
18998 if let Some(autoscroll) = autoscroll {
18999 self.request_autoscroll(autoscroll, cx);
19000 }
19001 cx.notify();
19002 blocks
19003 }
19004
19005 pub fn resize_blocks(
19006 &mut self,
19007 heights: HashMap<CustomBlockId, u32>,
19008 autoscroll: Option<Autoscroll>,
19009 cx: &mut Context<Self>,
19010 ) {
19011 self.display_map
19012 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19013 if let Some(autoscroll) = autoscroll {
19014 self.request_autoscroll(autoscroll, cx);
19015 }
19016 cx.notify();
19017 }
19018
19019 pub fn replace_blocks(
19020 &mut self,
19021 renderers: HashMap<CustomBlockId, RenderBlock>,
19022 autoscroll: Option<Autoscroll>,
19023 cx: &mut Context<Self>,
19024 ) {
19025 self.display_map
19026 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19027 if let Some(autoscroll) = autoscroll {
19028 self.request_autoscroll(autoscroll, cx);
19029 }
19030 cx.notify();
19031 }
19032
19033 pub fn remove_blocks(
19034 &mut self,
19035 block_ids: HashSet<CustomBlockId>,
19036 autoscroll: Option<Autoscroll>,
19037 cx: &mut Context<Self>,
19038 ) {
19039 self.display_map.update(cx, |display_map, cx| {
19040 display_map.remove_blocks(block_ids, cx)
19041 });
19042 if let Some(autoscroll) = autoscroll {
19043 self.request_autoscroll(autoscroll, cx);
19044 }
19045 cx.notify();
19046 }
19047
19048 pub fn row_for_block(
19049 &self,
19050 block_id: CustomBlockId,
19051 cx: &mut Context<Self>,
19052 ) -> Option<DisplayRow> {
19053 self.display_map
19054 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19055 }
19056
19057 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19058 self.focused_block = Some(focused_block);
19059 }
19060
19061 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19062 self.focused_block.take()
19063 }
19064
19065 pub fn insert_creases(
19066 &mut self,
19067 creases: impl IntoIterator<Item = Crease<Anchor>>,
19068 cx: &mut Context<Self>,
19069 ) -> Vec<CreaseId> {
19070 self.display_map
19071 .update(cx, |map, cx| map.insert_creases(creases, cx))
19072 }
19073
19074 pub fn remove_creases(
19075 &mut self,
19076 ids: impl IntoIterator<Item = CreaseId>,
19077 cx: &mut Context<Self>,
19078 ) -> Vec<(CreaseId, Range<Anchor>)> {
19079 self.display_map
19080 .update(cx, |map, cx| map.remove_creases(ids, cx))
19081 }
19082
19083 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19084 self.display_map
19085 .update(cx, |map, cx| map.snapshot(cx))
19086 .longest_row()
19087 }
19088
19089 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19090 self.display_map
19091 .update(cx, |map, cx| map.snapshot(cx))
19092 .max_point()
19093 }
19094
19095 pub fn text(&self, cx: &App) -> String {
19096 self.buffer.read(cx).read(cx).text()
19097 }
19098
19099 pub fn is_empty(&self, cx: &App) -> bool {
19100 self.buffer.read(cx).read(cx).is_empty()
19101 }
19102
19103 pub fn text_option(&self, cx: &App) -> Option<String> {
19104 let text = self.text(cx);
19105 let text = text.trim();
19106
19107 if text.is_empty() {
19108 return None;
19109 }
19110
19111 Some(text.to_string())
19112 }
19113
19114 pub fn set_text(
19115 &mut self,
19116 text: impl Into<Arc<str>>,
19117 window: &mut Window,
19118 cx: &mut Context<Self>,
19119 ) {
19120 self.transact(window, cx, |this, _, cx| {
19121 this.buffer
19122 .read(cx)
19123 .as_singleton()
19124 .expect("you can only call set_text on editors for singleton buffers")
19125 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19126 });
19127 }
19128
19129 pub fn display_text(&self, cx: &mut App) -> String {
19130 self.display_map
19131 .update(cx, |map, cx| map.snapshot(cx))
19132 .text()
19133 }
19134
19135 fn create_minimap(
19136 &self,
19137 minimap_settings: MinimapSettings,
19138 window: &mut Window,
19139 cx: &mut Context<Self>,
19140 ) -> Option<Entity<Self>> {
19141 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19142 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19143 }
19144
19145 fn initialize_new_minimap(
19146 &self,
19147 minimap_settings: MinimapSettings,
19148 window: &mut Window,
19149 cx: &mut Context<Self>,
19150 ) -> Entity<Self> {
19151 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19152
19153 let mut minimap = Editor::new_internal(
19154 EditorMode::Minimap {
19155 parent: cx.weak_entity(),
19156 },
19157 self.buffer.clone(),
19158 None,
19159 Some(self.display_map.clone()),
19160 window,
19161 cx,
19162 );
19163 minimap.scroll_manager.clone_state(&self.scroll_manager);
19164 minimap.set_text_style_refinement(TextStyleRefinement {
19165 font_size: Some(MINIMAP_FONT_SIZE),
19166 font_weight: Some(MINIMAP_FONT_WEIGHT),
19167 ..Default::default()
19168 });
19169 minimap.update_minimap_configuration(minimap_settings, cx);
19170 cx.new(|_| minimap)
19171 }
19172
19173 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19174 let current_line_highlight = minimap_settings
19175 .current_line_highlight
19176 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19177 self.set_current_line_highlight(Some(current_line_highlight));
19178 }
19179
19180 pub fn minimap(&self) -> Option<&Entity<Self>> {
19181 self.minimap
19182 .as_ref()
19183 .filter(|_| self.minimap_visibility.visible())
19184 }
19185
19186 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19187 let mut wrap_guides = smallvec![];
19188
19189 if self.show_wrap_guides == Some(false) {
19190 return wrap_guides;
19191 }
19192
19193 let settings = self.buffer.read(cx).language_settings(cx);
19194 if settings.show_wrap_guides {
19195 match self.soft_wrap_mode(cx) {
19196 SoftWrap::Column(soft_wrap) => {
19197 wrap_guides.push((soft_wrap as usize, true));
19198 }
19199 SoftWrap::Bounded(soft_wrap) => {
19200 wrap_guides.push((soft_wrap as usize, true));
19201 }
19202 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19203 }
19204 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19205 }
19206
19207 wrap_guides
19208 }
19209
19210 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19211 let settings = self.buffer.read(cx).language_settings(cx);
19212 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19213 match mode {
19214 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19215 SoftWrap::None
19216 }
19217 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19218 language_settings::SoftWrap::PreferredLineLength => {
19219 SoftWrap::Column(settings.preferred_line_length)
19220 }
19221 language_settings::SoftWrap::Bounded => {
19222 SoftWrap::Bounded(settings.preferred_line_length)
19223 }
19224 }
19225 }
19226
19227 pub fn set_soft_wrap_mode(
19228 &mut self,
19229 mode: language_settings::SoftWrap,
19230
19231 cx: &mut Context<Self>,
19232 ) {
19233 self.soft_wrap_mode_override = Some(mode);
19234 cx.notify();
19235 }
19236
19237 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19238 self.hard_wrap = hard_wrap;
19239 cx.notify();
19240 }
19241
19242 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19243 self.text_style_refinement = Some(style);
19244 }
19245
19246 /// called by the Element so we know what style we were most recently rendered with.
19247 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19248 // We intentionally do not inform the display map about the minimap style
19249 // so that wrapping is not recalculated and stays consistent for the editor
19250 // and its linked minimap.
19251 if !self.mode.is_minimap() {
19252 let font = style.text.font();
19253 let font_size = style.text.font_size.to_pixels(window.rem_size());
19254 let display_map = self
19255 .placeholder_display_map
19256 .as_ref()
19257 .filter(|_| self.is_empty(cx))
19258 .unwrap_or(&self.display_map);
19259
19260 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19261 }
19262 self.style = Some(style);
19263 }
19264
19265 pub fn style(&self) -> Option<&EditorStyle> {
19266 self.style.as_ref()
19267 }
19268
19269 // Called by the element. This method is not designed to be called outside of the editor
19270 // element's layout code because it does not notify when rewrapping is computed synchronously.
19271 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19272 if self.is_empty(cx) {
19273 self.placeholder_display_map
19274 .as_ref()
19275 .map_or(false, |display_map| {
19276 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19277 })
19278 } else {
19279 self.display_map
19280 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19281 }
19282 }
19283
19284 pub fn set_soft_wrap(&mut self) {
19285 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19286 }
19287
19288 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19289 if self.soft_wrap_mode_override.is_some() {
19290 self.soft_wrap_mode_override.take();
19291 } else {
19292 let soft_wrap = match self.soft_wrap_mode(cx) {
19293 SoftWrap::GitDiff => return,
19294 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19295 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19296 language_settings::SoftWrap::None
19297 }
19298 };
19299 self.soft_wrap_mode_override = Some(soft_wrap);
19300 }
19301 cx.notify();
19302 }
19303
19304 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19305 let Some(workspace) = self.workspace() else {
19306 return;
19307 };
19308 let fs = workspace.read(cx).app_state().fs.clone();
19309 let current_show = TabBarSettings::get_global(cx).show;
19310 update_settings_file(fs, cx, move |setting, _| {
19311 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19312 });
19313 }
19314
19315 pub fn toggle_indent_guides(
19316 &mut self,
19317 _: &ToggleIndentGuides,
19318 _: &mut Window,
19319 cx: &mut Context<Self>,
19320 ) {
19321 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19322 self.buffer
19323 .read(cx)
19324 .language_settings(cx)
19325 .indent_guides
19326 .enabled
19327 });
19328 self.show_indent_guides = Some(!currently_enabled);
19329 cx.notify();
19330 }
19331
19332 fn should_show_indent_guides(&self) -> Option<bool> {
19333 self.show_indent_guides
19334 }
19335
19336 pub fn toggle_line_numbers(
19337 &mut self,
19338 _: &ToggleLineNumbers,
19339 _: &mut Window,
19340 cx: &mut Context<Self>,
19341 ) {
19342 let mut editor_settings = EditorSettings::get_global(cx).clone();
19343 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19344 EditorSettings::override_global(editor_settings, cx);
19345 }
19346
19347 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19348 if let Some(show_line_numbers) = self.show_line_numbers {
19349 return show_line_numbers;
19350 }
19351 EditorSettings::get_global(cx).gutter.line_numbers
19352 }
19353
19354 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19355 self.use_relative_line_numbers
19356 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19357 }
19358
19359 pub fn toggle_relative_line_numbers(
19360 &mut self,
19361 _: &ToggleRelativeLineNumbers,
19362 _: &mut Window,
19363 cx: &mut Context<Self>,
19364 ) {
19365 let is_relative = self.should_use_relative_line_numbers(cx);
19366 self.set_relative_line_number(Some(!is_relative), cx)
19367 }
19368
19369 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19370 self.use_relative_line_numbers = is_relative;
19371 cx.notify();
19372 }
19373
19374 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19375 self.show_gutter = show_gutter;
19376 cx.notify();
19377 }
19378
19379 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19380 self.show_scrollbars = ScrollbarAxes {
19381 horizontal: show,
19382 vertical: show,
19383 };
19384 cx.notify();
19385 }
19386
19387 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19388 self.show_scrollbars.vertical = show;
19389 cx.notify();
19390 }
19391
19392 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19393 self.show_scrollbars.horizontal = show;
19394 cx.notify();
19395 }
19396
19397 pub fn set_minimap_visibility(
19398 &mut self,
19399 minimap_visibility: MinimapVisibility,
19400 window: &mut Window,
19401 cx: &mut Context<Self>,
19402 ) {
19403 if self.minimap_visibility != minimap_visibility {
19404 if minimap_visibility.visible() && self.minimap.is_none() {
19405 let minimap_settings = EditorSettings::get_global(cx).minimap;
19406 self.minimap =
19407 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19408 }
19409 self.minimap_visibility = minimap_visibility;
19410 cx.notify();
19411 }
19412 }
19413
19414 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19415 self.set_show_scrollbars(false, cx);
19416 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19417 }
19418
19419 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19420 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19421 }
19422
19423 /// Normally the text in full mode and auto height editors is padded on the
19424 /// left side by roughly half a character width for improved hit testing.
19425 ///
19426 /// Use this method to disable this for cases where this is not wanted (e.g.
19427 /// if you want to align the editor text with some other text above or below)
19428 /// or if you want to add this padding to single-line editors.
19429 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19430 self.offset_content = offset_content;
19431 cx.notify();
19432 }
19433
19434 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19435 self.show_line_numbers = Some(show_line_numbers);
19436 cx.notify();
19437 }
19438
19439 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19440 self.disable_expand_excerpt_buttons = true;
19441 cx.notify();
19442 }
19443
19444 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19445 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19446 cx.notify();
19447 }
19448
19449 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19450 self.show_code_actions = Some(show_code_actions);
19451 cx.notify();
19452 }
19453
19454 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19455 self.show_runnables = Some(show_runnables);
19456 cx.notify();
19457 }
19458
19459 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19460 self.show_breakpoints = Some(show_breakpoints);
19461 cx.notify();
19462 }
19463
19464 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19465 if self.display_map.read(cx).masked != masked {
19466 self.display_map.update(cx, |map, _| map.masked = masked);
19467 }
19468 cx.notify()
19469 }
19470
19471 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19472 self.show_wrap_guides = Some(show_wrap_guides);
19473 cx.notify();
19474 }
19475
19476 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19477 self.show_indent_guides = Some(show_indent_guides);
19478 cx.notify();
19479 }
19480
19481 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19482 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19483 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19484 && let Some(dir) = file.abs_path(cx).parent()
19485 {
19486 return Some(dir.to_owned());
19487 }
19488 }
19489
19490 None
19491 }
19492
19493 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19494 self.active_excerpt(cx)?
19495 .1
19496 .read(cx)
19497 .file()
19498 .and_then(|f| f.as_local())
19499 }
19500
19501 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19502 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19503 let buffer = buffer.read(cx);
19504 if let Some(project_path) = buffer.project_path(cx) {
19505 let project = self.project()?.read(cx);
19506 project.absolute_path(&project_path, cx)
19507 } else {
19508 buffer
19509 .file()
19510 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19511 }
19512 })
19513 }
19514
19515 pub fn reveal_in_finder(
19516 &mut self,
19517 _: &RevealInFileManager,
19518 _window: &mut Window,
19519 cx: &mut Context<Self>,
19520 ) {
19521 if let Some(target) = self.target_file(cx) {
19522 cx.reveal_path(&target.abs_path(cx));
19523 }
19524 }
19525
19526 pub fn copy_path(
19527 &mut self,
19528 _: &zed_actions::workspace::CopyPath,
19529 _window: &mut Window,
19530 cx: &mut Context<Self>,
19531 ) {
19532 if let Some(path) = self.target_file_abs_path(cx)
19533 && let Some(path) = path.to_str()
19534 {
19535 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19536 } else {
19537 cx.propagate();
19538 }
19539 }
19540
19541 pub fn copy_relative_path(
19542 &mut self,
19543 _: &zed_actions::workspace::CopyRelativePath,
19544 _window: &mut Window,
19545 cx: &mut Context<Self>,
19546 ) {
19547 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19548 let project = self.project()?.read(cx);
19549 let path = buffer.read(cx).file()?.path();
19550 let path = path.display(project.path_style(cx));
19551 Some(path)
19552 }) {
19553 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19554 } else {
19555 cx.propagate();
19556 }
19557 }
19558
19559 /// Returns the project path for the editor's buffer, if any buffer is
19560 /// opened in the editor.
19561 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19562 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19563 buffer.read(cx).project_path(cx)
19564 } else {
19565 None
19566 }
19567 }
19568
19569 // Returns true if the editor handled a go-to-line request
19570 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19571 maybe!({
19572 let breakpoint_store = self.breakpoint_store.as_ref()?;
19573
19574 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19575 else {
19576 self.clear_row_highlights::<ActiveDebugLine>();
19577 return None;
19578 };
19579
19580 let position = active_stack_frame.position;
19581 let buffer_id = position.buffer_id?;
19582 let snapshot = self
19583 .project
19584 .as_ref()?
19585 .read(cx)
19586 .buffer_for_id(buffer_id, cx)?
19587 .read(cx)
19588 .snapshot();
19589
19590 let mut handled = false;
19591 for (id, ExcerptRange { context, .. }) in
19592 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19593 {
19594 if context.start.cmp(&position, &snapshot).is_ge()
19595 || context.end.cmp(&position, &snapshot).is_lt()
19596 {
19597 continue;
19598 }
19599 let snapshot = self.buffer.read(cx).snapshot(cx);
19600 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19601
19602 handled = true;
19603 self.clear_row_highlights::<ActiveDebugLine>();
19604
19605 self.go_to_line::<ActiveDebugLine>(
19606 multibuffer_anchor,
19607 Some(cx.theme().colors().editor_debugger_active_line_background),
19608 window,
19609 cx,
19610 );
19611
19612 cx.notify();
19613 }
19614
19615 handled.then_some(())
19616 })
19617 .is_some()
19618 }
19619
19620 pub fn copy_file_name_without_extension(
19621 &mut self,
19622 _: &CopyFileNameWithoutExtension,
19623 _: &mut Window,
19624 cx: &mut Context<Self>,
19625 ) {
19626 if let Some(file) = self.target_file(cx)
19627 && let Some(file_stem) = file.path().file_stem()
19628 {
19629 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19630 }
19631 }
19632
19633 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19634 if let Some(file) = self.target_file(cx)
19635 && let Some(name) = file.path().file_name()
19636 {
19637 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19638 }
19639 }
19640
19641 pub fn toggle_git_blame(
19642 &mut self,
19643 _: &::git::Blame,
19644 window: &mut Window,
19645 cx: &mut Context<Self>,
19646 ) {
19647 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19648
19649 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19650 self.start_git_blame(true, window, cx);
19651 }
19652
19653 cx.notify();
19654 }
19655
19656 pub fn toggle_git_blame_inline(
19657 &mut self,
19658 _: &ToggleGitBlameInline,
19659 window: &mut Window,
19660 cx: &mut Context<Self>,
19661 ) {
19662 self.toggle_git_blame_inline_internal(true, window, cx);
19663 cx.notify();
19664 }
19665
19666 pub fn open_git_blame_commit(
19667 &mut self,
19668 _: &OpenGitBlameCommit,
19669 window: &mut Window,
19670 cx: &mut Context<Self>,
19671 ) {
19672 self.open_git_blame_commit_internal(window, cx);
19673 }
19674
19675 fn open_git_blame_commit_internal(
19676 &mut self,
19677 window: &mut Window,
19678 cx: &mut Context<Self>,
19679 ) -> Option<()> {
19680 let blame = self.blame.as_ref()?;
19681 let snapshot = self.snapshot(window, cx);
19682 let cursor = self
19683 .selections
19684 .newest::<Point>(&snapshot.display_snapshot)
19685 .head();
19686 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19687 let (_, blame_entry) = blame
19688 .update(cx, |blame, cx| {
19689 blame
19690 .blame_for_rows(
19691 &[RowInfo {
19692 buffer_id: Some(buffer.remote_id()),
19693 buffer_row: Some(point.row),
19694 ..Default::default()
19695 }],
19696 cx,
19697 )
19698 .next()
19699 })
19700 .flatten()?;
19701 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19702 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19703 let workspace = self.workspace()?.downgrade();
19704 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19705 None
19706 }
19707
19708 pub fn git_blame_inline_enabled(&self) -> bool {
19709 self.git_blame_inline_enabled
19710 }
19711
19712 pub fn toggle_selection_menu(
19713 &mut self,
19714 _: &ToggleSelectionMenu,
19715 _: &mut Window,
19716 cx: &mut Context<Self>,
19717 ) {
19718 self.show_selection_menu = self
19719 .show_selection_menu
19720 .map(|show_selections_menu| !show_selections_menu)
19721 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19722
19723 cx.notify();
19724 }
19725
19726 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19727 self.show_selection_menu
19728 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19729 }
19730
19731 fn start_git_blame(
19732 &mut self,
19733 user_triggered: bool,
19734 window: &mut Window,
19735 cx: &mut Context<Self>,
19736 ) {
19737 if let Some(project) = self.project() {
19738 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19739 && buffer.read(cx).file().is_none()
19740 {
19741 return;
19742 }
19743
19744 let focused = self.focus_handle(cx).contains_focused(window, cx);
19745
19746 let project = project.clone();
19747 let blame = cx
19748 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19749 self.blame_subscription =
19750 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19751 self.blame = Some(blame);
19752 }
19753 }
19754
19755 fn toggle_git_blame_inline_internal(
19756 &mut self,
19757 user_triggered: bool,
19758 window: &mut Window,
19759 cx: &mut Context<Self>,
19760 ) {
19761 if self.git_blame_inline_enabled {
19762 self.git_blame_inline_enabled = false;
19763 self.show_git_blame_inline = false;
19764 self.show_git_blame_inline_delay_task.take();
19765 } else {
19766 self.git_blame_inline_enabled = true;
19767 self.start_git_blame_inline(user_triggered, window, cx);
19768 }
19769
19770 cx.notify();
19771 }
19772
19773 fn start_git_blame_inline(
19774 &mut self,
19775 user_triggered: bool,
19776 window: &mut Window,
19777 cx: &mut Context<Self>,
19778 ) {
19779 self.start_git_blame(user_triggered, window, cx);
19780
19781 if ProjectSettings::get_global(cx)
19782 .git
19783 .inline_blame_delay()
19784 .is_some()
19785 {
19786 self.start_inline_blame_timer(window, cx);
19787 } else {
19788 self.show_git_blame_inline = true
19789 }
19790 }
19791
19792 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19793 self.blame.as_ref()
19794 }
19795
19796 pub fn show_git_blame_gutter(&self) -> bool {
19797 self.show_git_blame_gutter
19798 }
19799
19800 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19801 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19802 }
19803
19804 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19805 self.show_git_blame_inline
19806 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19807 && !self.newest_selection_head_on_empty_line(cx)
19808 && self.has_blame_entries(cx)
19809 }
19810
19811 fn has_blame_entries(&self, cx: &App) -> bool {
19812 self.blame()
19813 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19814 }
19815
19816 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
19817 let cursor_anchor = self.selections.newest_anchor().head();
19818
19819 let snapshot = self.buffer.read(cx).snapshot(cx);
19820 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
19821
19822 snapshot.line_len(buffer_row) == 0
19823 }
19824
19825 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
19826 let buffer_and_selection = maybe!({
19827 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19828 let selection_range = selection.range();
19829
19830 let multi_buffer = self.buffer().read(cx);
19831 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
19832 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
19833
19834 let (buffer, range, _) = if selection.reversed {
19835 buffer_ranges.first()
19836 } else {
19837 buffer_ranges.last()
19838 }?;
19839
19840 let selection = text::ToPoint::to_point(&range.start, buffer).row
19841 ..text::ToPoint::to_point(&range.end, buffer).row;
19842 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
19843 });
19844
19845 let Some((buffer, selection)) = buffer_and_selection else {
19846 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
19847 };
19848
19849 let Some(project) = self.project() else {
19850 return Task::ready(Err(anyhow!("editor does not have project")));
19851 };
19852
19853 project.update(cx, |project, cx| {
19854 project.get_permalink_to_line(&buffer, selection, cx)
19855 })
19856 }
19857
19858 pub fn copy_permalink_to_line(
19859 &mut self,
19860 _: &CopyPermalinkToLine,
19861 window: &mut Window,
19862 cx: &mut Context<Self>,
19863 ) {
19864 let permalink_task = self.get_permalink_to_line(cx);
19865 let workspace = self.workspace();
19866
19867 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19868 Ok(permalink) => {
19869 cx.update(|_, cx| {
19870 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
19871 })
19872 .ok();
19873 }
19874 Err(err) => {
19875 let message = format!("Failed to copy permalink: {err}");
19876
19877 anyhow::Result::<()>::Err(err).log_err();
19878
19879 if let Some(workspace) = workspace {
19880 workspace
19881 .update_in(cx, |workspace, _, cx| {
19882 struct CopyPermalinkToLine;
19883
19884 workspace.show_toast(
19885 Toast::new(
19886 NotificationId::unique::<CopyPermalinkToLine>(),
19887 message,
19888 ),
19889 cx,
19890 )
19891 })
19892 .ok();
19893 }
19894 }
19895 })
19896 .detach();
19897 }
19898
19899 pub fn copy_file_location(
19900 &mut self,
19901 _: &CopyFileLocation,
19902 _: &mut Window,
19903 cx: &mut Context<Self>,
19904 ) {
19905 let selection = self
19906 .selections
19907 .newest::<Point>(&self.display_snapshot(cx))
19908 .start
19909 .row
19910 + 1;
19911 if let Some(file) = self.target_file(cx) {
19912 let path = file.path().display(file.path_style(cx));
19913 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
19914 }
19915 }
19916
19917 pub fn open_permalink_to_line(
19918 &mut self,
19919 _: &OpenPermalinkToLine,
19920 window: &mut Window,
19921 cx: &mut Context<Self>,
19922 ) {
19923 let permalink_task = self.get_permalink_to_line(cx);
19924 let workspace = self.workspace();
19925
19926 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
19927 Ok(permalink) => {
19928 cx.update(|_, cx| {
19929 cx.open_url(permalink.as_ref());
19930 })
19931 .ok();
19932 }
19933 Err(err) => {
19934 let message = format!("Failed to open permalink: {err}");
19935
19936 anyhow::Result::<()>::Err(err).log_err();
19937
19938 if let Some(workspace) = workspace {
19939 workspace
19940 .update(cx, |workspace, cx| {
19941 struct OpenPermalinkToLine;
19942
19943 workspace.show_toast(
19944 Toast::new(
19945 NotificationId::unique::<OpenPermalinkToLine>(),
19946 message,
19947 ),
19948 cx,
19949 )
19950 })
19951 .ok();
19952 }
19953 }
19954 })
19955 .detach();
19956 }
19957
19958 pub fn insert_uuid_v4(
19959 &mut self,
19960 _: &InsertUuidV4,
19961 window: &mut Window,
19962 cx: &mut Context<Self>,
19963 ) {
19964 self.insert_uuid(UuidVersion::V4, window, cx);
19965 }
19966
19967 pub fn insert_uuid_v7(
19968 &mut self,
19969 _: &InsertUuidV7,
19970 window: &mut Window,
19971 cx: &mut Context<Self>,
19972 ) {
19973 self.insert_uuid(UuidVersion::V7, window, cx);
19974 }
19975
19976 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
19977 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19978 self.transact(window, cx, |this, window, cx| {
19979 let edits = this
19980 .selections
19981 .all::<Point>(&this.display_snapshot(cx))
19982 .into_iter()
19983 .map(|selection| {
19984 let uuid = match version {
19985 UuidVersion::V4 => uuid::Uuid::new_v4(),
19986 UuidVersion::V7 => uuid::Uuid::now_v7(),
19987 };
19988
19989 (selection.range(), uuid.to_string())
19990 });
19991 this.edit(edits, cx);
19992 this.refresh_edit_prediction(true, false, window, cx);
19993 });
19994 }
19995
19996 pub fn open_selections_in_multibuffer(
19997 &mut self,
19998 _: &OpenSelectionsInMultibuffer,
19999 window: &mut Window,
20000 cx: &mut Context<Self>,
20001 ) {
20002 let multibuffer = self.buffer.read(cx);
20003
20004 let Some(buffer) = multibuffer.as_singleton() else {
20005 return;
20006 };
20007
20008 let Some(workspace) = self.workspace() else {
20009 return;
20010 };
20011
20012 let title = multibuffer.title(cx).to_string();
20013
20014 let locations = self
20015 .selections
20016 .all_anchors(cx)
20017 .iter()
20018 .map(|selection| {
20019 (
20020 buffer.clone(),
20021 (selection.start.text_anchor..selection.end.text_anchor)
20022 .to_point(buffer.read(cx)),
20023 )
20024 })
20025 .into_group_map();
20026
20027 cx.spawn_in(window, async move |_, cx| {
20028 workspace.update_in(cx, |workspace, window, cx| {
20029 Self::open_locations_in_multibuffer(
20030 workspace,
20031 locations,
20032 format!("Selections for '{title}'"),
20033 false,
20034 MultibufferSelectionMode::All,
20035 window,
20036 cx,
20037 );
20038 })
20039 })
20040 .detach();
20041 }
20042
20043 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20044 /// last highlight added will be used.
20045 ///
20046 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20047 pub fn highlight_rows<T: 'static>(
20048 &mut self,
20049 range: Range<Anchor>,
20050 color: Hsla,
20051 options: RowHighlightOptions,
20052 cx: &mut Context<Self>,
20053 ) {
20054 let snapshot = self.buffer().read(cx).snapshot(cx);
20055 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20056 let ix = row_highlights.binary_search_by(|highlight| {
20057 Ordering::Equal
20058 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20059 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20060 });
20061
20062 if let Err(mut ix) = ix {
20063 let index = post_inc(&mut self.highlight_order);
20064
20065 // If this range intersects with the preceding highlight, then merge it with
20066 // the preceding highlight. Otherwise insert a new highlight.
20067 let mut merged = false;
20068 if ix > 0 {
20069 let prev_highlight = &mut row_highlights[ix - 1];
20070 if prev_highlight
20071 .range
20072 .end
20073 .cmp(&range.start, &snapshot)
20074 .is_ge()
20075 {
20076 ix -= 1;
20077 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20078 prev_highlight.range.end = range.end;
20079 }
20080 merged = true;
20081 prev_highlight.index = index;
20082 prev_highlight.color = color;
20083 prev_highlight.options = options;
20084 }
20085 }
20086
20087 if !merged {
20088 row_highlights.insert(
20089 ix,
20090 RowHighlight {
20091 range,
20092 index,
20093 color,
20094 options,
20095 type_id: TypeId::of::<T>(),
20096 },
20097 );
20098 }
20099
20100 // If any of the following highlights intersect with this one, merge them.
20101 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20102 let highlight = &row_highlights[ix];
20103 if next_highlight
20104 .range
20105 .start
20106 .cmp(&highlight.range.end, &snapshot)
20107 .is_le()
20108 {
20109 if next_highlight
20110 .range
20111 .end
20112 .cmp(&highlight.range.end, &snapshot)
20113 .is_gt()
20114 {
20115 row_highlights[ix].range.end = next_highlight.range.end;
20116 }
20117 row_highlights.remove(ix + 1);
20118 } else {
20119 break;
20120 }
20121 }
20122 }
20123 }
20124
20125 /// Remove any highlighted row ranges of the given type that intersect the
20126 /// given ranges.
20127 pub fn remove_highlighted_rows<T: 'static>(
20128 &mut self,
20129 ranges_to_remove: Vec<Range<Anchor>>,
20130 cx: &mut Context<Self>,
20131 ) {
20132 let snapshot = self.buffer().read(cx).snapshot(cx);
20133 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20134 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20135 row_highlights.retain(|highlight| {
20136 while let Some(range_to_remove) = ranges_to_remove.peek() {
20137 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20138 Ordering::Less | Ordering::Equal => {
20139 ranges_to_remove.next();
20140 }
20141 Ordering::Greater => {
20142 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20143 Ordering::Less | Ordering::Equal => {
20144 return false;
20145 }
20146 Ordering::Greater => break,
20147 }
20148 }
20149 }
20150 }
20151
20152 true
20153 })
20154 }
20155
20156 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20157 pub fn clear_row_highlights<T: 'static>(&mut self) {
20158 self.highlighted_rows.remove(&TypeId::of::<T>());
20159 }
20160
20161 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20162 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20163 self.highlighted_rows
20164 .get(&TypeId::of::<T>())
20165 .map_or(&[] as &[_], |vec| vec.as_slice())
20166 .iter()
20167 .map(|highlight| (highlight.range.clone(), highlight.color))
20168 }
20169
20170 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20171 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20172 /// Allows to ignore certain kinds of highlights.
20173 pub fn highlighted_display_rows(
20174 &self,
20175 window: &mut Window,
20176 cx: &mut App,
20177 ) -> BTreeMap<DisplayRow, LineHighlight> {
20178 let snapshot = self.snapshot(window, cx);
20179 let mut used_highlight_orders = HashMap::default();
20180 self.highlighted_rows
20181 .iter()
20182 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20183 .fold(
20184 BTreeMap::<DisplayRow, LineHighlight>::new(),
20185 |mut unique_rows, highlight| {
20186 let start = highlight.range.start.to_display_point(&snapshot);
20187 let end = highlight.range.end.to_display_point(&snapshot);
20188 let start_row = start.row().0;
20189 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20190 && end.column() == 0
20191 {
20192 end.row().0.saturating_sub(1)
20193 } else {
20194 end.row().0
20195 };
20196 for row in start_row..=end_row {
20197 let used_index =
20198 used_highlight_orders.entry(row).or_insert(highlight.index);
20199 if highlight.index >= *used_index {
20200 *used_index = highlight.index;
20201 unique_rows.insert(
20202 DisplayRow(row),
20203 LineHighlight {
20204 include_gutter: highlight.options.include_gutter,
20205 border: None,
20206 background: highlight.color.into(),
20207 type_id: Some(highlight.type_id),
20208 },
20209 );
20210 }
20211 }
20212 unique_rows
20213 },
20214 )
20215 }
20216
20217 pub fn highlighted_display_row_for_autoscroll(
20218 &self,
20219 snapshot: &DisplaySnapshot,
20220 ) -> Option<DisplayRow> {
20221 self.highlighted_rows
20222 .values()
20223 .flat_map(|highlighted_rows| highlighted_rows.iter())
20224 .filter_map(|highlight| {
20225 if highlight.options.autoscroll {
20226 Some(highlight.range.start.to_display_point(snapshot).row())
20227 } else {
20228 None
20229 }
20230 })
20231 .min()
20232 }
20233
20234 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20235 self.highlight_background::<SearchWithinRange>(
20236 ranges,
20237 |colors| colors.colors().editor_document_highlight_read_background,
20238 cx,
20239 )
20240 }
20241
20242 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20243 self.breadcrumb_header = Some(new_header);
20244 }
20245
20246 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20247 self.clear_background_highlights::<SearchWithinRange>(cx);
20248 }
20249
20250 pub fn highlight_background<T: 'static>(
20251 &mut self,
20252 ranges: &[Range<Anchor>],
20253 color_fetcher: fn(&Theme) -> Hsla,
20254 cx: &mut Context<Self>,
20255 ) {
20256 self.background_highlights.insert(
20257 HighlightKey::Type(TypeId::of::<T>()),
20258 (color_fetcher, Arc::from(ranges)),
20259 );
20260 self.scrollbar_marker_state.dirty = true;
20261 cx.notify();
20262 }
20263
20264 pub fn highlight_background_key<T: 'static>(
20265 &mut self,
20266 key: usize,
20267 ranges: &[Range<Anchor>],
20268 color_fetcher: fn(&Theme) -> Hsla,
20269 cx: &mut Context<Self>,
20270 ) {
20271 self.background_highlights.insert(
20272 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20273 (color_fetcher, Arc::from(ranges)),
20274 );
20275 self.scrollbar_marker_state.dirty = true;
20276 cx.notify();
20277 }
20278
20279 pub fn clear_background_highlights<T: 'static>(
20280 &mut self,
20281 cx: &mut Context<Self>,
20282 ) -> Option<BackgroundHighlight> {
20283 let text_highlights = self
20284 .background_highlights
20285 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20286 if !text_highlights.1.is_empty() {
20287 self.scrollbar_marker_state.dirty = true;
20288 cx.notify();
20289 }
20290 Some(text_highlights)
20291 }
20292
20293 pub fn highlight_gutter<T: 'static>(
20294 &mut self,
20295 ranges: impl Into<Vec<Range<Anchor>>>,
20296 color_fetcher: fn(&App) -> Hsla,
20297 cx: &mut Context<Self>,
20298 ) {
20299 self.gutter_highlights
20300 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20301 cx.notify();
20302 }
20303
20304 pub fn clear_gutter_highlights<T: 'static>(
20305 &mut self,
20306 cx: &mut Context<Self>,
20307 ) -> Option<GutterHighlight> {
20308 cx.notify();
20309 self.gutter_highlights.remove(&TypeId::of::<T>())
20310 }
20311
20312 pub fn insert_gutter_highlight<T: 'static>(
20313 &mut self,
20314 range: Range<Anchor>,
20315 color_fetcher: fn(&App) -> Hsla,
20316 cx: &mut Context<Self>,
20317 ) {
20318 let snapshot = self.buffer().read(cx).snapshot(cx);
20319 let mut highlights = self
20320 .gutter_highlights
20321 .remove(&TypeId::of::<T>())
20322 .map(|(_, highlights)| highlights)
20323 .unwrap_or_default();
20324 let ix = highlights.binary_search_by(|highlight| {
20325 Ordering::Equal
20326 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20327 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20328 });
20329 if let Err(ix) = ix {
20330 highlights.insert(ix, range);
20331 }
20332 self.gutter_highlights
20333 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20334 }
20335
20336 pub fn remove_gutter_highlights<T: 'static>(
20337 &mut self,
20338 ranges_to_remove: Vec<Range<Anchor>>,
20339 cx: &mut Context<Self>,
20340 ) {
20341 let snapshot = self.buffer().read(cx).snapshot(cx);
20342 let Some((color_fetcher, mut gutter_highlights)) =
20343 self.gutter_highlights.remove(&TypeId::of::<T>())
20344 else {
20345 return;
20346 };
20347 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20348 gutter_highlights.retain(|highlight| {
20349 while let Some(range_to_remove) = ranges_to_remove.peek() {
20350 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20351 Ordering::Less | Ordering::Equal => {
20352 ranges_to_remove.next();
20353 }
20354 Ordering::Greater => {
20355 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20356 Ordering::Less | Ordering::Equal => {
20357 return false;
20358 }
20359 Ordering::Greater => break,
20360 }
20361 }
20362 }
20363 }
20364
20365 true
20366 });
20367 self.gutter_highlights
20368 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20369 }
20370
20371 #[cfg(feature = "test-support")]
20372 pub fn all_text_highlights(
20373 &self,
20374 window: &mut Window,
20375 cx: &mut Context<Self>,
20376 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20377 let snapshot = self.snapshot(window, cx);
20378 self.display_map.update(cx, |display_map, _| {
20379 display_map
20380 .all_text_highlights()
20381 .map(|highlight| {
20382 let (style, ranges) = highlight.as_ref();
20383 (
20384 *style,
20385 ranges
20386 .iter()
20387 .map(|range| range.clone().to_display_points(&snapshot))
20388 .collect(),
20389 )
20390 })
20391 .collect()
20392 })
20393 }
20394
20395 #[cfg(feature = "test-support")]
20396 pub fn all_text_background_highlights(
20397 &self,
20398 window: &mut Window,
20399 cx: &mut Context<Self>,
20400 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20401 let snapshot = self.snapshot(window, cx);
20402 let buffer = &snapshot.buffer_snapshot();
20403 let start = buffer.anchor_before(0);
20404 let end = buffer.anchor_after(buffer.len());
20405 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20406 }
20407
20408 #[cfg(any(test, feature = "test-support"))]
20409 pub fn sorted_background_highlights_in_range(
20410 &self,
20411 search_range: Range<Anchor>,
20412 display_snapshot: &DisplaySnapshot,
20413 theme: &Theme,
20414 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20415 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20416 res.sort_by(|a, b| {
20417 a.0.start
20418 .cmp(&b.0.start)
20419 .then_with(|| a.0.end.cmp(&b.0.end))
20420 .then_with(|| a.1.cmp(&b.1))
20421 });
20422 res
20423 }
20424
20425 #[cfg(feature = "test-support")]
20426 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20427 let snapshot = self.buffer().read(cx).snapshot(cx);
20428
20429 let highlights = self
20430 .background_highlights
20431 .get(&HighlightKey::Type(TypeId::of::<
20432 items::BufferSearchHighlights,
20433 >()));
20434
20435 if let Some((_color, ranges)) = highlights {
20436 ranges
20437 .iter()
20438 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20439 .collect_vec()
20440 } else {
20441 vec![]
20442 }
20443 }
20444
20445 fn document_highlights_for_position<'a>(
20446 &'a self,
20447 position: Anchor,
20448 buffer: &'a MultiBufferSnapshot,
20449 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20450 let read_highlights = self
20451 .background_highlights
20452 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20453 .map(|h| &h.1);
20454 let write_highlights = self
20455 .background_highlights
20456 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20457 .map(|h| &h.1);
20458 let left_position = position.bias_left(buffer);
20459 let right_position = position.bias_right(buffer);
20460 read_highlights
20461 .into_iter()
20462 .chain(write_highlights)
20463 .flat_map(move |ranges| {
20464 let start_ix = match ranges.binary_search_by(|probe| {
20465 let cmp = probe.end.cmp(&left_position, buffer);
20466 if cmp.is_ge() {
20467 Ordering::Greater
20468 } else {
20469 Ordering::Less
20470 }
20471 }) {
20472 Ok(i) | Err(i) => i,
20473 };
20474
20475 ranges[start_ix..]
20476 .iter()
20477 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20478 })
20479 }
20480
20481 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20482 self.background_highlights
20483 .get(&HighlightKey::Type(TypeId::of::<T>()))
20484 .is_some_and(|(_, highlights)| !highlights.is_empty())
20485 }
20486
20487 /// Returns all background highlights for a given range.
20488 ///
20489 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20490 pub fn background_highlights_in_range(
20491 &self,
20492 search_range: Range<Anchor>,
20493 display_snapshot: &DisplaySnapshot,
20494 theme: &Theme,
20495 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20496 let mut results = Vec::new();
20497 for (color_fetcher, ranges) in self.background_highlights.values() {
20498 let color = color_fetcher(theme);
20499 let start_ix = match ranges.binary_search_by(|probe| {
20500 let cmp = probe
20501 .end
20502 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20503 if cmp.is_gt() {
20504 Ordering::Greater
20505 } else {
20506 Ordering::Less
20507 }
20508 }) {
20509 Ok(i) | Err(i) => i,
20510 };
20511 for range in &ranges[start_ix..] {
20512 if range
20513 .start
20514 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20515 .is_ge()
20516 {
20517 break;
20518 }
20519
20520 let start = range.start.to_display_point(display_snapshot);
20521 let end = range.end.to_display_point(display_snapshot);
20522 results.push((start..end, color))
20523 }
20524 }
20525 results
20526 }
20527
20528 pub fn gutter_highlights_in_range(
20529 &self,
20530 search_range: Range<Anchor>,
20531 display_snapshot: &DisplaySnapshot,
20532 cx: &App,
20533 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20534 let mut results = Vec::new();
20535 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20536 let color = color_fetcher(cx);
20537 let start_ix = match ranges.binary_search_by(|probe| {
20538 let cmp = probe
20539 .end
20540 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20541 if cmp.is_gt() {
20542 Ordering::Greater
20543 } else {
20544 Ordering::Less
20545 }
20546 }) {
20547 Ok(i) | Err(i) => i,
20548 };
20549 for range in &ranges[start_ix..] {
20550 if range
20551 .start
20552 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20553 .is_ge()
20554 {
20555 break;
20556 }
20557
20558 let start = range.start.to_display_point(display_snapshot);
20559 let end = range.end.to_display_point(display_snapshot);
20560 results.push((start..end, color))
20561 }
20562 }
20563 results
20564 }
20565
20566 /// Get the text ranges corresponding to the redaction query
20567 pub fn redacted_ranges(
20568 &self,
20569 search_range: Range<Anchor>,
20570 display_snapshot: &DisplaySnapshot,
20571 cx: &App,
20572 ) -> Vec<Range<DisplayPoint>> {
20573 display_snapshot
20574 .buffer_snapshot()
20575 .redacted_ranges(search_range, |file| {
20576 if let Some(file) = file {
20577 file.is_private()
20578 && EditorSettings::get(
20579 Some(SettingsLocation {
20580 worktree_id: file.worktree_id(cx),
20581 path: file.path().as_ref(),
20582 }),
20583 cx,
20584 )
20585 .redact_private_values
20586 } else {
20587 false
20588 }
20589 })
20590 .map(|range| {
20591 range.start.to_display_point(display_snapshot)
20592 ..range.end.to_display_point(display_snapshot)
20593 })
20594 .collect()
20595 }
20596
20597 pub fn highlight_text_key<T: 'static>(
20598 &mut self,
20599 key: usize,
20600 ranges: Vec<Range<Anchor>>,
20601 style: HighlightStyle,
20602 cx: &mut Context<Self>,
20603 ) {
20604 self.display_map.update(cx, |map, _| {
20605 map.highlight_text(
20606 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20607 ranges,
20608 style,
20609 );
20610 });
20611 cx.notify();
20612 }
20613
20614 pub fn highlight_text<T: 'static>(
20615 &mut self,
20616 ranges: Vec<Range<Anchor>>,
20617 style: HighlightStyle,
20618 cx: &mut Context<Self>,
20619 ) {
20620 self.display_map.update(cx, |map, _| {
20621 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20622 });
20623 cx.notify();
20624 }
20625
20626 pub fn text_highlights<'a, T: 'static>(
20627 &'a self,
20628 cx: &'a App,
20629 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20630 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20631 }
20632
20633 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20634 let cleared = self
20635 .display_map
20636 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20637 if cleared {
20638 cx.notify();
20639 }
20640 }
20641
20642 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20643 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20644 && self.focus_handle.is_focused(window)
20645 }
20646
20647 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20648 self.show_cursor_when_unfocused = is_enabled;
20649 cx.notify();
20650 }
20651
20652 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20653 cx.notify();
20654 }
20655
20656 fn on_debug_session_event(
20657 &mut self,
20658 _session: Entity<Session>,
20659 event: &SessionEvent,
20660 cx: &mut Context<Self>,
20661 ) {
20662 if let SessionEvent::InvalidateInlineValue = event {
20663 self.refresh_inline_values(cx);
20664 }
20665 }
20666
20667 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20668 let Some(project) = self.project.clone() else {
20669 return;
20670 };
20671
20672 if !self.inline_value_cache.enabled {
20673 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20674 self.splice_inlays(&inlays, Vec::new(), cx);
20675 return;
20676 }
20677
20678 let current_execution_position = self
20679 .highlighted_rows
20680 .get(&TypeId::of::<ActiveDebugLine>())
20681 .and_then(|lines| lines.last().map(|line| line.range.end));
20682
20683 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20684 let inline_values = editor
20685 .update(cx, |editor, cx| {
20686 let Some(current_execution_position) = current_execution_position else {
20687 return Some(Task::ready(Ok(Vec::new())));
20688 };
20689
20690 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20691 let snapshot = buffer.snapshot(cx);
20692
20693 let excerpt = snapshot.excerpt_containing(
20694 current_execution_position..current_execution_position,
20695 )?;
20696
20697 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20698 })?;
20699
20700 let range =
20701 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20702
20703 project.inline_values(buffer, range, cx)
20704 })
20705 .ok()
20706 .flatten()?
20707 .await
20708 .context("refreshing debugger inlays")
20709 .log_err()?;
20710
20711 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20712
20713 for (buffer_id, inline_value) in inline_values
20714 .into_iter()
20715 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20716 {
20717 buffer_inline_values
20718 .entry(buffer_id)
20719 .or_default()
20720 .push(inline_value);
20721 }
20722
20723 editor
20724 .update(cx, |editor, cx| {
20725 let snapshot = editor.buffer.read(cx).snapshot(cx);
20726 let mut new_inlays = Vec::default();
20727
20728 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20729 let buffer_id = buffer_snapshot.remote_id();
20730 buffer_inline_values
20731 .get(&buffer_id)
20732 .into_iter()
20733 .flatten()
20734 .for_each(|hint| {
20735 let inlay = Inlay::debugger(
20736 post_inc(&mut editor.next_inlay_id),
20737 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20738 hint.text(),
20739 );
20740 if !inlay.text().chars().contains(&'\n') {
20741 new_inlays.push(inlay);
20742 }
20743 });
20744 }
20745
20746 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20747 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20748
20749 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20750 })
20751 .ok()?;
20752 Some(())
20753 });
20754 }
20755
20756 fn on_buffer_event(
20757 &mut self,
20758 multibuffer: &Entity<MultiBuffer>,
20759 event: &multi_buffer::Event,
20760 window: &mut Window,
20761 cx: &mut Context<Self>,
20762 ) {
20763 match event {
20764 multi_buffer::Event::Edited { edited_buffer } => {
20765 self.scrollbar_marker_state.dirty = true;
20766 self.active_indent_guides_state.dirty = true;
20767 self.refresh_active_diagnostics(cx);
20768 self.refresh_code_actions(window, cx);
20769 self.refresh_selected_text_highlights(true, window, cx);
20770 self.refresh_single_line_folds(window, cx);
20771 self.refresh_matching_bracket_highlights(window, cx);
20772 if self.has_active_edit_prediction() {
20773 self.update_visible_edit_prediction(window, cx);
20774 }
20775
20776 if let Some(buffer) = edited_buffer {
20777 if buffer.read(cx).file().is_none() {
20778 cx.emit(EditorEvent::TitleChanged);
20779 }
20780
20781 if self.project.is_some() {
20782 let buffer_id = buffer.read(cx).remote_id();
20783 self.register_buffer(buffer_id, cx);
20784 self.update_lsp_data(Some(buffer_id), window, cx);
20785 self.refresh_inlay_hints(
20786 InlayHintRefreshReason::BufferEdited(buffer_id),
20787 cx,
20788 );
20789 }
20790 }
20791
20792 cx.emit(EditorEvent::BufferEdited);
20793 cx.emit(SearchEvent::MatchesInvalidated);
20794
20795 let Some(project) = &self.project else { return };
20796 let (telemetry, is_via_ssh) = {
20797 let project = project.read(cx);
20798 let telemetry = project.client().telemetry().clone();
20799 let is_via_ssh = project.is_via_remote_server();
20800 (telemetry, is_via_ssh)
20801 };
20802 telemetry.log_edit_event("editor", is_via_ssh);
20803 }
20804 multi_buffer::Event::ExcerptsAdded {
20805 buffer,
20806 predecessor,
20807 excerpts,
20808 } => {
20809 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20810 let buffer_id = buffer.read(cx).remote_id();
20811 if self.buffer.read(cx).diff_for(buffer_id).is_none()
20812 && let Some(project) = &self.project
20813 {
20814 update_uncommitted_diff_for_buffer(
20815 cx.entity(),
20816 project,
20817 [buffer.clone()],
20818 self.buffer.clone(),
20819 cx,
20820 )
20821 .detach();
20822 }
20823 self.update_lsp_data(Some(buffer_id), window, cx);
20824 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20825 cx.emit(EditorEvent::ExcerptsAdded {
20826 buffer: buffer.clone(),
20827 predecessor: *predecessor,
20828 excerpts: excerpts.clone(),
20829 });
20830 }
20831 multi_buffer::Event::ExcerptsRemoved {
20832 ids,
20833 removed_buffer_ids,
20834 } => {
20835 if let Some(inlay_hints) = &mut self.inlay_hints {
20836 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
20837 }
20838 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
20839 for buffer_id in removed_buffer_ids {
20840 self.registered_buffers.remove(buffer_id);
20841 }
20842 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20843 cx.emit(EditorEvent::ExcerptsRemoved {
20844 ids: ids.clone(),
20845 removed_buffer_ids: removed_buffer_ids.clone(),
20846 });
20847 }
20848 multi_buffer::Event::ExcerptsEdited {
20849 excerpt_ids,
20850 buffer_ids,
20851 } => {
20852 self.display_map.update(cx, |map, cx| {
20853 map.unfold_buffers(buffer_ids.iter().copied(), cx)
20854 });
20855 cx.emit(EditorEvent::ExcerptsEdited {
20856 ids: excerpt_ids.clone(),
20857 });
20858 }
20859 multi_buffer::Event::ExcerptsExpanded { ids } => {
20860 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
20861 self.refresh_document_highlights(cx);
20862 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
20863 }
20864 multi_buffer::Event::Reparsed(buffer_id) => {
20865 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20866 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20867
20868 cx.emit(EditorEvent::Reparsed(*buffer_id));
20869 }
20870 multi_buffer::Event::DiffHunksToggled => {
20871 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20872 }
20873 multi_buffer::Event::LanguageChanged(buffer_id) => {
20874 self.registered_buffers.remove(&buffer_id);
20875 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
20876 cx.emit(EditorEvent::Reparsed(*buffer_id));
20877 cx.notify();
20878 }
20879 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
20880 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
20881 multi_buffer::Event::FileHandleChanged
20882 | multi_buffer::Event::Reloaded
20883 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
20884 multi_buffer::Event::DiagnosticsUpdated => {
20885 self.update_diagnostics_state(window, cx);
20886 }
20887 _ => {}
20888 };
20889 }
20890
20891 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
20892 if !self.diagnostics_enabled() {
20893 return;
20894 }
20895 self.refresh_active_diagnostics(cx);
20896 self.refresh_inline_diagnostics(true, window, cx);
20897 self.scrollbar_marker_state.dirty = true;
20898 cx.notify();
20899 }
20900
20901 pub fn start_temporary_diff_override(&mut self) {
20902 self.load_diff_task.take();
20903 self.temporary_diff_override = true;
20904 }
20905
20906 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
20907 self.temporary_diff_override = false;
20908 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
20909 self.buffer.update(cx, |buffer, cx| {
20910 buffer.set_all_diff_hunks_collapsed(cx);
20911 });
20912
20913 if let Some(project) = self.project.clone() {
20914 self.load_diff_task = Some(
20915 update_uncommitted_diff_for_buffer(
20916 cx.entity(),
20917 &project,
20918 self.buffer.read(cx).all_buffers(),
20919 self.buffer.clone(),
20920 cx,
20921 )
20922 .shared(),
20923 );
20924 }
20925 }
20926
20927 fn on_display_map_changed(
20928 &mut self,
20929 _: Entity<DisplayMap>,
20930 _: &mut Window,
20931 cx: &mut Context<Self>,
20932 ) {
20933 cx.notify();
20934 }
20935
20936 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20937 if self.diagnostics_enabled() {
20938 let new_severity = EditorSettings::get_global(cx)
20939 .diagnostics_max_severity
20940 .unwrap_or(DiagnosticSeverity::Hint);
20941 self.set_max_diagnostics_severity(new_severity, cx);
20942 }
20943 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
20944 self.update_edit_prediction_settings(cx);
20945 self.refresh_edit_prediction(true, false, window, cx);
20946 self.refresh_inline_values(cx);
20947 self.refresh_inlay_hints(
20948 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
20949 self.selections.newest_anchor().head(),
20950 &self.buffer.read(cx).snapshot(cx),
20951 cx,
20952 )),
20953 cx,
20954 );
20955
20956 let old_cursor_shape = self.cursor_shape;
20957 let old_show_breadcrumbs = self.show_breadcrumbs;
20958
20959 {
20960 let editor_settings = EditorSettings::get_global(cx);
20961 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
20962 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
20963 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
20964 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
20965 }
20966
20967 if old_cursor_shape != self.cursor_shape {
20968 cx.emit(EditorEvent::CursorShapeChanged);
20969 }
20970
20971 if old_show_breadcrumbs != self.show_breadcrumbs {
20972 cx.emit(EditorEvent::BreadcrumbsChanged);
20973 }
20974
20975 let project_settings = ProjectSettings::get_global(cx);
20976 self.serialize_dirty_buffers =
20977 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
20978
20979 if self.mode.is_full() {
20980 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
20981 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
20982 if self.show_inline_diagnostics != show_inline_diagnostics {
20983 self.show_inline_diagnostics = show_inline_diagnostics;
20984 self.refresh_inline_diagnostics(false, window, cx);
20985 }
20986
20987 if self.git_blame_inline_enabled != inline_blame_enabled {
20988 self.toggle_git_blame_inline_internal(false, window, cx);
20989 }
20990
20991 let minimap_settings = EditorSettings::get_global(cx).minimap;
20992 if self.minimap_visibility != MinimapVisibility::Disabled {
20993 if self.minimap_visibility.settings_visibility()
20994 != minimap_settings.minimap_enabled()
20995 {
20996 self.set_minimap_visibility(
20997 MinimapVisibility::for_mode(self.mode(), cx),
20998 window,
20999 cx,
21000 );
21001 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21002 minimap_entity.update(cx, |minimap_editor, cx| {
21003 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21004 })
21005 }
21006 }
21007 }
21008
21009 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21010 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21011 }) {
21012 if !inlay_splice.is_empty() {
21013 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21014 }
21015 self.refresh_colors_for_visible_range(None, window, cx);
21016 }
21017
21018 cx.notify();
21019 }
21020
21021 pub fn set_searchable(&mut self, searchable: bool) {
21022 self.searchable = searchable;
21023 }
21024
21025 pub fn searchable(&self) -> bool {
21026 self.searchable
21027 }
21028
21029 pub fn open_excerpts_in_split(
21030 &mut self,
21031 _: &OpenExcerptsSplit,
21032 window: &mut Window,
21033 cx: &mut Context<Self>,
21034 ) {
21035 self.open_excerpts_common(None, true, window, cx)
21036 }
21037
21038 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21039 self.open_excerpts_common(None, false, window, cx)
21040 }
21041
21042 fn open_excerpts_common(
21043 &mut self,
21044 jump_data: Option<JumpData>,
21045 split: bool,
21046 window: &mut Window,
21047 cx: &mut Context<Self>,
21048 ) {
21049 let Some(workspace) = self.workspace() else {
21050 cx.propagate();
21051 return;
21052 };
21053
21054 if self.buffer.read(cx).is_singleton() {
21055 cx.propagate();
21056 return;
21057 }
21058
21059 let mut new_selections_by_buffer = HashMap::default();
21060 match &jump_data {
21061 Some(JumpData::MultiBufferPoint {
21062 excerpt_id,
21063 position,
21064 anchor,
21065 line_offset_from_top,
21066 }) => {
21067 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21068 if let Some(buffer) = multi_buffer_snapshot
21069 .buffer_id_for_excerpt(*excerpt_id)
21070 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21071 {
21072 let buffer_snapshot = buffer.read(cx).snapshot();
21073 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21074 language::ToPoint::to_point(anchor, &buffer_snapshot)
21075 } else {
21076 buffer_snapshot.clip_point(*position, Bias::Left)
21077 };
21078 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21079 new_selections_by_buffer.insert(
21080 buffer,
21081 (
21082 vec![jump_to_offset..jump_to_offset],
21083 Some(*line_offset_from_top),
21084 ),
21085 );
21086 }
21087 }
21088 Some(JumpData::MultiBufferRow {
21089 row,
21090 line_offset_from_top,
21091 }) => {
21092 let point = MultiBufferPoint::new(row.0, 0);
21093 if let Some((buffer, buffer_point, _)) =
21094 self.buffer.read(cx).point_to_buffer_point(point, cx)
21095 {
21096 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21097 new_selections_by_buffer
21098 .entry(buffer)
21099 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21100 .0
21101 .push(buffer_offset..buffer_offset)
21102 }
21103 }
21104 None => {
21105 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21106 let multi_buffer = self.buffer.read(cx);
21107 for selection in selections {
21108 for (snapshot, range, _, anchor) in multi_buffer
21109 .snapshot(cx)
21110 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21111 {
21112 if let Some(anchor) = anchor {
21113 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21114 else {
21115 continue;
21116 };
21117 let offset = text::ToOffset::to_offset(
21118 &anchor.text_anchor,
21119 &buffer_handle.read(cx).snapshot(),
21120 );
21121 let range = offset..offset;
21122 new_selections_by_buffer
21123 .entry(buffer_handle)
21124 .or_insert((Vec::new(), None))
21125 .0
21126 .push(range)
21127 } else {
21128 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21129 else {
21130 continue;
21131 };
21132 new_selections_by_buffer
21133 .entry(buffer_handle)
21134 .or_insert((Vec::new(), None))
21135 .0
21136 .push(range)
21137 }
21138 }
21139 }
21140 }
21141 }
21142
21143 new_selections_by_buffer
21144 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21145
21146 if new_selections_by_buffer.is_empty() {
21147 return;
21148 }
21149
21150 // We defer the pane interaction because we ourselves are a workspace item
21151 // and activating a new item causes the pane to call a method on us reentrantly,
21152 // which panics if we're on the stack.
21153 window.defer(cx, move |window, cx| {
21154 workspace.update(cx, |workspace, cx| {
21155 let pane = if split {
21156 workspace.adjacent_pane(window, cx)
21157 } else {
21158 workspace.active_pane().clone()
21159 };
21160
21161 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21162 let editor = buffer
21163 .read(cx)
21164 .file()
21165 .is_none()
21166 .then(|| {
21167 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21168 // so `workspace.open_project_item` will never find them, always opening a new editor.
21169 // Instead, we try to activate the existing editor in the pane first.
21170 let (editor, pane_item_index) =
21171 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21172 let editor = item.downcast::<Editor>()?;
21173 let singleton_buffer =
21174 editor.read(cx).buffer().read(cx).as_singleton()?;
21175 if singleton_buffer == buffer {
21176 Some((editor, i))
21177 } else {
21178 None
21179 }
21180 })?;
21181 pane.update(cx, |pane, cx| {
21182 pane.activate_item(pane_item_index, true, true, window, cx)
21183 });
21184 Some(editor)
21185 })
21186 .flatten()
21187 .unwrap_or_else(|| {
21188 workspace.open_project_item::<Self>(
21189 pane.clone(),
21190 buffer,
21191 true,
21192 true,
21193 window,
21194 cx,
21195 )
21196 });
21197
21198 editor.update(cx, |editor, cx| {
21199 let autoscroll = match scroll_offset {
21200 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21201 None => Autoscroll::newest(),
21202 };
21203 let nav_history = editor.nav_history.take();
21204 editor.change_selections(
21205 SelectionEffects::scroll(autoscroll),
21206 window,
21207 cx,
21208 |s| {
21209 s.select_ranges(ranges);
21210 },
21211 );
21212 editor.nav_history = nav_history;
21213 });
21214 }
21215 })
21216 });
21217 }
21218
21219 // For now, don't allow opening excerpts in buffers that aren't backed by
21220 // regular project files.
21221 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21222 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21223 }
21224
21225 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21226 let snapshot = self.buffer.read(cx).read(cx);
21227 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21228 Some(
21229 ranges
21230 .iter()
21231 .map(move |range| {
21232 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21233 })
21234 .collect(),
21235 )
21236 }
21237
21238 fn selection_replacement_ranges(
21239 &self,
21240 range: Range<OffsetUtf16>,
21241 cx: &mut App,
21242 ) -> Vec<Range<OffsetUtf16>> {
21243 let selections = self
21244 .selections
21245 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21246 let newest_selection = selections
21247 .iter()
21248 .max_by_key(|selection| selection.id)
21249 .unwrap();
21250 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21251 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21252 let snapshot = self.buffer.read(cx).read(cx);
21253 selections
21254 .into_iter()
21255 .map(|mut selection| {
21256 selection.start.0 =
21257 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21258 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21259 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21260 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21261 })
21262 .collect()
21263 }
21264
21265 fn report_editor_event(
21266 &self,
21267 reported_event: ReportEditorEvent,
21268 file_extension: Option<String>,
21269 cx: &App,
21270 ) {
21271 if cfg!(any(test, feature = "test-support")) {
21272 return;
21273 }
21274
21275 let Some(project) = &self.project else { return };
21276
21277 // If None, we are in a file without an extension
21278 let file = self
21279 .buffer
21280 .read(cx)
21281 .as_singleton()
21282 .and_then(|b| b.read(cx).file());
21283 let file_extension = file_extension.or(file
21284 .as_ref()
21285 .and_then(|file| Path::new(file.file_name(cx)).extension())
21286 .and_then(|e| e.to_str())
21287 .map(|a| a.to_string()));
21288
21289 let vim_mode = vim_enabled(cx);
21290
21291 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21292 let copilot_enabled = edit_predictions_provider
21293 == language::language_settings::EditPredictionProvider::Copilot;
21294 let copilot_enabled_for_language = self
21295 .buffer
21296 .read(cx)
21297 .language_settings(cx)
21298 .show_edit_predictions;
21299
21300 let project = project.read(cx);
21301 let event_type = reported_event.event_type();
21302
21303 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21304 telemetry::event!(
21305 event_type,
21306 type = if auto_saved {"autosave"} else {"manual"},
21307 file_extension,
21308 vim_mode,
21309 copilot_enabled,
21310 copilot_enabled_for_language,
21311 edit_predictions_provider,
21312 is_via_ssh = project.is_via_remote_server(),
21313 );
21314 } else {
21315 telemetry::event!(
21316 event_type,
21317 file_extension,
21318 vim_mode,
21319 copilot_enabled,
21320 copilot_enabled_for_language,
21321 edit_predictions_provider,
21322 is_via_ssh = project.is_via_remote_server(),
21323 );
21324 };
21325 }
21326
21327 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21328 /// with each line being an array of {text, highlight} objects.
21329 fn copy_highlight_json(
21330 &mut self,
21331 _: &CopyHighlightJson,
21332 window: &mut Window,
21333 cx: &mut Context<Self>,
21334 ) {
21335 #[derive(Serialize)]
21336 struct Chunk<'a> {
21337 text: String,
21338 highlight: Option<&'a str>,
21339 }
21340
21341 let snapshot = self.buffer.read(cx).snapshot(cx);
21342 let range = self
21343 .selected_text_range(false, window, cx)
21344 .and_then(|selection| {
21345 if selection.range.is_empty() {
21346 None
21347 } else {
21348 Some(
21349 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21350 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21351 )
21352 }
21353 })
21354 .unwrap_or_else(|| 0..snapshot.len());
21355
21356 let chunks = snapshot.chunks(range, true);
21357 let mut lines = Vec::new();
21358 let mut line: VecDeque<Chunk> = VecDeque::new();
21359
21360 let Some(style) = self.style.as_ref() else {
21361 return;
21362 };
21363
21364 for chunk in chunks {
21365 let highlight = chunk
21366 .syntax_highlight_id
21367 .and_then(|id| id.name(&style.syntax));
21368 let mut chunk_lines = chunk.text.split('\n').peekable();
21369 while let Some(text) = chunk_lines.next() {
21370 let mut merged_with_last_token = false;
21371 if let Some(last_token) = line.back_mut()
21372 && last_token.highlight == highlight
21373 {
21374 last_token.text.push_str(text);
21375 merged_with_last_token = true;
21376 }
21377
21378 if !merged_with_last_token {
21379 line.push_back(Chunk {
21380 text: text.into(),
21381 highlight,
21382 });
21383 }
21384
21385 if chunk_lines.peek().is_some() {
21386 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21387 line.pop_front();
21388 }
21389 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21390 line.pop_back();
21391 }
21392
21393 lines.push(mem::take(&mut line));
21394 }
21395 }
21396 }
21397
21398 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21399 return;
21400 };
21401 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21402 }
21403
21404 pub fn open_context_menu(
21405 &mut self,
21406 _: &OpenContextMenu,
21407 window: &mut Window,
21408 cx: &mut Context<Self>,
21409 ) {
21410 self.request_autoscroll(Autoscroll::newest(), cx);
21411 let position = self
21412 .selections
21413 .newest_display(&self.display_snapshot(cx))
21414 .start;
21415 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21416 }
21417
21418 pub fn replay_insert_event(
21419 &mut self,
21420 text: &str,
21421 relative_utf16_range: Option<Range<isize>>,
21422 window: &mut Window,
21423 cx: &mut Context<Self>,
21424 ) {
21425 if !self.input_enabled {
21426 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21427 return;
21428 }
21429 if let Some(relative_utf16_range) = relative_utf16_range {
21430 let selections = self
21431 .selections
21432 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21433 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21434 let new_ranges = selections.into_iter().map(|range| {
21435 let start = OffsetUtf16(
21436 range
21437 .head()
21438 .0
21439 .saturating_add_signed(relative_utf16_range.start),
21440 );
21441 let end = OffsetUtf16(
21442 range
21443 .head()
21444 .0
21445 .saturating_add_signed(relative_utf16_range.end),
21446 );
21447 start..end
21448 });
21449 s.select_ranges(new_ranges);
21450 });
21451 }
21452
21453 self.handle_input(text, window, cx);
21454 }
21455
21456 pub fn is_focused(&self, window: &Window) -> bool {
21457 self.focus_handle.is_focused(window)
21458 }
21459
21460 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21461 cx.emit(EditorEvent::Focused);
21462
21463 if let Some(descendant) = self
21464 .last_focused_descendant
21465 .take()
21466 .and_then(|descendant| descendant.upgrade())
21467 {
21468 window.focus(&descendant);
21469 } else {
21470 if let Some(blame) = self.blame.as_ref() {
21471 blame.update(cx, GitBlame::focus)
21472 }
21473
21474 self.blink_manager.update(cx, BlinkManager::enable);
21475 self.show_cursor_names(window, cx);
21476 self.buffer.update(cx, |buffer, cx| {
21477 buffer.finalize_last_transaction(cx);
21478 if self.leader_id.is_none() {
21479 buffer.set_active_selections(
21480 &self.selections.disjoint_anchors_arc(),
21481 self.selections.line_mode(),
21482 self.cursor_shape,
21483 cx,
21484 );
21485 }
21486 });
21487 }
21488 }
21489
21490 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21491 cx.emit(EditorEvent::FocusedIn)
21492 }
21493
21494 fn handle_focus_out(
21495 &mut self,
21496 event: FocusOutEvent,
21497 _window: &mut Window,
21498 cx: &mut Context<Self>,
21499 ) {
21500 if event.blurred != self.focus_handle {
21501 self.last_focused_descendant = Some(event.blurred);
21502 }
21503 self.selection_drag_state = SelectionDragState::None;
21504 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21505 }
21506
21507 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21508 self.blink_manager.update(cx, BlinkManager::disable);
21509 self.buffer
21510 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21511
21512 if let Some(blame) = self.blame.as_ref() {
21513 blame.update(cx, GitBlame::blur)
21514 }
21515 if !self.hover_state.focused(window, cx) {
21516 hide_hover(self, cx);
21517 }
21518 if !self
21519 .context_menu
21520 .borrow()
21521 .as_ref()
21522 .is_some_and(|context_menu| context_menu.focused(window, cx))
21523 {
21524 self.hide_context_menu(window, cx);
21525 }
21526 self.take_active_edit_prediction(cx);
21527 cx.emit(EditorEvent::Blurred);
21528 cx.notify();
21529 }
21530
21531 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21532 let mut pending: String = window
21533 .pending_input_keystrokes()
21534 .into_iter()
21535 .flatten()
21536 .filter_map(|keystroke| {
21537 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21538 keystroke.key_char.clone()
21539 } else {
21540 None
21541 }
21542 })
21543 .collect();
21544
21545 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21546 pending = "".to_string();
21547 }
21548
21549 let existing_pending = self
21550 .text_highlights::<PendingInput>(cx)
21551 .map(|(_, ranges)| ranges.to_vec());
21552 if existing_pending.is_none() && pending.is_empty() {
21553 return;
21554 }
21555 let transaction =
21556 self.transact(window, cx, |this, window, cx| {
21557 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21558 let edits = selections
21559 .iter()
21560 .map(|selection| (selection.end..selection.end, pending.clone()));
21561 this.edit(edits, cx);
21562 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21563 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21564 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21565 }));
21566 });
21567 if let Some(existing_ranges) = existing_pending {
21568 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21569 this.edit(edits, cx);
21570 }
21571 });
21572
21573 let snapshot = self.snapshot(window, cx);
21574 let ranges = self
21575 .selections
21576 .all::<usize>(&snapshot.display_snapshot)
21577 .into_iter()
21578 .map(|selection| {
21579 snapshot.buffer_snapshot().anchor_after(selection.end)
21580 ..snapshot
21581 .buffer_snapshot()
21582 .anchor_before(selection.end + pending.len())
21583 })
21584 .collect();
21585
21586 if pending.is_empty() {
21587 self.clear_highlights::<PendingInput>(cx);
21588 } else {
21589 self.highlight_text::<PendingInput>(
21590 ranges,
21591 HighlightStyle {
21592 underline: Some(UnderlineStyle {
21593 thickness: px(1.),
21594 color: None,
21595 wavy: false,
21596 }),
21597 ..Default::default()
21598 },
21599 cx,
21600 );
21601 }
21602
21603 self.ime_transaction = self.ime_transaction.or(transaction);
21604 if let Some(transaction) = self.ime_transaction {
21605 self.buffer.update(cx, |buffer, cx| {
21606 buffer.group_until_transaction(transaction, cx);
21607 });
21608 }
21609
21610 if self.text_highlights::<PendingInput>(cx).is_none() {
21611 self.ime_transaction.take();
21612 }
21613 }
21614
21615 pub fn register_action_renderer(
21616 &mut self,
21617 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21618 ) -> Subscription {
21619 let id = self.next_editor_action_id.post_inc();
21620 self.editor_actions
21621 .borrow_mut()
21622 .insert(id, Box::new(listener));
21623
21624 let editor_actions = self.editor_actions.clone();
21625 Subscription::new(move || {
21626 editor_actions.borrow_mut().remove(&id);
21627 })
21628 }
21629
21630 pub fn register_action<A: Action>(
21631 &mut self,
21632 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21633 ) -> Subscription {
21634 let id = self.next_editor_action_id.post_inc();
21635 let listener = Arc::new(listener);
21636 self.editor_actions.borrow_mut().insert(
21637 id,
21638 Box::new(move |_, window, _| {
21639 let listener = listener.clone();
21640 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21641 let action = action.downcast_ref().unwrap();
21642 if phase == DispatchPhase::Bubble {
21643 listener(action, window, cx)
21644 }
21645 })
21646 }),
21647 );
21648
21649 let editor_actions = self.editor_actions.clone();
21650 Subscription::new(move || {
21651 editor_actions.borrow_mut().remove(&id);
21652 })
21653 }
21654
21655 pub fn file_header_size(&self) -> u32 {
21656 FILE_HEADER_HEIGHT
21657 }
21658
21659 pub fn restore(
21660 &mut self,
21661 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21662 window: &mut Window,
21663 cx: &mut Context<Self>,
21664 ) {
21665 let workspace = self.workspace();
21666 let project = self.project();
21667 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21668 let mut tasks = Vec::new();
21669 for (buffer_id, changes) in revert_changes {
21670 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21671 buffer.update(cx, |buffer, cx| {
21672 buffer.edit(
21673 changes
21674 .into_iter()
21675 .map(|(range, text)| (range, text.to_string())),
21676 None,
21677 cx,
21678 );
21679 });
21680
21681 if let Some(project) =
21682 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21683 {
21684 project.update(cx, |project, cx| {
21685 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21686 })
21687 }
21688 }
21689 }
21690 tasks
21691 });
21692 cx.spawn_in(window, async move |_, cx| {
21693 for (buffer, task) in save_tasks {
21694 let result = task.await;
21695 if result.is_err() {
21696 let Some(path) = buffer
21697 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21698 .ok()
21699 else {
21700 continue;
21701 };
21702 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21703 let Some(task) = cx
21704 .update_window_entity(workspace, |workspace, window, cx| {
21705 workspace
21706 .open_path_preview(path, None, false, false, false, window, cx)
21707 })
21708 .ok()
21709 else {
21710 continue;
21711 };
21712 task.await.log_err();
21713 }
21714 }
21715 }
21716 })
21717 .detach();
21718 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21719 selections.refresh()
21720 });
21721 }
21722
21723 pub fn to_pixel_point(
21724 &self,
21725 source: multi_buffer::Anchor,
21726 editor_snapshot: &EditorSnapshot,
21727 window: &mut Window,
21728 ) -> Option<gpui::Point<Pixels>> {
21729 let source_point = source.to_display_point(editor_snapshot);
21730 self.display_to_pixel_point(source_point, editor_snapshot, window)
21731 }
21732
21733 pub fn display_to_pixel_point(
21734 &self,
21735 source: DisplayPoint,
21736 editor_snapshot: &EditorSnapshot,
21737 window: &mut Window,
21738 ) -> Option<gpui::Point<Pixels>> {
21739 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
21740 let text_layout_details = self.text_layout_details(window);
21741 let scroll_top = text_layout_details
21742 .scroll_anchor
21743 .scroll_position(editor_snapshot)
21744 .y;
21745
21746 if source.row().as_f64() < scroll_top.floor() {
21747 return None;
21748 }
21749 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
21750 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
21751 Some(gpui::Point::new(source_x, source_y))
21752 }
21753
21754 pub fn has_visible_completions_menu(&self) -> bool {
21755 !self.edit_prediction_preview_is_active()
21756 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
21757 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
21758 })
21759 }
21760
21761 pub fn register_addon<T: Addon>(&mut self, instance: T) {
21762 if self.mode.is_minimap() {
21763 return;
21764 }
21765 self.addons
21766 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
21767 }
21768
21769 pub fn unregister_addon<T: Addon>(&mut self) {
21770 self.addons.remove(&std::any::TypeId::of::<T>());
21771 }
21772
21773 pub fn addon<T: Addon>(&self) -> Option<&T> {
21774 let type_id = std::any::TypeId::of::<T>();
21775 self.addons
21776 .get(&type_id)
21777 .and_then(|item| item.to_any().downcast_ref::<T>())
21778 }
21779
21780 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
21781 let type_id = std::any::TypeId::of::<T>();
21782 self.addons
21783 .get_mut(&type_id)
21784 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
21785 }
21786
21787 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
21788 let text_layout_details = self.text_layout_details(window);
21789 let style = &text_layout_details.editor_style;
21790 let font_id = window.text_system().resolve_font(&style.text.font());
21791 let font_size = style.text.font_size.to_pixels(window.rem_size());
21792 let line_height = style.text.line_height_in_pixels(window.rem_size());
21793 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
21794 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
21795
21796 CharacterDimensions {
21797 em_width,
21798 em_advance,
21799 line_height,
21800 }
21801 }
21802
21803 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
21804 self.load_diff_task.clone()
21805 }
21806
21807 fn read_metadata_from_db(
21808 &mut self,
21809 item_id: u64,
21810 workspace_id: WorkspaceId,
21811 window: &mut Window,
21812 cx: &mut Context<Editor>,
21813 ) {
21814 if self.buffer_kind(cx) == ItemBufferKind::Singleton
21815 && !self.mode.is_minimap()
21816 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
21817 {
21818 let buffer_snapshot = OnceCell::new();
21819
21820 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
21821 && !folds.is_empty()
21822 {
21823 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21824 self.fold_ranges(
21825 folds
21826 .into_iter()
21827 .map(|(start, end)| {
21828 snapshot.clip_offset(start, Bias::Left)
21829 ..snapshot.clip_offset(end, Bias::Right)
21830 })
21831 .collect(),
21832 false,
21833 window,
21834 cx,
21835 );
21836 }
21837
21838 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
21839 && !selections.is_empty()
21840 {
21841 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
21842 // skip adding the initial selection to selection history
21843 self.selection_history.mode = SelectionHistoryMode::Skipping;
21844 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21845 s.select_ranges(selections.into_iter().map(|(start, end)| {
21846 snapshot.clip_offset(start, Bias::Left)
21847 ..snapshot.clip_offset(end, Bias::Right)
21848 }));
21849 });
21850 self.selection_history.mode = SelectionHistoryMode::Normal;
21851 };
21852 }
21853
21854 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
21855 }
21856
21857 fn update_lsp_data(
21858 &mut self,
21859 for_buffer: Option<BufferId>,
21860 window: &mut Window,
21861 cx: &mut Context<'_, Self>,
21862 ) {
21863 self.pull_diagnostics(for_buffer, window, cx);
21864 self.refresh_colors_for_visible_range(for_buffer, window, cx);
21865 }
21866
21867 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
21868 if self.ignore_lsp_data() {
21869 return;
21870 }
21871 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
21872 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
21873 }
21874 }
21875
21876 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
21877 if !self.registered_buffers.contains_key(&buffer_id)
21878 && let Some(project) = self.project.as_ref()
21879 {
21880 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
21881 project.update(cx, |project, cx| {
21882 self.registered_buffers.insert(
21883 buffer_id,
21884 project.register_buffer_with_language_servers(&buffer, cx),
21885 );
21886 });
21887 } else {
21888 self.registered_buffers.remove(&buffer_id);
21889 }
21890 }
21891 }
21892
21893 fn ignore_lsp_data(&self) -> bool {
21894 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
21895 // skip any LSP updates for it.
21896 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
21897 }
21898}
21899
21900fn edit_for_markdown_paste<'a>(
21901 buffer: &MultiBufferSnapshot,
21902 range: Range<usize>,
21903 to_insert: &'a str,
21904 url: Option<url::Url>,
21905) -> (Range<usize>, Cow<'a, str>) {
21906 if url.is_none() {
21907 return (range, Cow::Borrowed(to_insert));
21908 };
21909
21910 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
21911
21912 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
21913 Cow::Borrowed(to_insert)
21914 } else {
21915 Cow::Owned(format!("[{old_text}]({to_insert})"))
21916 };
21917 (range, new_text)
21918}
21919
21920fn vim_enabled(cx: &App) -> bool {
21921 vim_mode_setting::VimModeSetting::try_get(cx)
21922 .map(|vim_mode| vim_mode.0)
21923 .unwrap_or(false)
21924}
21925
21926fn process_completion_for_edit(
21927 completion: &Completion,
21928 intent: CompletionIntent,
21929 buffer: &Entity<Buffer>,
21930 cursor_position: &text::Anchor,
21931 cx: &mut Context<Editor>,
21932) -> CompletionEdit {
21933 let buffer = buffer.read(cx);
21934 let buffer_snapshot = buffer.snapshot();
21935 let (snippet, new_text) = if completion.is_snippet() {
21936 let mut snippet_source = completion.new_text.clone();
21937 // Workaround for typescript language server issues so that methods don't expand within
21938 // strings and functions with type expressions. The previous point is used because the query
21939 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
21940 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
21941 let previous_point = if previous_point.column > 0 {
21942 cursor_position.to_previous_offset(&buffer_snapshot)
21943 } else {
21944 cursor_position.to_offset(&buffer_snapshot)
21945 };
21946 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
21947 && scope.prefers_label_for_snippet_in_completion()
21948 && let Some(label) = completion.label()
21949 && matches!(
21950 completion.kind(),
21951 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
21952 )
21953 {
21954 snippet_source = label;
21955 }
21956 match Snippet::parse(&snippet_source).log_err() {
21957 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
21958 None => (None, completion.new_text.clone()),
21959 }
21960 } else {
21961 (None, completion.new_text.clone())
21962 };
21963
21964 let mut range_to_replace = {
21965 let replace_range = &completion.replace_range;
21966 if let CompletionSource::Lsp {
21967 insert_range: Some(insert_range),
21968 ..
21969 } = &completion.source
21970 {
21971 debug_assert_eq!(
21972 insert_range.start, replace_range.start,
21973 "insert_range and replace_range should start at the same position"
21974 );
21975 debug_assert!(
21976 insert_range
21977 .start
21978 .cmp(cursor_position, &buffer_snapshot)
21979 .is_le(),
21980 "insert_range should start before or at cursor position"
21981 );
21982 debug_assert!(
21983 replace_range
21984 .start
21985 .cmp(cursor_position, &buffer_snapshot)
21986 .is_le(),
21987 "replace_range should start before or at cursor position"
21988 );
21989
21990 let should_replace = match intent {
21991 CompletionIntent::CompleteWithInsert => false,
21992 CompletionIntent::CompleteWithReplace => true,
21993 CompletionIntent::Complete | CompletionIntent::Compose => {
21994 let insert_mode =
21995 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
21996 .completions
21997 .lsp_insert_mode;
21998 match insert_mode {
21999 LspInsertMode::Insert => false,
22000 LspInsertMode::Replace => true,
22001 LspInsertMode::ReplaceSubsequence => {
22002 let mut text_to_replace = buffer.chars_for_range(
22003 buffer.anchor_before(replace_range.start)
22004 ..buffer.anchor_after(replace_range.end),
22005 );
22006 let mut current_needle = text_to_replace.next();
22007 for haystack_ch in completion.label.text.chars() {
22008 if let Some(needle_ch) = current_needle
22009 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22010 {
22011 current_needle = text_to_replace.next();
22012 }
22013 }
22014 current_needle.is_none()
22015 }
22016 LspInsertMode::ReplaceSuffix => {
22017 if replace_range
22018 .end
22019 .cmp(cursor_position, &buffer_snapshot)
22020 .is_gt()
22021 {
22022 let range_after_cursor = *cursor_position..replace_range.end;
22023 let text_after_cursor = buffer
22024 .text_for_range(
22025 buffer.anchor_before(range_after_cursor.start)
22026 ..buffer.anchor_after(range_after_cursor.end),
22027 )
22028 .collect::<String>()
22029 .to_ascii_lowercase();
22030 completion
22031 .label
22032 .text
22033 .to_ascii_lowercase()
22034 .ends_with(&text_after_cursor)
22035 } else {
22036 true
22037 }
22038 }
22039 }
22040 }
22041 };
22042
22043 if should_replace {
22044 replace_range.clone()
22045 } else {
22046 insert_range.clone()
22047 }
22048 } else {
22049 replace_range.clone()
22050 }
22051 };
22052
22053 if range_to_replace
22054 .end
22055 .cmp(cursor_position, &buffer_snapshot)
22056 .is_lt()
22057 {
22058 range_to_replace.end = *cursor_position;
22059 }
22060
22061 CompletionEdit {
22062 new_text,
22063 replace_range: range_to_replace.to_offset(buffer),
22064 snippet,
22065 }
22066}
22067
22068struct CompletionEdit {
22069 new_text: String,
22070 replace_range: Range<usize>,
22071 snippet: Option<Snippet>,
22072}
22073
22074fn insert_extra_newline_brackets(
22075 buffer: &MultiBufferSnapshot,
22076 range: Range<usize>,
22077 language: &language::LanguageScope,
22078) -> bool {
22079 let leading_whitespace_len = buffer
22080 .reversed_chars_at(range.start)
22081 .take_while(|c| c.is_whitespace() && *c != '\n')
22082 .map(|c| c.len_utf8())
22083 .sum::<usize>();
22084 let trailing_whitespace_len = buffer
22085 .chars_at(range.end)
22086 .take_while(|c| c.is_whitespace() && *c != '\n')
22087 .map(|c| c.len_utf8())
22088 .sum::<usize>();
22089 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22090
22091 language.brackets().any(|(pair, enabled)| {
22092 let pair_start = pair.start.trim_end();
22093 let pair_end = pair.end.trim_start();
22094
22095 enabled
22096 && pair.newline
22097 && buffer.contains_str_at(range.end, pair_end)
22098 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22099 })
22100}
22101
22102fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22103 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22104 [(buffer, range, _)] => (*buffer, range.clone()),
22105 _ => return false,
22106 };
22107 let pair = {
22108 let mut result: Option<BracketMatch> = None;
22109
22110 for pair in buffer
22111 .all_bracket_ranges(range.clone())
22112 .filter(move |pair| {
22113 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22114 })
22115 {
22116 let len = pair.close_range.end - pair.open_range.start;
22117
22118 if let Some(existing) = &result {
22119 let existing_len = existing.close_range.end - existing.open_range.start;
22120 if len > existing_len {
22121 continue;
22122 }
22123 }
22124
22125 result = Some(pair);
22126 }
22127
22128 result
22129 };
22130 let Some(pair) = pair else {
22131 return false;
22132 };
22133 pair.newline_only
22134 && buffer
22135 .chars_for_range(pair.open_range.end..range.start)
22136 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22137 .all(|c| c.is_whitespace() && c != '\n')
22138}
22139
22140fn update_uncommitted_diff_for_buffer(
22141 editor: Entity<Editor>,
22142 project: &Entity<Project>,
22143 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22144 buffer: Entity<MultiBuffer>,
22145 cx: &mut App,
22146) -> Task<()> {
22147 let mut tasks = Vec::new();
22148 project.update(cx, |project, cx| {
22149 for buffer in buffers {
22150 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22151 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22152 }
22153 }
22154 });
22155 cx.spawn(async move |cx| {
22156 let diffs = future::join_all(tasks).await;
22157 if editor
22158 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22159 .unwrap_or(false)
22160 {
22161 return;
22162 }
22163
22164 buffer
22165 .update(cx, |buffer, cx| {
22166 for diff in diffs.into_iter().flatten() {
22167 buffer.add_diff(diff, cx);
22168 }
22169 })
22170 .ok();
22171 })
22172}
22173
22174fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22175 let tab_size = tab_size.get() as usize;
22176 let mut width = offset;
22177
22178 for ch in text.chars() {
22179 width += if ch == '\t' {
22180 tab_size - (width % tab_size)
22181 } else {
22182 1
22183 };
22184 }
22185
22186 width - offset
22187}
22188
22189#[cfg(test)]
22190mod tests {
22191 use super::*;
22192
22193 #[test]
22194 fn test_string_size_with_expanded_tabs() {
22195 let nz = |val| NonZeroU32::new(val).unwrap();
22196 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22197 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22198 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22199 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22200 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22201 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22202 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22203 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22204 }
22205}
22206
22207/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22208struct WordBreakingTokenizer<'a> {
22209 input: &'a str,
22210}
22211
22212impl<'a> WordBreakingTokenizer<'a> {
22213 fn new(input: &'a str) -> Self {
22214 Self { input }
22215 }
22216}
22217
22218fn is_char_ideographic(ch: char) -> bool {
22219 use unicode_script::Script::*;
22220 use unicode_script::UnicodeScript;
22221 matches!(ch.script(), Han | Tangut | Yi)
22222}
22223
22224fn is_grapheme_ideographic(text: &str) -> bool {
22225 text.chars().any(is_char_ideographic)
22226}
22227
22228fn is_grapheme_whitespace(text: &str) -> bool {
22229 text.chars().any(|x| x.is_whitespace())
22230}
22231
22232fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22233 text.chars()
22234 .next()
22235 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22236}
22237
22238#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22239enum WordBreakToken<'a> {
22240 Word { token: &'a str, grapheme_len: usize },
22241 InlineWhitespace { token: &'a str, grapheme_len: usize },
22242 Newline,
22243}
22244
22245impl<'a> Iterator for WordBreakingTokenizer<'a> {
22246 /// Yields a span, the count of graphemes in the token, and whether it was
22247 /// whitespace. Note that it also breaks at word boundaries.
22248 type Item = WordBreakToken<'a>;
22249
22250 fn next(&mut self) -> Option<Self::Item> {
22251 use unicode_segmentation::UnicodeSegmentation;
22252 if self.input.is_empty() {
22253 return None;
22254 }
22255
22256 let mut iter = self.input.graphemes(true).peekable();
22257 let mut offset = 0;
22258 let mut grapheme_len = 0;
22259 if let Some(first_grapheme) = iter.next() {
22260 let is_newline = first_grapheme == "\n";
22261 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22262 offset += first_grapheme.len();
22263 grapheme_len += 1;
22264 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22265 if let Some(grapheme) = iter.peek().copied()
22266 && should_stay_with_preceding_ideograph(grapheme)
22267 {
22268 offset += grapheme.len();
22269 grapheme_len += 1;
22270 }
22271 } else {
22272 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22273 let mut next_word_bound = words.peek().copied();
22274 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22275 next_word_bound = words.next();
22276 }
22277 while let Some(grapheme) = iter.peek().copied() {
22278 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22279 break;
22280 };
22281 if is_grapheme_whitespace(grapheme) != is_whitespace
22282 || (grapheme == "\n") != is_newline
22283 {
22284 break;
22285 };
22286 offset += grapheme.len();
22287 grapheme_len += 1;
22288 iter.next();
22289 }
22290 }
22291 let token = &self.input[..offset];
22292 self.input = &self.input[offset..];
22293 if token == "\n" {
22294 Some(WordBreakToken::Newline)
22295 } else if is_whitespace {
22296 Some(WordBreakToken::InlineWhitespace {
22297 token,
22298 grapheme_len,
22299 })
22300 } else {
22301 Some(WordBreakToken::Word {
22302 token,
22303 grapheme_len,
22304 })
22305 }
22306 } else {
22307 None
22308 }
22309 }
22310}
22311
22312#[test]
22313fn test_word_breaking_tokenizer() {
22314 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22315 ("", &[]),
22316 (" ", &[whitespace(" ", 2)]),
22317 ("Ʒ", &[word("Ʒ", 1)]),
22318 ("Ǽ", &[word("Ǽ", 1)]),
22319 ("⋑", &[word("⋑", 1)]),
22320 ("⋑⋑", &[word("⋑⋑", 2)]),
22321 (
22322 "原理,进而",
22323 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22324 ),
22325 (
22326 "hello world",
22327 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22328 ),
22329 (
22330 "hello, world",
22331 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22332 ),
22333 (
22334 " hello world",
22335 &[
22336 whitespace(" ", 2),
22337 word("hello", 5),
22338 whitespace(" ", 1),
22339 word("world", 5),
22340 ],
22341 ),
22342 (
22343 "这是什么 \n 钢笔",
22344 &[
22345 word("这", 1),
22346 word("是", 1),
22347 word("什", 1),
22348 word("么", 1),
22349 whitespace(" ", 1),
22350 newline(),
22351 whitespace(" ", 1),
22352 word("钢", 1),
22353 word("笔", 1),
22354 ],
22355 ),
22356 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22357 ];
22358
22359 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22360 WordBreakToken::Word {
22361 token,
22362 grapheme_len,
22363 }
22364 }
22365
22366 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22367 WordBreakToken::InlineWhitespace {
22368 token,
22369 grapheme_len,
22370 }
22371 }
22372
22373 fn newline() -> WordBreakToken<'static> {
22374 WordBreakToken::Newline
22375 }
22376
22377 for (input, result) in tests {
22378 assert_eq!(
22379 WordBreakingTokenizer::new(input)
22380 .collect::<Vec<_>>()
22381 .as_slice(),
22382 *result,
22383 );
22384 }
22385}
22386
22387fn wrap_with_prefix(
22388 first_line_prefix: String,
22389 subsequent_lines_prefix: String,
22390 unwrapped_text: String,
22391 wrap_column: usize,
22392 tab_size: NonZeroU32,
22393 preserve_existing_whitespace: bool,
22394) -> String {
22395 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22396 let subsequent_lines_prefix_len =
22397 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22398 let mut wrapped_text = String::new();
22399 let mut current_line = first_line_prefix;
22400 let mut is_first_line = true;
22401
22402 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22403 let mut current_line_len = first_line_prefix_len;
22404 let mut in_whitespace = false;
22405 for token in tokenizer {
22406 let have_preceding_whitespace = in_whitespace;
22407 match token {
22408 WordBreakToken::Word {
22409 token,
22410 grapheme_len,
22411 } => {
22412 in_whitespace = false;
22413 let current_prefix_len = if is_first_line {
22414 first_line_prefix_len
22415 } else {
22416 subsequent_lines_prefix_len
22417 };
22418 if current_line_len + grapheme_len > wrap_column
22419 && current_line_len != current_prefix_len
22420 {
22421 wrapped_text.push_str(current_line.trim_end());
22422 wrapped_text.push('\n');
22423 is_first_line = false;
22424 current_line = subsequent_lines_prefix.clone();
22425 current_line_len = subsequent_lines_prefix_len;
22426 }
22427 current_line.push_str(token);
22428 current_line_len += grapheme_len;
22429 }
22430 WordBreakToken::InlineWhitespace {
22431 mut token,
22432 mut grapheme_len,
22433 } => {
22434 in_whitespace = true;
22435 if have_preceding_whitespace && !preserve_existing_whitespace {
22436 continue;
22437 }
22438 if !preserve_existing_whitespace {
22439 // Keep a single whitespace grapheme as-is
22440 if let Some(first) =
22441 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22442 {
22443 token = first;
22444 } else {
22445 token = " ";
22446 }
22447 grapheme_len = 1;
22448 }
22449 let current_prefix_len = if is_first_line {
22450 first_line_prefix_len
22451 } else {
22452 subsequent_lines_prefix_len
22453 };
22454 if current_line_len + grapheme_len > wrap_column {
22455 wrapped_text.push_str(current_line.trim_end());
22456 wrapped_text.push('\n');
22457 is_first_line = false;
22458 current_line = subsequent_lines_prefix.clone();
22459 current_line_len = subsequent_lines_prefix_len;
22460 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22461 current_line.push_str(token);
22462 current_line_len += grapheme_len;
22463 }
22464 }
22465 WordBreakToken::Newline => {
22466 in_whitespace = true;
22467 let current_prefix_len = if is_first_line {
22468 first_line_prefix_len
22469 } else {
22470 subsequent_lines_prefix_len
22471 };
22472 if preserve_existing_whitespace {
22473 wrapped_text.push_str(current_line.trim_end());
22474 wrapped_text.push('\n');
22475 is_first_line = false;
22476 current_line = subsequent_lines_prefix.clone();
22477 current_line_len = subsequent_lines_prefix_len;
22478 } else if have_preceding_whitespace {
22479 continue;
22480 } else if current_line_len + 1 > wrap_column
22481 && current_line_len != current_prefix_len
22482 {
22483 wrapped_text.push_str(current_line.trim_end());
22484 wrapped_text.push('\n');
22485 is_first_line = false;
22486 current_line = subsequent_lines_prefix.clone();
22487 current_line_len = subsequent_lines_prefix_len;
22488 } else if current_line_len != current_prefix_len {
22489 current_line.push(' ');
22490 current_line_len += 1;
22491 }
22492 }
22493 }
22494 }
22495
22496 if !current_line.is_empty() {
22497 wrapped_text.push_str(¤t_line);
22498 }
22499 wrapped_text
22500}
22501
22502#[test]
22503fn test_wrap_with_prefix() {
22504 assert_eq!(
22505 wrap_with_prefix(
22506 "# ".to_string(),
22507 "# ".to_string(),
22508 "abcdefg".to_string(),
22509 4,
22510 NonZeroU32::new(4).unwrap(),
22511 false,
22512 ),
22513 "# abcdefg"
22514 );
22515 assert_eq!(
22516 wrap_with_prefix(
22517 "".to_string(),
22518 "".to_string(),
22519 "\thello world".to_string(),
22520 8,
22521 NonZeroU32::new(4).unwrap(),
22522 false,
22523 ),
22524 "hello\nworld"
22525 );
22526 assert_eq!(
22527 wrap_with_prefix(
22528 "// ".to_string(),
22529 "// ".to_string(),
22530 "xx \nyy zz aa bb cc".to_string(),
22531 12,
22532 NonZeroU32::new(4).unwrap(),
22533 false,
22534 ),
22535 "// xx yy zz\n// aa bb cc"
22536 );
22537 assert_eq!(
22538 wrap_with_prefix(
22539 String::new(),
22540 String::new(),
22541 "这是什么 \n 钢笔".to_string(),
22542 3,
22543 NonZeroU32::new(4).unwrap(),
22544 false,
22545 ),
22546 "这是什\n么 钢\n笔"
22547 );
22548 assert_eq!(
22549 wrap_with_prefix(
22550 String::new(),
22551 String::new(),
22552 format!("foo{}bar", '\u{2009}'), // thin space
22553 80,
22554 NonZeroU32::new(4).unwrap(),
22555 false,
22556 ),
22557 format!("foo{}bar", '\u{2009}')
22558 );
22559}
22560
22561pub trait CollaborationHub {
22562 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22563 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22564 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22565}
22566
22567impl CollaborationHub for Entity<Project> {
22568 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22569 self.read(cx).collaborators()
22570 }
22571
22572 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22573 self.read(cx).user_store().read(cx).participant_indices()
22574 }
22575
22576 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22577 let this = self.read(cx);
22578 let user_ids = this.collaborators().values().map(|c| c.user_id);
22579 this.user_store().read(cx).participant_names(user_ids, cx)
22580 }
22581}
22582
22583pub trait SemanticsProvider {
22584 fn hover(
22585 &self,
22586 buffer: &Entity<Buffer>,
22587 position: text::Anchor,
22588 cx: &mut App,
22589 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22590
22591 fn inline_values(
22592 &self,
22593 buffer_handle: Entity<Buffer>,
22594 range: Range<text::Anchor>,
22595 cx: &mut App,
22596 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22597
22598 fn applicable_inlay_chunks(
22599 &self,
22600 buffer: &Entity<Buffer>,
22601 ranges: &[Range<text::Anchor>],
22602 cx: &mut App,
22603 ) -> Vec<Range<BufferRow>>;
22604
22605 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22606
22607 fn inlay_hints(
22608 &self,
22609 invalidate: InvalidationStrategy,
22610 buffer: Entity<Buffer>,
22611 ranges: Vec<Range<text::Anchor>>,
22612 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22613 cx: &mut App,
22614 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22615
22616 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22617
22618 fn document_highlights(
22619 &self,
22620 buffer: &Entity<Buffer>,
22621 position: text::Anchor,
22622 cx: &mut App,
22623 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22624
22625 fn definitions(
22626 &self,
22627 buffer: &Entity<Buffer>,
22628 position: text::Anchor,
22629 kind: GotoDefinitionKind,
22630 cx: &mut App,
22631 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22632
22633 fn range_for_rename(
22634 &self,
22635 buffer: &Entity<Buffer>,
22636 position: text::Anchor,
22637 cx: &mut App,
22638 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22639
22640 fn perform_rename(
22641 &self,
22642 buffer: &Entity<Buffer>,
22643 position: text::Anchor,
22644 new_name: String,
22645 cx: &mut App,
22646 ) -> Option<Task<Result<ProjectTransaction>>>;
22647}
22648
22649pub trait CompletionProvider {
22650 fn completions(
22651 &self,
22652 excerpt_id: ExcerptId,
22653 buffer: &Entity<Buffer>,
22654 buffer_position: text::Anchor,
22655 trigger: CompletionContext,
22656 window: &mut Window,
22657 cx: &mut Context<Editor>,
22658 ) -> Task<Result<Vec<CompletionResponse>>>;
22659
22660 fn resolve_completions(
22661 &self,
22662 _buffer: Entity<Buffer>,
22663 _completion_indices: Vec<usize>,
22664 _completions: Rc<RefCell<Box<[Completion]>>>,
22665 _cx: &mut Context<Editor>,
22666 ) -> Task<Result<bool>> {
22667 Task::ready(Ok(false))
22668 }
22669
22670 fn apply_additional_edits_for_completion(
22671 &self,
22672 _buffer: Entity<Buffer>,
22673 _completions: Rc<RefCell<Box<[Completion]>>>,
22674 _completion_index: usize,
22675 _push_to_history: bool,
22676 _cx: &mut Context<Editor>,
22677 ) -> Task<Result<Option<language::Transaction>>> {
22678 Task::ready(Ok(None))
22679 }
22680
22681 fn is_completion_trigger(
22682 &self,
22683 buffer: &Entity<Buffer>,
22684 position: language::Anchor,
22685 text: &str,
22686 trigger_in_words: bool,
22687 menu_is_open: bool,
22688 cx: &mut Context<Editor>,
22689 ) -> bool;
22690
22691 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22692
22693 fn sort_completions(&self) -> bool {
22694 true
22695 }
22696
22697 fn filter_completions(&self) -> bool {
22698 true
22699 }
22700}
22701
22702pub trait CodeActionProvider {
22703 fn id(&self) -> Arc<str>;
22704
22705 fn code_actions(
22706 &self,
22707 buffer: &Entity<Buffer>,
22708 range: Range<text::Anchor>,
22709 window: &mut Window,
22710 cx: &mut App,
22711 ) -> Task<Result<Vec<CodeAction>>>;
22712
22713 fn apply_code_action(
22714 &self,
22715 buffer_handle: Entity<Buffer>,
22716 action: CodeAction,
22717 excerpt_id: ExcerptId,
22718 push_to_history: bool,
22719 window: &mut Window,
22720 cx: &mut App,
22721 ) -> Task<Result<ProjectTransaction>>;
22722}
22723
22724impl CodeActionProvider for Entity<Project> {
22725 fn id(&self) -> Arc<str> {
22726 "project".into()
22727 }
22728
22729 fn code_actions(
22730 &self,
22731 buffer: &Entity<Buffer>,
22732 range: Range<text::Anchor>,
22733 _window: &mut Window,
22734 cx: &mut App,
22735 ) -> Task<Result<Vec<CodeAction>>> {
22736 self.update(cx, |project, cx| {
22737 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
22738 let code_actions = project.code_actions(buffer, range, None, cx);
22739 cx.background_spawn(async move {
22740 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
22741 Ok(code_lens_actions
22742 .context("code lens fetch")?
22743 .into_iter()
22744 .flatten()
22745 .chain(
22746 code_actions
22747 .context("code action fetch")?
22748 .into_iter()
22749 .flatten(),
22750 )
22751 .collect())
22752 })
22753 })
22754 }
22755
22756 fn apply_code_action(
22757 &self,
22758 buffer_handle: Entity<Buffer>,
22759 action: CodeAction,
22760 _excerpt_id: ExcerptId,
22761 push_to_history: bool,
22762 _window: &mut Window,
22763 cx: &mut App,
22764 ) -> Task<Result<ProjectTransaction>> {
22765 self.update(cx, |project, cx| {
22766 project.apply_code_action(buffer_handle, action, push_to_history, cx)
22767 })
22768 }
22769}
22770
22771fn snippet_completions(
22772 project: &Project,
22773 buffer: &Entity<Buffer>,
22774 buffer_position: text::Anchor,
22775 cx: &mut App,
22776) -> Task<Result<CompletionResponse>> {
22777 let languages = buffer.read(cx).languages_at(buffer_position);
22778 let snippet_store = project.snippets().read(cx);
22779
22780 let scopes: Vec<_> = languages
22781 .iter()
22782 .filter_map(|language| {
22783 let language_name = language.lsp_id();
22784 let snippets = snippet_store.snippets_for(Some(language_name), cx);
22785
22786 if snippets.is_empty() {
22787 None
22788 } else {
22789 Some((language.default_scope(), snippets))
22790 }
22791 })
22792 .collect();
22793
22794 if scopes.is_empty() {
22795 return Task::ready(Ok(CompletionResponse {
22796 completions: vec![],
22797 display_options: CompletionDisplayOptions::default(),
22798 is_incomplete: false,
22799 }));
22800 }
22801
22802 let snapshot = buffer.read(cx).text_snapshot();
22803 let executor = cx.background_executor().clone();
22804
22805 cx.background_spawn(async move {
22806 let mut is_incomplete = false;
22807 let mut completions: Vec<Completion> = Vec::new();
22808 for (scope, snippets) in scopes.into_iter() {
22809 let classifier =
22810 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
22811
22812 const MAX_WORD_PREFIX_LEN: usize = 128;
22813 let last_word: String = snapshot
22814 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
22815 .take(MAX_WORD_PREFIX_LEN)
22816 .take_while(|c| classifier.is_word(*c))
22817 .collect::<String>()
22818 .chars()
22819 .rev()
22820 .collect();
22821
22822 if last_word.is_empty() {
22823 return Ok(CompletionResponse {
22824 completions: vec![],
22825 display_options: CompletionDisplayOptions::default(),
22826 is_incomplete: true,
22827 });
22828 }
22829
22830 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
22831 let to_lsp = |point: &text::Anchor| {
22832 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
22833 point_to_lsp(end)
22834 };
22835 let lsp_end = to_lsp(&buffer_position);
22836
22837 let candidates = snippets
22838 .iter()
22839 .enumerate()
22840 .flat_map(|(ix, snippet)| {
22841 snippet
22842 .prefix
22843 .iter()
22844 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
22845 })
22846 .collect::<Vec<StringMatchCandidate>>();
22847
22848 const MAX_RESULTS: usize = 100;
22849 let mut matches = fuzzy::match_strings(
22850 &candidates,
22851 &last_word,
22852 last_word.chars().any(|c| c.is_uppercase()),
22853 true,
22854 MAX_RESULTS,
22855 &Default::default(),
22856 executor.clone(),
22857 )
22858 .await;
22859
22860 if matches.len() >= MAX_RESULTS {
22861 is_incomplete = true;
22862 }
22863
22864 // Remove all candidates where the query's start does not match the start of any word in the candidate
22865 if let Some(query_start) = last_word.chars().next() {
22866 matches.retain(|string_match| {
22867 split_words(&string_match.string).any(|word| {
22868 // Check that the first codepoint of the word as lowercase matches the first
22869 // codepoint of the query as lowercase
22870 word.chars()
22871 .flat_map(|codepoint| codepoint.to_lowercase())
22872 .zip(query_start.to_lowercase())
22873 .all(|(word_cp, query_cp)| word_cp == query_cp)
22874 })
22875 });
22876 }
22877
22878 let matched_strings = matches
22879 .into_iter()
22880 .map(|m| m.string)
22881 .collect::<HashSet<_>>();
22882
22883 completions.extend(snippets.iter().filter_map(|snippet| {
22884 let matching_prefix = snippet
22885 .prefix
22886 .iter()
22887 .find(|prefix| matched_strings.contains(*prefix))?;
22888 let start = as_offset - last_word.len();
22889 let start = snapshot.anchor_before(start);
22890 let range = start..buffer_position;
22891 let lsp_start = to_lsp(&start);
22892 let lsp_range = lsp::Range {
22893 start: lsp_start,
22894 end: lsp_end,
22895 };
22896 Some(Completion {
22897 replace_range: range,
22898 new_text: snippet.body.clone(),
22899 source: CompletionSource::Lsp {
22900 insert_range: None,
22901 server_id: LanguageServerId(usize::MAX),
22902 resolved: true,
22903 lsp_completion: Box::new(lsp::CompletionItem {
22904 label: snippet.prefix.first().unwrap().clone(),
22905 kind: Some(CompletionItemKind::SNIPPET),
22906 label_details: snippet.description.as_ref().map(|description| {
22907 lsp::CompletionItemLabelDetails {
22908 detail: Some(description.clone()),
22909 description: None,
22910 }
22911 }),
22912 insert_text_format: Some(InsertTextFormat::SNIPPET),
22913 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22914 lsp::InsertReplaceEdit {
22915 new_text: snippet.body.clone(),
22916 insert: lsp_range,
22917 replace: lsp_range,
22918 },
22919 )),
22920 filter_text: Some(snippet.body.clone()),
22921 sort_text: Some(char::MAX.to_string()),
22922 ..lsp::CompletionItem::default()
22923 }),
22924 lsp_defaults: None,
22925 },
22926 label: CodeLabel::plain(matching_prefix.clone(), None),
22927 icon_path: None,
22928 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
22929 single_line: snippet.name.clone().into(),
22930 plain_text: snippet
22931 .description
22932 .clone()
22933 .map(|description| description.into()),
22934 }),
22935 insert_text_mode: None,
22936 confirm: None,
22937 })
22938 }))
22939 }
22940
22941 Ok(CompletionResponse {
22942 completions,
22943 display_options: CompletionDisplayOptions::default(),
22944 is_incomplete,
22945 })
22946 })
22947}
22948
22949impl CompletionProvider for Entity<Project> {
22950 fn completions(
22951 &self,
22952 _excerpt_id: ExcerptId,
22953 buffer: &Entity<Buffer>,
22954 buffer_position: text::Anchor,
22955 options: CompletionContext,
22956 _window: &mut Window,
22957 cx: &mut Context<Editor>,
22958 ) -> Task<Result<Vec<CompletionResponse>>> {
22959 self.update(cx, |project, cx| {
22960 let snippets = snippet_completions(project, buffer, buffer_position, cx);
22961 let project_completions = project.completions(buffer, buffer_position, options, cx);
22962 cx.background_spawn(async move {
22963 let mut responses = project_completions.await?;
22964 let snippets = snippets.await?;
22965 if !snippets.completions.is_empty() {
22966 responses.push(snippets);
22967 }
22968 Ok(responses)
22969 })
22970 })
22971 }
22972
22973 fn resolve_completions(
22974 &self,
22975 buffer: Entity<Buffer>,
22976 completion_indices: Vec<usize>,
22977 completions: Rc<RefCell<Box<[Completion]>>>,
22978 cx: &mut Context<Editor>,
22979 ) -> Task<Result<bool>> {
22980 self.update(cx, |project, cx| {
22981 project.lsp_store().update(cx, |lsp_store, cx| {
22982 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
22983 })
22984 })
22985 }
22986
22987 fn apply_additional_edits_for_completion(
22988 &self,
22989 buffer: Entity<Buffer>,
22990 completions: Rc<RefCell<Box<[Completion]>>>,
22991 completion_index: usize,
22992 push_to_history: bool,
22993 cx: &mut Context<Editor>,
22994 ) -> Task<Result<Option<language::Transaction>>> {
22995 self.update(cx, |project, cx| {
22996 project.lsp_store().update(cx, |lsp_store, cx| {
22997 lsp_store.apply_additional_edits_for_completion(
22998 buffer,
22999 completions,
23000 completion_index,
23001 push_to_history,
23002 cx,
23003 )
23004 })
23005 })
23006 }
23007
23008 fn is_completion_trigger(
23009 &self,
23010 buffer: &Entity<Buffer>,
23011 position: language::Anchor,
23012 text: &str,
23013 trigger_in_words: bool,
23014 menu_is_open: bool,
23015 cx: &mut Context<Editor>,
23016 ) -> bool {
23017 let mut chars = text.chars();
23018 let char = if let Some(char) = chars.next() {
23019 char
23020 } else {
23021 return false;
23022 };
23023 if chars.next().is_some() {
23024 return false;
23025 }
23026
23027 let buffer = buffer.read(cx);
23028 let snapshot = buffer.snapshot();
23029 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23030 return false;
23031 }
23032 let classifier = snapshot
23033 .char_classifier_at(position)
23034 .scope_context(Some(CharScopeContext::Completion));
23035 if trigger_in_words && classifier.is_word(char) {
23036 return true;
23037 }
23038
23039 buffer.completion_triggers().contains(text)
23040 }
23041}
23042
23043impl SemanticsProvider for Entity<Project> {
23044 fn hover(
23045 &self,
23046 buffer: &Entity<Buffer>,
23047 position: text::Anchor,
23048 cx: &mut App,
23049 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23050 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23051 }
23052
23053 fn document_highlights(
23054 &self,
23055 buffer: &Entity<Buffer>,
23056 position: text::Anchor,
23057 cx: &mut App,
23058 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23059 Some(self.update(cx, |project, cx| {
23060 project.document_highlights(buffer, position, cx)
23061 }))
23062 }
23063
23064 fn definitions(
23065 &self,
23066 buffer: &Entity<Buffer>,
23067 position: text::Anchor,
23068 kind: GotoDefinitionKind,
23069 cx: &mut App,
23070 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23071 Some(self.update(cx, |project, cx| match kind {
23072 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23073 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23074 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23075 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23076 }))
23077 }
23078
23079 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23080 self.update(cx, |project, cx| {
23081 if project
23082 .active_debug_session(cx)
23083 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23084 {
23085 return true;
23086 }
23087
23088 buffer.update(cx, |buffer, cx| {
23089 project.any_language_server_supports_inlay_hints(buffer, cx)
23090 })
23091 })
23092 }
23093
23094 fn inline_values(
23095 &self,
23096 buffer_handle: Entity<Buffer>,
23097 range: Range<text::Anchor>,
23098 cx: &mut App,
23099 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23100 self.update(cx, |project, cx| {
23101 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23102
23103 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23104 })
23105 }
23106
23107 fn applicable_inlay_chunks(
23108 &self,
23109 buffer: &Entity<Buffer>,
23110 ranges: &[Range<text::Anchor>],
23111 cx: &mut App,
23112 ) -> Vec<Range<BufferRow>> {
23113 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23114 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23115 })
23116 }
23117
23118 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23119 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23120 lsp_store.invalidate_inlay_hints(for_buffers)
23121 });
23122 }
23123
23124 fn inlay_hints(
23125 &self,
23126 invalidate: InvalidationStrategy,
23127 buffer: Entity<Buffer>,
23128 ranges: Vec<Range<text::Anchor>>,
23129 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23130 cx: &mut App,
23131 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23132 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23133 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23134 }))
23135 }
23136
23137 fn range_for_rename(
23138 &self,
23139 buffer: &Entity<Buffer>,
23140 position: text::Anchor,
23141 cx: &mut App,
23142 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23143 Some(self.update(cx, |project, cx| {
23144 let buffer = buffer.clone();
23145 let task = project.prepare_rename(buffer.clone(), position, cx);
23146 cx.spawn(async move |_, cx| {
23147 Ok(match task.await? {
23148 PrepareRenameResponse::Success(range) => Some(range),
23149 PrepareRenameResponse::InvalidPosition => None,
23150 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23151 // Fallback on using TreeSitter info to determine identifier range
23152 buffer.read_with(cx, |buffer, _| {
23153 let snapshot = buffer.snapshot();
23154 let (range, kind) = snapshot.surrounding_word(position, None);
23155 if kind != Some(CharKind::Word) {
23156 return None;
23157 }
23158 Some(
23159 snapshot.anchor_before(range.start)
23160 ..snapshot.anchor_after(range.end),
23161 )
23162 })?
23163 }
23164 })
23165 })
23166 }))
23167 }
23168
23169 fn perform_rename(
23170 &self,
23171 buffer: &Entity<Buffer>,
23172 position: text::Anchor,
23173 new_name: String,
23174 cx: &mut App,
23175 ) -> Option<Task<Result<ProjectTransaction>>> {
23176 Some(self.update(cx, |project, cx| {
23177 project.perform_rename(buffer.clone(), position, new_name, cx)
23178 }))
23179 }
23180}
23181
23182fn consume_contiguous_rows(
23183 contiguous_row_selections: &mut Vec<Selection<Point>>,
23184 selection: &Selection<Point>,
23185 display_map: &DisplaySnapshot,
23186 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23187) -> (MultiBufferRow, MultiBufferRow) {
23188 contiguous_row_selections.push(selection.clone());
23189 let start_row = starting_row(selection, display_map);
23190 let mut end_row = ending_row(selection, display_map);
23191
23192 while let Some(next_selection) = selections.peek() {
23193 if next_selection.start.row <= end_row.0 {
23194 end_row = ending_row(next_selection, display_map);
23195 contiguous_row_selections.push(selections.next().unwrap().clone());
23196 } else {
23197 break;
23198 }
23199 }
23200 (start_row, end_row)
23201}
23202
23203fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23204 if selection.start.column > 0 {
23205 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23206 } else {
23207 MultiBufferRow(selection.start.row)
23208 }
23209}
23210
23211fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23212 if next_selection.end.column > 0 || next_selection.is_empty() {
23213 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23214 } else {
23215 MultiBufferRow(next_selection.end.row)
23216 }
23217}
23218
23219impl EditorSnapshot {
23220 pub fn remote_selections_in_range<'a>(
23221 &'a self,
23222 range: &'a Range<Anchor>,
23223 collaboration_hub: &dyn CollaborationHub,
23224 cx: &'a App,
23225 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23226 let participant_names = collaboration_hub.user_names(cx);
23227 let participant_indices = collaboration_hub.user_participant_indices(cx);
23228 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23229 let collaborators_by_replica_id = collaborators_by_peer_id
23230 .values()
23231 .map(|collaborator| (collaborator.replica_id, collaborator))
23232 .collect::<HashMap<_, _>>();
23233 self.buffer_snapshot()
23234 .selections_in_range(range, false)
23235 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23236 if replica_id == ReplicaId::AGENT {
23237 Some(RemoteSelection {
23238 replica_id,
23239 selection,
23240 cursor_shape,
23241 line_mode,
23242 collaborator_id: CollaboratorId::Agent,
23243 user_name: Some("Agent".into()),
23244 color: cx.theme().players().agent(),
23245 })
23246 } else {
23247 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23248 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23249 let user_name = participant_names.get(&collaborator.user_id).cloned();
23250 Some(RemoteSelection {
23251 replica_id,
23252 selection,
23253 cursor_shape,
23254 line_mode,
23255 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23256 user_name,
23257 color: if let Some(index) = participant_index {
23258 cx.theme().players().color_for_participant(index.0)
23259 } else {
23260 cx.theme().players().absent()
23261 },
23262 })
23263 }
23264 })
23265 }
23266
23267 pub fn hunks_for_ranges(
23268 &self,
23269 ranges: impl IntoIterator<Item = Range<Point>>,
23270 ) -> Vec<MultiBufferDiffHunk> {
23271 let mut hunks = Vec::new();
23272 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23273 HashMap::default();
23274 for query_range in ranges {
23275 let query_rows =
23276 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23277 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23278 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23279 ) {
23280 // Include deleted hunks that are adjacent to the query range, because
23281 // otherwise they would be missed.
23282 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23283 if hunk.status().is_deleted() {
23284 intersects_range |= hunk.row_range.start == query_rows.end;
23285 intersects_range |= hunk.row_range.end == query_rows.start;
23286 }
23287 if intersects_range {
23288 if !processed_buffer_rows
23289 .entry(hunk.buffer_id)
23290 .or_default()
23291 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23292 {
23293 continue;
23294 }
23295 hunks.push(hunk);
23296 }
23297 }
23298 }
23299
23300 hunks
23301 }
23302
23303 fn display_diff_hunks_for_rows<'a>(
23304 &'a self,
23305 display_rows: Range<DisplayRow>,
23306 folded_buffers: &'a HashSet<BufferId>,
23307 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23308 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23309 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23310
23311 self.buffer_snapshot()
23312 .diff_hunks_in_range(buffer_start..buffer_end)
23313 .filter_map(|hunk| {
23314 if folded_buffers.contains(&hunk.buffer_id) {
23315 return None;
23316 }
23317
23318 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23319 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23320
23321 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23322 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23323
23324 let display_hunk = if hunk_display_start.column() != 0 {
23325 DisplayDiffHunk::Folded {
23326 display_row: hunk_display_start.row(),
23327 }
23328 } else {
23329 let mut end_row = hunk_display_end.row();
23330 if hunk_display_end.column() > 0 {
23331 end_row.0 += 1;
23332 }
23333 let is_created_file = hunk.is_created_file();
23334 DisplayDiffHunk::Unfolded {
23335 status: hunk.status(),
23336 diff_base_byte_range: hunk.diff_base_byte_range,
23337 display_row_range: hunk_display_start.row()..end_row,
23338 multi_buffer_range: Anchor::range_in_buffer(
23339 hunk.excerpt_id,
23340 hunk.buffer_id,
23341 hunk.buffer_range,
23342 ),
23343 is_created_file,
23344 }
23345 };
23346
23347 Some(display_hunk)
23348 })
23349 }
23350
23351 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23352 self.display_snapshot
23353 .buffer_snapshot()
23354 .language_at(position)
23355 }
23356
23357 pub fn is_focused(&self) -> bool {
23358 self.is_focused
23359 }
23360
23361 pub fn placeholder_text(&self) -> Option<String> {
23362 self.placeholder_display_snapshot
23363 .as_ref()
23364 .map(|display_map| display_map.text())
23365 }
23366
23367 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23368 self.scroll_anchor.scroll_position(&self.display_snapshot)
23369 }
23370
23371 fn gutter_dimensions(
23372 &self,
23373 font_id: FontId,
23374 font_size: Pixels,
23375 max_line_number_width: Pixels,
23376 cx: &App,
23377 ) -> Option<GutterDimensions> {
23378 if !self.show_gutter {
23379 return None;
23380 }
23381
23382 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23383 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23384
23385 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23386 matches!(
23387 ProjectSettings::get_global(cx).git.git_gutter,
23388 GitGutterSetting::TrackedFiles
23389 )
23390 });
23391 let gutter_settings = EditorSettings::get_global(cx).gutter;
23392 let show_line_numbers = self
23393 .show_line_numbers
23394 .unwrap_or(gutter_settings.line_numbers);
23395 let line_gutter_width = if show_line_numbers {
23396 // Avoid flicker-like gutter resizes when the line number gains another digit by
23397 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23398 let min_width_for_number_on_gutter =
23399 ch_advance * gutter_settings.min_line_number_digits as f32;
23400 max_line_number_width.max(min_width_for_number_on_gutter)
23401 } else {
23402 0.0.into()
23403 };
23404
23405 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23406 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23407
23408 let git_blame_entries_width =
23409 self.git_blame_gutter_max_author_length
23410 .map(|max_author_length| {
23411 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23412 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23413
23414 /// The number of characters to dedicate to gaps and margins.
23415 const SPACING_WIDTH: usize = 4;
23416
23417 let max_char_count = max_author_length.min(renderer.max_author_length())
23418 + ::git::SHORT_SHA_LENGTH
23419 + MAX_RELATIVE_TIMESTAMP.len()
23420 + SPACING_WIDTH;
23421
23422 ch_advance * max_char_count
23423 });
23424
23425 let is_singleton = self.buffer_snapshot().is_singleton();
23426
23427 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23428 left_padding += if !is_singleton {
23429 ch_width * 4.0
23430 } else if show_runnables || show_breakpoints {
23431 ch_width * 3.0
23432 } else if show_git_gutter && show_line_numbers {
23433 ch_width * 2.0
23434 } else if show_git_gutter || show_line_numbers {
23435 ch_width
23436 } else {
23437 px(0.)
23438 };
23439
23440 let shows_folds = is_singleton && gutter_settings.folds;
23441
23442 let right_padding = if shows_folds && show_line_numbers {
23443 ch_width * 4.0
23444 } else if shows_folds || (!is_singleton && show_line_numbers) {
23445 ch_width * 3.0
23446 } else if show_line_numbers {
23447 ch_width
23448 } else {
23449 px(0.)
23450 };
23451
23452 Some(GutterDimensions {
23453 left_padding,
23454 right_padding,
23455 width: line_gutter_width + left_padding + right_padding,
23456 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23457 git_blame_entries_width,
23458 })
23459 }
23460
23461 pub fn render_crease_toggle(
23462 &self,
23463 buffer_row: MultiBufferRow,
23464 row_contains_cursor: bool,
23465 editor: Entity<Editor>,
23466 window: &mut Window,
23467 cx: &mut App,
23468 ) -> Option<AnyElement> {
23469 let folded = self.is_line_folded(buffer_row);
23470 let mut is_foldable = false;
23471
23472 if let Some(crease) = self
23473 .crease_snapshot
23474 .query_row(buffer_row, self.buffer_snapshot())
23475 {
23476 is_foldable = true;
23477 match crease {
23478 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23479 if let Some(render_toggle) = render_toggle {
23480 let toggle_callback =
23481 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23482 if folded {
23483 editor.update(cx, |editor, cx| {
23484 editor.fold_at(buffer_row, window, cx)
23485 });
23486 } else {
23487 editor.update(cx, |editor, cx| {
23488 editor.unfold_at(buffer_row, window, cx)
23489 });
23490 }
23491 });
23492 return Some((render_toggle)(
23493 buffer_row,
23494 folded,
23495 toggle_callback,
23496 window,
23497 cx,
23498 ));
23499 }
23500 }
23501 }
23502 }
23503
23504 is_foldable |= self.starts_indent(buffer_row);
23505
23506 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23507 Some(
23508 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23509 .toggle_state(folded)
23510 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23511 if folded {
23512 this.unfold_at(buffer_row, window, cx);
23513 } else {
23514 this.fold_at(buffer_row, window, cx);
23515 }
23516 }))
23517 .into_any_element(),
23518 )
23519 } else {
23520 None
23521 }
23522 }
23523
23524 pub fn render_crease_trailer(
23525 &self,
23526 buffer_row: MultiBufferRow,
23527 window: &mut Window,
23528 cx: &mut App,
23529 ) -> Option<AnyElement> {
23530 let folded = self.is_line_folded(buffer_row);
23531 if let Crease::Inline { render_trailer, .. } = self
23532 .crease_snapshot
23533 .query_row(buffer_row, self.buffer_snapshot())?
23534 {
23535 let render_trailer = render_trailer.as_ref()?;
23536 Some(render_trailer(buffer_row, folded, window, cx))
23537 } else {
23538 None
23539 }
23540 }
23541}
23542
23543impl Deref for EditorSnapshot {
23544 type Target = DisplaySnapshot;
23545
23546 fn deref(&self) -> &Self::Target {
23547 &self.display_snapshot
23548 }
23549}
23550
23551#[derive(Clone, Debug, PartialEq, Eq)]
23552pub enum EditorEvent {
23553 InputIgnored {
23554 text: Arc<str>,
23555 },
23556 InputHandled {
23557 utf16_range_to_replace: Option<Range<isize>>,
23558 text: Arc<str>,
23559 },
23560 ExcerptsAdded {
23561 buffer: Entity<Buffer>,
23562 predecessor: ExcerptId,
23563 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23564 },
23565 ExcerptsRemoved {
23566 ids: Vec<ExcerptId>,
23567 removed_buffer_ids: Vec<BufferId>,
23568 },
23569 BufferFoldToggled {
23570 ids: Vec<ExcerptId>,
23571 folded: bool,
23572 },
23573 ExcerptsEdited {
23574 ids: Vec<ExcerptId>,
23575 },
23576 ExcerptsExpanded {
23577 ids: Vec<ExcerptId>,
23578 },
23579 BufferEdited,
23580 Edited {
23581 transaction_id: clock::Lamport,
23582 },
23583 Reparsed(BufferId),
23584 Focused,
23585 FocusedIn,
23586 Blurred,
23587 DirtyChanged,
23588 Saved,
23589 TitleChanged,
23590 SelectionsChanged {
23591 local: bool,
23592 },
23593 ScrollPositionChanged {
23594 local: bool,
23595 autoscroll: bool,
23596 },
23597 TransactionUndone {
23598 transaction_id: clock::Lamport,
23599 },
23600 TransactionBegun {
23601 transaction_id: clock::Lamport,
23602 },
23603 CursorShapeChanged,
23604 BreadcrumbsChanged,
23605 PushedToNavHistory {
23606 anchor: Anchor,
23607 is_deactivate: bool,
23608 },
23609}
23610
23611impl EventEmitter<EditorEvent> for Editor {}
23612
23613impl Focusable for Editor {
23614 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23615 self.focus_handle.clone()
23616 }
23617}
23618
23619impl Render for Editor {
23620 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23621 let settings = ThemeSettings::get_global(cx);
23622
23623 let mut text_style = match self.mode {
23624 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23625 color: cx.theme().colors().editor_foreground,
23626 font_family: settings.ui_font.family.clone(),
23627 font_features: settings.ui_font.features.clone(),
23628 font_fallbacks: settings.ui_font.fallbacks.clone(),
23629 font_size: rems(0.875).into(),
23630 font_weight: settings.ui_font.weight,
23631 line_height: relative(settings.buffer_line_height.value()),
23632 ..Default::default()
23633 },
23634 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23635 color: cx.theme().colors().editor_foreground,
23636 font_family: settings.buffer_font.family.clone(),
23637 font_features: settings.buffer_font.features.clone(),
23638 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23639 font_size: settings.buffer_font_size(cx).into(),
23640 font_weight: settings.buffer_font.weight,
23641 line_height: relative(settings.buffer_line_height.value()),
23642 ..Default::default()
23643 },
23644 };
23645 if let Some(text_style_refinement) = &self.text_style_refinement {
23646 text_style.refine(text_style_refinement)
23647 }
23648
23649 let background = match self.mode {
23650 EditorMode::SingleLine => cx.theme().system().transparent,
23651 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23652 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23653 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23654 };
23655
23656 EditorElement::new(
23657 &cx.entity(),
23658 EditorStyle {
23659 background,
23660 border: cx.theme().colors().border,
23661 local_player: cx.theme().players().local(),
23662 text: text_style,
23663 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23664 syntax: cx.theme().syntax().clone(),
23665 status: cx.theme().status().clone(),
23666 inlay_hints_style: make_inlay_hints_style(cx),
23667 edit_prediction_styles: make_suggestion_styles(cx),
23668 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23669 show_underlines: self.diagnostics_enabled(),
23670 },
23671 )
23672 }
23673}
23674
23675impl EntityInputHandler for Editor {
23676 fn text_for_range(
23677 &mut self,
23678 range_utf16: Range<usize>,
23679 adjusted_range: &mut Option<Range<usize>>,
23680 _: &mut Window,
23681 cx: &mut Context<Self>,
23682 ) -> Option<String> {
23683 let snapshot = self.buffer.read(cx).read(cx);
23684 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23685 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23686 if (start.0..end.0) != range_utf16 {
23687 adjusted_range.replace(start.0..end.0);
23688 }
23689 Some(snapshot.text_for_range(start..end).collect())
23690 }
23691
23692 fn selected_text_range(
23693 &mut self,
23694 ignore_disabled_input: bool,
23695 _: &mut Window,
23696 cx: &mut Context<Self>,
23697 ) -> Option<UTF16Selection> {
23698 // Prevent the IME menu from appearing when holding down an alphabetic key
23699 // while input is disabled.
23700 if !ignore_disabled_input && !self.input_enabled {
23701 return None;
23702 }
23703
23704 let selection = self
23705 .selections
23706 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23707 let range = selection.range();
23708
23709 Some(UTF16Selection {
23710 range: range.start.0..range.end.0,
23711 reversed: selection.reversed,
23712 })
23713 }
23714
23715 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
23716 let snapshot = self.buffer.read(cx).read(cx);
23717 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
23718 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
23719 }
23720
23721 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
23722 self.clear_highlights::<InputComposition>(cx);
23723 self.ime_transaction.take();
23724 }
23725
23726 fn replace_text_in_range(
23727 &mut self,
23728 range_utf16: Option<Range<usize>>,
23729 text: &str,
23730 window: &mut Window,
23731 cx: &mut Context<Self>,
23732 ) {
23733 if !self.input_enabled {
23734 cx.emit(EditorEvent::InputIgnored { text: text.into() });
23735 return;
23736 }
23737
23738 self.transact(window, cx, |this, window, cx| {
23739 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
23740 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23741 Some(this.selection_replacement_ranges(range_utf16, cx))
23742 } else {
23743 this.marked_text_ranges(cx)
23744 };
23745
23746 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
23747 let newest_selection_id = this.selections.newest_anchor().id;
23748 this.selections
23749 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23750 .iter()
23751 .zip(ranges_to_replace.iter())
23752 .find_map(|(selection, range)| {
23753 if selection.id == newest_selection_id {
23754 Some(
23755 (range.start.0 as isize - selection.head().0 as isize)
23756 ..(range.end.0 as isize - selection.head().0 as isize),
23757 )
23758 } else {
23759 None
23760 }
23761 })
23762 });
23763
23764 cx.emit(EditorEvent::InputHandled {
23765 utf16_range_to_replace: range_to_replace,
23766 text: text.into(),
23767 });
23768
23769 if let Some(new_selected_ranges) = new_selected_ranges {
23770 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23771 selections.select_ranges(new_selected_ranges)
23772 });
23773 this.backspace(&Default::default(), window, cx);
23774 }
23775
23776 this.handle_input(text, window, cx);
23777 });
23778
23779 if let Some(transaction) = self.ime_transaction {
23780 self.buffer.update(cx, |buffer, cx| {
23781 buffer.group_until_transaction(transaction, cx);
23782 });
23783 }
23784
23785 self.unmark_text(window, cx);
23786 }
23787
23788 fn replace_and_mark_text_in_range(
23789 &mut self,
23790 range_utf16: Option<Range<usize>>,
23791 text: &str,
23792 new_selected_range_utf16: Option<Range<usize>>,
23793 window: &mut Window,
23794 cx: &mut Context<Self>,
23795 ) {
23796 if !self.input_enabled {
23797 return;
23798 }
23799
23800 let transaction = self.transact(window, cx, |this, window, cx| {
23801 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
23802 let snapshot = this.buffer.read(cx).read(cx);
23803 if let Some(relative_range_utf16) = range_utf16.as_ref() {
23804 for marked_range in &mut marked_ranges {
23805 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
23806 marked_range.start.0 += relative_range_utf16.start;
23807 marked_range.start =
23808 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
23809 marked_range.end =
23810 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
23811 }
23812 }
23813 Some(marked_ranges)
23814 } else if let Some(range_utf16) = range_utf16 {
23815 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
23816 Some(this.selection_replacement_ranges(range_utf16, cx))
23817 } else {
23818 None
23819 };
23820
23821 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
23822 let newest_selection_id = this.selections.newest_anchor().id;
23823 this.selections
23824 .all::<OffsetUtf16>(&this.display_snapshot(cx))
23825 .iter()
23826 .zip(ranges_to_replace.iter())
23827 .find_map(|(selection, range)| {
23828 if selection.id == newest_selection_id {
23829 Some(
23830 (range.start.0 as isize - selection.head().0 as isize)
23831 ..(range.end.0 as isize - selection.head().0 as isize),
23832 )
23833 } else {
23834 None
23835 }
23836 })
23837 });
23838
23839 cx.emit(EditorEvent::InputHandled {
23840 utf16_range_to_replace: range_to_replace,
23841 text: text.into(),
23842 });
23843
23844 if let Some(ranges) = ranges_to_replace {
23845 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23846 s.select_ranges(ranges)
23847 });
23848 }
23849
23850 let marked_ranges = {
23851 let snapshot = this.buffer.read(cx).read(cx);
23852 this.selections
23853 .disjoint_anchors_arc()
23854 .iter()
23855 .map(|selection| {
23856 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
23857 })
23858 .collect::<Vec<_>>()
23859 };
23860
23861 if text.is_empty() {
23862 this.unmark_text(window, cx);
23863 } else {
23864 this.highlight_text::<InputComposition>(
23865 marked_ranges.clone(),
23866 HighlightStyle {
23867 underline: Some(UnderlineStyle {
23868 thickness: px(1.),
23869 color: None,
23870 wavy: false,
23871 }),
23872 ..Default::default()
23873 },
23874 cx,
23875 );
23876 }
23877
23878 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
23879 let use_autoclose = this.use_autoclose;
23880 let use_auto_surround = this.use_auto_surround;
23881 this.set_use_autoclose(false);
23882 this.set_use_auto_surround(false);
23883 this.handle_input(text, window, cx);
23884 this.set_use_autoclose(use_autoclose);
23885 this.set_use_auto_surround(use_auto_surround);
23886
23887 if let Some(new_selected_range) = new_selected_range_utf16 {
23888 let snapshot = this.buffer.read(cx).read(cx);
23889 let new_selected_ranges = marked_ranges
23890 .into_iter()
23891 .map(|marked_range| {
23892 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
23893 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
23894 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
23895 snapshot.clip_offset_utf16(new_start, Bias::Left)
23896 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
23897 })
23898 .collect::<Vec<_>>();
23899
23900 drop(snapshot);
23901 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23902 selections.select_ranges(new_selected_ranges)
23903 });
23904 }
23905 });
23906
23907 self.ime_transaction = self.ime_transaction.or(transaction);
23908 if let Some(transaction) = self.ime_transaction {
23909 self.buffer.update(cx, |buffer, cx| {
23910 buffer.group_until_transaction(transaction, cx);
23911 });
23912 }
23913
23914 if self.text_highlights::<InputComposition>(cx).is_none() {
23915 self.ime_transaction.take();
23916 }
23917 }
23918
23919 fn bounds_for_range(
23920 &mut self,
23921 range_utf16: Range<usize>,
23922 element_bounds: gpui::Bounds<Pixels>,
23923 window: &mut Window,
23924 cx: &mut Context<Self>,
23925 ) -> Option<gpui::Bounds<Pixels>> {
23926 let text_layout_details = self.text_layout_details(window);
23927 let CharacterDimensions {
23928 em_width,
23929 em_advance,
23930 line_height,
23931 } = self.character_dimensions(window);
23932
23933 let snapshot = self.snapshot(window, cx);
23934 let scroll_position = snapshot.scroll_position();
23935 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
23936
23937 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
23938 let x = Pixels::from(
23939 ScrollOffset::from(
23940 snapshot.x_for_display_point(start, &text_layout_details)
23941 + self.gutter_dimensions.full_width(),
23942 ) - scroll_left,
23943 );
23944 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
23945
23946 Some(Bounds {
23947 origin: element_bounds.origin + point(x, y),
23948 size: size(em_width, line_height),
23949 })
23950 }
23951
23952 fn character_index_for_point(
23953 &mut self,
23954 point: gpui::Point<Pixels>,
23955 _window: &mut Window,
23956 _cx: &mut Context<Self>,
23957 ) -> Option<usize> {
23958 let position_map = self.last_position_map.as_ref()?;
23959 if !position_map.text_hitbox.contains(&point) {
23960 return None;
23961 }
23962 let display_point = position_map.point_for_position(point).previous_valid;
23963 let anchor = position_map
23964 .snapshot
23965 .display_point_to_anchor(display_point, Bias::Left);
23966 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
23967 Some(utf16_offset.0)
23968 }
23969}
23970
23971trait SelectionExt {
23972 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
23973 fn spanned_rows(
23974 &self,
23975 include_end_if_at_line_start: bool,
23976 map: &DisplaySnapshot,
23977 ) -> Range<MultiBufferRow>;
23978}
23979
23980impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
23981 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
23982 let start = self
23983 .start
23984 .to_point(map.buffer_snapshot())
23985 .to_display_point(map);
23986 let end = self
23987 .end
23988 .to_point(map.buffer_snapshot())
23989 .to_display_point(map);
23990 if self.reversed {
23991 end..start
23992 } else {
23993 start..end
23994 }
23995 }
23996
23997 fn spanned_rows(
23998 &self,
23999 include_end_if_at_line_start: bool,
24000 map: &DisplaySnapshot,
24001 ) -> Range<MultiBufferRow> {
24002 let start = self.start.to_point(map.buffer_snapshot());
24003 let mut end = self.end.to_point(map.buffer_snapshot());
24004 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24005 end.row -= 1;
24006 }
24007
24008 let buffer_start = map.prev_line_boundary(start).0;
24009 let buffer_end = map.next_line_boundary(end).0;
24010 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24011 }
24012}
24013
24014impl<T: InvalidationRegion> InvalidationStack<T> {
24015 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24016 where
24017 S: Clone + ToOffset,
24018 {
24019 while let Some(region) = self.last() {
24020 let all_selections_inside_invalidation_ranges =
24021 if selections.len() == region.ranges().len() {
24022 selections
24023 .iter()
24024 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24025 .all(|(selection, invalidation_range)| {
24026 let head = selection.head().to_offset(buffer);
24027 invalidation_range.start <= head && invalidation_range.end >= head
24028 })
24029 } else {
24030 false
24031 };
24032
24033 if all_selections_inside_invalidation_ranges {
24034 break;
24035 } else {
24036 self.pop();
24037 }
24038 }
24039 }
24040}
24041
24042impl<T> Default for InvalidationStack<T> {
24043 fn default() -> Self {
24044 Self(Default::default())
24045 }
24046}
24047
24048impl<T> Deref for InvalidationStack<T> {
24049 type Target = Vec<T>;
24050
24051 fn deref(&self) -> &Self::Target {
24052 &self.0
24053 }
24054}
24055
24056impl<T> DerefMut for InvalidationStack<T> {
24057 fn deref_mut(&mut self) -> &mut Self::Target {
24058 &mut self.0
24059 }
24060}
24061
24062impl InvalidationRegion for SnippetState {
24063 fn ranges(&self) -> &[Range<Anchor>] {
24064 &self.ranges[self.active_index]
24065 }
24066}
24067
24068fn edit_prediction_edit_text(
24069 current_snapshot: &BufferSnapshot,
24070 edits: &[(Range<Anchor>, String)],
24071 edit_preview: &EditPreview,
24072 include_deletions: bool,
24073 cx: &App,
24074) -> HighlightedText {
24075 let edits = edits
24076 .iter()
24077 .map(|(anchor, text)| {
24078 (
24079 anchor.start.text_anchor..anchor.end.text_anchor,
24080 text.clone(),
24081 )
24082 })
24083 .collect::<Vec<_>>();
24084
24085 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24086}
24087
24088fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24089 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24090 // Just show the raw edit text with basic styling
24091 let mut text = String::new();
24092 let mut highlights = Vec::new();
24093
24094 let insertion_highlight_style = HighlightStyle {
24095 color: Some(cx.theme().colors().text),
24096 ..Default::default()
24097 };
24098
24099 for (_, edit_text) in edits {
24100 let start_offset = text.len();
24101 text.push_str(edit_text);
24102 let end_offset = text.len();
24103
24104 if start_offset < end_offset {
24105 highlights.push((start_offset..end_offset, insertion_highlight_style));
24106 }
24107 }
24108
24109 HighlightedText {
24110 text: text.into(),
24111 highlights,
24112 }
24113}
24114
24115pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24116 match severity {
24117 lsp::DiagnosticSeverity::ERROR => colors.error,
24118 lsp::DiagnosticSeverity::WARNING => colors.warning,
24119 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24120 lsp::DiagnosticSeverity::HINT => colors.info,
24121 _ => colors.ignored,
24122 }
24123}
24124
24125pub fn styled_runs_for_code_label<'a>(
24126 label: &'a CodeLabel,
24127 syntax_theme: &'a theme::SyntaxTheme,
24128) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24129 let fade_out = HighlightStyle {
24130 fade_out: Some(0.35),
24131 ..Default::default()
24132 };
24133
24134 let mut prev_end = label.filter_range.end;
24135 label
24136 .runs
24137 .iter()
24138 .enumerate()
24139 .flat_map(move |(ix, (range, highlight_id))| {
24140 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24141 style
24142 } else {
24143 return Default::default();
24144 };
24145 let muted_style = style.highlight(fade_out);
24146
24147 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24148 if range.start >= label.filter_range.end {
24149 if range.start > prev_end {
24150 runs.push((prev_end..range.start, fade_out));
24151 }
24152 runs.push((range.clone(), muted_style));
24153 } else if range.end <= label.filter_range.end {
24154 runs.push((range.clone(), style));
24155 } else {
24156 runs.push((range.start..label.filter_range.end, style));
24157 runs.push((label.filter_range.end..range.end, muted_style));
24158 }
24159 prev_end = cmp::max(prev_end, range.end);
24160
24161 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24162 runs.push((prev_end..label.text.len(), fade_out));
24163 }
24164
24165 runs
24166 })
24167}
24168
24169pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24170 let mut prev_index = 0;
24171 let mut prev_codepoint: Option<char> = None;
24172 text.char_indices()
24173 .chain([(text.len(), '\0')])
24174 .filter_map(move |(index, codepoint)| {
24175 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24176 let is_boundary = index == text.len()
24177 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24178 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24179 if is_boundary {
24180 let chunk = &text[prev_index..index];
24181 prev_index = index;
24182 Some(chunk)
24183 } else {
24184 None
24185 }
24186 })
24187}
24188
24189pub trait RangeToAnchorExt: Sized {
24190 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24191
24192 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24193 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24194 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24195 }
24196}
24197
24198impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24199 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24200 let start_offset = self.start.to_offset(snapshot);
24201 let end_offset = self.end.to_offset(snapshot);
24202 if start_offset == end_offset {
24203 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24204 } else {
24205 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24206 }
24207 }
24208}
24209
24210pub trait RowExt {
24211 fn as_f64(&self) -> f64;
24212
24213 fn next_row(&self) -> Self;
24214
24215 fn previous_row(&self) -> Self;
24216
24217 fn minus(&self, other: Self) -> u32;
24218}
24219
24220impl RowExt for DisplayRow {
24221 fn as_f64(&self) -> f64 {
24222 self.0 as _
24223 }
24224
24225 fn next_row(&self) -> Self {
24226 Self(self.0 + 1)
24227 }
24228
24229 fn previous_row(&self) -> Self {
24230 Self(self.0.saturating_sub(1))
24231 }
24232
24233 fn minus(&self, other: Self) -> u32 {
24234 self.0 - other.0
24235 }
24236}
24237
24238impl RowExt for MultiBufferRow {
24239 fn as_f64(&self) -> f64 {
24240 self.0 as _
24241 }
24242
24243 fn next_row(&self) -> Self {
24244 Self(self.0 + 1)
24245 }
24246
24247 fn previous_row(&self) -> Self {
24248 Self(self.0.saturating_sub(1))
24249 }
24250
24251 fn minus(&self, other: Self) -> u32 {
24252 self.0 - other.0
24253 }
24254}
24255
24256trait RowRangeExt {
24257 type Row;
24258
24259 fn len(&self) -> usize;
24260
24261 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24262}
24263
24264impl RowRangeExt for Range<MultiBufferRow> {
24265 type Row = MultiBufferRow;
24266
24267 fn len(&self) -> usize {
24268 (self.end.0 - self.start.0) as usize
24269 }
24270
24271 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24272 (self.start.0..self.end.0).map(MultiBufferRow)
24273 }
24274}
24275
24276impl RowRangeExt for Range<DisplayRow> {
24277 type Row = DisplayRow;
24278
24279 fn len(&self) -> usize {
24280 (self.end.0 - self.start.0) as usize
24281 }
24282
24283 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24284 (self.start.0..self.end.0).map(DisplayRow)
24285 }
24286}
24287
24288/// If select range has more than one line, we
24289/// just point the cursor to range.start.
24290fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24291 if range.start.row == range.end.row {
24292 range
24293 } else {
24294 range.start..range.start
24295 }
24296}
24297pub struct KillRing(ClipboardItem);
24298impl Global for KillRing {}
24299
24300const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24301
24302enum BreakpointPromptEditAction {
24303 Log,
24304 Condition,
24305 HitCondition,
24306}
24307
24308struct BreakpointPromptEditor {
24309 pub(crate) prompt: Entity<Editor>,
24310 editor: WeakEntity<Editor>,
24311 breakpoint_anchor: Anchor,
24312 breakpoint: Breakpoint,
24313 edit_action: BreakpointPromptEditAction,
24314 block_ids: HashSet<CustomBlockId>,
24315 editor_margins: Arc<Mutex<EditorMargins>>,
24316 _subscriptions: Vec<Subscription>,
24317}
24318
24319impl BreakpointPromptEditor {
24320 const MAX_LINES: u8 = 4;
24321
24322 fn new(
24323 editor: WeakEntity<Editor>,
24324 breakpoint_anchor: Anchor,
24325 breakpoint: Breakpoint,
24326 edit_action: BreakpointPromptEditAction,
24327 window: &mut Window,
24328 cx: &mut Context<Self>,
24329 ) -> Self {
24330 let base_text = match edit_action {
24331 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24332 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24333 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24334 }
24335 .map(|msg| msg.to_string())
24336 .unwrap_or_default();
24337
24338 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24339 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24340
24341 let prompt = cx.new(|cx| {
24342 let mut prompt = Editor::new(
24343 EditorMode::AutoHeight {
24344 min_lines: 1,
24345 max_lines: Some(Self::MAX_LINES as usize),
24346 },
24347 buffer,
24348 None,
24349 window,
24350 cx,
24351 );
24352 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24353 prompt.set_show_cursor_when_unfocused(false, cx);
24354 prompt.set_placeholder_text(
24355 match edit_action {
24356 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24357 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24358 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24359 },
24360 window,
24361 cx,
24362 );
24363
24364 prompt
24365 });
24366
24367 Self {
24368 prompt,
24369 editor,
24370 breakpoint_anchor,
24371 breakpoint,
24372 edit_action,
24373 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24374 block_ids: Default::default(),
24375 _subscriptions: vec![],
24376 }
24377 }
24378
24379 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24380 self.block_ids.extend(block_ids)
24381 }
24382
24383 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24384 if let Some(editor) = self.editor.upgrade() {
24385 let message = self
24386 .prompt
24387 .read(cx)
24388 .buffer
24389 .read(cx)
24390 .as_singleton()
24391 .expect("A multi buffer in breakpoint prompt isn't possible")
24392 .read(cx)
24393 .as_rope()
24394 .to_string();
24395
24396 editor.update(cx, |editor, cx| {
24397 editor.edit_breakpoint_at_anchor(
24398 self.breakpoint_anchor,
24399 self.breakpoint.clone(),
24400 match self.edit_action {
24401 BreakpointPromptEditAction::Log => {
24402 BreakpointEditAction::EditLogMessage(message.into())
24403 }
24404 BreakpointPromptEditAction::Condition => {
24405 BreakpointEditAction::EditCondition(message.into())
24406 }
24407 BreakpointPromptEditAction::HitCondition => {
24408 BreakpointEditAction::EditHitCondition(message.into())
24409 }
24410 },
24411 cx,
24412 );
24413
24414 editor.remove_blocks(self.block_ids.clone(), None, cx);
24415 cx.focus_self(window);
24416 });
24417 }
24418 }
24419
24420 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24421 self.editor
24422 .update(cx, |editor, cx| {
24423 editor.remove_blocks(self.block_ids.clone(), None, cx);
24424 window.focus(&editor.focus_handle);
24425 })
24426 .log_err();
24427 }
24428
24429 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24430 let settings = ThemeSettings::get_global(cx);
24431 let text_style = TextStyle {
24432 color: if self.prompt.read(cx).read_only(cx) {
24433 cx.theme().colors().text_disabled
24434 } else {
24435 cx.theme().colors().text
24436 },
24437 font_family: settings.buffer_font.family.clone(),
24438 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24439 font_size: settings.buffer_font_size(cx).into(),
24440 font_weight: settings.buffer_font.weight,
24441 line_height: relative(settings.buffer_line_height.value()),
24442 ..Default::default()
24443 };
24444 EditorElement::new(
24445 &self.prompt,
24446 EditorStyle {
24447 background: cx.theme().colors().editor_background,
24448 local_player: cx.theme().players().local(),
24449 text: text_style,
24450 ..Default::default()
24451 },
24452 )
24453 }
24454}
24455
24456impl Render for BreakpointPromptEditor {
24457 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24458 let editor_margins = *self.editor_margins.lock();
24459 let gutter_dimensions = editor_margins.gutter;
24460 h_flex()
24461 .key_context("Editor")
24462 .bg(cx.theme().colors().editor_background)
24463 .border_y_1()
24464 .border_color(cx.theme().status().info_border)
24465 .size_full()
24466 .py(window.line_height() / 2.5)
24467 .on_action(cx.listener(Self::confirm))
24468 .on_action(cx.listener(Self::cancel))
24469 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24470 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24471 }
24472}
24473
24474impl Focusable for BreakpointPromptEditor {
24475 fn focus_handle(&self, cx: &App) -> FocusHandle {
24476 self.prompt.focus_handle(cx)
24477 }
24478}
24479
24480fn all_edits_insertions_or_deletions(
24481 edits: &Vec<(Range<Anchor>, String)>,
24482 snapshot: &MultiBufferSnapshot,
24483) -> bool {
24484 let mut all_insertions = true;
24485 let mut all_deletions = true;
24486
24487 for (range, new_text) in edits.iter() {
24488 let range_is_empty = range.to_offset(snapshot).is_empty();
24489 let text_is_empty = new_text.is_empty();
24490
24491 if range_is_empty != text_is_empty {
24492 if range_is_empty {
24493 all_deletions = false;
24494 } else {
24495 all_insertions = false;
24496 }
24497 } else {
24498 return false;
24499 }
24500
24501 if !all_insertions && !all_deletions {
24502 return false;
24503 }
24504 }
24505 all_insertions || all_deletions
24506}
24507
24508struct MissingEditPredictionKeybindingTooltip;
24509
24510impl Render for MissingEditPredictionKeybindingTooltip {
24511 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24512 ui::tooltip_container(cx, |container, cx| {
24513 container
24514 .flex_shrink_0()
24515 .max_w_80()
24516 .min_h(rems_from_px(124.))
24517 .justify_between()
24518 .child(
24519 v_flex()
24520 .flex_1()
24521 .text_ui_sm(cx)
24522 .child(Label::new("Conflict with Accept Keybinding"))
24523 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24524 )
24525 .child(
24526 h_flex()
24527 .pb_1()
24528 .gap_1()
24529 .items_end()
24530 .w_full()
24531 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24532 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24533 }))
24534 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24535 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24536 })),
24537 )
24538 })
24539 }
24540}
24541
24542#[derive(Debug, Clone, Copy, PartialEq)]
24543pub struct LineHighlight {
24544 pub background: Background,
24545 pub border: Option<gpui::Hsla>,
24546 pub include_gutter: bool,
24547 pub type_id: Option<TypeId>,
24548}
24549
24550struct LineManipulationResult {
24551 pub new_text: String,
24552 pub line_count_before: usize,
24553 pub line_count_after: usize,
24554}
24555
24556fn render_diff_hunk_controls(
24557 row: u32,
24558 status: &DiffHunkStatus,
24559 hunk_range: Range<Anchor>,
24560 is_created_file: bool,
24561 line_height: Pixels,
24562 editor: &Entity<Editor>,
24563 _window: &mut Window,
24564 cx: &mut App,
24565) -> AnyElement {
24566 h_flex()
24567 .h(line_height)
24568 .mr_1()
24569 .gap_1()
24570 .px_0p5()
24571 .pb_1()
24572 .border_x_1()
24573 .border_b_1()
24574 .border_color(cx.theme().colors().border_variant)
24575 .rounded_b_lg()
24576 .bg(cx.theme().colors().editor_background)
24577 .gap_1()
24578 .block_mouse_except_scroll()
24579 .shadow_md()
24580 .child(if status.has_secondary_hunk() {
24581 Button::new(("stage", row as u64), "Stage")
24582 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24583 .tooltip({
24584 let focus_handle = editor.focus_handle(cx);
24585 move |_window, cx| {
24586 Tooltip::for_action_in(
24587 "Stage Hunk",
24588 &::git::ToggleStaged,
24589 &focus_handle,
24590 cx,
24591 )
24592 }
24593 })
24594 .on_click({
24595 let editor = editor.clone();
24596 move |_event, _window, cx| {
24597 editor.update(cx, |editor, cx| {
24598 editor.stage_or_unstage_diff_hunks(
24599 true,
24600 vec![hunk_range.start..hunk_range.start],
24601 cx,
24602 );
24603 });
24604 }
24605 })
24606 } else {
24607 Button::new(("unstage", row as u64), "Unstage")
24608 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24609 .tooltip({
24610 let focus_handle = editor.focus_handle(cx);
24611 move |_window, cx| {
24612 Tooltip::for_action_in(
24613 "Unstage Hunk",
24614 &::git::ToggleStaged,
24615 &focus_handle,
24616 cx,
24617 )
24618 }
24619 })
24620 .on_click({
24621 let editor = editor.clone();
24622 move |_event, _window, cx| {
24623 editor.update(cx, |editor, cx| {
24624 editor.stage_or_unstage_diff_hunks(
24625 false,
24626 vec![hunk_range.start..hunk_range.start],
24627 cx,
24628 );
24629 });
24630 }
24631 })
24632 })
24633 .child(
24634 Button::new(("restore", row as u64), "Restore")
24635 .tooltip({
24636 let focus_handle = editor.focus_handle(cx);
24637 move |_window, cx| {
24638 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
24639 }
24640 })
24641 .on_click({
24642 let editor = editor.clone();
24643 move |_event, window, cx| {
24644 editor.update(cx, |editor, cx| {
24645 let snapshot = editor.snapshot(window, cx);
24646 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24647 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24648 });
24649 }
24650 })
24651 .disabled(is_created_file),
24652 )
24653 .when(
24654 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24655 |el| {
24656 el.child(
24657 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24658 .shape(IconButtonShape::Square)
24659 .icon_size(IconSize::Small)
24660 // .disabled(!has_multiple_hunks)
24661 .tooltip({
24662 let focus_handle = editor.focus_handle(cx);
24663 move |_window, cx| {
24664 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
24665 }
24666 })
24667 .on_click({
24668 let editor = editor.clone();
24669 move |_event, window, cx| {
24670 editor.update(cx, |editor, cx| {
24671 let snapshot = editor.snapshot(window, cx);
24672 let position =
24673 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24674 editor.go_to_hunk_before_or_after_position(
24675 &snapshot,
24676 position,
24677 Direction::Next,
24678 window,
24679 cx,
24680 );
24681 editor.expand_selected_diff_hunks(cx);
24682 });
24683 }
24684 }),
24685 )
24686 .child(
24687 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24688 .shape(IconButtonShape::Square)
24689 .icon_size(IconSize::Small)
24690 // .disabled(!has_multiple_hunks)
24691 .tooltip({
24692 let focus_handle = editor.focus_handle(cx);
24693 move |_window, cx| {
24694 Tooltip::for_action_in(
24695 "Previous Hunk",
24696 &GoToPreviousHunk,
24697 &focus_handle,
24698 cx,
24699 )
24700 }
24701 })
24702 .on_click({
24703 let editor = editor.clone();
24704 move |_event, window, cx| {
24705 editor.update(cx, |editor, cx| {
24706 let snapshot = editor.snapshot(window, cx);
24707 let point =
24708 hunk_range.start.to_point(&snapshot.buffer_snapshot());
24709 editor.go_to_hunk_before_or_after_position(
24710 &snapshot,
24711 point,
24712 Direction::Prev,
24713 window,
24714 cx,
24715 );
24716 editor.expand_selected_diff_hunks(cx);
24717 });
24718 }
24719 }),
24720 )
24721 },
24722 )
24723 .into_any_element()
24724}
24725
24726pub fn multibuffer_context_lines(cx: &App) -> u32 {
24727 EditorSettings::try_get(cx)
24728 .map(|settings| settings.excerpt_context_lines)
24729 .unwrap_or(2)
24730 .min(32)
24731}