1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39pub mod tasks;
40
41#[cfg(test)]
42mod code_completion_tests;
43#[cfg(test)]
44mod edit_prediction_tests;
45#[cfg(test)]
46mod editor_tests;
47mod signature_help;
48#[cfg(any(test, feature = "test-support"))]
49pub mod test;
50
51pub(crate) use actions::*;
52pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
53pub use edit_prediction::Direction;
54pub use editor_settings::{
55 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
56 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
57};
58pub use element::{
59 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
60};
61pub use git::blame::BlameRenderer;
62pub use hover_popover::hover_markdown_style;
63pub use inlays::Inlay;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
69 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
70 ToPoint,
71};
72pub use text::Bias;
73
74use ::git::{
75 Restore,
76 blame::{BlameEntry, ParsedCommitMessage},
77 status::FileStatus,
78};
79use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
80use anyhow::{Context as _, Result, anyhow};
81use blink_manager::BlinkManager;
82use buffer_diff::DiffHunkStatus;
83use client::{Collaborator, ParticipantIndex, parse_zed_link};
84use clock::ReplicaId;
85use code_context_menus::{
86 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
87 CompletionsMenu, ContextMenuOrigin,
88};
89use collections::{BTreeMap, HashMap, HashSet, VecDeque};
90use convert_case::{Case, Casing};
91use dap::TelemetrySpawnLocation;
92use display_map::*;
93use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
94use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
95use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
96use futures::{
97 FutureExt, StreamExt as _,
98 future::{self, Shared, join},
99 stream::FuturesUnordered,
100};
101use fuzzy::{StringMatch, StringMatchCandidate};
102use git::blame::{GitBlame, GlobalBlameRenderer};
103use gpui::{
104 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
105 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
106 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
107 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
108 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, Render,
109 ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle,
110 TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
111 WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative, size,
112};
113use hover_links::{HoverLink, HoveredLinkState, find_file};
114use hover_popover::{HoverState, hide_hover};
115use indent_guides::ActiveIndentGuidesState;
116use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
117use itertools::{Either, Itertools};
118use language::{
119 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
120 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
121 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
122 IndentSize, Language, LanguageName, LanguageRegistry, OffsetRangeExt, OutlineItem, Point,
123 Runnable, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
124 language_settings::{
125 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
126 all_language_settings, language_settings,
127 },
128 point_from_lsp, point_to_lsp, text_diff_with_options,
129};
130use linked_editing_ranges::refresh_linked_ranges;
131use lsp::{
132 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
133 LanguageServerId,
134};
135use lsp_colors::LspColorData;
136use markdown::Markdown;
137use mouse_context_menu::MouseContextMenu;
138use movement::TextLayoutDetails;
139use multi_buffer::{
140 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
141};
142use parking_lot::Mutex;
143use persistence::DB;
144use project::{
145 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
146 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
147 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
148 ProjectPath, ProjectTransaction, TaskSourceKind,
149 debugger::{
150 breakpoint_store::{
151 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
152 BreakpointStore, BreakpointStoreEvent,
153 },
154 session::{Session, SessionEvent},
155 },
156 git_store::GitStoreEvent,
157 lsp_store::{
158 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
159 OpenLspBufferHandle,
160 },
161 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
162};
163use rand::seq::SliceRandom;
164use rpc::{ErrorCode, ErrorExt, proto::PeerId};
165use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
166use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
167use serde::{Deserialize, Serialize};
168use settings::{
169 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
170 update_settings_file,
171};
172use smallvec::{SmallVec, smallvec};
173use snippet::Snippet;
174use std::{
175 any::{Any, TypeId},
176 borrow::Cow,
177 cell::{OnceCell, RefCell},
178 cmp::{self, Ordering, Reverse},
179 collections::hash_map,
180 iter::{self, Peekable},
181 mem,
182 num::NonZeroU32,
183 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
184 path::{Path, PathBuf},
185 rc::Rc,
186 sync::Arc,
187 time::{Duration, Instant},
188};
189use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
190use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
191use theme::{
192 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
193 observe_buffer_font_size_adjustment,
194};
195use ui::{
196 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
197 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
198};
199use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
200use workspace::{
201 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
202 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
203 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
204 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
205 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
206 searchable::SearchEvent,
207};
208
209use crate::{
210 code_context_menus::CompletionsMenuSource,
211 editor_settings::MultiCursorModifier,
212 hover_links::{find_url, find_url_from_range},
213 inlays::{
214 InlineValueCache,
215 inlay_hints::{LspInlayHintData, inlay_hint_settings},
216 },
217 scroll::{ScrollOffset, ScrollPixelOffset},
218 selections_collection::resolve_selections_wrapping_blocks,
219 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
220};
221
222pub const FILE_HEADER_HEIGHT: u32 = 2;
223pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
224const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
225const MAX_LINE_LEN: usize = 1024;
226const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
227const MAX_SELECTION_HISTORY_LEN: usize = 1024;
228pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
229#[doc(hidden)]
230pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
231pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
232
233pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
235pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
236pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
237
238pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
239pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
240pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
241
242pub type RenderDiffHunkControlsFn = Arc<
243 dyn Fn(
244 u32,
245 &DiffHunkStatus,
246 Range<Anchor>,
247 bool,
248 Pixels,
249 &Entity<Editor>,
250 &mut Window,
251 &mut App,
252 ) -> AnyElement,
253>;
254
255enum ReportEditorEvent {
256 Saved { auto_saved: bool },
257 EditorOpened,
258 Closed,
259}
260
261impl ReportEditorEvent {
262 pub fn event_type(&self) -> &'static str {
263 match self {
264 Self::Saved { .. } => "Editor Saved",
265 Self::EditorOpened => "Editor Opened",
266 Self::Closed => "Editor Closed",
267 }
268 }
269}
270
271pub enum ActiveDebugLine {}
272pub enum DebugStackFrameLine {}
273enum DocumentHighlightRead {}
274enum DocumentHighlightWrite {}
275enum InputComposition {}
276pub enum PendingInput {}
277enum SelectedTextHighlight {}
278
279pub enum ConflictsOuter {}
280pub enum ConflictsOurs {}
281pub enum ConflictsTheirs {}
282pub enum ConflictsOursMarker {}
283pub enum ConflictsTheirsMarker {}
284
285#[derive(Debug, Copy, Clone, PartialEq, Eq)]
286pub enum Navigated {
287 Yes,
288 No,
289}
290
291impl Navigated {
292 pub fn from_bool(yes: bool) -> Navigated {
293 if yes { Navigated::Yes } else { Navigated::No }
294 }
295}
296
297#[derive(Debug, Clone, PartialEq, Eq)]
298enum DisplayDiffHunk {
299 Folded {
300 display_row: DisplayRow,
301 },
302 Unfolded {
303 is_created_file: bool,
304 diff_base_byte_range: Range<usize>,
305 display_row_range: Range<DisplayRow>,
306 multi_buffer_range: Range<Anchor>,
307 status: DiffHunkStatus,
308 },
309}
310
311pub enum HideMouseCursorOrigin {
312 TypingAction,
313 MovementAction,
314}
315
316pub fn init(cx: &mut App) {
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 language_registry: Option<Arc<LanguageRegistry>>,
378 cx: &mut App,
379 ) -> Vec<BlockProperties<Anchor>>;
380
381 fn render_hover(
382 &self,
383 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
384 range: Range<Point>,
385 buffer_id: BufferId,
386 language_registry: Option<Arc<LanguageRegistry>>,
387 cx: &mut App,
388 ) -> Option<Entity<markdown::Markdown>>;
389
390 fn open_link(
391 &self,
392 editor: &mut Editor,
393 link: SharedString,
394 window: &mut Window,
395 cx: &mut Context<Editor>,
396 );
397}
398
399pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
400
401impl GlobalDiagnosticRenderer {
402 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
403 cx.try_global::<Self>().map(|g| g.0.clone())
404 }
405}
406
407impl gpui::Global for GlobalDiagnosticRenderer {}
408pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
409 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
410}
411
412pub struct SearchWithinRange;
413
414trait InvalidationRegion {
415 fn ranges(&self) -> &[Range<Anchor>];
416}
417
418#[derive(Clone, Debug, PartialEq)]
419pub enum SelectPhase {
420 Begin {
421 position: DisplayPoint,
422 add: bool,
423 click_count: usize,
424 },
425 BeginColumnar {
426 position: DisplayPoint,
427 reset: bool,
428 mode: ColumnarMode,
429 goal_column: u32,
430 },
431 Extend {
432 position: DisplayPoint,
433 click_count: usize,
434 },
435 Update {
436 position: DisplayPoint,
437 goal_column: u32,
438 scroll_delta: gpui::Point<f32>,
439 },
440 End,
441}
442
443#[derive(Clone, Debug, PartialEq)]
444pub enum ColumnarMode {
445 FromMouse,
446 FromSelection,
447}
448
449#[derive(Clone, Debug)]
450pub enum SelectMode {
451 Character,
452 Word(Range<Anchor>),
453 Line(Range<Anchor>),
454 All,
455}
456
457#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
458pub enum SizingBehavior {
459 /// The editor will layout itself using `size_full` and will include the vertical
460 /// scroll margin as requested by user settings.
461 #[default]
462 Default,
463 /// The editor will layout itself using `size_full`, but will not have any
464 /// vertical overscroll.
465 ExcludeOverscrollMargin,
466 /// The editor will request a vertical size according to its content and will be
467 /// layouted without a vertical scroll margin.
468 SizeByContent,
469}
470
471#[derive(Clone, PartialEq, Eq, Debug)]
472pub enum EditorMode {
473 SingleLine,
474 AutoHeight {
475 min_lines: usize,
476 max_lines: Option<usize>,
477 },
478 Full {
479 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
480 scale_ui_elements_with_buffer_font_size: bool,
481 /// When set to `true`, the editor will render a background for the active line.
482 show_active_line_background: bool,
483 /// Determines the sizing behavior for this editor
484 sizing_behavior: SizingBehavior,
485 },
486 Minimap {
487 parent: WeakEntity<Editor>,
488 },
489}
490
491impl EditorMode {
492 pub fn full() -> Self {
493 Self::Full {
494 scale_ui_elements_with_buffer_font_size: true,
495 show_active_line_background: true,
496 sizing_behavior: SizingBehavior::Default,
497 }
498 }
499
500 #[inline]
501 pub fn is_full(&self) -> bool {
502 matches!(self, Self::Full { .. })
503 }
504
505 #[inline]
506 pub fn is_single_line(&self) -> bool {
507 matches!(self, Self::SingleLine { .. })
508 }
509
510 #[inline]
511 fn is_minimap(&self) -> bool {
512 matches!(self, Self::Minimap { .. })
513 }
514}
515
516#[derive(Copy, Clone, Debug)]
517pub enum SoftWrap {
518 /// Prefer not to wrap at all.
519 ///
520 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
521 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
522 GitDiff,
523 /// Prefer a single line generally, unless an overly long line is encountered.
524 None,
525 /// Soft wrap lines that exceed the editor width.
526 EditorWidth,
527 /// Soft wrap lines at the preferred line length.
528 Column(u32),
529 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
530 Bounded(u32),
531}
532
533#[derive(Clone)]
534pub struct EditorStyle {
535 pub background: Hsla,
536 pub border: Hsla,
537 pub local_player: PlayerColor,
538 pub text: TextStyle,
539 pub scrollbar_width: Pixels,
540 pub syntax: Arc<SyntaxTheme>,
541 pub status: StatusColors,
542 pub inlay_hints_style: HighlightStyle,
543 pub edit_prediction_styles: EditPredictionStyles,
544 pub unnecessary_code_fade: f32,
545 pub show_underlines: bool,
546}
547
548impl Default for EditorStyle {
549 fn default() -> Self {
550 Self {
551 background: Hsla::default(),
552 border: Hsla::default(),
553 local_player: PlayerColor::default(),
554 text: TextStyle::default(),
555 scrollbar_width: Pixels::default(),
556 syntax: Default::default(),
557 // HACK: Status colors don't have a real default.
558 // We should look into removing the status colors from the editor
559 // style and retrieve them directly from the theme.
560 status: StatusColors::dark(),
561 inlay_hints_style: HighlightStyle::default(),
562 edit_prediction_styles: EditPredictionStyles {
563 insertion: HighlightStyle::default(),
564 whitespace: HighlightStyle::default(),
565 },
566 unnecessary_code_fade: Default::default(),
567 show_underlines: true,
568 }
569 }
570}
571
572pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
573 let show_background = language_settings::language_settings(None, None, cx)
574 .inlay_hints
575 .show_background;
576
577 let mut style = cx.theme().syntax().get("hint");
578
579 if style.color.is_none() {
580 style.color = Some(cx.theme().status().hint);
581 }
582
583 if !show_background {
584 style.background_color = None;
585 return style;
586 }
587
588 if style.background_color.is_none() {
589 style.background_color = Some(cx.theme().status().hint_background);
590 }
591
592 style
593}
594
595pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
596 EditPredictionStyles {
597 insertion: HighlightStyle {
598 color: Some(cx.theme().status().predictive),
599 ..HighlightStyle::default()
600 },
601 whitespace: HighlightStyle {
602 background_color: Some(cx.theme().status().created_background),
603 ..HighlightStyle::default()
604 },
605 }
606}
607
608type CompletionId = usize;
609
610pub(crate) enum EditDisplayMode {
611 TabAccept,
612 DiffPopover,
613 Inline,
614}
615
616enum EditPrediction {
617 Edit {
618 edits: Vec<(Range<Anchor>, Arc<str>)>,
619 edit_preview: Option<EditPreview>,
620 display_mode: EditDisplayMode,
621 snapshot: BufferSnapshot,
622 },
623 /// Move to a specific location in the active editor
624 MoveWithin {
625 target: Anchor,
626 snapshot: BufferSnapshot,
627 },
628 /// Move to a specific location in a different editor (not the active one)
629 MoveOutside {
630 target: language::Anchor,
631 snapshot: BufferSnapshot,
632 },
633}
634
635struct EditPredictionState {
636 inlay_ids: Vec<InlayId>,
637 completion: EditPrediction,
638 completion_id: Option<SharedString>,
639 invalidation_range: Option<Range<Anchor>>,
640}
641
642enum EditPredictionSettings {
643 Disabled,
644 Enabled {
645 show_in_menu: bool,
646 preview_requires_modifier: bool,
647 },
648}
649
650enum EditPredictionHighlight {}
651
652#[derive(Debug, Clone)]
653struct InlineDiagnostic {
654 message: SharedString,
655 group_id: usize,
656 is_primary: bool,
657 start: Point,
658 severity: lsp::DiagnosticSeverity,
659}
660
661pub enum MenuEditPredictionsPolicy {
662 Never,
663 ByProvider,
664}
665
666pub enum EditPredictionPreview {
667 /// Modifier is not pressed
668 Inactive { released_too_fast: bool },
669 /// Modifier pressed
670 Active {
671 since: Instant,
672 previous_scroll_position: Option<ScrollAnchor>,
673 },
674}
675
676impl EditPredictionPreview {
677 pub fn released_too_fast(&self) -> bool {
678 match self {
679 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
680 EditPredictionPreview::Active { .. } => false,
681 }
682 }
683
684 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
685 if let EditPredictionPreview::Active {
686 previous_scroll_position,
687 ..
688 } = self
689 {
690 *previous_scroll_position = scroll_position;
691 }
692 }
693}
694
695pub struct ContextMenuOptions {
696 pub min_entries_visible: usize,
697 pub max_entries_visible: usize,
698 pub placement: Option<ContextMenuPlacement>,
699}
700
701#[derive(Debug, Clone, PartialEq, Eq)]
702pub enum ContextMenuPlacement {
703 Above,
704 Below,
705}
706
707#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
708struct EditorActionId(usize);
709
710impl EditorActionId {
711 pub fn post_inc(&mut self) -> Self {
712 let answer = self.0;
713
714 *self = Self(answer + 1);
715
716 Self(answer)
717 }
718}
719
720// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
721// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
722
723type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
724type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
725
726#[derive(Default)]
727struct ScrollbarMarkerState {
728 scrollbar_size: Size<Pixels>,
729 dirty: bool,
730 markers: Arc<[PaintQuad]>,
731 pending_refresh: Option<Task<Result<()>>>,
732}
733
734impl ScrollbarMarkerState {
735 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
736 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
737 }
738}
739
740#[derive(Clone, Copy, PartialEq, Eq)]
741pub enum MinimapVisibility {
742 Disabled,
743 Enabled {
744 /// The configuration currently present in the users settings.
745 setting_configuration: bool,
746 /// Whether to override the currently set visibility from the users setting.
747 toggle_override: bool,
748 },
749}
750
751impl MinimapVisibility {
752 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
753 if mode.is_full() {
754 Self::Enabled {
755 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
756 toggle_override: false,
757 }
758 } else {
759 Self::Disabled
760 }
761 }
762
763 fn hidden(&self) -> Self {
764 match *self {
765 Self::Enabled {
766 setting_configuration,
767 ..
768 } => Self::Enabled {
769 setting_configuration,
770 toggle_override: setting_configuration,
771 },
772 Self::Disabled => Self::Disabled,
773 }
774 }
775
776 fn disabled(&self) -> bool {
777 matches!(*self, Self::Disabled)
778 }
779
780 fn settings_visibility(&self) -> bool {
781 match *self {
782 Self::Enabled {
783 setting_configuration,
784 ..
785 } => setting_configuration,
786 _ => false,
787 }
788 }
789
790 fn visible(&self) -> bool {
791 match *self {
792 Self::Enabled {
793 setting_configuration,
794 toggle_override,
795 } => setting_configuration ^ toggle_override,
796 _ => false,
797 }
798 }
799
800 fn toggle_visibility(&self) -> Self {
801 match *self {
802 Self::Enabled {
803 toggle_override,
804 setting_configuration,
805 } => Self::Enabled {
806 setting_configuration,
807 toggle_override: !toggle_override,
808 },
809 Self::Disabled => Self::Disabled,
810 }
811 }
812}
813
814#[derive(Debug, Clone, Copy, PartialEq, Eq)]
815pub enum BufferSerialization {
816 All,
817 NonDirtyBuffers,
818}
819
820impl BufferSerialization {
821 fn new(restore_unsaved_buffers: bool) -> Self {
822 if restore_unsaved_buffers {
823 Self::All
824 } else {
825 Self::NonDirtyBuffers
826 }
827 }
828}
829
830#[derive(Clone, Debug)]
831struct RunnableTasks {
832 templates: Vec<(TaskSourceKind, TaskTemplate)>,
833 offset: multi_buffer::Anchor,
834 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
835 column: u32,
836 // Values of all named captures, including those starting with '_'
837 extra_variables: HashMap<String, String>,
838 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
839 context_range: Range<BufferOffset>,
840}
841
842impl RunnableTasks {
843 fn resolve<'a>(
844 &'a self,
845 cx: &'a task::TaskContext,
846 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
847 self.templates.iter().filter_map(|(kind, template)| {
848 template
849 .resolve_task(&kind.to_id_base(), cx)
850 .map(|task| (kind.clone(), task))
851 })
852 }
853}
854
855#[derive(Clone)]
856pub struct ResolvedTasks {
857 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
858 position: Anchor,
859}
860
861/// Addons allow storing per-editor state in other crates (e.g. Vim)
862pub trait Addon: 'static {
863 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
864
865 fn render_buffer_header_controls(
866 &self,
867 _: &ExcerptInfo,
868 _: &Window,
869 _: &App,
870 ) -> Option<AnyElement> {
871 None
872 }
873
874 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
875 None
876 }
877
878 fn to_any(&self) -> &dyn std::any::Any;
879
880 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
881 None
882 }
883}
884
885struct ChangeLocation {
886 current: Option<Vec<Anchor>>,
887 original: Vec<Anchor>,
888}
889impl ChangeLocation {
890 fn locations(&self) -> &[Anchor] {
891 self.current.as_ref().unwrap_or(&self.original)
892 }
893}
894
895/// A set of caret positions, registered when the editor was edited.
896pub struct ChangeList {
897 changes: Vec<ChangeLocation>,
898 /// Currently "selected" change.
899 position: Option<usize>,
900}
901
902impl ChangeList {
903 pub fn new() -> Self {
904 Self {
905 changes: Vec::new(),
906 position: None,
907 }
908 }
909
910 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
911 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
912 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
913 if self.changes.is_empty() {
914 return None;
915 }
916
917 let prev = self.position.unwrap_or(self.changes.len());
918 let next = if direction == Direction::Prev {
919 prev.saturating_sub(count)
920 } else {
921 (prev + count).min(self.changes.len() - 1)
922 };
923 self.position = Some(next);
924 self.changes.get(next).map(|change| change.locations())
925 }
926
927 /// Adds a new change to the list, resetting the change list position.
928 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
929 self.position.take();
930 if let Some(last) = self.changes.last_mut()
931 && group
932 {
933 last.current = Some(new_positions)
934 } else {
935 self.changes.push(ChangeLocation {
936 original: new_positions,
937 current: None,
938 });
939 }
940 }
941
942 pub fn last(&self) -> Option<&[Anchor]> {
943 self.changes.last().map(|change| change.locations())
944 }
945
946 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
947 self.changes.last().map(|change| change.original.as_slice())
948 }
949
950 pub fn invert_last_group(&mut self) {
951 if let Some(last) = self.changes.last_mut()
952 && let Some(current) = last.current.as_mut()
953 {
954 mem::swap(&mut last.original, current);
955 }
956 }
957}
958
959#[derive(Clone)]
960struct InlineBlamePopoverState {
961 scroll_handle: ScrollHandle,
962 commit_message: Option<ParsedCommitMessage>,
963 markdown: Entity<Markdown>,
964}
965
966struct InlineBlamePopover {
967 position: gpui::Point<Pixels>,
968 hide_task: Option<Task<()>>,
969 popover_bounds: Option<Bounds<Pixels>>,
970 popover_state: InlineBlamePopoverState,
971 keyboard_grace: bool,
972}
973
974enum SelectionDragState {
975 /// State when no drag related activity is detected.
976 None,
977 /// State when the mouse is down on a selection that is about to be dragged.
978 ReadyToDrag {
979 selection: Selection<Anchor>,
980 click_position: gpui::Point<Pixels>,
981 mouse_down_time: Instant,
982 },
983 /// State when the mouse is dragging the selection in the editor.
984 Dragging {
985 selection: Selection<Anchor>,
986 drop_cursor: Selection<Anchor>,
987 hide_drop_cursor: bool,
988 },
989}
990
991enum ColumnarSelectionState {
992 FromMouse {
993 selection_tail: Anchor,
994 display_point: Option<DisplayPoint>,
995 },
996 FromSelection {
997 selection_tail: Anchor,
998 },
999}
1000
1001/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1002/// a breakpoint on them.
1003#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1004struct PhantomBreakpointIndicator {
1005 display_row: DisplayRow,
1006 /// There's a small debounce between hovering over the line and showing the indicator.
1007 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1008 is_active: bool,
1009 collides_with_existing_breakpoint: bool,
1010}
1011
1012/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1013///
1014/// See the [module level documentation](self) for more information.
1015pub struct Editor {
1016 focus_handle: FocusHandle,
1017 last_focused_descendant: Option<WeakFocusHandle>,
1018 /// The text buffer being edited
1019 buffer: Entity<MultiBuffer>,
1020 /// Map of how text in the buffer should be displayed.
1021 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1022 pub display_map: Entity<DisplayMap>,
1023 placeholder_display_map: Option<Entity<DisplayMap>>,
1024 pub selections: SelectionsCollection,
1025 pub scroll_manager: ScrollManager,
1026 /// When inline assist editors are linked, they all render cursors because
1027 /// typing enters text into each of them, even the ones that aren't focused.
1028 pub(crate) show_cursor_when_unfocused: bool,
1029 columnar_selection_state: Option<ColumnarSelectionState>,
1030 add_selections_state: Option<AddSelectionsState>,
1031 select_next_state: Option<SelectNextState>,
1032 select_prev_state: Option<SelectNextState>,
1033 selection_history: SelectionHistory,
1034 defer_selection_effects: bool,
1035 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1036 autoclose_regions: Vec<AutocloseRegion>,
1037 snippet_stack: InvalidationStack<SnippetState>,
1038 select_syntax_node_history: SelectSyntaxNodeHistory,
1039 ime_transaction: Option<TransactionId>,
1040 pub diagnostics_max_severity: DiagnosticSeverity,
1041 active_diagnostics: ActiveDiagnostic,
1042 show_inline_diagnostics: bool,
1043 inline_diagnostics_update: Task<()>,
1044 inline_diagnostics_enabled: bool,
1045 diagnostics_enabled: bool,
1046 word_completions_enabled: bool,
1047 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1048 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1049 hard_wrap: Option<usize>,
1050 project: Option<Entity<Project>>,
1051 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1052 completion_provider: Option<Rc<dyn CompletionProvider>>,
1053 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1054 blink_manager: Entity<BlinkManager>,
1055 show_cursor_names: bool,
1056 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1057 pub show_local_selections: bool,
1058 mode: EditorMode,
1059 show_breadcrumbs: bool,
1060 show_gutter: bool,
1061 show_scrollbars: ScrollbarAxes,
1062 minimap_visibility: MinimapVisibility,
1063 offset_content: bool,
1064 disable_expand_excerpt_buttons: bool,
1065 show_line_numbers: Option<bool>,
1066 use_relative_line_numbers: Option<bool>,
1067 show_git_diff_gutter: Option<bool>,
1068 show_code_actions: Option<bool>,
1069 show_runnables: Option<bool>,
1070 show_breakpoints: Option<bool>,
1071 show_wrap_guides: Option<bool>,
1072 show_indent_guides: Option<bool>,
1073 highlight_order: usize,
1074 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1075 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1076 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1077 scrollbar_marker_state: ScrollbarMarkerState,
1078 active_indent_guides_state: ActiveIndentGuidesState,
1079 nav_history: Option<ItemNavHistory>,
1080 context_menu: RefCell<Option<CodeContextMenu>>,
1081 context_menu_options: Option<ContextMenuOptions>,
1082 mouse_context_menu: Option<MouseContextMenu>,
1083 completion_tasks: Vec<(CompletionId, Task<()>)>,
1084 inline_blame_popover: Option<InlineBlamePopover>,
1085 inline_blame_popover_show_task: Option<Task<()>>,
1086 signature_help_state: SignatureHelpState,
1087 auto_signature_help: Option<bool>,
1088 find_all_references_task_sources: Vec<Anchor>,
1089 next_completion_id: CompletionId,
1090 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1091 code_actions_task: Option<Task<Result<()>>>,
1092 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1093 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1094 document_highlights_task: Option<Task<()>>,
1095 linked_editing_range_task: Option<Task<Option<()>>>,
1096 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1097 pending_rename: Option<RenameState>,
1098 searchable: bool,
1099 cursor_shape: CursorShape,
1100 current_line_highlight: Option<CurrentLineHighlight>,
1101 pub collapse_matches: bool,
1102 autoindent_mode: Option<AutoindentMode>,
1103 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1104 input_enabled: bool,
1105 use_modal_editing: bool,
1106 read_only: bool,
1107 leader_id: Option<CollaboratorId>,
1108 remote_id: Option<ViewId>,
1109 pub hover_state: HoverState,
1110 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1111 gutter_hovered: bool,
1112 hovered_link_state: Option<HoveredLinkState>,
1113 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1114 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1115 active_edit_prediction: Option<EditPredictionState>,
1116 /// Used to prevent flickering as the user types while the menu is open
1117 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1118 edit_prediction_settings: EditPredictionSettings,
1119 edit_predictions_hidden_for_vim_mode: bool,
1120 show_edit_predictions_override: Option<bool>,
1121 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1122 edit_prediction_preview: EditPredictionPreview,
1123 edit_prediction_indent_conflict: bool,
1124 edit_prediction_requires_modifier_in_indent_conflict: bool,
1125 next_inlay_id: usize,
1126 next_color_inlay_id: usize,
1127 _subscriptions: Vec<Subscription>,
1128 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1129 gutter_dimensions: GutterDimensions,
1130 style: Option<EditorStyle>,
1131 text_style_refinement: Option<TextStyleRefinement>,
1132 next_editor_action_id: EditorActionId,
1133 editor_actions: Rc<
1134 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1135 >,
1136 use_autoclose: bool,
1137 use_auto_surround: bool,
1138 auto_replace_emoji_shortcode: bool,
1139 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1140 show_git_blame_gutter: bool,
1141 show_git_blame_inline: bool,
1142 show_git_blame_inline_delay_task: Option<Task<()>>,
1143 git_blame_inline_enabled: bool,
1144 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1145 buffer_serialization: Option<BufferSerialization>,
1146 show_selection_menu: Option<bool>,
1147 blame: Option<Entity<GitBlame>>,
1148 blame_subscription: Option<Subscription>,
1149 custom_context_menu: Option<
1150 Box<
1151 dyn 'static
1152 + Fn(
1153 &mut Self,
1154 DisplayPoint,
1155 &mut Window,
1156 &mut Context<Self>,
1157 ) -> Option<Entity<ui::ContextMenu>>,
1158 >,
1159 >,
1160 last_bounds: Option<Bounds<Pixels>>,
1161 last_position_map: Option<Rc<PositionMap>>,
1162 expect_bounds_change: Option<Bounds<Pixels>>,
1163 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1164 tasks_update_task: Option<Task<()>>,
1165 breakpoint_store: Option<Entity<BreakpointStore>>,
1166 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1167 hovered_diff_hunk_row: Option<DisplayRow>,
1168 pull_diagnostics_task: Task<()>,
1169 in_project_search: bool,
1170 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1171 breadcrumb_header: Option<String>,
1172 focused_block: Option<FocusedBlock>,
1173 next_scroll_position: NextScrollCursorCenterTopBottom,
1174 addons: HashMap<TypeId, Box<dyn Addon>>,
1175 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1176 load_diff_task: Option<Shared<Task<()>>>,
1177 /// Whether we are temporarily displaying a diff other than git's
1178 temporary_diff_override: bool,
1179 selection_mark_mode: bool,
1180 toggle_fold_multiple_buffers: Task<()>,
1181 _scroll_cursor_center_top_bottom_task: Task<()>,
1182 serialize_selections: Task<()>,
1183 serialize_folds: Task<()>,
1184 mouse_cursor_hidden: bool,
1185 minimap: Option<Entity<Self>>,
1186 hide_mouse_mode: HideMouseMode,
1187 pub change_list: ChangeList,
1188 inline_value_cache: InlineValueCache,
1189
1190 selection_drag_state: SelectionDragState,
1191 colors: Option<LspColorData>,
1192 post_scroll_update: Task<()>,
1193 refresh_colors_task: Task<()>,
1194 inlay_hints: Option<LspInlayHintData>,
1195 folding_newlines: Task<()>,
1196 select_next_is_case_sensitive: Option<bool>,
1197 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1198 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1199 accent_overrides: Vec<SharedString>,
1200 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1201}
1202
1203fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1204 if debounce_ms > 0 {
1205 Some(Duration::from_millis(debounce_ms))
1206 } else {
1207 None
1208 }
1209}
1210
1211#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1212enum NextScrollCursorCenterTopBottom {
1213 #[default]
1214 Center,
1215 Top,
1216 Bottom,
1217}
1218
1219impl NextScrollCursorCenterTopBottom {
1220 fn next(&self) -> Self {
1221 match self {
1222 Self::Center => Self::Top,
1223 Self::Top => Self::Bottom,
1224 Self::Bottom => Self::Center,
1225 }
1226 }
1227}
1228
1229#[derive(Clone)]
1230pub struct EditorSnapshot {
1231 pub mode: EditorMode,
1232 show_gutter: bool,
1233 show_line_numbers: Option<bool>,
1234 show_git_diff_gutter: Option<bool>,
1235 show_code_actions: Option<bool>,
1236 show_runnables: Option<bool>,
1237 show_breakpoints: Option<bool>,
1238 git_blame_gutter_max_author_length: Option<usize>,
1239 pub display_snapshot: DisplaySnapshot,
1240 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1241 is_focused: bool,
1242 scroll_anchor: ScrollAnchor,
1243 ongoing_scroll: OngoingScroll,
1244 current_line_highlight: CurrentLineHighlight,
1245 gutter_hovered: bool,
1246}
1247
1248#[derive(Default, Debug, Clone, Copy)]
1249pub struct GutterDimensions {
1250 pub left_padding: Pixels,
1251 pub right_padding: Pixels,
1252 pub width: Pixels,
1253 pub margin: Pixels,
1254 pub git_blame_entries_width: Option<Pixels>,
1255}
1256
1257impl GutterDimensions {
1258 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1259 Self {
1260 margin: Self::default_gutter_margin(font_id, font_size, cx),
1261 ..Default::default()
1262 }
1263 }
1264
1265 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1266 -cx.text_system().descent(font_id, font_size)
1267 }
1268 /// The full width of the space taken up by the gutter.
1269 pub fn full_width(&self) -> Pixels {
1270 self.margin + self.width
1271 }
1272
1273 /// The width of the space reserved for the fold indicators,
1274 /// use alongside 'justify_end' and `gutter_width` to
1275 /// right align content with the line numbers
1276 pub fn fold_area_width(&self) -> Pixels {
1277 self.margin + self.right_padding
1278 }
1279}
1280
1281struct CharacterDimensions {
1282 em_width: Pixels,
1283 em_advance: Pixels,
1284 line_height: Pixels,
1285}
1286
1287#[derive(Debug)]
1288pub struct RemoteSelection {
1289 pub replica_id: ReplicaId,
1290 pub selection: Selection<Anchor>,
1291 pub cursor_shape: CursorShape,
1292 pub collaborator_id: CollaboratorId,
1293 pub line_mode: bool,
1294 pub user_name: Option<SharedString>,
1295 pub color: PlayerColor,
1296}
1297
1298#[derive(Clone, Debug)]
1299struct SelectionHistoryEntry {
1300 selections: Arc<[Selection<Anchor>]>,
1301 select_next_state: Option<SelectNextState>,
1302 select_prev_state: Option<SelectNextState>,
1303 add_selections_state: Option<AddSelectionsState>,
1304}
1305
1306#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1307enum SelectionHistoryMode {
1308 #[default]
1309 Normal,
1310 Undoing,
1311 Redoing,
1312 Skipping,
1313}
1314
1315#[derive(Clone, PartialEq, Eq, Hash)]
1316struct HoveredCursor {
1317 replica_id: ReplicaId,
1318 selection_id: usize,
1319}
1320
1321#[derive(Debug)]
1322/// SelectionEffects controls the side-effects of updating the selection.
1323///
1324/// The default behaviour does "what you mostly want":
1325/// - it pushes to the nav history if the cursor moved by >10 lines
1326/// - it re-triggers completion requests
1327/// - it scrolls to fit
1328///
1329/// You might want to modify these behaviours. For example when doing a "jump"
1330/// like go to definition, we always want to add to nav history; but when scrolling
1331/// in vim mode we never do.
1332///
1333/// Similarly, you might want to disable scrolling if you don't want the viewport to
1334/// move.
1335#[derive(Clone)]
1336pub struct SelectionEffects {
1337 nav_history: Option<bool>,
1338 completions: bool,
1339 scroll: Option<Autoscroll>,
1340}
1341
1342impl Default for SelectionEffects {
1343 fn default() -> Self {
1344 Self {
1345 nav_history: None,
1346 completions: true,
1347 scroll: Some(Autoscroll::fit()),
1348 }
1349 }
1350}
1351impl SelectionEffects {
1352 pub fn scroll(scroll: Autoscroll) -> Self {
1353 Self {
1354 scroll: Some(scroll),
1355 ..Default::default()
1356 }
1357 }
1358
1359 pub fn no_scroll() -> Self {
1360 Self {
1361 scroll: None,
1362 ..Default::default()
1363 }
1364 }
1365
1366 pub fn completions(self, completions: bool) -> Self {
1367 Self {
1368 completions,
1369 ..self
1370 }
1371 }
1372
1373 pub fn nav_history(self, nav_history: bool) -> Self {
1374 Self {
1375 nav_history: Some(nav_history),
1376 ..self
1377 }
1378 }
1379}
1380
1381struct DeferredSelectionEffectsState {
1382 changed: bool,
1383 effects: SelectionEffects,
1384 old_cursor_position: Anchor,
1385 history_entry: SelectionHistoryEntry,
1386}
1387
1388#[derive(Default)]
1389struct SelectionHistory {
1390 #[allow(clippy::type_complexity)]
1391 selections_by_transaction:
1392 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1393 mode: SelectionHistoryMode,
1394 undo_stack: VecDeque<SelectionHistoryEntry>,
1395 redo_stack: VecDeque<SelectionHistoryEntry>,
1396}
1397
1398impl SelectionHistory {
1399 #[track_caller]
1400 fn insert_transaction(
1401 &mut self,
1402 transaction_id: TransactionId,
1403 selections: Arc<[Selection<Anchor>]>,
1404 ) {
1405 if selections.is_empty() {
1406 log::error!(
1407 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1408 std::panic::Location::caller()
1409 );
1410 return;
1411 }
1412 self.selections_by_transaction
1413 .insert(transaction_id, (selections, None));
1414 }
1415
1416 #[allow(clippy::type_complexity)]
1417 fn transaction(
1418 &self,
1419 transaction_id: TransactionId,
1420 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1421 self.selections_by_transaction.get(&transaction_id)
1422 }
1423
1424 #[allow(clippy::type_complexity)]
1425 fn transaction_mut(
1426 &mut self,
1427 transaction_id: TransactionId,
1428 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1429 self.selections_by_transaction.get_mut(&transaction_id)
1430 }
1431
1432 fn push(&mut self, entry: SelectionHistoryEntry) {
1433 if !entry.selections.is_empty() {
1434 match self.mode {
1435 SelectionHistoryMode::Normal => {
1436 self.push_undo(entry);
1437 self.redo_stack.clear();
1438 }
1439 SelectionHistoryMode::Undoing => self.push_redo(entry),
1440 SelectionHistoryMode::Redoing => self.push_undo(entry),
1441 SelectionHistoryMode::Skipping => {}
1442 }
1443 }
1444 }
1445
1446 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1447 if self
1448 .undo_stack
1449 .back()
1450 .is_none_or(|e| e.selections != entry.selections)
1451 {
1452 self.undo_stack.push_back(entry);
1453 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1454 self.undo_stack.pop_front();
1455 }
1456 }
1457 }
1458
1459 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1460 if self
1461 .redo_stack
1462 .back()
1463 .is_none_or(|e| e.selections != entry.selections)
1464 {
1465 self.redo_stack.push_back(entry);
1466 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1467 self.redo_stack.pop_front();
1468 }
1469 }
1470 }
1471}
1472
1473#[derive(Clone, Copy)]
1474pub struct RowHighlightOptions {
1475 pub autoscroll: bool,
1476 pub include_gutter: bool,
1477}
1478
1479impl Default for RowHighlightOptions {
1480 fn default() -> Self {
1481 Self {
1482 autoscroll: Default::default(),
1483 include_gutter: true,
1484 }
1485 }
1486}
1487
1488struct RowHighlight {
1489 index: usize,
1490 range: Range<Anchor>,
1491 color: Hsla,
1492 options: RowHighlightOptions,
1493 type_id: TypeId,
1494}
1495
1496#[derive(Clone, Debug)]
1497struct AddSelectionsState {
1498 groups: Vec<AddSelectionsGroup>,
1499}
1500
1501#[derive(Clone, Debug)]
1502struct AddSelectionsGroup {
1503 above: bool,
1504 stack: Vec<usize>,
1505}
1506
1507#[derive(Clone)]
1508struct SelectNextState {
1509 query: AhoCorasick,
1510 wordwise: bool,
1511 done: bool,
1512}
1513
1514impl std::fmt::Debug for SelectNextState {
1515 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1516 f.debug_struct(std::any::type_name::<Self>())
1517 .field("wordwise", &self.wordwise)
1518 .field("done", &self.done)
1519 .finish()
1520 }
1521}
1522
1523#[derive(Debug)]
1524struct AutocloseRegion {
1525 selection_id: usize,
1526 range: Range<Anchor>,
1527 pair: BracketPair,
1528}
1529
1530#[derive(Debug)]
1531struct SnippetState {
1532 ranges: Vec<Vec<Range<Anchor>>>,
1533 active_index: usize,
1534 choices: Vec<Option<Vec<String>>>,
1535}
1536
1537#[doc(hidden)]
1538pub struct RenameState {
1539 pub range: Range<Anchor>,
1540 pub old_name: Arc<str>,
1541 pub editor: Entity<Editor>,
1542 block_id: CustomBlockId,
1543}
1544
1545struct InvalidationStack<T>(Vec<T>);
1546
1547struct RegisteredEditPredictionProvider {
1548 provider: Arc<dyn EditPredictionProviderHandle>,
1549 _subscription: Subscription,
1550}
1551
1552#[derive(Debug, PartialEq, Eq)]
1553pub struct ActiveDiagnosticGroup {
1554 pub active_range: Range<Anchor>,
1555 pub active_message: String,
1556 pub group_id: usize,
1557 pub blocks: HashSet<CustomBlockId>,
1558}
1559
1560#[derive(Debug, PartialEq, Eq)]
1561
1562pub(crate) enum ActiveDiagnostic {
1563 None,
1564 All,
1565 Group(ActiveDiagnosticGroup),
1566}
1567
1568#[derive(Serialize, Deserialize, Clone, Debug)]
1569pub struct ClipboardSelection {
1570 /// The number of bytes in this selection.
1571 pub len: usize,
1572 /// Whether this was a full-line selection.
1573 pub is_entire_line: bool,
1574 /// The indentation of the first line when this content was originally copied.
1575 pub first_line_indent: u32,
1576}
1577
1578// selections, scroll behavior, was newest selection reversed
1579type SelectSyntaxNodeHistoryState = (
1580 Box<[Selection<MultiBufferOffset>]>,
1581 SelectSyntaxNodeScrollBehavior,
1582 bool,
1583);
1584
1585#[derive(Default)]
1586struct SelectSyntaxNodeHistory {
1587 stack: Vec<SelectSyntaxNodeHistoryState>,
1588 // disable temporarily to allow changing selections without losing the stack
1589 pub disable_clearing: bool,
1590}
1591
1592impl SelectSyntaxNodeHistory {
1593 pub fn try_clear(&mut self) {
1594 if !self.disable_clearing {
1595 self.stack.clear();
1596 }
1597 }
1598
1599 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1600 self.stack.push(selection);
1601 }
1602
1603 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1604 self.stack.pop()
1605 }
1606}
1607
1608enum SelectSyntaxNodeScrollBehavior {
1609 CursorTop,
1610 FitSelection,
1611 CursorBottom,
1612}
1613
1614#[derive(Debug)]
1615pub(crate) struct NavigationData {
1616 cursor_anchor: Anchor,
1617 cursor_position: Point,
1618 scroll_anchor: ScrollAnchor,
1619 scroll_top_row: u32,
1620}
1621
1622#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1623pub enum GotoDefinitionKind {
1624 Symbol,
1625 Declaration,
1626 Type,
1627 Implementation,
1628}
1629
1630pub enum FormatTarget {
1631 Buffers(HashSet<Entity<Buffer>>),
1632 Ranges(Vec<Range<MultiBufferPoint>>),
1633}
1634
1635pub(crate) struct FocusedBlock {
1636 id: BlockId,
1637 focus_handle: WeakFocusHandle,
1638}
1639
1640#[derive(Clone)]
1641enum JumpData {
1642 MultiBufferRow {
1643 row: MultiBufferRow,
1644 line_offset_from_top: u32,
1645 },
1646 MultiBufferPoint {
1647 excerpt_id: ExcerptId,
1648 position: Point,
1649 anchor: text::Anchor,
1650 line_offset_from_top: u32,
1651 },
1652}
1653
1654pub enum MultibufferSelectionMode {
1655 First,
1656 All,
1657}
1658
1659#[derive(Clone, Copy, Debug, Default)]
1660pub struct RewrapOptions {
1661 pub override_language_settings: bool,
1662 pub preserve_existing_whitespace: bool,
1663}
1664
1665impl Editor {
1666 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1667 let buffer = cx.new(|cx| Buffer::local("", cx));
1668 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1669 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1670 }
1671
1672 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1673 let buffer = cx.new(|cx| Buffer::local("", cx));
1674 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1675 Self::new(EditorMode::full(), buffer, None, window, cx)
1676 }
1677
1678 pub fn auto_height(
1679 min_lines: usize,
1680 max_lines: usize,
1681 window: &mut Window,
1682 cx: &mut Context<Self>,
1683 ) -> Self {
1684 let buffer = cx.new(|cx| Buffer::local("", cx));
1685 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1686 Self::new(
1687 EditorMode::AutoHeight {
1688 min_lines,
1689 max_lines: Some(max_lines),
1690 },
1691 buffer,
1692 None,
1693 window,
1694 cx,
1695 )
1696 }
1697
1698 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1699 /// The editor grows as tall as needed to fit its content.
1700 pub fn auto_height_unbounded(
1701 min_lines: usize,
1702 window: &mut Window,
1703 cx: &mut Context<Self>,
1704 ) -> Self {
1705 let buffer = cx.new(|cx| Buffer::local("", cx));
1706 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1707 Self::new(
1708 EditorMode::AutoHeight {
1709 min_lines,
1710 max_lines: None,
1711 },
1712 buffer,
1713 None,
1714 window,
1715 cx,
1716 )
1717 }
1718
1719 pub fn for_buffer(
1720 buffer: Entity<Buffer>,
1721 project: Option<Entity<Project>>,
1722 window: &mut Window,
1723 cx: &mut Context<Self>,
1724 ) -> Self {
1725 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1726 Self::new(EditorMode::full(), buffer, project, window, cx)
1727 }
1728
1729 pub fn for_multibuffer(
1730 buffer: Entity<MultiBuffer>,
1731 project: Option<Entity<Project>>,
1732 window: &mut Window,
1733 cx: &mut Context<Self>,
1734 ) -> Self {
1735 Self::new(EditorMode::full(), buffer, project, window, cx)
1736 }
1737
1738 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1739 let mut clone = Self::new(
1740 self.mode.clone(),
1741 self.buffer.clone(),
1742 self.project.clone(),
1743 window,
1744 cx,
1745 );
1746 self.display_map.update(cx, |display_map, cx| {
1747 let snapshot = display_map.snapshot(cx);
1748 clone.display_map.update(cx, |display_map, cx| {
1749 display_map.set_state(&snapshot, cx);
1750 });
1751 });
1752 clone.folds_did_change(cx);
1753 clone.selections.clone_state(&self.selections);
1754 clone.scroll_manager.clone_state(&self.scroll_manager);
1755 clone.searchable = self.searchable;
1756 clone.read_only = self.read_only;
1757 clone
1758 }
1759
1760 pub fn new(
1761 mode: EditorMode,
1762 buffer: Entity<MultiBuffer>,
1763 project: Option<Entity<Project>>,
1764 window: &mut Window,
1765 cx: &mut Context<Self>,
1766 ) -> Self {
1767 Editor::new_internal(mode, buffer, project, None, window, cx)
1768 }
1769
1770 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1771 let multi_buffer = self.buffer().read(cx);
1772 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1773 let multi_buffer_visible_start = self
1774 .scroll_manager
1775 .anchor()
1776 .anchor
1777 .to_point(&multi_buffer_snapshot);
1778 let max_row = multi_buffer_snapshot.max_point().row;
1779
1780 let start_row = (multi_buffer_visible_start.row).min(max_row);
1781 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1782
1783 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1784 let outline_items = buffer
1785 .outline_items_containing(
1786 Point::new(start_row, 0)..Point::new(end_row, 0),
1787 true,
1788 self.style().map(|style| style.syntax.as_ref()),
1789 )
1790 .into_iter()
1791 .map(|outline_item| OutlineItem {
1792 depth: outline_item.depth,
1793 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1794 source_range_for_text: Anchor::range_in_buffer(
1795 *excerpt_id,
1796 outline_item.source_range_for_text,
1797 ),
1798 text: outline_item.text,
1799 highlight_ranges: outline_item.highlight_ranges,
1800 name_ranges: outline_item.name_ranges,
1801 body_range: outline_item
1802 .body_range
1803 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1804 annotation_range: outline_item
1805 .annotation_range
1806 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1807 });
1808 return Some(outline_items.collect());
1809 }
1810
1811 None
1812 }
1813
1814 fn new_internal(
1815 mode: EditorMode,
1816 multi_buffer: Entity<MultiBuffer>,
1817 project: Option<Entity<Project>>,
1818 display_map: Option<Entity<DisplayMap>>,
1819 window: &mut Window,
1820 cx: &mut Context<Self>,
1821 ) -> Self {
1822 debug_assert!(
1823 display_map.is_none() || mode.is_minimap(),
1824 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1825 );
1826
1827 let full_mode = mode.is_full();
1828 let is_minimap = mode.is_minimap();
1829 let diagnostics_max_severity = if full_mode {
1830 EditorSettings::get_global(cx)
1831 .diagnostics_max_severity
1832 .unwrap_or(DiagnosticSeverity::Hint)
1833 } else {
1834 DiagnosticSeverity::Off
1835 };
1836 let style = window.text_style();
1837 let font_size = style.font_size.to_pixels(window.rem_size());
1838 let editor = cx.entity().downgrade();
1839 let fold_placeholder = FoldPlaceholder {
1840 constrain_width: false,
1841 render: Arc::new(move |fold_id, fold_range, cx| {
1842 let editor = editor.clone();
1843 div()
1844 .id(fold_id)
1845 .bg(cx.theme().colors().ghost_element_background)
1846 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1847 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1848 .rounded_xs()
1849 .size_full()
1850 .cursor_pointer()
1851 .child("⋯")
1852 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1853 .on_click(move |_, _window, cx| {
1854 editor
1855 .update(cx, |editor, cx| {
1856 editor.unfold_ranges(
1857 &[fold_range.start..fold_range.end],
1858 true,
1859 false,
1860 cx,
1861 );
1862 cx.stop_propagation();
1863 })
1864 .ok();
1865 })
1866 .into_any()
1867 }),
1868 merge_adjacent: true,
1869 ..FoldPlaceholder::default()
1870 };
1871 let display_map = display_map.unwrap_or_else(|| {
1872 cx.new(|cx| {
1873 DisplayMap::new(
1874 multi_buffer.clone(),
1875 style.font(),
1876 font_size,
1877 None,
1878 FILE_HEADER_HEIGHT,
1879 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1880 fold_placeholder,
1881 diagnostics_max_severity,
1882 cx,
1883 )
1884 })
1885 });
1886
1887 let selections = SelectionsCollection::new();
1888
1889 let blink_manager = cx.new(|cx| {
1890 let mut blink_manager = BlinkManager::new(
1891 CURSOR_BLINK_INTERVAL,
1892 |cx| EditorSettings::get_global(cx).cursor_blink,
1893 cx,
1894 );
1895 if is_minimap {
1896 blink_manager.disable(cx);
1897 }
1898 blink_manager
1899 });
1900
1901 let soft_wrap_mode_override =
1902 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1903
1904 let mut project_subscriptions = Vec::new();
1905 if full_mode && let Some(project) = project.as_ref() {
1906 project_subscriptions.push(cx.subscribe_in(
1907 project,
1908 window,
1909 |editor, _, event, window, cx| match event {
1910 project::Event::RefreshCodeLens => {
1911 // we always query lens with actions, without storing them, always refreshing them
1912 }
1913 project::Event::RefreshInlayHints {
1914 server_id,
1915 request_id,
1916 } => {
1917 editor.refresh_inlay_hints(
1918 InlayHintRefreshReason::RefreshRequested {
1919 server_id: *server_id,
1920 request_id: *request_id,
1921 },
1922 cx,
1923 );
1924 }
1925 project::Event::LanguageServerRemoved(..) => {
1926 if editor.tasks_update_task.is_none() {
1927 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1928 }
1929 editor.registered_buffers.clear();
1930 editor.register_visible_buffers(cx);
1931 }
1932 project::Event::LanguageServerAdded(..) => {
1933 if editor.tasks_update_task.is_none() {
1934 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1935 }
1936 }
1937 project::Event::SnippetEdit(id, snippet_edits) => {
1938 // todo(lw): Non singletons
1939 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
1940 let snapshot = buffer.read(cx).snapshot();
1941 let focus_handle = editor.focus_handle(cx);
1942 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
1943 for (range, snippet) in snippet_edits {
1944 let buffer_range =
1945 language::range_from_lsp(*range).to_offset(&snapshot);
1946 editor
1947 .insert_snippet(
1948 &[MultiBufferOffset(buffer_range.start)
1949 ..MultiBufferOffset(buffer_range.end)],
1950 snippet.clone(),
1951 window,
1952 cx,
1953 )
1954 .ok();
1955 }
1956 }
1957 }
1958 }
1959 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1960 let buffer_id = *buffer_id;
1961 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1962 editor.register_buffer(buffer_id, cx);
1963 editor.update_lsp_data(Some(buffer_id), window, cx);
1964 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1965 refresh_linked_ranges(editor, window, cx);
1966 editor.refresh_code_actions(window, cx);
1967 editor.refresh_document_highlights(cx);
1968 }
1969 }
1970
1971 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1972 let Some(workspace) = editor.workspace() else {
1973 return;
1974 };
1975 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1976 else {
1977 return;
1978 };
1979
1980 if active_editor.entity_id() == cx.entity_id() {
1981 let entity_id = cx.entity_id();
1982 workspace.update(cx, |this, cx| {
1983 this.panes_mut()
1984 .iter_mut()
1985 .filter(|pane| pane.entity_id() != entity_id)
1986 .for_each(|p| {
1987 p.update(cx, |pane, _| {
1988 pane.nav_history_mut().rename_item(
1989 entity_id,
1990 project_path.clone(),
1991 abs_path.clone().into(),
1992 );
1993 })
1994 });
1995 });
1996 let edited_buffers_already_open = {
1997 let other_editors: Vec<Entity<Editor>> = workspace
1998 .read(cx)
1999 .panes()
2000 .iter()
2001 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
2002 .filter(|editor| editor.entity_id() != cx.entity_id())
2003 .collect();
2004
2005 transaction.0.keys().all(|buffer| {
2006 other_editors.iter().any(|editor| {
2007 let multi_buffer = editor.read(cx).buffer();
2008 multi_buffer.read(cx).is_singleton()
2009 && multi_buffer.read(cx).as_singleton().map_or(
2010 false,
2011 |singleton| {
2012 singleton.entity_id() == buffer.entity_id()
2013 },
2014 )
2015 })
2016 })
2017 };
2018 if !edited_buffers_already_open {
2019 let workspace = workspace.downgrade();
2020 let transaction = transaction.clone();
2021 cx.defer_in(window, move |_, window, cx| {
2022 cx.spawn_in(window, async move |editor, cx| {
2023 Self::open_project_transaction(
2024 &editor,
2025 workspace,
2026 transaction,
2027 "Rename".to_string(),
2028 cx,
2029 )
2030 .await
2031 .ok()
2032 })
2033 .detach();
2034 });
2035 }
2036 }
2037 }
2038
2039 _ => {}
2040 },
2041 ));
2042 if let Some(task_inventory) = project
2043 .read(cx)
2044 .task_store()
2045 .read(cx)
2046 .task_inventory()
2047 .cloned()
2048 {
2049 project_subscriptions.push(cx.observe_in(
2050 &task_inventory,
2051 window,
2052 |editor, _, window, cx| {
2053 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2054 },
2055 ));
2056 };
2057
2058 project_subscriptions.push(cx.subscribe_in(
2059 &project.read(cx).breakpoint_store(),
2060 window,
2061 |editor, _, event, window, cx| match event {
2062 BreakpointStoreEvent::ClearDebugLines => {
2063 editor.clear_row_highlights::<ActiveDebugLine>();
2064 editor.refresh_inline_values(cx);
2065 }
2066 BreakpointStoreEvent::SetDebugLine => {
2067 if editor.go_to_active_debug_line(window, cx) {
2068 cx.stop_propagation();
2069 }
2070
2071 editor.refresh_inline_values(cx);
2072 }
2073 _ => {}
2074 },
2075 ));
2076 let git_store = project.read(cx).git_store().clone();
2077 let project = project.clone();
2078 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2079 if let GitStoreEvent::RepositoryAdded = event {
2080 this.load_diff_task = Some(
2081 update_uncommitted_diff_for_buffer(
2082 cx.entity(),
2083 &project,
2084 this.buffer.read(cx).all_buffers(),
2085 this.buffer.clone(),
2086 cx,
2087 )
2088 .shared(),
2089 );
2090 }
2091 }));
2092 }
2093
2094 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2095
2096 let inlay_hint_settings =
2097 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2098 let focus_handle = cx.focus_handle();
2099 if !is_minimap {
2100 cx.on_focus(&focus_handle, window, Self::handle_focus)
2101 .detach();
2102 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2103 .detach();
2104 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2105 .detach();
2106 cx.on_blur(&focus_handle, window, Self::handle_blur)
2107 .detach();
2108 cx.observe_pending_input(window, Self::observe_pending_input)
2109 .detach();
2110 }
2111
2112 let show_indent_guides =
2113 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2114 Some(false)
2115 } else {
2116 None
2117 };
2118
2119 let breakpoint_store = match (&mode, project.as_ref()) {
2120 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2121 _ => None,
2122 };
2123
2124 let mut code_action_providers = Vec::new();
2125 let mut load_uncommitted_diff = None;
2126 if let Some(project) = project.clone() {
2127 load_uncommitted_diff = Some(
2128 update_uncommitted_diff_for_buffer(
2129 cx.entity(),
2130 &project,
2131 multi_buffer.read(cx).all_buffers(),
2132 multi_buffer.clone(),
2133 cx,
2134 )
2135 .shared(),
2136 );
2137 code_action_providers.push(Rc::new(project) as Rc<_>);
2138 }
2139
2140 let mut editor = Self {
2141 focus_handle,
2142 show_cursor_when_unfocused: false,
2143 last_focused_descendant: None,
2144 buffer: multi_buffer.clone(),
2145 display_map: display_map.clone(),
2146 placeholder_display_map: None,
2147 selections,
2148 scroll_manager: ScrollManager::new(cx),
2149 columnar_selection_state: None,
2150 add_selections_state: None,
2151 select_next_state: None,
2152 select_prev_state: None,
2153 selection_history: SelectionHistory::default(),
2154 defer_selection_effects: false,
2155 deferred_selection_effects_state: None,
2156 autoclose_regions: Vec::new(),
2157 snippet_stack: InvalidationStack::default(),
2158 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2159 ime_transaction: None,
2160 active_diagnostics: ActiveDiagnostic::None,
2161 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2162 inline_diagnostics_update: Task::ready(()),
2163 inline_diagnostics: Vec::new(),
2164 soft_wrap_mode_override,
2165 diagnostics_max_severity,
2166 hard_wrap: None,
2167 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2168 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2169 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2170 project,
2171 blink_manager: blink_manager.clone(),
2172 show_local_selections: true,
2173 show_scrollbars: ScrollbarAxes {
2174 horizontal: full_mode,
2175 vertical: full_mode,
2176 },
2177 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2178 offset_content: !matches!(mode, EditorMode::SingleLine),
2179 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2180 show_gutter: full_mode,
2181 show_line_numbers: (!full_mode).then_some(false),
2182 use_relative_line_numbers: None,
2183 disable_expand_excerpt_buttons: !full_mode,
2184 show_git_diff_gutter: None,
2185 show_code_actions: None,
2186 show_runnables: None,
2187 show_breakpoints: None,
2188 show_wrap_guides: None,
2189 show_indent_guides,
2190 highlight_order: 0,
2191 highlighted_rows: HashMap::default(),
2192 background_highlights: HashMap::default(),
2193 gutter_highlights: HashMap::default(),
2194 scrollbar_marker_state: ScrollbarMarkerState::default(),
2195 active_indent_guides_state: ActiveIndentGuidesState::default(),
2196 nav_history: None,
2197 context_menu: RefCell::new(None),
2198 context_menu_options: None,
2199 mouse_context_menu: None,
2200 completion_tasks: Vec::new(),
2201 inline_blame_popover: None,
2202 inline_blame_popover_show_task: None,
2203 signature_help_state: SignatureHelpState::default(),
2204 auto_signature_help: None,
2205 find_all_references_task_sources: Vec::new(),
2206 next_completion_id: 0,
2207 next_inlay_id: 0,
2208 code_action_providers,
2209 available_code_actions: None,
2210 code_actions_task: None,
2211 quick_selection_highlight_task: None,
2212 debounced_selection_highlight_task: None,
2213 document_highlights_task: None,
2214 linked_editing_range_task: None,
2215 pending_rename: None,
2216 searchable: !is_minimap,
2217 cursor_shape: EditorSettings::get_global(cx)
2218 .cursor_shape
2219 .unwrap_or_default(),
2220 current_line_highlight: None,
2221 autoindent_mode: Some(AutoindentMode::EachLine),
2222 collapse_matches: false,
2223 workspace: None,
2224 input_enabled: !is_minimap,
2225 use_modal_editing: full_mode,
2226 read_only: is_minimap,
2227 use_autoclose: true,
2228 use_auto_surround: true,
2229 auto_replace_emoji_shortcode: false,
2230 jsx_tag_auto_close_enabled_in_any_buffer: false,
2231 leader_id: None,
2232 remote_id: None,
2233 hover_state: HoverState::default(),
2234 pending_mouse_down: None,
2235 hovered_link_state: None,
2236 edit_prediction_provider: None,
2237 active_edit_prediction: None,
2238 stale_edit_prediction_in_menu: None,
2239 edit_prediction_preview: EditPredictionPreview::Inactive {
2240 released_too_fast: false,
2241 },
2242 inline_diagnostics_enabled: full_mode,
2243 diagnostics_enabled: full_mode,
2244 word_completions_enabled: full_mode,
2245 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2246 gutter_hovered: false,
2247 pixel_position_of_newest_cursor: None,
2248 last_bounds: None,
2249 last_position_map: None,
2250 expect_bounds_change: None,
2251 gutter_dimensions: GutterDimensions::default(),
2252 style: None,
2253 show_cursor_names: false,
2254 hovered_cursors: HashMap::default(),
2255 next_editor_action_id: EditorActionId::default(),
2256 editor_actions: Rc::default(),
2257 edit_predictions_hidden_for_vim_mode: false,
2258 show_edit_predictions_override: None,
2259 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2260 edit_prediction_settings: EditPredictionSettings::Disabled,
2261 edit_prediction_indent_conflict: false,
2262 edit_prediction_requires_modifier_in_indent_conflict: true,
2263 custom_context_menu: None,
2264 show_git_blame_gutter: false,
2265 show_git_blame_inline: false,
2266 show_selection_menu: None,
2267 show_git_blame_inline_delay_task: None,
2268 git_blame_inline_enabled: full_mode
2269 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2270 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2271 buffer_serialization: is_minimap.not().then(|| {
2272 BufferSerialization::new(
2273 ProjectSettings::get_global(cx)
2274 .session
2275 .restore_unsaved_buffers,
2276 )
2277 }),
2278 blame: None,
2279 blame_subscription: None,
2280 tasks: BTreeMap::default(),
2281
2282 breakpoint_store,
2283 gutter_breakpoint_indicator: (None, None),
2284 hovered_diff_hunk_row: None,
2285 _subscriptions: (!is_minimap)
2286 .then(|| {
2287 vec![
2288 cx.observe(&multi_buffer, Self::on_buffer_changed),
2289 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2290 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2291 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2292 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2293 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2294 cx.observe_window_activation(window, |editor, window, cx| {
2295 let active = window.is_window_active();
2296 editor.blink_manager.update(cx, |blink_manager, cx| {
2297 if active {
2298 blink_manager.enable(cx);
2299 } else {
2300 blink_manager.disable(cx);
2301 }
2302 });
2303 if active {
2304 editor.show_mouse_cursor(cx);
2305 }
2306 }),
2307 ]
2308 })
2309 .unwrap_or_default(),
2310 tasks_update_task: None,
2311 pull_diagnostics_task: Task::ready(()),
2312 colors: None,
2313 refresh_colors_task: Task::ready(()),
2314 inlay_hints: None,
2315 next_color_inlay_id: 0,
2316 post_scroll_update: Task::ready(()),
2317 linked_edit_ranges: Default::default(),
2318 in_project_search: false,
2319 previous_search_ranges: None,
2320 breadcrumb_header: None,
2321 focused_block: None,
2322 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2323 addons: HashMap::default(),
2324 registered_buffers: HashMap::default(),
2325 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2326 selection_mark_mode: false,
2327 toggle_fold_multiple_buffers: Task::ready(()),
2328 serialize_selections: Task::ready(()),
2329 serialize_folds: Task::ready(()),
2330 text_style_refinement: None,
2331 load_diff_task: load_uncommitted_diff,
2332 temporary_diff_override: false,
2333 mouse_cursor_hidden: false,
2334 minimap: None,
2335 hide_mouse_mode: EditorSettings::get_global(cx)
2336 .hide_mouse
2337 .unwrap_or_default(),
2338 change_list: ChangeList::new(),
2339 mode,
2340 selection_drag_state: SelectionDragState::None,
2341 folding_newlines: Task::ready(()),
2342 lookup_key: None,
2343 select_next_is_case_sensitive: None,
2344 applicable_language_settings: HashMap::default(),
2345 accent_overrides: Vec::new(),
2346 fetched_tree_sitter_chunks: HashMap::default(),
2347 };
2348
2349 if is_minimap {
2350 return editor;
2351 }
2352
2353 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2354 editor.accent_overrides = editor.fetch_accent_overrides(cx);
2355
2356 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2357 editor
2358 ._subscriptions
2359 .push(cx.observe(breakpoints, |_, _, cx| {
2360 cx.notify();
2361 }));
2362 }
2363 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2364 editor._subscriptions.extend(project_subscriptions);
2365
2366 editor._subscriptions.push(cx.subscribe_in(
2367 &cx.entity(),
2368 window,
2369 |editor, _, e: &EditorEvent, window, cx| match e {
2370 EditorEvent::ScrollPositionChanged { local, .. } => {
2371 if *local {
2372 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2373 editor.inline_blame_popover.take();
2374 let new_anchor = editor.scroll_manager.anchor();
2375 let snapshot = editor.snapshot(window, cx);
2376 editor.update_restoration_data(cx, move |data| {
2377 data.scroll_position = (
2378 new_anchor.top_row(snapshot.buffer_snapshot()),
2379 new_anchor.offset,
2380 );
2381 });
2382
2383 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2384 cx.background_executor()
2385 .timer(Duration::from_millis(50))
2386 .await;
2387 editor
2388 .update_in(cx, |editor, window, cx| {
2389 editor.register_visible_buffers(cx);
2390 editor.refresh_colors_for_visible_range(None, window, cx);
2391 editor.refresh_inlay_hints(
2392 InlayHintRefreshReason::NewLinesShown,
2393 cx,
2394 );
2395 editor.colorize_brackets(false, cx);
2396 })
2397 .ok();
2398 });
2399 }
2400 }
2401 EditorEvent::Edited { .. } => {
2402 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2403 .map(|vim_mode| vim_mode.0)
2404 .unwrap_or(false);
2405 if !vim_mode {
2406 let display_map = editor.display_snapshot(cx);
2407 let selections = editor.selections.all_adjusted_display(&display_map);
2408 let pop_state = editor
2409 .change_list
2410 .last()
2411 .map(|previous| {
2412 previous.len() == selections.len()
2413 && previous.iter().enumerate().all(|(ix, p)| {
2414 p.to_display_point(&display_map).row()
2415 == selections[ix].head().row()
2416 })
2417 })
2418 .unwrap_or(false);
2419 let new_positions = selections
2420 .into_iter()
2421 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2422 .collect();
2423 editor
2424 .change_list
2425 .push_to_change_list(pop_state, new_positions);
2426 }
2427 }
2428 _ => (),
2429 },
2430 ));
2431
2432 if let Some(dap_store) = editor
2433 .project
2434 .as_ref()
2435 .map(|project| project.read(cx).dap_store())
2436 {
2437 let weak_editor = cx.weak_entity();
2438
2439 editor
2440 ._subscriptions
2441 .push(
2442 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2443 let session_entity = cx.entity();
2444 weak_editor
2445 .update(cx, |editor, cx| {
2446 editor._subscriptions.push(
2447 cx.subscribe(&session_entity, Self::on_debug_session_event),
2448 );
2449 })
2450 .ok();
2451 }),
2452 );
2453
2454 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2455 editor
2456 ._subscriptions
2457 .push(cx.subscribe(&session, Self::on_debug_session_event));
2458 }
2459 }
2460
2461 // skip adding the initial selection to selection history
2462 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2463 editor.end_selection(window, cx);
2464 editor.selection_history.mode = SelectionHistoryMode::Normal;
2465
2466 editor.scroll_manager.show_scrollbars(window, cx);
2467 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2468
2469 if full_mode {
2470 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2471 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2472
2473 if editor.git_blame_inline_enabled {
2474 editor.start_git_blame_inline(false, window, cx);
2475 }
2476
2477 editor.go_to_active_debug_line(window, cx);
2478
2479 editor.minimap =
2480 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2481 editor.colors = Some(LspColorData::new(cx));
2482 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2483
2484 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2485 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2486 }
2487 editor.update_lsp_data(None, window, cx);
2488 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2489 }
2490
2491 editor
2492 }
2493
2494 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2495 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2496 }
2497
2498 pub fn deploy_mouse_context_menu(
2499 &mut self,
2500 position: gpui::Point<Pixels>,
2501 context_menu: Entity<ContextMenu>,
2502 window: &mut Window,
2503 cx: &mut Context<Self>,
2504 ) {
2505 self.mouse_context_menu = Some(MouseContextMenu::new(
2506 self,
2507 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2508 context_menu,
2509 window,
2510 cx,
2511 ));
2512 }
2513
2514 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2515 self.mouse_context_menu
2516 .as_ref()
2517 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2518 }
2519
2520 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2521 if self
2522 .selections
2523 .pending_anchor()
2524 .is_some_and(|pending_selection| {
2525 let snapshot = self.buffer().read(cx).snapshot(cx);
2526 pending_selection.range().includes(range, &snapshot)
2527 })
2528 {
2529 return true;
2530 }
2531
2532 self.selections
2533 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2534 .into_iter()
2535 .any(|selection| {
2536 // This is needed to cover a corner case, if we just check for an existing
2537 // selection in the fold range, having a cursor at the start of the fold
2538 // marks it as selected. Non-empty selections don't cause this.
2539 let length = selection.end - selection.start;
2540 length > 0
2541 })
2542 }
2543
2544 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2545 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2546 }
2547
2548 fn key_context_internal(
2549 &self,
2550 has_active_edit_prediction: bool,
2551 window: &mut Window,
2552 cx: &mut App,
2553 ) -> KeyContext {
2554 let mut key_context = KeyContext::new_with_defaults();
2555 key_context.add("Editor");
2556 let mode = match self.mode {
2557 EditorMode::SingleLine => "single_line",
2558 EditorMode::AutoHeight { .. } => "auto_height",
2559 EditorMode::Minimap { .. } => "minimap",
2560 EditorMode::Full { .. } => "full",
2561 };
2562
2563 if EditorSettings::jupyter_enabled(cx) {
2564 key_context.add("jupyter");
2565 }
2566
2567 key_context.set("mode", mode);
2568 if self.pending_rename.is_some() {
2569 key_context.add("renaming");
2570 }
2571
2572 if let Some(snippet_stack) = self.snippet_stack.last() {
2573 key_context.add("in_snippet");
2574
2575 if snippet_stack.active_index > 0 {
2576 key_context.add("has_previous_tabstop");
2577 }
2578
2579 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2580 key_context.add("has_next_tabstop");
2581 }
2582 }
2583
2584 match self.context_menu.borrow().as_ref() {
2585 Some(CodeContextMenu::Completions(menu)) => {
2586 if menu.visible() {
2587 key_context.add("menu");
2588 key_context.add("showing_completions");
2589 }
2590 }
2591 Some(CodeContextMenu::CodeActions(menu)) => {
2592 if menu.visible() {
2593 key_context.add("menu");
2594 key_context.add("showing_code_actions")
2595 }
2596 }
2597 None => {}
2598 }
2599
2600 if self.signature_help_state.has_multiple_signatures() {
2601 key_context.add("showing_signature_help");
2602 }
2603
2604 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2605 if !self.focus_handle(cx).contains_focused(window, cx)
2606 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2607 {
2608 for addon in self.addons.values() {
2609 addon.extend_key_context(&mut key_context, cx)
2610 }
2611 }
2612
2613 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2614 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2615 Some(
2616 file.full_path(cx)
2617 .extension()?
2618 .to_string_lossy()
2619 .into_owned(),
2620 )
2621 }) {
2622 key_context.set("extension", extension);
2623 }
2624 } else {
2625 key_context.add("multibuffer");
2626 }
2627
2628 if has_active_edit_prediction {
2629 if self.edit_prediction_in_conflict() {
2630 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2631 } else {
2632 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2633 key_context.add("copilot_suggestion");
2634 }
2635 }
2636
2637 if self.selection_mark_mode {
2638 key_context.add("selection_mode");
2639 }
2640
2641 let disjoint = self.selections.disjoint_anchors();
2642 let snapshot = self.snapshot(window, cx);
2643 let snapshot = snapshot.buffer_snapshot();
2644 if self.mode == EditorMode::SingleLine
2645 && let [selection] = disjoint
2646 && selection.start == selection.end
2647 && selection.end.to_offset(snapshot) == snapshot.len()
2648 {
2649 key_context.add("end_of_input");
2650 }
2651
2652 if self.has_any_expanded_diff_hunks(cx) {
2653 key_context.add("diffs_expanded");
2654 }
2655
2656 key_context
2657 }
2658
2659 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2660 self.last_bounds.as_ref()
2661 }
2662
2663 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2664 if self.mouse_cursor_hidden {
2665 self.mouse_cursor_hidden = false;
2666 cx.notify();
2667 }
2668 }
2669
2670 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2671 let hide_mouse_cursor = match origin {
2672 HideMouseCursorOrigin::TypingAction => {
2673 matches!(
2674 self.hide_mouse_mode,
2675 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2676 )
2677 }
2678 HideMouseCursorOrigin::MovementAction => {
2679 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2680 }
2681 };
2682 if self.mouse_cursor_hidden != hide_mouse_cursor {
2683 self.mouse_cursor_hidden = hide_mouse_cursor;
2684 cx.notify();
2685 }
2686 }
2687
2688 pub fn edit_prediction_in_conflict(&self) -> bool {
2689 if !self.show_edit_predictions_in_menu() {
2690 return false;
2691 }
2692
2693 let showing_completions = self
2694 .context_menu
2695 .borrow()
2696 .as_ref()
2697 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2698
2699 showing_completions
2700 || self.edit_prediction_requires_modifier()
2701 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2702 // bindings to insert tab characters.
2703 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2704 }
2705
2706 pub fn accept_edit_prediction_keybind(
2707 &self,
2708 accept_partial: bool,
2709 window: &mut Window,
2710 cx: &mut App,
2711 ) -> AcceptEditPredictionBinding {
2712 let key_context = self.key_context_internal(true, window, cx);
2713 let in_conflict = self.edit_prediction_in_conflict();
2714
2715 let bindings = if accept_partial {
2716 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2717 } else {
2718 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2719 };
2720
2721 // TODO: if the binding contains multiple keystrokes, display all of them, not
2722 // just the first one.
2723 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2724 !in_conflict
2725 || binding
2726 .keystrokes()
2727 .first()
2728 .is_some_and(|keystroke| keystroke.modifiers().modified())
2729 }))
2730 }
2731
2732 pub fn new_file(
2733 workspace: &mut Workspace,
2734 _: &workspace::NewFile,
2735 window: &mut Window,
2736 cx: &mut Context<Workspace>,
2737 ) {
2738 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2739 "Failed to create buffer",
2740 window,
2741 cx,
2742 |e, _, _| match e.error_code() {
2743 ErrorCode::RemoteUpgradeRequired => Some(format!(
2744 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2745 e.error_tag("required").unwrap_or("the latest version")
2746 )),
2747 _ => None,
2748 },
2749 );
2750 }
2751
2752 pub fn new_in_workspace(
2753 workspace: &mut Workspace,
2754 window: &mut Window,
2755 cx: &mut Context<Workspace>,
2756 ) -> Task<Result<Entity<Editor>>> {
2757 let project = workspace.project().clone();
2758 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2759
2760 cx.spawn_in(window, async move |workspace, cx| {
2761 let buffer = create.await?;
2762 workspace.update_in(cx, |workspace, window, cx| {
2763 let editor =
2764 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2765 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2766 editor
2767 })
2768 })
2769 }
2770
2771 fn new_file_vertical(
2772 workspace: &mut Workspace,
2773 _: &workspace::NewFileSplitVertical,
2774 window: &mut Window,
2775 cx: &mut Context<Workspace>,
2776 ) {
2777 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2778 }
2779
2780 fn new_file_horizontal(
2781 workspace: &mut Workspace,
2782 _: &workspace::NewFileSplitHorizontal,
2783 window: &mut Window,
2784 cx: &mut Context<Workspace>,
2785 ) {
2786 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2787 }
2788
2789 fn new_file_split(
2790 workspace: &mut Workspace,
2791 action: &workspace::NewFileSplit,
2792 window: &mut Window,
2793 cx: &mut Context<Workspace>,
2794 ) {
2795 Self::new_file_in_direction(workspace, action.0, window, cx)
2796 }
2797
2798 fn new_file_in_direction(
2799 workspace: &mut Workspace,
2800 direction: SplitDirection,
2801 window: &mut Window,
2802 cx: &mut Context<Workspace>,
2803 ) {
2804 let project = workspace.project().clone();
2805 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2806
2807 cx.spawn_in(window, async move |workspace, cx| {
2808 let buffer = create.await?;
2809 workspace.update_in(cx, move |workspace, window, cx| {
2810 workspace.split_item(
2811 direction,
2812 Box::new(
2813 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2814 ),
2815 window,
2816 cx,
2817 )
2818 })?;
2819 anyhow::Ok(())
2820 })
2821 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2822 match e.error_code() {
2823 ErrorCode::RemoteUpgradeRequired => Some(format!(
2824 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2825 e.error_tag("required").unwrap_or("the latest version")
2826 )),
2827 _ => None,
2828 }
2829 });
2830 }
2831
2832 pub fn leader_id(&self) -> Option<CollaboratorId> {
2833 self.leader_id
2834 }
2835
2836 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2837 &self.buffer
2838 }
2839
2840 pub fn project(&self) -> Option<&Entity<Project>> {
2841 self.project.as_ref()
2842 }
2843
2844 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2845 self.workspace.as_ref()?.0.upgrade()
2846 }
2847
2848 /// Returns the workspace serialization ID if this editor should be serialized.
2849 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2850 self.workspace
2851 .as_ref()
2852 .filter(|_| self.should_serialize_buffer())
2853 .and_then(|workspace| workspace.1)
2854 }
2855
2856 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2857 self.buffer().read(cx).title(cx)
2858 }
2859
2860 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2861 let git_blame_gutter_max_author_length = self
2862 .render_git_blame_gutter(cx)
2863 .then(|| {
2864 if let Some(blame) = self.blame.as_ref() {
2865 let max_author_length =
2866 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2867 Some(max_author_length)
2868 } else {
2869 None
2870 }
2871 })
2872 .flatten();
2873
2874 EditorSnapshot {
2875 mode: self.mode.clone(),
2876 show_gutter: self.show_gutter,
2877 show_line_numbers: self.show_line_numbers,
2878 show_git_diff_gutter: self.show_git_diff_gutter,
2879 show_code_actions: self.show_code_actions,
2880 show_runnables: self.show_runnables,
2881 show_breakpoints: self.show_breakpoints,
2882 git_blame_gutter_max_author_length,
2883 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2884 placeholder_display_snapshot: self
2885 .placeholder_display_map
2886 .as_ref()
2887 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2888 scroll_anchor: self.scroll_manager.anchor(),
2889 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2890 is_focused: self.focus_handle.is_focused(window),
2891 current_line_highlight: self
2892 .current_line_highlight
2893 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2894 gutter_hovered: self.gutter_hovered,
2895 }
2896 }
2897
2898 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2899 self.buffer.read(cx).language_at(point, cx)
2900 }
2901
2902 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2903 self.buffer.read(cx).read(cx).file_at(point).cloned()
2904 }
2905
2906 pub fn active_excerpt(
2907 &self,
2908 cx: &App,
2909 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2910 self.buffer
2911 .read(cx)
2912 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2913 }
2914
2915 pub fn mode(&self) -> &EditorMode {
2916 &self.mode
2917 }
2918
2919 pub fn set_mode(&mut self, mode: EditorMode) {
2920 self.mode = mode;
2921 }
2922
2923 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2924 self.collaboration_hub.as_deref()
2925 }
2926
2927 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2928 self.collaboration_hub = Some(hub);
2929 }
2930
2931 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2932 self.in_project_search = in_project_search;
2933 }
2934
2935 pub fn set_custom_context_menu(
2936 &mut self,
2937 f: impl 'static
2938 + Fn(
2939 &mut Self,
2940 DisplayPoint,
2941 &mut Window,
2942 &mut Context<Self>,
2943 ) -> Option<Entity<ui::ContextMenu>>,
2944 ) {
2945 self.custom_context_menu = Some(Box::new(f))
2946 }
2947
2948 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2949 self.completion_provider = provider;
2950 }
2951
2952 #[cfg(any(test, feature = "test-support"))]
2953 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2954 self.completion_provider.clone()
2955 }
2956
2957 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2958 self.semantics_provider.clone()
2959 }
2960
2961 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2962 self.semantics_provider = provider;
2963 }
2964
2965 pub fn set_edit_prediction_provider<T>(
2966 &mut self,
2967 provider: Option<Entity<T>>,
2968 window: &mut Window,
2969 cx: &mut Context<Self>,
2970 ) where
2971 T: EditPredictionProvider,
2972 {
2973 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2974 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2975 if this.focus_handle.is_focused(window) {
2976 this.update_visible_edit_prediction(window, cx);
2977 }
2978 }),
2979 provider: Arc::new(provider),
2980 });
2981 self.update_edit_prediction_settings(cx);
2982 self.refresh_edit_prediction(false, false, window, cx);
2983 }
2984
2985 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2986 self.placeholder_display_map
2987 .as_ref()
2988 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2989 }
2990
2991 pub fn set_placeholder_text(
2992 &mut self,
2993 placeholder_text: &str,
2994 window: &mut Window,
2995 cx: &mut Context<Self>,
2996 ) {
2997 let multibuffer = cx
2998 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2999
3000 let style = window.text_style();
3001
3002 self.placeholder_display_map = Some(cx.new(|cx| {
3003 DisplayMap::new(
3004 multibuffer,
3005 style.font(),
3006 style.font_size.to_pixels(window.rem_size()),
3007 None,
3008 FILE_HEADER_HEIGHT,
3009 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3010 Default::default(),
3011 DiagnosticSeverity::Off,
3012 cx,
3013 )
3014 }));
3015 cx.notify();
3016 }
3017
3018 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3019 self.cursor_shape = cursor_shape;
3020
3021 // Disrupt blink for immediate user feedback that the cursor shape has changed
3022 self.blink_manager.update(cx, BlinkManager::show_cursor);
3023
3024 cx.notify();
3025 }
3026
3027 pub fn cursor_shape(&self) -> CursorShape {
3028 self.cursor_shape
3029 }
3030
3031 pub fn set_current_line_highlight(
3032 &mut self,
3033 current_line_highlight: Option<CurrentLineHighlight>,
3034 ) {
3035 self.current_line_highlight = current_line_highlight;
3036 }
3037
3038 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3039 self.collapse_matches = collapse_matches;
3040 }
3041
3042 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3043 if self.collapse_matches {
3044 return range.start..range.start;
3045 }
3046 range.clone()
3047 }
3048
3049 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3050 self.display_map.read(cx).clip_at_line_ends
3051 }
3052
3053 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3054 if self.display_map.read(cx).clip_at_line_ends != clip {
3055 self.display_map
3056 .update(cx, |map, _| map.clip_at_line_ends = clip);
3057 }
3058 }
3059
3060 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3061 self.input_enabled = input_enabled;
3062 }
3063
3064 pub fn set_edit_predictions_hidden_for_vim_mode(
3065 &mut self,
3066 hidden: bool,
3067 window: &mut Window,
3068 cx: &mut Context<Self>,
3069 ) {
3070 if hidden != self.edit_predictions_hidden_for_vim_mode {
3071 self.edit_predictions_hidden_for_vim_mode = hidden;
3072 if hidden {
3073 self.update_visible_edit_prediction(window, cx);
3074 } else {
3075 self.refresh_edit_prediction(true, false, window, cx);
3076 }
3077 }
3078 }
3079
3080 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3081 self.menu_edit_predictions_policy = value;
3082 }
3083
3084 pub fn set_autoindent(&mut self, autoindent: bool) {
3085 if autoindent {
3086 self.autoindent_mode = Some(AutoindentMode::EachLine);
3087 } else {
3088 self.autoindent_mode = None;
3089 }
3090 }
3091
3092 pub fn read_only(&self, cx: &App) -> bool {
3093 self.read_only || self.buffer.read(cx).read_only()
3094 }
3095
3096 pub fn set_read_only(&mut self, read_only: bool) {
3097 self.read_only = read_only;
3098 }
3099
3100 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3101 self.use_autoclose = autoclose;
3102 }
3103
3104 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3105 self.use_auto_surround = auto_surround;
3106 }
3107
3108 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3109 self.auto_replace_emoji_shortcode = auto_replace;
3110 }
3111
3112 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3113 self.buffer_serialization = should_serialize.then(|| {
3114 BufferSerialization::new(
3115 ProjectSettings::get_global(cx)
3116 .session
3117 .restore_unsaved_buffers,
3118 )
3119 })
3120 }
3121
3122 fn should_serialize_buffer(&self) -> bool {
3123 self.buffer_serialization.is_some()
3124 }
3125
3126 pub fn toggle_edit_predictions(
3127 &mut self,
3128 _: &ToggleEditPrediction,
3129 window: &mut Window,
3130 cx: &mut Context<Self>,
3131 ) {
3132 if self.show_edit_predictions_override.is_some() {
3133 self.set_show_edit_predictions(None, window, cx);
3134 } else {
3135 let show_edit_predictions = !self.edit_predictions_enabled();
3136 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3137 }
3138 }
3139
3140 pub fn set_show_edit_predictions(
3141 &mut self,
3142 show_edit_predictions: Option<bool>,
3143 window: &mut Window,
3144 cx: &mut Context<Self>,
3145 ) {
3146 self.show_edit_predictions_override = show_edit_predictions;
3147 self.update_edit_prediction_settings(cx);
3148
3149 if let Some(false) = show_edit_predictions {
3150 self.discard_edit_prediction(false, cx);
3151 } else {
3152 self.refresh_edit_prediction(false, true, window, cx);
3153 }
3154 }
3155
3156 fn edit_predictions_disabled_in_scope(
3157 &self,
3158 buffer: &Entity<Buffer>,
3159 buffer_position: language::Anchor,
3160 cx: &App,
3161 ) -> bool {
3162 let snapshot = buffer.read(cx).snapshot();
3163 let settings = snapshot.settings_at(buffer_position, cx);
3164
3165 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3166 return false;
3167 };
3168
3169 scope.override_name().is_some_and(|scope_name| {
3170 settings
3171 .edit_predictions_disabled_in
3172 .iter()
3173 .any(|s| s == scope_name)
3174 })
3175 }
3176
3177 pub fn set_use_modal_editing(&mut self, to: bool) {
3178 self.use_modal_editing = to;
3179 }
3180
3181 pub fn use_modal_editing(&self) -> bool {
3182 self.use_modal_editing
3183 }
3184
3185 fn selections_did_change(
3186 &mut self,
3187 local: bool,
3188 old_cursor_position: &Anchor,
3189 effects: SelectionEffects,
3190 window: &mut Window,
3191 cx: &mut Context<Self>,
3192 ) {
3193 window.invalidate_character_coordinates();
3194
3195 // Copy selections to primary selection buffer
3196 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3197 if local {
3198 let selections = self
3199 .selections
3200 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3201 let buffer_handle = self.buffer.read(cx).read(cx);
3202
3203 let mut text = String::new();
3204 for (index, selection) in selections.iter().enumerate() {
3205 let text_for_selection = buffer_handle
3206 .text_for_range(selection.start..selection.end)
3207 .collect::<String>();
3208
3209 text.push_str(&text_for_selection);
3210 if index != selections.len() - 1 {
3211 text.push('\n');
3212 }
3213 }
3214
3215 if !text.is_empty() {
3216 cx.write_to_primary(ClipboardItem::new_string(text));
3217 }
3218 }
3219
3220 let selection_anchors = self.selections.disjoint_anchors_arc();
3221
3222 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3223 self.buffer.update(cx, |buffer, cx| {
3224 buffer.set_active_selections(
3225 &selection_anchors,
3226 self.selections.line_mode(),
3227 self.cursor_shape,
3228 cx,
3229 )
3230 });
3231 }
3232 let display_map = self
3233 .display_map
3234 .update(cx, |display_map, cx| display_map.snapshot(cx));
3235 let buffer = display_map.buffer_snapshot();
3236 if self.selections.count() == 1 {
3237 self.add_selections_state = None;
3238 }
3239 self.select_next_state = None;
3240 self.select_prev_state = None;
3241 self.select_syntax_node_history.try_clear();
3242 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3243 self.snippet_stack.invalidate(&selection_anchors, buffer);
3244 self.take_rename(false, window, cx);
3245
3246 let newest_selection = self.selections.newest_anchor();
3247 let new_cursor_position = newest_selection.head();
3248 let selection_start = newest_selection.start;
3249
3250 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3251 self.push_to_nav_history(
3252 *old_cursor_position,
3253 Some(new_cursor_position.to_point(buffer)),
3254 false,
3255 effects.nav_history == Some(true),
3256 cx,
3257 );
3258 }
3259
3260 if local {
3261 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3262 self.register_buffer(buffer_id, cx);
3263 }
3264
3265 let mut context_menu = self.context_menu.borrow_mut();
3266 let completion_menu = match context_menu.as_ref() {
3267 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3268 Some(CodeContextMenu::CodeActions(_)) => {
3269 *context_menu = None;
3270 None
3271 }
3272 None => None,
3273 };
3274 let completion_position = completion_menu.map(|menu| menu.initial_position);
3275 drop(context_menu);
3276
3277 if effects.completions
3278 && let Some(completion_position) = completion_position
3279 {
3280 let start_offset = selection_start.to_offset(buffer);
3281 let position_matches = start_offset == completion_position.to_offset(buffer);
3282 let continue_showing = if position_matches {
3283 if self.snippet_stack.is_empty() {
3284 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3285 == Some(CharKind::Word)
3286 } else {
3287 // Snippet choices can be shown even when the cursor is in whitespace.
3288 // Dismissing the menu with actions like backspace is handled by
3289 // invalidation regions.
3290 true
3291 }
3292 } else {
3293 false
3294 };
3295
3296 if continue_showing {
3297 self.open_or_update_completions_menu(None, None, false, window, cx);
3298 } else {
3299 self.hide_context_menu(window, cx);
3300 }
3301 }
3302
3303 hide_hover(self, cx);
3304
3305 if old_cursor_position.to_display_point(&display_map).row()
3306 != new_cursor_position.to_display_point(&display_map).row()
3307 {
3308 self.available_code_actions.take();
3309 }
3310 self.refresh_code_actions(window, cx);
3311 self.refresh_document_highlights(cx);
3312 refresh_linked_ranges(self, window, cx);
3313
3314 self.refresh_selected_text_highlights(false, window, cx);
3315 self.refresh_matching_bracket_highlights(window, cx);
3316 self.update_visible_edit_prediction(window, cx);
3317 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3318 self.inline_blame_popover.take();
3319 if self.git_blame_inline_enabled {
3320 self.start_inline_blame_timer(window, cx);
3321 }
3322 }
3323
3324 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3325 cx.emit(EditorEvent::SelectionsChanged { local });
3326
3327 let selections = &self.selections.disjoint_anchors_arc();
3328 if selections.len() == 1 {
3329 cx.emit(SearchEvent::ActiveMatchChanged)
3330 }
3331 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3332 let inmemory_selections = selections
3333 .iter()
3334 .map(|s| {
3335 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3336 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3337 })
3338 .collect();
3339 self.update_restoration_data(cx, |data| {
3340 data.selections = inmemory_selections;
3341 });
3342
3343 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3344 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3345 {
3346 let snapshot = self.buffer().read(cx).snapshot(cx);
3347 let selections = selections.clone();
3348 let background_executor = cx.background_executor().clone();
3349 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3350 self.serialize_selections = cx.background_spawn(async move {
3351 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3352 let db_selections = selections
3353 .iter()
3354 .map(|selection| {
3355 (
3356 selection.start.to_offset(&snapshot).0,
3357 selection.end.to_offset(&snapshot).0,
3358 )
3359 })
3360 .collect();
3361
3362 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3363 .await
3364 .with_context(|| {
3365 format!(
3366 "persisting editor selections for editor {editor_id}, \
3367 workspace {workspace_id:?}"
3368 )
3369 })
3370 .log_err();
3371 });
3372 }
3373 }
3374
3375 cx.notify();
3376 }
3377
3378 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3379 use text::ToOffset as _;
3380 use text::ToPoint as _;
3381
3382 if self.mode.is_minimap()
3383 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3384 {
3385 return;
3386 }
3387
3388 if !self.buffer().read(cx).is_singleton() {
3389 return;
3390 }
3391
3392 let display_snapshot = self
3393 .display_map
3394 .update(cx, |display_map, cx| display_map.snapshot(cx));
3395 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3396 return;
3397 };
3398 let inmemory_folds = display_snapshot
3399 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3400 .map(|fold| {
3401 fold.range.start.text_anchor.to_point(&snapshot)
3402 ..fold.range.end.text_anchor.to_point(&snapshot)
3403 })
3404 .collect();
3405 self.update_restoration_data(cx, |data| {
3406 data.folds = inmemory_folds;
3407 });
3408
3409 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3410 return;
3411 };
3412 let background_executor = cx.background_executor().clone();
3413 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3414 let db_folds = display_snapshot
3415 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3416 .map(|fold| {
3417 (
3418 fold.range.start.text_anchor.to_offset(&snapshot),
3419 fold.range.end.text_anchor.to_offset(&snapshot),
3420 )
3421 })
3422 .collect();
3423 self.serialize_folds = cx.background_spawn(async move {
3424 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3425 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3426 .await
3427 .with_context(|| {
3428 format!(
3429 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3430 )
3431 })
3432 .log_err();
3433 });
3434 }
3435
3436 pub fn sync_selections(
3437 &mut self,
3438 other: Entity<Editor>,
3439 cx: &mut Context<Self>,
3440 ) -> gpui::Subscription {
3441 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3442 if !other_selections.is_empty() {
3443 self.selections
3444 .change_with(&self.display_snapshot(cx), |selections| {
3445 selections.select_anchors(other_selections);
3446 });
3447 }
3448
3449 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3450 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3451 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3452 if other_selections.is_empty() {
3453 return;
3454 }
3455 let snapshot = this.display_snapshot(cx);
3456 this.selections.change_with(&snapshot, |selections| {
3457 selections.select_anchors(other_selections);
3458 });
3459 }
3460 });
3461
3462 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3463 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3464 let these_selections = this.selections.disjoint_anchors().to_vec();
3465 if these_selections.is_empty() {
3466 return;
3467 }
3468 other.update(cx, |other_editor, cx| {
3469 let snapshot = other_editor.display_snapshot(cx);
3470 other_editor
3471 .selections
3472 .change_with(&snapshot, |selections| {
3473 selections.select_anchors(these_selections);
3474 })
3475 });
3476 }
3477 });
3478
3479 Subscription::join(other_subscription, this_subscription)
3480 }
3481
3482 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3483 if self.buffer().read(cx).is_singleton() {
3484 return;
3485 }
3486 let snapshot = self.buffer.read(cx).snapshot(cx);
3487 let buffer_ids: HashSet<BufferId> = self
3488 .selections
3489 .disjoint_anchor_ranges()
3490 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3491 .collect();
3492 for buffer_id in buffer_ids {
3493 self.unfold_buffer(buffer_id, cx);
3494 }
3495 }
3496
3497 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3498 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3499 /// effects of selection change occur at the end of the transaction.
3500 pub fn change_selections<R>(
3501 &mut self,
3502 effects: SelectionEffects,
3503 window: &mut Window,
3504 cx: &mut Context<Self>,
3505 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3506 ) -> R {
3507 let snapshot = self.display_snapshot(cx);
3508 if let Some(state) = &mut self.deferred_selection_effects_state {
3509 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3510 state.effects.completions = effects.completions;
3511 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3512 let (changed, result) = self.selections.change_with(&snapshot, change);
3513 state.changed |= changed;
3514 return result;
3515 }
3516 let mut state = DeferredSelectionEffectsState {
3517 changed: false,
3518 effects,
3519 old_cursor_position: self.selections.newest_anchor().head(),
3520 history_entry: SelectionHistoryEntry {
3521 selections: self.selections.disjoint_anchors_arc(),
3522 select_next_state: self.select_next_state.clone(),
3523 select_prev_state: self.select_prev_state.clone(),
3524 add_selections_state: self.add_selections_state.clone(),
3525 },
3526 };
3527 let (changed, result) = self.selections.change_with(&snapshot, change);
3528 state.changed = state.changed || changed;
3529 if self.defer_selection_effects {
3530 self.deferred_selection_effects_state = Some(state);
3531 } else {
3532 self.apply_selection_effects(state, window, cx);
3533 }
3534 result
3535 }
3536
3537 /// Defers the effects of selection change, so that the effects of multiple calls to
3538 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3539 /// to selection history and the state of popovers based on selection position aren't
3540 /// erroneously updated.
3541 pub fn with_selection_effects_deferred<R>(
3542 &mut self,
3543 window: &mut Window,
3544 cx: &mut Context<Self>,
3545 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3546 ) -> R {
3547 let already_deferred = self.defer_selection_effects;
3548 self.defer_selection_effects = true;
3549 let result = update(self, window, cx);
3550 if !already_deferred {
3551 self.defer_selection_effects = false;
3552 if let Some(state) = self.deferred_selection_effects_state.take() {
3553 self.apply_selection_effects(state, window, cx);
3554 }
3555 }
3556 result
3557 }
3558
3559 fn apply_selection_effects(
3560 &mut self,
3561 state: DeferredSelectionEffectsState,
3562 window: &mut Window,
3563 cx: &mut Context<Self>,
3564 ) {
3565 if state.changed {
3566 self.selection_history.push(state.history_entry);
3567
3568 if let Some(autoscroll) = state.effects.scroll {
3569 self.request_autoscroll(autoscroll, cx);
3570 }
3571
3572 let old_cursor_position = &state.old_cursor_position;
3573
3574 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3575
3576 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3577 self.show_signature_help(&ShowSignatureHelp, window, cx);
3578 }
3579 }
3580 }
3581
3582 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3583 where
3584 I: IntoIterator<Item = (Range<S>, T)>,
3585 S: ToOffset,
3586 T: Into<Arc<str>>,
3587 {
3588 if self.read_only(cx) {
3589 return;
3590 }
3591
3592 self.buffer
3593 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3594 }
3595
3596 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3597 where
3598 I: IntoIterator<Item = (Range<S>, T)>,
3599 S: ToOffset,
3600 T: Into<Arc<str>>,
3601 {
3602 if self.read_only(cx) {
3603 return;
3604 }
3605
3606 self.buffer.update(cx, |buffer, cx| {
3607 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3608 });
3609 }
3610
3611 pub fn edit_with_block_indent<I, S, T>(
3612 &mut self,
3613 edits: I,
3614 original_indent_columns: Vec<Option<u32>>,
3615 cx: &mut Context<Self>,
3616 ) where
3617 I: IntoIterator<Item = (Range<S>, T)>,
3618 S: ToOffset,
3619 T: Into<Arc<str>>,
3620 {
3621 if self.read_only(cx) {
3622 return;
3623 }
3624
3625 self.buffer.update(cx, |buffer, cx| {
3626 buffer.edit(
3627 edits,
3628 Some(AutoindentMode::Block {
3629 original_indent_columns,
3630 }),
3631 cx,
3632 )
3633 });
3634 }
3635
3636 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3637 self.hide_context_menu(window, cx);
3638
3639 match phase {
3640 SelectPhase::Begin {
3641 position,
3642 add,
3643 click_count,
3644 } => self.begin_selection(position, add, click_count, window, cx),
3645 SelectPhase::BeginColumnar {
3646 position,
3647 goal_column,
3648 reset,
3649 mode,
3650 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3651 SelectPhase::Extend {
3652 position,
3653 click_count,
3654 } => self.extend_selection(position, click_count, window, cx),
3655 SelectPhase::Update {
3656 position,
3657 goal_column,
3658 scroll_delta,
3659 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3660 SelectPhase::End => self.end_selection(window, cx),
3661 }
3662 }
3663
3664 fn extend_selection(
3665 &mut self,
3666 position: DisplayPoint,
3667 click_count: usize,
3668 window: &mut Window,
3669 cx: &mut Context<Self>,
3670 ) {
3671 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3672 let tail = self
3673 .selections
3674 .newest::<MultiBufferOffset>(&display_map)
3675 .tail();
3676 let click_count = click_count.max(match self.selections.select_mode() {
3677 SelectMode::Character => 1,
3678 SelectMode::Word(_) => 2,
3679 SelectMode::Line(_) => 3,
3680 SelectMode::All => 4,
3681 });
3682 self.begin_selection(position, false, click_count, window, cx);
3683
3684 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3685
3686 let current_selection = match self.selections.select_mode() {
3687 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3688 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3689 };
3690
3691 let mut pending_selection = self
3692 .selections
3693 .pending_anchor()
3694 .cloned()
3695 .expect("extend_selection not called with pending selection");
3696
3697 if pending_selection
3698 .start
3699 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3700 == Ordering::Greater
3701 {
3702 pending_selection.start = current_selection.start;
3703 }
3704 if pending_selection
3705 .end
3706 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3707 == Ordering::Less
3708 {
3709 pending_selection.end = current_selection.end;
3710 pending_selection.reversed = true;
3711 }
3712
3713 let mut pending_mode = self.selections.pending_mode().unwrap();
3714 match &mut pending_mode {
3715 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3716 _ => {}
3717 }
3718
3719 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3720 SelectionEffects::scroll(Autoscroll::fit())
3721 } else {
3722 SelectionEffects::no_scroll()
3723 };
3724
3725 self.change_selections(effects, window, cx, |s| {
3726 s.set_pending(pending_selection.clone(), pending_mode);
3727 s.set_is_extending(true);
3728 });
3729 }
3730
3731 fn begin_selection(
3732 &mut self,
3733 position: DisplayPoint,
3734 add: bool,
3735 click_count: usize,
3736 window: &mut Window,
3737 cx: &mut Context<Self>,
3738 ) {
3739 if !self.focus_handle.is_focused(window) {
3740 self.last_focused_descendant = None;
3741 window.focus(&self.focus_handle);
3742 }
3743
3744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3745 let buffer = display_map.buffer_snapshot();
3746 let position = display_map.clip_point(position, Bias::Left);
3747
3748 let start;
3749 let end;
3750 let mode;
3751 let mut auto_scroll;
3752 match click_count {
3753 1 => {
3754 start = buffer.anchor_before(position.to_point(&display_map));
3755 end = start;
3756 mode = SelectMode::Character;
3757 auto_scroll = true;
3758 }
3759 2 => {
3760 let position = display_map
3761 .clip_point(position, Bias::Left)
3762 .to_offset(&display_map, Bias::Left);
3763 let (range, _) = buffer.surrounding_word(position, None);
3764 start = buffer.anchor_before(range.start);
3765 end = buffer.anchor_before(range.end);
3766 mode = SelectMode::Word(start..end);
3767 auto_scroll = true;
3768 }
3769 3 => {
3770 let position = display_map
3771 .clip_point(position, Bias::Left)
3772 .to_point(&display_map);
3773 let line_start = display_map.prev_line_boundary(position).0;
3774 let next_line_start = buffer.clip_point(
3775 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3776 Bias::Left,
3777 );
3778 start = buffer.anchor_before(line_start);
3779 end = buffer.anchor_before(next_line_start);
3780 mode = SelectMode::Line(start..end);
3781 auto_scroll = true;
3782 }
3783 _ => {
3784 start = buffer.anchor_before(MultiBufferOffset(0));
3785 end = buffer.anchor_before(buffer.len());
3786 mode = SelectMode::All;
3787 auto_scroll = false;
3788 }
3789 }
3790 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3791
3792 let point_to_delete: Option<usize> = {
3793 let selected_points: Vec<Selection<Point>> =
3794 self.selections.disjoint_in_range(start..end, &display_map);
3795
3796 if !add || click_count > 1 {
3797 None
3798 } else if !selected_points.is_empty() {
3799 Some(selected_points[0].id)
3800 } else {
3801 let clicked_point_already_selected =
3802 self.selections.disjoint_anchors().iter().find(|selection| {
3803 selection.start.to_point(buffer) == start.to_point(buffer)
3804 || selection.end.to_point(buffer) == end.to_point(buffer)
3805 });
3806
3807 clicked_point_already_selected.map(|selection| selection.id)
3808 }
3809 };
3810
3811 let selections_count = self.selections.count();
3812 let effects = if auto_scroll {
3813 SelectionEffects::default()
3814 } else {
3815 SelectionEffects::no_scroll()
3816 };
3817
3818 self.change_selections(effects, window, cx, |s| {
3819 if let Some(point_to_delete) = point_to_delete {
3820 s.delete(point_to_delete);
3821
3822 if selections_count == 1 {
3823 s.set_pending_anchor_range(start..end, mode);
3824 }
3825 } else {
3826 if !add {
3827 s.clear_disjoint();
3828 }
3829
3830 s.set_pending_anchor_range(start..end, mode);
3831 }
3832 });
3833 }
3834
3835 fn begin_columnar_selection(
3836 &mut self,
3837 position: DisplayPoint,
3838 goal_column: u32,
3839 reset: bool,
3840 mode: ColumnarMode,
3841 window: &mut Window,
3842 cx: &mut Context<Self>,
3843 ) {
3844 if !self.focus_handle.is_focused(window) {
3845 self.last_focused_descendant = None;
3846 window.focus(&self.focus_handle);
3847 }
3848
3849 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3850
3851 if reset {
3852 let pointer_position = display_map
3853 .buffer_snapshot()
3854 .anchor_before(position.to_point(&display_map));
3855
3856 self.change_selections(
3857 SelectionEffects::scroll(Autoscroll::newest()),
3858 window,
3859 cx,
3860 |s| {
3861 s.clear_disjoint();
3862 s.set_pending_anchor_range(
3863 pointer_position..pointer_position,
3864 SelectMode::Character,
3865 );
3866 },
3867 );
3868 };
3869
3870 let tail = self.selections.newest::<Point>(&display_map).tail();
3871 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3872 self.columnar_selection_state = match mode {
3873 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3874 selection_tail: selection_anchor,
3875 display_point: if reset {
3876 if position.column() != goal_column {
3877 Some(DisplayPoint::new(position.row(), goal_column))
3878 } else {
3879 None
3880 }
3881 } else {
3882 None
3883 },
3884 }),
3885 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3886 selection_tail: selection_anchor,
3887 }),
3888 };
3889
3890 if !reset {
3891 self.select_columns(position, goal_column, &display_map, window, cx);
3892 }
3893 }
3894
3895 fn update_selection(
3896 &mut self,
3897 position: DisplayPoint,
3898 goal_column: u32,
3899 scroll_delta: gpui::Point<f32>,
3900 window: &mut Window,
3901 cx: &mut Context<Self>,
3902 ) {
3903 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3904
3905 if self.columnar_selection_state.is_some() {
3906 self.select_columns(position, goal_column, &display_map, window, cx);
3907 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3908 let buffer = display_map.buffer_snapshot();
3909 let head;
3910 let tail;
3911 let mode = self.selections.pending_mode().unwrap();
3912 match &mode {
3913 SelectMode::Character => {
3914 head = position.to_point(&display_map);
3915 tail = pending.tail().to_point(buffer);
3916 }
3917 SelectMode::Word(original_range) => {
3918 let offset = display_map
3919 .clip_point(position, Bias::Left)
3920 .to_offset(&display_map, Bias::Left);
3921 let original_range = original_range.to_offset(buffer);
3922
3923 let head_offset = if buffer.is_inside_word(offset, None)
3924 || original_range.contains(&offset)
3925 {
3926 let (word_range, _) = buffer.surrounding_word(offset, None);
3927 if word_range.start < original_range.start {
3928 word_range.start
3929 } else {
3930 word_range.end
3931 }
3932 } else {
3933 offset
3934 };
3935
3936 head = head_offset.to_point(buffer);
3937 if head_offset <= original_range.start {
3938 tail = original_range.end.to_point(buffer);
3939 } else {
3940 tail = original_range.start.to_point(buffer);
3941 }
3942 }
3943 SelectMode::Line(original_range) => {
3944 let original_range = original_range.to_point(display_map.buffer_snapshot());
3945
3946 let position = display_map
3947 .clip_point(position, Bias::Left)
3948 .to_point(&display_map);
3949 let line_start = display_map.prev_line_boundary(position).0;
3950 let next_line_start = buffer.clip_point(
3951 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3952 Bias::Left,
3953 );
3954
3955 if line_start < original_range.start {
3956 head = line_start
3957 } else {
3958 head = next_line_start
3959 }
3960
3961 if head <= original_range.start {
3962 tail = original_range.end;
3963 } else {
3964 tail = original_range.start;
3965 }
3966 }
3967 SelectMode::All => {
3968 return;
3969 }
3970 };
3971
3972 if head < tail {
3973 pending.start = buffer.anchor_before(head);
3974 pending.end = buffer.anchor_before(tail);
3975 pending.reversed = true;
3976 } else {
3977 pending.start = buffer.anchor_before(tail);
3978 pending.end = buffer.anchor_before(head);
3979 pending.reversed = false;
3980 }
3981
3982 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3983 s.set_pending(pending.clone(), mode);
3984 });
3985 } else {
3986 log::error!("update_selection dispatched with no pending selection");
3987 return;
3988 }
3989
3990 self.apply_scroll_delta(scroll_delta, window, cx);
3991 cx.notify();
3992 }
3993
3994 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3995 self.columnar_selection_state.take();
3996 if let Some(pending_mode) = self.selections.pending_mode() {
3997 let selections = self
3998 .selections
3999 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4000 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4001 s.select(selections);
4002 s.clear_pending();
4003 if s.is_extending() {
4004 s.set_is_extending(false);
4005 } else {
4006 s.set_select_mode(pending_mode);
4007 }
4008 });
4009 }
4010 }
4011
4012 fn select_columns(
4013 &mut self,
4014 head: DisplayPoint,
4015 goal_column: u32,
4016 display_map: &DisplaySnapshot,
4017 window: &mut Window,
4018 cx: &mut Context<Self>,
4019 ) {
4020 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4021 return;
4022 };
4023
4024 let tail = match columnar_state {
4025 ColumnarSelectionState::FromMouse {
4026 selection_tail,
4027 display_point,
4028 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4029 ColumnarSelectionState::FromSelection { selection_tail } => {
4030 selection_tail.to_display_point(display_map)
4031 }
4032 };
4033
4034 let start_row = cmp::min(tail.row(), head.row());
4035 let end_row = cmp::max(tail.row(), head.row());
4036 let start_column = cmp::min(tail.column(), goal_column);
4037 let end_column = cmp::max(tail.column(), goal_column);
4038 let reversed = start_column < tail.column();
4039
4040 let selection_ranges = (start_row.0..=end_row.0)
4041 .map(DisplayRow)
4042 .filter_map(|row| {
4043 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4044 || start_column <= display_map.line_len(row))
4045 && !display_map.is_block_line(row)
4046 {
4047 let start = display_map
4048 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4049 .to_point(display_map);
4050 let end = display_map
4051 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4052 .to_point(display_map);
4053 if reversed {
4054 Some(end..start)
4055 } else {
4056 Some(start..end)
4057 }
4058 } else {
4059 None
4060 }
4061 })
4062 .collect::<Vec<_>>();
4063 if selection_ranges.is_empty() {
4064 return;
4065 }
4066
4067 let ranges = match columnar_state {
4068 ColumnarSelectionState::FromMouse { .. } => {
4069 let mut non_empty_ranges = selection_ranges
4070 .iter()
4071 .filter(|selection_range| selection_range.start != selection_range.end)
4072 .peekable();
4073 if non_empty_ranges.peek().is_some() {
4074 non_empty_ranges.cloned().collect()
4075 } else {
4076 selection_ranges
4077 }
4078 }
4079 _ => selection_ranges,
4080 };
4081
4082 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4083 s.select_ranges(ranges);
4084 });
4085 cx.notify();
4086 }
4087
4088 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4089 self.selections
4090 .all_adjusted(snapshot)
4091 .iter()
4092 .any(|selection| !selection.is_empty())
4093 }
4094
4095 pub fn has_pending_nonempty_selection(&self) -> bool {
4096 let pending_nonempty_selection = match self.selections.pending_anchor() {
4097 Some(Selection { start, end, .. }) => start != end,
4098 None => false,
4099 };
4100
4101 pending_nonempty_selection
4102 || (self.columnar_selection_state.is_some()
4103 && self.selections.disjoint_anchors().len() > 1)
4104 }
4105
4106 pub fn has_pending_selection(&self) -> bool {
4107 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4108 }
4109
4110 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4111 self.selection_mark_mode = false;
4112 self.selection_drag_state = SelectionDragState::None;
4113
4114 if self.dismiss_menus_and_popups(true, window, cx) {
4115 cx.notify();
4116 return;
4117 }
4118 if self.clear_expanded_diff_hunks(cx) {
4119 cx.notify();
4120 return;
4121 }
4122 if self.show_git_blame_gutter {
4123 self.show_git_blame_gutter = false;
4124 cx.notify();
4125 return;
4126 }
4127
4128 if self.mode.is_full()
4129 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4130 {
4131 cx.notify();
4132 return;
4133 }
4134
4135 cx.propagate();
4136 }
4137
4138 pub fn dismiss_menus_and_popups(
4139 &mut self,
4140 is_user_requested: bool,
4141 window: &mut Window,
4142 cx: &mut Context<Self>,
4143 ) -> bool {
4144 let mut dismissed = false;
4145
4146 dismissed |= self.take_rename(false, window, cx).is_some();
4147 dismissed |= self.hide_blame_popover(true, cx);
4148 dismissed |= hide_hover(self, cx);
4149 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4150 dismissed |= self.hide_context_menu(window, cx).is_some();
4151 dismissed |= self.mouse_context_menu.take().is_some();
4152 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4153 dismissed |= self.snippet_stack.pop().is_some();
4154
4155 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4156 self.dismiss_diagnostics(cx);
4157 dismissed = true;
4158 }
4159
4160 dismissed
4161 }
4162
4163 fn linked_editing_ranges_for(
4164 &self,
4165 selection: Range<text::Anchor>,
4166 cx: &App,
4167 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4168 if self.linked_edit_ranges.is_empty() {
4169 return None;
4170 }
4171 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4172 selection.end.buffer_id.and_then(|end_buffer_id| {
4173 if selection.start.buffer_id != Some(end_buffer_id) {
4174 return None;
4175 }
4176 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4177 let snapshot = buffer.read(cx).snapshot();
4178 self.linked_edit_ranges
4179 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4180 .map(|ranges| (ranges, snapshot, buffer))
4181 })?;
4182 use text::ToOffset as TO;
4183 // find offset from the start of current range to current cursor position
4184 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4185
4186 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4187 let start_difference = start_offset - start_byte_offset;
4188 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4189 let end_difference = end_offset - start_byte_offset;
4190 // Current range has associated linked ranges.
4191 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4192 for range in linked_ranges.iter() {
4193 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4194 let end_offset = start_offset + end_difference;
4195 let start_offset = start_offset + start_difference;
4196 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4197 continue;
4198 }
4199 if self.selections.disjoint_anchor_ranges().any(|s| {
4200 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4201 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4202 {
4203 return false;
4204 }
4205 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4206 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4207 }) {
4208 continue;
4209 }
4210 let start = buffer_snapshot.anchor_after(start_offset);
4211 let end = buffer_snapshot.anchor_after(end_offset);
4212 linked_edits
4213 .entry(buffer.clone())
4214 .or_default()
4215 .push(start..end);
4216 }
4217 Some(linked_edits)
4218 }
4219
4220 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4221 let text: Arc<str> = text.into();
4222
4223 if self.read_only(cx) {
4224 return;
4225 }
4226
4227 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4228
4229 self.unfold_buffers_with_selections(cx);
4230
4231 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4232 let mut bracket_inserted = false;
4233 let mut edits = Vec::new();
4234 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4235 let mut new_selections = Vec::with_capacity(selections.len());
4236 let mut new_autoclose_regions = Vec::new();
4237 let snapshot = self.buffer.read(cx).read(cx);
4238 let mut clear_linked_edit_ranges = false;
4239
4240 for (selection, autoclose_region) in
4241 self.selections_with_autoclose_regions(selections, &snapshot)
4242 {
4243 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4244 // Determine if the inserted text matches the opening or closing
4245 // bracket of any of this language's bracket pairs.
4246 let mut bracket_pair = None;
4247 let mut is_bracket_pair_start = false;
4248 let mut is_bracket_pair_end = false;
4249 if !text.is_empty() {
4250 let mut bracket_pair_matching_end = None;
4251 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4252 // and they are removing the character that triggered IME popup.
4253 for (pair, enabled) in scope.brackets() {
4254 if !pair.close && !pair.surround {
4255 continue;
4256 }
4257
4258 if enabled && pair.start.ends_with(text.as_ref()) {
4259 let prefix_len = pair.start.len() - text.len();
4260 let preceding_text_matches_prefix = prefix_len == 0
4261 || (selection.start.column >= (prefix_len as u32)
4262 && snapshot.contains_str_at(
4263 Point::new(
4264 selection.start.row,
4265 selection.start.column - (prefix_len as u32),
4266 ),
4267 &pair.start[..prefix_len],
4268 ));
4269 if preceding_text_matches_prefix {
4270 bracket_pair = Some(pair.clone());
4271 is_bracket_pair_start = true;
4272 break;
4273 }
4274 }
4275 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4276 {
4277 // take first bracket pair matching end, but don't break in case a later bracket
4278 // pair matches start
4279 bracket_pair_matching_end = Some(pair.clone());
4280 }
4281 }
4282 if let Some(end) = bracket_pair_matching_end
4283 && bracket_pair.is_none()
4284 {
4285 bracket_pair = Some(end);
4286 is_bracket_pair_end = true;
4287 }
4288 }
4289
4290 if let Some(bracket_pair) = bracket_pair {
4291 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4292 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4293 let auto_surround =
4294 self.use_auto_surround && snapshot_settings.use_auto_surround;
4295 if selection.is_empty() {
4296 if is_bracket_pair_start {
4297 // If the inserted text is a suffix of an opening bracket and the
4298 // selection is preceded by the rest of the opening bracket, then
4299 // insert the closing bracket.
4300 let following_text_allows_autoclose = snapshot
4301 .chars_at(selection.start)
4302 .next()
4303 .is_none_or(|c| scope.should_autoclose_before(c));
4304
4305 let preceding_text_allows_autoclose = selection.start.column == 0
4306 || snapshot
4307 .reversed_chars_at(selection.start)
4308 .next()
4309 .is_none_or(|c| {
4310 bracket_pair.start != bracket_pair.end
4311 || !snapshot
4312 .char_classifier_at(selection.start)
4313 .is_word(c)
4314 });
4315
4316 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4317 && bracket_pair.start.len() == 1
4318 {
4319 let target = bracket_pair.start.chars().next().unwrap();
4320 let current_line_count = snapshot
4321 .reversed_chars_at(selection.start)
4322 .take_while(|&c| c != '\n')
4323 .filter(|&c| c == target)
4324 .count();
4325 current_line_count % 2 == 1
4326 } else {
4327 false
4328 };
4329
4330 if autoclose
4331 && bracket_pair.close
4332 && following_text_allows_autoclose
4333 && preceding_text_allows_autoclose
4334 && !is_closing_quote
4335 {
4336 let anchor = snapshot.anchor_before(selection.end);
4337 new_selections.push((selection.map(|_| anchor), text.len()));
4338 new_autoclose_regions.push((
4339 anchor,
4340 text.len(),
4341 selection.id,
4342 bracket_pair.clone(),
4343 ));
4344 edits.push((
4345 selection.range(),
4346 format!("{}{}", text, bracket_pair.end).into(),
4347 ));
4348 bracket_inserted = true;
4349 continue;
4350 }
4351 }
4352
4353 if let Some(region) = autoclose_region {
4354 // If the selection is followed by an auto-inserted closing bracket,
4355 // then don't insert that closing bracket again; just move the selection
4356 // past the closing bracket.
4357 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4358 && text.as_ref() == region.pair.end.as_str()
4359 && snapshot.contains_str_at(region.range.end, text.as_ref());
4360 if should_skip {
4361 let anchor = snapshot.anchor_after(selection.end);
4362 new_selections
4363 .push((selection.map(|_| anchor), region.pair.end.len()));
4364 continue;
4365 }
4366 }
4367
4368 let always_treat_brackets_as_autoclosed = snapshot
4369 .language_settings_at(selection.start, cx)
4370 .always_treat_brackets_as_autoclosed;
4371 if always_treat_brackets_as_autoclosed
4372 && is_bracket_pair_end
4373 && snapshot.contains_str_at(selection.end, text.as_ref())
4374 {
4375 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4376 // and the inserted text is a closing bracket and the selection is followed
4377 // by the closing bracket then move the selection past the closing bracket.
4378 let anchor = snapshot.anchor_after(selection.end);
4379 new_selections.push((selection.map(|_| anchor), text.len()));
4380 continue;
4381 }
4382 }
4383 // If an opening bracket is 1 character long and is typed while
4384 // text is selected, then surround that text with the bracket pair.
4385 else if auto_surround
4386 && bracket_pair.surround
4387 && is_bracket_pair_start
4388 && bracket_pair.start.chars().count() == 1
4389 {
4390 edits.push((selection.start..selection.start, text.clone()));
4391 edits.push((
4392 selection.end..selection.end,
4393 bracket_pair.end.as_str().into(),
4394 ));
4395 bracket_inserted = true;
4396 new_selections.push((
4397 Selection {
4398 id: selection.id,
4399 start: snapshot.anchor_after(selection.start),
4400 end: snapshot.anchor_before(selection.end),
4401 reversed: selection.reversed,
4402 goal: selection.goal,
4403 },
4404 0,
4405 ));
4406 continue;
4407 }
4408 }
4409 }
4410
4411 if self.auto_replace_emoji_shortcode
4412 && selection.is_empty()
4413 && text.as_ref().ends_with(':')
4414 && let Some(possible_emoji_short_code) =
4415 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4416 && !possible_emoji_short_code.is_empty()
4417 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4418 {
4419 let emoji_shortcode_start = Point::new(
4420 selection.start.row,
4421 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4422 );
4423
4424 // Remove shortcode from buffer
4425 edits.push((
4426 emoji_shortcode_start..selection.start,
4427 "".to_string().into(),
4428 ));
4429 new_selections.push((
4430 Selection {
4431 id: selection.id,
4432 start: snapshot.anchor_after(emoji_shortcode_start),
4433 end: snapshot.anchor_before(selection.start),
4434 reversed: selection.reversed,
4435 goal: selection.goal,
4436 },
4437 0,
4438 ));
4439
4440 // Insert emoji
4441 let selection_start_anchor = snapshot.anchor_after(selection.start);
4442 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4443 edits.push((selection.start..selection.end, emoji.to_string().into()));
4444
4445 continue;
4446 }
4447
4448 // If not handling any auto-close operation, then just replace the selected
4449 // text with the given input and move the selection to the end of the
4450 // newly inserted text.
4451 let anchor = snapshot.anchor_after(selection.end);
4452 if !self.linked_edit_ranges.is_empty() {
4453 let start_anchor = snapshot.anchor_before(selection.start);
4454
4455 let is_word_char = text.chars().next().is_none_or(|char| {
4456 let classifier = snapshot
4457 .char_classifier_at(start_anchor.to_offset(&snapshot))
4458 .scope_context(Some(CharScopeContext::LinkedEdit));
4459 classifier.is_word(char)
4460 });
4461
4462 if is_word_char {
4463 if let Some(ranges) = self
4464 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4465 {
4466 for (buffer, edits) in ranges {
4467 linked_edits
4468 .entry(buffer.clone())
4469 .or_default()
4470 .extend(edits.into_iter().map(|range| (range, text.clone())));
4471 }
4472 }
4473 } else {
4474 clear_linked_edit_ranges = true;
4475 }
4476 }
4477
4478 new_selections.push((selection.map(|_| anchor), 0));
4479 edits.push((selection.start..selection.end, text.clone()));
4480 }
4481
4482 drop(snapshot);
4483
4484 self.transact(window, cx, |this, window, cx| {
4485 if clear_linked_edit_ranges {
4486 this.linked_edit_ranges.clear();
4487 }
4488 let initial_buffer_versions =
4489 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4490
4491 this.buffer.update(cx, |buffer, cx| {
4492 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4493 });
4494 for (buffer, edits) in linked_edits {
4495 buffer.update(cx, |buffer, cx| {
4496 let snapshot = buffer.snapshot();
4497 let edits = edits
4498 .into_iter()
4499 .map(|(range, text)| {
4500 use text::ToPoint as TP;
4501 let end_point = TP::to_point(&range.end, &snapshot);
4502 let start_point = TP::to_point(&range.start, &snapshot);
4503 (start_point..end_point, text)
4504 })
4505 .sorted_by_key(|(range, _)| range.start);
4506 buffer.edit(edits, None, cx);
4507 })
4508 }
4509 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4510 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4511 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4512 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4513 new_anchor_selections,
4514 &map,
4515 )
4516 .zip(new_selection_deltas)
4517 .map(|(selection, delta)| Selection {
4518 id: selection.id,
4519 start: selection.start + delta,
4520 end: selection.end + delta,
4521 reversed: selection.reversed,
4522 goal: SelectionGoal::None,
4523 })
4524 .collect::<Vec<_>>();
4525
4526 let mut i = 0;
4527 for (position, delta, selection_id, pair) in new_autoclose_regions {
4528 let position = position.to_offset(map.buffer_snapshot()) + delta;
4529 let start = map.buffer_snapshot().anchor_before(position);
4530 let end = map.buffer_snapshot().anchor_after(position);
4531 while let Some(existing_state) = this.autoclose_regions.get(i) {
4532 match existing_state
4533 .range
4534 .start
4535 .cmp(&start, map.buffer_snapshot())
4536 {
4537 Ordering::Less => i += 1,
4538 Ordering::Greater => break,
4539 Ordering::Equal => {
4540 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4541 Ordering::Less => i += 1,
4542 Ordering::Equal => break,
4543 Ordering::Greater => break,
4544 }
4545 }
4546 }
4547 }
4548 this.autoclose_regions.insert(
4549 i,
4550 AutocloseRegion {
4551 selection_id,
4552 range: start..end,
4553 pair,
4554 },
4555 );
4556 }
4557
4558 let had_active_edit_prediction = this.has_active_edit_prediction();
4559 this.change_selections(
4560 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4561 window,
4562 cx,
4563 |s| s.select(new_selections),
4564 );
4565
4566 if !bracket_inserted
4567 && let Some(on_type_format_task) =
4568 this.trigger_on_type_formatting(text.to_string(), window, cx)
4569 {
4570 on_type_format_task.detach_and_log_err(cx);
4571 }
4572
4573 let editor_settings = EditorSettings::get_global(cx);
4574 if bracket_inserted
4575 && (editor_settings.auto_signature_help
4576 || editor_settings.show_signature_help_after_edits)
4577 {
4578 this.show_signature_help(&ShowSignatureHelp, window, cx);
4579 }
4580
4581 let trigger_in_words =
4582 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4583 if this.hard_wrap.is_some() {
4584 let latest: Range<Point> = this.selections.newest(&map).range();
4585 if latest.is_empty()
4586 && this
4587 .buffer()
4588 .read(cx)
4589 .snapshot(cx)
4590 .line_len(MultiBufferRow(latest.start.row))
4591 == latest.start.column
4592 {
4593 this.rewrap_impl(
4594 RewrapOptions {
4595 override_language_settings: true,
4596 preserve_existing_whitespace: true,
4597 },
4598 cx,
4599 )
4600 }
4601 }
4602 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4603 refresh_linked_ranges(this, window, cx);
4604 this.refresh_edit_prediction(true, false, window, cx);
4605 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4606 });
4607 }
4608
4609 fn find_possible_emoji_shortcode_at_position(
4610 snapshot: &MultiBufferSnapshot,
4611 position: Point,
4612 ) -> Option<String> {
4613 let mut chars = Vec::new();
4614 let mut found_colon = false;
4615 for char in snapshot.reversed_chars_at(position).take(100) {
4616 // Found a possible emoji shortcode in the middle of the buffer
4617 if found_colon {
4618 if char.is_whitespace() {
4619 chars.reverse();
4620 return Some(chars.iter().collect());
4621 }
4622 // If the previous character is not a whitespace, we are in the middle of a word
4623 // and we only want to complete the shortcode if the word is made up of other emojis
4624 let mut containing_word = String::new();
4625 for ch in snapshot
4626 .reversed_chars_at(position)
4627 .skip(chars.len() + 1)
4628 .take(100)
4629 {
4630 if ch.is_whitespace() {
4631 break;
4632 }
4633 containing_word.push(ch);
4634 }
4635 let containing_word = containing_word.chars().rev().collect::<String>();
4636 if util::word_consists_of_emojis(containing_word.as_str()) {
4637 chars.reverse();
4638 return Some(chars.iter().collect());
4639 }
4640 }
4641
4642 if char.is_whitespace() || !char.is_ascii() {
4643 return None;
4644 }
4645 if char == ':' {
4646 found_colon = true;
4647 } else {
4648 chars.push(char);
4649 }
4650 }
4651 // Found a possible emoji shortcode at the beginning of the buffer
4652 chars.reverse();
4653 Some(chars.iter().collect())
4654 }
4655
4656 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4657 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4658 self.transact(window, cx, |this, window, cx| {
4659 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4660 let selections = this
4661 .selections
4662 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4663 let multi_buffer = this.buffer.read(cx);
4664 let buffer = multi_buffer.snapshot(cx);
4665 selections
4666 .iter()
4667 .map(|selection| {
4668 let start_point = selection.start.to_point(&buffer);
4669 let mut existing_indent =
4670 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4671 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4672 let start = selection.start;
4673 let end = selection.end;
4674 let selection_is_empty = start == end;
4675 let language_scope = buffer.language_scope_at(start);
4676 let (
4677 comment_delimiter,
4678 doc_delimiter,
4679 insert_extra_newline,
4680 indent_on_newline,
4681 indent_on_extra_newline,
4682 ) = if let Some(language) = &language_scope {
4683 let mut insert_extra_newline =
4684 insert_extra_newline_brackets(&buffer, start..end, language)
4685 || insert_extra_newline_tree_sitter(&buffer, start..end);
4686
4687 // Comment extension on newline is allowed only for cursor selections
4688 let comment_delimiter = maybe!({
4689 if !selection_is_empty {
4690 return None;
4691 }
4692
4693 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4694 return None;
4695 }
4696
4697 let delimiters = language.line_comment_prefixes();
4698 let max_len_of_delimiter =
4699 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4700 let (snapshot, range) =
4701 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4702
4703 let num_of_whitespaces = snapshot
4704 .chars_for_range(range.clone())
4705 .take_while(|c| c.is_whitespace())
4706 .count();
4707 let comment_candidate = snapshot
4708 .chars_for_range(range.clone())
4709 .skip(num_of_whitespaces)
4710 .take(max_len_of_delimiter)
4711 .collect::<String>();
4712 let (delimiter, trimmed_len) = delimiters
4713 .iter()
4714 .filter_map(|delimiter| {
4715 let prefix = delimiter.trim_end();
4716 if comment_candidate.starts_with(prefix) {
4717 Some((delimiter, prefix.len()))
4718 } else {
4719 None
4720 }
4721 })
4722 .max_by_key(|(_, len)| *len)?;
4723
4724 if let Some(BlockCommentConfig {
4725 start: block_start, ..
4726 }) = language.block_comment()
4727 {
4728 let block_start_trimmed = block_start.trim_end();
4729 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4730 let line_content = snapshot
4731 .chars_for_range(range)
4732 .skip(num_of_whitespaces)
4733 .take(block_start_trimmed.len())
4734 .collect::<String>();
4735
4736 if line_content.starts_with(block_start_trimmed) {
4737 return None;
4738 }
4739 }
4740 }
4741
4742 let cursor_is_placed_after_comment_marker =
4743 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4744 if cursor_is_placed_after_comment_marker {
4745 Some(delimiter.clone())
4746 } else {
4747 None
4748 }
4749 });
4750
4751 let mut indent_on_newline = IndentSize::spaces(0);
4752 let mut indent_on_extra_newline = IndentSize::spaces(0);
4753
4754 let doc_delimiter = maybe!({
4755 if !selection_is_empty {
4756 return None;
4757 }
4758
4759 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4760 return None;
4761 }
4762
4763 let BlockCommentConfig {
4764 start: start_tag,
4765 end: end_tag,
4766 prefix: delimiter,
4767 tab_size: len,
4768 } = language.documentation_comment()?;
4769 let is_within_block_comment = buffer
4770 .language_scope_at(start_point)
4771 .is_some_and(|scope| scope.override_name() == Some("comment"));
4772 if !is_within_block_comment {
4773 return None;
4774 }
4775
4776 let (snapshot, range) =
4777 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4778
4779 let num_of_whitespaces = snapshot
4780 .chars_for_range(range.clone())
4781 .take_while(|c| c.is_whitespace())
4782 .count();
4783
4784 // 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.
4785 let column = start_point.column;
4786 let cursor_is_after_start_tag = {
4787 let start_tag_len = start_tag.len();
4788 let start_tag_line = snapshot
4789 .chars_for_range(range.clone())
4790 .skip(num_of_whitespaces)
4791 .take(start_tag_len)
4792 .collect::<String>();
4793 if start_tag_line.starts_with(start_tag.as_ref()) {
4794 num_of_whitespaces + start_tag_len <= column as usize
4795 } else {
4796 false
4797 }
4798 };
4799
4800 let cursor_is_after_delimiter = {
4801 let delimiter_trim = delimiter.trim_end();
4802 let delimiter_line = snapshot
4803 .chars_for_range(range.clone())
4804 .skip(num_of_whitespaces)
4805 .take(delimiter_trim.len())
4806 .collect::<String>();
4807 if delimiter_line.starts_with(delimiter_trim) {
4808 num_of_whitespaces + delimiter_trim.len() <= column as usize
4809 } else {
4810 false
4811 }
4812 };
4813
4814 let cursor_is_before_end_tag_if_exists = {
4815 let mut char_position = 0u32;
4816 let mut end_tag_offset = None;
4817
4818 'outer: for chunk in snapshot.text_for_range(range) {
4819 if let Some(byte_pos) = chunk.find(&**end_tag) {
4820 let chars_before_match =
4821 chunk[..byte_pos].chars().count() as u32;
4822 end_tag_offset =
4823 Some(char_position + chars_before_match);
4824 break 'outer;
4825 }
4826 char_position += chunk.chars().count() as u32;
4827 }
4828
4829 if let Some(end_tag_offset) = end_tag_offset {
4830 let cursor_is_before_end_tag = column <= end_tag_offset;
4831 if cursor_is_after_start_tag {
4832 if cursor_is_before_end_tag {
4833 insert_extra_newline = true;
4834 }
4835 let cursor_is_at_start_of_end_tag =
4836 column == end_tag_offset;
4837 if cursor_is_at_start_of_end_tag {
4838 indent_on_extra_newline.len = *len;
4839 }
4840 }
4841 cursor_is_before_end_tag
4842 } else {
4843 true
4844 }
4845 };
4846
4847 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4848 && cursor_is_before_end_tag_if_exists
4849 {
4850 if cursor_is_after_start_tag {
4851 indent_on_newline.len = *len;
4852 }
4853 Some(delimiter.clone())
4854 } else {
4855 None
4856 }
4857 });
4858
4859 (
4860 comment_delimiter,
4861 doc_delimiter,
4862 insert_extra_newline,
4863 indent_on_newline,
4864 indent_on_extra_newline,
4865 )
4866 } else {
4867 (
4868 None,
4869 None,
4870 false,
4871 IndentSize::default(),
4872 IndentSize::default(),
4873 )
4874 };
4875
4876 let prevent_auto_indent = doc_delimiter.is_some();
4877 let delimiter = comment_delimiter.or(doc_delimiter);
4878
4879 let capacity_for_delimiter =
4880 delimiter.as_deref().map(str::len).unwrap_or_default();
4881 let mut new_text = String::with_capacity(
4882 1 + capacity_for_delimiter
4883 + existing_indent.len as usize
4884 + indent_on_newline.len as usize
4885 + indent_on_extra_newline.len as usize,
4886 );
4887 new_text.push('\n');
4888 new_text.extend(existing_indent.chars());
4889 new_text.extend(indent_on_newline.chars());
4890
4891 if let Some(delimiter) = &delimiter {
4892 new_text.push_str(delimiter);
4893 }
4894
4895 if insert_extra_newline {
4896 new_text.push('\n');
4897 new_text.extend(existing_indent.chars());
4898 new_text.extend(indent_on_extra_newline.chars());
4899 }
4900
4901 let anchor = buffer.anchor_after(end);
4902 let new_selection = selection.map(|_| anchor);
4903 (
4904 ((start..end, new_text), prevent_auto_indent),
4905 (insert_extra_newline, new_selection),
4906 )
4907 })
4908 .unzip()
4909 };
4910
4911 let mut auto_indent_edits = Vec::new();
4912 let mut edits = Vec::new();
4913 for (edit, prevent_auto_indent) in edits_with_flags {
4914 if prevent_auto_indent {
4915 edits.push(edit);
4916 } else {
4917 auto_indent_edits.push(edit);
4918 }
4919 }
4920 if !edits.is_empty() {
4921 this.edit(edits, cx);
4922 }
4923 if !auto_indent_edits.is_empty() {
4924 this.edit_with_autoindent(auto_indent_edits, cx);
4925 }
4926
4927 let buffer = this.buffer.read(cx).snapshot(cx);
4928 let new_selections = selection_info
4929 .into_iter()
4930 .map(|(extra_newline_inserted, new_selection)| {
4931 let mut cursor = new_selection.end.to_point(&buffer);
4932 if extra_newline_inserted {
4933 cursor.row -= 1;
4934 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4935 }
4936 new_selection.map(|_| cursor)
4937 })
4938 .collect();
4939
4940 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4941 this.refresh_edit_prediction(true, false, window, cx);
4942 });
4943 }
4944
4945 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4946 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4947
4948 let buffer = self.buffer.read(cx);
4949 let snapshot = buffer.snapshot(cx);
4950
4951 let mut edits = Vec::new();
4952 let mut rows = Vec::new();
4953
4954 for (rows_inserted, selection) in self
4955 .selections
4956 .all_adjusted(&self.display_snapshot(cx))
4957 .into_iter()
4958 .enumerate()
4959 {
4960 let cursor = selection.head();
4961 let row = cursor.row;
4962
4963 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4964
4965 let newline = "\n".to_string();
4966 edits.push((start_of_line..start_of_line, newline));
4967
4968 rows.push(row + rows_inserted as u32);
4969 }
4970
4971 self.transact(window, cx, |editor, window, cx| {
4972 editor.edit(edits, cx);
4973
4974 editor.change_selections(Default::default(), window, cx, |s| {
4975 let mut index = 0;
4976 s.move_cursors_with(|map, _, _| {
4977 let row = rows[index];
4978 index += 1;
4979
4980 let point = Point::new(row, 0);
4981 let boundary = map.next_line_boundary(point).1;
4982 let clipped = map.clip_point(boundary, Bias::Left);
4983
4984 (clipped, SelectionGoal::None)
4985 });
4986 });
4987
4988 let mut indent_edits = Vec::new();
4989 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4990 for row in rows {
4991 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4992 for (row, indent) in indents {
4993 if indent.len == 0 {
4994 continue;
4995 }
4996
4997 let text = match indent.kind {
4998 IndentKind::Space => " ".repeat(indent.len as usize),
4999 IndentKind::Tab => "\t".repeat(indent.len as usize),
5000 };
5001 let point = Point::new(row.0, 0);
5002 indent_edits.push((point..point, text));
5003 }
5004 }
5005 editor.edit(indent_edits, cx);
5006 });
5007 }
5008
5009 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5010 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5011
5012 let buffer = self.buffer.read(cx);
5013 let snapshot = buffer.snapshot(cx);
5014
5015 let mut edits = Vec::new();
5016 let mut rows = Vec::new();
5017 let mut rows_inserted = 0;
5018
5019 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5020 let cursor = selection.head();
5021 let row = cursor.row;
5022
5023 let point = Point::new(row + 1, 0);
5024 let start_of_line = snapshot.clip_point(point, Bias::Left);
5025
5026 let newline = "\n".to_string();
5027 edits.push((start_of_line..start_of_line, newline));
5028
5029 rows_inserted += 1;
5030 rows.push(row + rows_inserted);
5031 }
5032
5033 self.transact(window, cx, |editor, window, cx| {
5034 editor.edit(edits, cx);
5035
5036 editor.change_selections(Default::default(), window, cx, |s| {
5037 let mut index = 0;
5038 s.move_cursors_with(|map, _, _| {
5039 let row = rows[index];
5040 index += 1;
5041
5042 let point = Point::new(row, 0);
5043 let boundary = map.next_line_boundary(point).1;
5044 let clipped = map.clip_point(boundary, Bias::Left);
5045
5046 (clipped, SelectionGoal::None)
5047 });
5048 });
5049
5050 let mut indent_edits = Vec::new();
5051 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5052 for row in rows {
5053 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5054 for (row, indent) in indents {
5055 if indent.len == 0 {
5056 continue;
5057 }
5058
5059 let text = match indent.kind {
5060 IndentKind::Space => " ".repeat(indent.len as usize),
5061 IndentKind::Tab => "\t".repeat(indent.len as usize),
5062 };
5063 let point = Point::new(row.0, 0);
5064 indent_edits.push((point..point, text));
5065 }
5066 }
5067 editor.edit(indent_edits, cx);
5068 });
5069 }
5070
5071 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5072 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5073 original_indent_columns: Vec::new(),
5074 });
5075 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5076 }
5077
5078 fn insert_with_autoindent_mode(
5079 &mut self,
5080 text: &str,
5081 autoindent_mode: Option<AutoindentMode>,
5082 window: &mut Window,
5083 cx: &mut Context<Self>,
5084 ) {
5085 if self.read_only(cx) {
5086 return;
5087 }
5088
5089 let text: Arc<str> = text.into();
5090 self.transact(window, cx, |this, window, cx| {
5091 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5092 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5093 let anchors = {
5094 let snapshot = buffer.read(cx);
5095 old_selections
5096 .iter()
5097 .map(|s| {
5098 let anchor = snapshot.anchor_after(s.head());
5099 s.map(|_| anchor)
5100 })
5101 .collect::<Vec<_>>()
5102 };
5103 buffer.edit(
5104 old_selections
5105 .iter()
5106 .map(|s| (s.start..s.end, text.clone())),
5107 autoindent_mode,
5108 cx,
5109 );
5110 anchors
5111 });
5112
5113 this.change_selections(Default::default(), window, cx, |s| {
5114 s.select_anchors(selection_anchors);
5115 });
5116
5117 cx.notify();
5118 });
5119 }
5120
5121 fn trigger_completion_on_input(
5122 &mut self,
5123 text: &str,
5124 trigger_in_words: bool,
5125 window: &mut Window,
5126 cx: &mut Context<Self>,
5127 ) {
5128 let completions_source = self
5129 .context_menu
5130 .borrow()
5131 .as_ref()
5132 .and_then(|menu| match menu {
5133 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5134 CodeContextMenu::CodeActions(_) => None,
5135 });
5136
5137 match completions_source {
5138 Some(CompletionsMenuSource::Words { .. }) => {
5139 self.open_or_update_completions_menu(
5140 Some(CompletionsMenuSource::Words {
5141 ignore_threshold: false,
5142 }),
5143 None,
5144 trigger_in_words,
5145 window,
5146 cx,
5147 );
5148 }
5149 _ => self.open_or_update_completions_menu(
5150 None,
5151 Some(text.to_owned()).filter(|x| !x.is_empty()),
5152 true,
5153 window,
5154 cx,
5155 ),
5156 }
5157 }
5158
5159 /// If any empty selections is touching the start of its innermost containing autoclose
5160 /// region, expand it to select the brackets.
5161 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5162 let selections = self
5163 .selections
5164 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5165 let buffer = self.buffer.read(cx).read(cx);
5166 let new_selections = self
5167 .selections_with_autoclose_regions(selections, &buffer)
5168 .map(|(mut selection, region)| {
5169 if !selection.is_empty() {
5170 return selection;
5171 }
5172
5173 if let Some(region) = region {
5174 let mut range = region.range.to_offset(&buffer);
5175 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5176 range.start -= region.pair.start.len();
5177 if buffer.contains_str_at(range.start, ®ion.pair.start)
5178 && buffer.contains_str_at(range.end, ®ion.pair.end)
5179 {
5180 range.end += region.pair.end.len();
5181 selection.start = range.start;
5182 selection.end = range.end;
5183
5184 return selection;
5185 }
5186 }
5187 }
5188
5189 let always_treat_brackets_as_autoclosed = buffer
5190 .language_settings_at(selection.start, cx)
5191 .always_treat_brackets_as_autoclosed;
5192
5193 if !always_treat_brackets_as_autoclosed {
5194 return selection;
5195 }
5196
5197 if let Some(scope) = buffer.language_scope_at(selection.start) {
5198 for (pair, enabled) in scope.brackets() {
5199 if !enabled || !pair.close {
5200 continue;
5201 }
5202
5203 if buffer.contains_str_at(selection.start, &pair.end) {
5204 let pair_start_len = pair.start.len();
5205 if buffer.contains_str_at(
5206 selection.start.saturating_sub_usize(pair_start_len),
5207 &pair.start,
5208 ) {
5209 selection.start -= pair_start_len;
5210 selection.end += pair.end.len();
5211
5212 return selection;
5213 }
5214 }
5215 }
5216 }
5217
5218 selection
5219 })
5220 .collect();
5221
5222 drop(buffer);
5223 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5224 selections.select(new_selections)
5225 });
5226 }
5227
5228 /// Iterate the given selections, and for each one, find the smallest surrounding
5229 /// autoclose region. This uses the ordering of the selections and the autoclose
5230 /// regions to avoid repeated comparisons.
5231 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5232 &'a self,
5233 selections: impl IntoIterator<Item = Selection<D>>,
5234 buffer: &'a MultiBufferSnapshot,
5235 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5236 let mut i = 0;
5237 let mut regions = self.autoclose_regions.as_slice();
5238 selections.into_iter().map(move |selection| {
5239 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5240
5241 let mut enclosing = None;
5242 while let Some(pair_state) = regions.get(i) {
5243 if pair_state.range.end.to_offset(buffer) < range.start {
5244 regions = ®ions[i + 1..];
5245 i = 0;
5246 } else if pair_state.range.start.to_offset(buffer) > range.end {
5247 break;
5248 } else {
5249 if pair_state.selection_id == selection.id {
5250 enclosing = Some(pair_state);
5251 }
5252 i += 1;
5253 }
5254 }
5255
5256 (selection, enclosing)
5257 })
5258 }
5259
5260 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5261 fn invalidate_autoclose_regions(
5262 &mut self,
5263 mut selections: &[Selection<Anchor>],
5264 buffer: &MultiBufferSnapshot,
5265 ) {
5266 self.autoclose_regions.retain(|state| {
5267 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5268 return false;
5269 }
5270
5271 let mut i = 0;
5272 while let Some(selection) = selections.get(i) {
5273 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5274 selections = &selections[1..];
5275 continue;
5276 }
5277 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5278 break;
5279 }
5280 if selection.id == state.selection_id {
5281 return true;
5282 } else {
5283 i += 1;
5284 }
5285 }
5286 false
5287 });
5288 }
5289
5290 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5291 let offset = position.to_offset(buffer);
5292 let (word_range, kind) =
5293 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5294 if offset > word_range.start && kind == Some(CharKind::Word) {
5295 Some(
5296 buffer
5297 .text_for_range(word_range.start..offset)
5298 .collect::<String>(),
5299 )
5300 } else {
5301 None
5302 }
5303 }
5304
5305 pub fn visible_excerpts(
5306 &self,
5307 cx: &mut Context<Editor>,
5308 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5309 let Some(project) = self.project() else {
5310 return HashMap::default();
5311 };
5312 let project = project.read(cx);
5313 let multi_buffer = self.buffer().read(cx);
5314 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5315 let multi_buffer_visible_start = self
5316 .scroll_manager
5317 .anchor()
5318 .anchor
5319 .to_point(&multi_buffer_snapshot);
5320 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5321 multi_buffer_visible_start
5322 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5323 Bias::Left,
5324 );
5325 multi_buffer_snapshot
5326 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5327 .into_iter()
5328 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5329 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5330 let buffer_file = project::File::from_dyn(buffer.file())?;
5331 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5332 let worktree_entry = buffer_worktree
5333 .read(cx)
5334 .entry_for_id(buffer_file.project_entry_id()?)?;
5335 if worktree_entry.is_ignored {
5336 None
5337 } else {
5338 Some((
5339 excerpt_id,
5340 (
5341 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5342 buffer.version().clone(),
5343 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5344 ),
5345 ))
5346 }
5347 })
5348 .collect()
5349 }
5350
5351 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5352 TextLayoutDetails {
5353 text_system: window.text_system().clone(),
5354 editor_style: self.style.clone().unwrap(),
5355 rem_size: window.rem_size(),
5356 scroll_anchor: self.scroll_manager.anchor(),
5357 visible_rows: self.visible_line_count(),
5358 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5359 }
5360 }
5361
5362 fn trigger_on_type_formatting(
5363 &self,
5364 input: String,
5365 window: &mut Window,
5366 cx: &mut Context<Self>,
5367 ) -> Option<Task<Result<()>>> {
5368 if input.len() != 1 {
5369 return None;
5370 }
5371
5372 let project = self.project()?;
5373 let position = self.selections.newest_anchor().head();
5374 let (buffer, buffer_position) = self
5375 .buffer
5376 .read(cx)
5377 .text_anchor_for_position(position, cx)?;
5378
5379 let settings = language_settings::language_settings(
5380 buffer
5381 .read(cx)
5382 .language_at(buffer_position)
5383 .map(|l| l.name()),
5384 buffer.read(cx).file(),
5385 cx,
5386 );
5387 if !settings.use_on_type_format {
5388 return None;
5389 }
5390
5391 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5392 // hence we do LSP request & edit on host side only — add formats to host's history.
5393 let push_to_lsp_host_history = true;
5394 // If this is not the host, append its history with new edits.
5395 let push_to_client_history = project.read(cx).is_via_collab();
5396
5397 let on_type_formatting = project.update(cx, |project, cx| {
5398 project.on_type_format(
5399 buffer.clone(),
5400 buffer_position,
5401 input,
5402 push_to_lsp_host_history,
5403 cx,
5404 )
5405 });
5406 Some(cx.spawn_in(window, async move |editor, cx| {
5407 if let Some(transaction) = on_type_formatting.await? {
5408 if push_to_client_history {
5409 buffer
5410 .update(cx, |buffer, _| {
5411 buffer.push_transaction(transaction, Instant::now());
5412 buffer.finalize_last_transaction();
5413 })
5414 .ok();
5415 }
5416 editor.update(cx, |editor, cx| {
5417 editor.refresh_document_highlights(cx);
5418 })?;
5419 }
5420 Ok(())
5421 }))
5422 }
5423
5424 pub fn show_word_completions(
5425 &mut self,
5426 _: &ShowWordCompletions,
5427 window: &mut Window,
5428 cx: &mut Context<Self>,
5429 ) {
5430 self.open_or_update_completions_menu(
5431 Some(CompletionsMenuSource::Words {
5432 ignore_threshold: true,
5433 }),
5434 None,
5435 false,
5436 window,
5437 cx,
5438 );
5439 }
5440
5441 pub fn show_completions(
5442 &mut self,
5443 _: &ShowCompletions,
5444 window: &mut Window,
5445 cx: &mut Context<Self>,
5446 ) {
5447 self.open_or_update_completions_menu(None, None, false, window, cx);
5448 }
5449
5450 fn open_or_update_completions_menu(
5451 &mut self,
5452 requested_source: Option<CompletionsMenuSource>,
5453 trigger: Option<String>,
5454 trigger_in_words: bool,
5455 window: &mut Window,
5456 cx: &mut Context<Self>,
5457 ) {
5458 if self.pending_rename.is_some() {
5459 return;
5460 }
5461
5462 let completions_source = self
5463 .context_menu
5464 .borrow()
5465 .as_ref()
5466 .and_then(|menu| match menu {
5467 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5468 CodeContextMenu::CodeActions(_) => None,
5469 });
5470
5471 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5472
5473 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5474 // inserted and selected. To handle that case, the start of the selection is used so that
5475 // the menu starts with all choices.
5476 let position = self
5477 .selections
5478 .newest_anchor()
5479 .start
5480 .bias_right(&multibuffer_snapshot);
5481 if position.diff_base_anchor.is_some() {
5482 return;
5483 }
5484 let buffer_position = multibuffer_snapshot.anchor_before(position);
5485 let Some(buffer) = buffer_position
5486 .text_anchor
5487 .buffer_id
5488 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5489 else {
5490 return;
5491 };
5492 let buffer_snapshot = buffer.read(cx).snapshot();
5493
5494 let query: Option<Arc<String>> =
5495 Self::completion_query(&multibuffer_snapshot, buffer_position)
5496 .map(|query| query.into());
5497
5498 drop(multibuffer_snapshot);
5499
5500 // Hide the current completions menu when query is empty. Without this, cached
5501 // completions from before the trigger char may be reused (#32774).
5502 if query.is_none() {
5503 let menu_is_open = matches!(
5504 self.context_menu.borrow().as_ref(),
5505 Some(CodeContextMenu::Completions(_))
5506 );
5507 if menu_is_open {
5508 self.hide_context_menu(window, cx);
5509 }
5510 }
5511
5512 let mut ignore_word_threshold = false;
5513 let provider = match requested_source {
5514 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5515 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5516 ignore_word_threshold = ignore_threshold;
5517 None
5518 }
5519 Some(CompletionsMenuSource::SnippetChoices)
5520 | Some(CompletionsMenuSource::SnippetsOnly) => {
5521 log::error!("bug: SnippetChoices requested_source is not handled");
5522 None
5523 }
5524 };
5525
5526 let sort_completions = provider
5527 .as_ref()
5528 .is_some_and(|provider| provider.sort_completions());
5529
5530 let filter_completions = provider
5531 .as_ref()
5532 .is_none_or(|provider| provider.filter_completions());
5533
5534 let was_snippets_only = matches!(
5535 completions_source,
5536 Some(CompletionsMenuSource::SnippetsOnly)
5537 );
5538
5539 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5540 if filter_completions {
5541 menu.filter(
5542 query.clone().unwrap_or_default(),
5543 buffer_position.text_anchor,
5544 &buffer,
5545 provider.clone(),
5546 window,
5547 cx,
5548 );
5549 }
5550 // When `is_incomplete` is false, no need to re-query completions when the current query
5551 // is a suffix of the initial query.
5552 let was_complete = !menu.is_incomplete;
5553 if was_complete && !was_snippets_only {
5554 // If the new query is a suffix of the old query (typing more characters) and
5555 // the previous result was complete, the existing completions can be filtered.
5556 //
5557 // Note that snippet completions are always complete.
5558 let query_matches = match (&menu.initial_query, &query) {
5559 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5560 (None, _) => true,
5561 _ => false,
5562 };
5563 if query_matches {
5564 let position_matches = if menu.initial_position == position {
5565 true
5566 } else {
5567 let snapshot = self.buffer.read(cx).read(cx);
5568 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5569 };
5570 if position_matches {
5571 return;
5572 }
5573 }
5574 }
5575 };
5576
5577 let Anchor {
5578 excerpt_id: buffer_excerpt_id,
5579 text_anchor: buffer_position,
5580 ..
5581 } = buffer_position;
5582
5583 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5584 buffer_snapshot.surrounding_word(buffer_position, None)
5585 {
5586 let word_to_exclude = buffer_snapshot
5587 .text_for_range(word_range.clone())
5588 .collect::<String>();
5589 (
5590 buffer_snapshot.anchor_before(word_range.start)
5591 ..buffer_snapshot.anchor_after(buffer_position),
5592 Some(word_to_exclude),
5593 )
5594 } else {
5595 (buffer_position..buffer_position, None)
5596 };
5597
5598 let language = buffer_snapshot
5599 .language_at(buffer_position)
5600 .map(|language| language.name());
5601
5602 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5603 .completions
5604 .clone();
5605
5606 let show_completion_documentation = buffer_snapshot
5607 .settings_at(buffer_position, cx)
5608 .show_completion_documentation;
5609
5610 // The document can be large, so stay in reasonable bounds when searching for words,
5611 // otherwise completion pop-up might be slow to appear.
5612 const WORD_LOOKUP_ROWS: u32 = 5_000;
5613 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5614 let min_word_search = buffer_snapshot.clip_point(
5615 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5616 Bias::Left,
5617 );
5618 let max_word_search = buffer_snapshot.clip_point(
5619 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5620 Bias::Right,
5621 );
5622 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5623 ..buffer_snapshot.point_to_offset(max_word_search);
5624
5625 let skip_digits = query
5626 .as_ref()
5627 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5628
5629 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5630 trigger.as_ref().is_none_or(|trigger| {
5631 provider.is_completion_trigger(
5632 &buffer,
5633 position.text_anchor,
5634 trigger,
5635 trigger_in_words,
5636 completions_source.is_some(),
5637 cx,
5638 )
5639 })
5640 });
5641
5642 let provider_responses = if let Some(provider) = &provider
5643 && load_provider_completions
5644 {
5645 let trigger_character =
5646 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5647 let completion_context = CompletionContext {
5648 trigger_kind: match &trigger_character {
5649 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5650 None => CompletionTriggerKind::INVOKED,
5651 },
5652 trigger_character,
5653 };
5654
5655 provider.completions(
5656 buffer_excerpt_id,
5657 &buffer,
5658 buffer_position,
5659 completion_context,
5660 window,
5661 cx,
5662 )
5663 } else {
5664 Task::ready(Ok(Vec::new()))
5665 };
5666
5667 let load_word_completions = if !self.word_completions_enabled {
5668 false
5669 } else if requested_source
5670 == Some(CompletionsMenuSource::Words {
5671 ignore_threshold: true,
5672 })
5673 {
5674 true
5675 } else {
5676 load_provider_completions
5677 && completion_settings.words != WordsCompletionMode::Disabled
5678 && (ignore_word_threshold || {
5679 let words_min_length = completion_settings.words_min_length;
5680 // check whether word has at least `words_min_length` characters
5681 let query_chars = query.iter().flat_map(|q| q.chars());
5682 query_chars.take(words_min_length).count() == words_min_length
5683 })
5684 };
5685
5686 let mut words = if load_word_completions {
5687 cx.background_spawn({
5688 let buffer_snapshot = buffer_snapshot.clone();
5689 async move {
5690 buffer_snapshot.words_in_range(WordsQuery {
5691 fuzzy_contents: None,
5692 range: word_search_range,
5693 skip_digits,
5694 })
5695 }
5696 })
5697 } else {
5698 Task::ready(BTreeMap::default())
5699 };
5700
5701 let snippets = if let Some(provider) = &provider
5702 && provider.show_snippets()
5703 && let Some(project) = self.project()
5704 {
5705 let char_classifier = buffer_snapshot
5706 .char_classifier_at(buffer_position)
5707 .scope_context(Some(CharScopeContext::Completion));
5708 project.update(cx, |project, cx| {
5709 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5710 })
5711 } else {
5712 Task::ready(Ok(CompletionResponse {
5713 completions: Vec::new(),
5714 display_options: Default::default(),
5715 is_incomplete: false,
5716 }))
5717 };
5718
5719 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5720
5721 let id = post_inc(&mut self.next_completion_id);
5722 let task = cx.spawn_in(window, async move |editor, cx| {
5723 let Ok(()) = editor.update(cx, |this, _| {
5724 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5725 }) else {
5726 return;
5727 };
5728
5729 // TODO: Ideally completions from different sources would be selectively re-queried, so
5730 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5731 let mut completions = Vec::new();
5732 let mut is_incomplete = false;
5733 let mut display_options: Option<CompletionDisplayOptions> = None;
5734 if let Some(provider_responses) = provider_responses.await.log_err()
5735 && !provider_responses.is_empty()
5736 {
5737 for response in provider_responses {
5738 completions.extend(response.completions);
5739 is_incomplete = is_incomplete || response.is_incomplete;
5740 match display_options.as_mut() {
5741 None => {
5742 display_options = Some(response.display_options);
5743 }
5744 Some(options) => options.merge(&response.display_options),
5745 }
5746 }
5747 if completion_settings.words == WordsCompletionMode::Fallback {
5748 words = Task::ready(BTreeMap::default());
5749 }
5750 }
5751 let display_options = display_options.unwrap_or_default();
5752
5753 let mut words = words.await;
5754 if let Some(word_to_exclude) = &word_to_exclude {
5755 words.remove(word_to_exclude);
5756 }
5757 for lsp_completion in &completions {
5758 words.remove(&lsp_completion.new_text);
5759 }
5760 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5761 replace_range: word_replace_range.clone(),
5762 new_text: word.clone(),
5763 label: CodeLabel::plain(word, None),
5764 match_start: None,
5765 snippet_deduplication_key: None,
5766 icon_path: None,
5767 documentation: None,
5768 source: CompletionSource::BufferWord {
5769 word_range,
5770 resolved: false,
5771 },
5772 insert_text_mode: Some(InsertTextMode::AS_IS),
5773 confirm: None,
5774 }));
5775
5776 completions.extend(
5777 snippets
5778 .await
5779 .into_iter()
5780 .flat_map(|response| response.completions),
5781 );
5782
5783 let menu = if completions.is_empty() {
5784 None
5785 } else {
5786 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5787 let languages = editor
5788 .workspace
5789 .as_ref()
5790 .and_then(|(workspace, _)| workspace.upgrade())
5791 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5792 let menu = CompletionsMenu::new(
5793 id,
5794 requested_source.unwrap_or(if load_provider_completions {
5795 CompletionsMenuSource::Normal
5796 } else {
5797 CompletionsMenuSource::SnippetsOnly
5798 }),
5799 sort_completions,
5800 show_completion_documentation,
5801 position,
5802 query.clone(),
5803 is_incomplete,
5804 buffer.clone(),
5805 completions.into(),
5806 display_options,
5807 snippet_sort_order,
5808 languages,
5809 language,
5810 cx,
5811 );
5812
5813 let query = if filter_completions { query } else { None };
5814 let matches_task = menu.do_async_filtering(
5815 query.unwrap_or_default(),
5816 buffer_position,
5817 &buffer,
5818 cx,
5819 );
5820 (menu, matches_task)
5821 }) else {
5822 return;
5823 };
5824
5825 let matches = matches_task.await;
5826
5827 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5828 // Newer menu already set, so exit.
5829 if let Some(CodeContextMenu::Completions(prev_menu)) =
5830 editor.context_menu.borrow().as_ref()
5831 && prev_menu.id > id
5832 {
5833 return;
5834 };
5835
5836 // Only valid to take prev_menu because either the new menu is immediately set
5837 // below, or the menu is hidden.
5838 if let Some(CodeContextMenu::Completions(prev_menu)) =
5839 editor.context_menu.borrow_mut().take()
5840 {
5841 let position_matches =
5842 if prev_menu.initial_position == menu.initial_position {
5843 true
5844 } else {
5845 let snapshot = editor.buffer.read(cx).read(cx);
5846 prev_menu.initial_position.to_offset(&snapshot)
5847 == menu.initial_position.to_offset(&snapshot)
5848 };
5849 if position_matches {
5850 // Preserve markdown cache before `set_filter_results` because it will
5851 // try to populate the documentation cache.
5852 menu.preserve_markdown_cache(prev_menu);
5853 }
5854 };
5855
5856 menu.set_filter_results(matches, provider, window, cx);
5857 }) else {
5858 return;
5859 };
5860
5861 menu.visible().then_some(menu)
5862 };
5863
5864 editor
5865 .update_in(cx, |editor, window, cx| {
5866 if editor.focus_handle.is_focused(window)
5867 && let Some(menu) = menu
5868 {
5869 *editor.context_menu.borrow_mut() =
5870 Some(CodeContextMenu::Completions(menu));
5871
5872 crate::hover_popover::hide_hover(editor, cx);
5873 if editor.show_edit_predictions_in_menu() {
5874 editor.update_visible_edit_prediction(window, cx);
5875 } else {
5876 editor.discard_edit_prediction(false, cx);
5877 }
5878
5879 cx.notify();
5880 return;
5881 }
5882
5883 if editor.completion_tasks.len() <= 1 {
5884 // If there are no more completion tasks and the last menu was empty, we should hide it.
5885 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5886 // If it was already hidden and we don't show edit predictions in the menu,
5887 // we should also show the edit prediction when available.
5888 if was_hidden && editor.show_edit_predictions_in_menu() {
5889 editor.update_visible_edit_prediction(window, cx);
5890 }
5891 }
5892 })
5893 .ok();
5894 });
5895
5896 self.completion_tasks.push((id, task));
5897 }
5898
5899 #[cfg(feature = "test-support")]
5900 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5901 let menu = self.context_menu.borrow();
5902 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5903 let completions = menu.completions.borrow();
5904 Some(completions.to_vec())
5905 } else {
5906 None
5907 }
5908 }
5909
5910 pub fn with_completions_menu_matching_id<R>(
5911 &self,
5912 id: CompletionId,
5913 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5914 ) -> R {
5915 let mut context_menu = self.context_menu.borrow_mut();
5916 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5917 return f(None);
5918 };
5919 if completions_menu.id != id {
5920 return f(None);
5921 }
5922 f(Some(completions_menu))
5923 }
5924
5925 pub fn confirm_completion(
5926 &mut self,
5927 action: &ConfirmCompletion,
5928 window: &mut Window,
5929 cx: &mut Context<Self>,
5930 ) -> Option<Task<Result<()>>> {
5931 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5932 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5933 }
5934
5935 pub fn confirm_completion_insert(
5936 &mut self,
5937 _: &ConfirmCompletionInsert,
5938 window: &mut Window,
5939 cx: &mut Context<Self>,
5940 ) -> Option<Task<Result<()>>> {
5941 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5942 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5943 }
5944
5945 pub fn confirm_completion_replace(
5946 &mut self,
5947 _: &ConfirmCompletionReplace,
5948 window: &mut Window,
5949 cx: &mut Context<Self>,
5950 ) -> Option<Task<Result<()>>> {
5951 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5952 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5953 }
5954
5955 pub fn compose_completion(
5956 &mut self,
5957 action: &ComposeCompletion,
5958 window: &mut Window,
5959 cx: &mut Context<Self>,
5960 ) -> Option<Task<Result<()>>> {
5961 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5962 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5963 }
5964
5965 fn do_completion(
5966 &mut self,
5967 item_ix: Option<usize>,
5968 intent: CompletionIntent,
5969 window: &mut Window,
5970 cx: &mut Context<Editor>,
5971 ) -> Option<Task<Result<()>>> {
5972 use language::ToOffset as _;
5973
5974 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5975 else {
5976 return None;
5977 };
5978
5979 let candidate_id = {
5980 let entries = completions_menu.entries.borrow();
5981 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5982 if self.show_edit_predictions_in_menu() {
5983 self.discard_edit_prediction(true, cx);
5984 }
5985 mat.candidate_id
5986 };
5987
5988 let completion = completions_menu
5989 .completions
5990 .borrow()
5991 .get(candidate_id)?
5992 .clone();
5993 cx.stop_propagation();
5994
5995 let buffer_handle = completions_menu.buffer.clone();
5996
5997 let CompletionEdit {
5998 new_text,
5999 snippet,
6000 replace_range,
6001 } = process_completion_for_edit(
6002 &completion,
6003 intent,
6004 &buffer_handle,
6005 &completions_menu.initial_position.text_anchor,
6006 cx,
6007 );
6008
6009 let buffer = buffer_handle.read(cx);
6010 let snapshot = self.buffer.read(cx).snapshot(cx);
6011 let newest_anchor = self.selections.newest_anchor();
6012 let replace_range_multibuffer = {
6013 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6014 excerpt.map_range_from_buffer(replace_range.clone())
6015 };
6016 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6017 return None;
6018 }
6019
6020 let old_text = buffer
6021 .text_for_range(replace_range.clone())
6022 .collect::<String>();
6023 let lookbehind = newest_anchor
6024 .start
6025 .text_anchor
6026 .to_offset(buffer)
6027 .saturating_sub(replace_range.start.0);
6028 let lookahead = replace_range
6029 .end
6030 .0
6031 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6032 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6033 let suffix = &old_text[lookbehind.min(old_text.len())..];
6034
6035 let selections = self
6036 .selections
6037 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6038 let mut ranges = Vec::new();
6039 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6040
6041 for selection in &selections {
6042 let range = if selection.id == newest_anchor.id {
6043 replace_range_multibuffer.clone()
6044 } else {
6045 let mut range = selection.range();
6046
6047 // if prefix is present, don't duplicate it
6048 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6049 range.start = range.start.saturating_sub_usize(lookbehind);
6050
6051 // if suffix is also present, mimic the newest cursor and replace it
6052 if selection.id != newest_anchor.id
6053 && snapshot.contains_str_at(range.end, suffix)
6054 {
6055 range.end += lookahead;
6056 }
6057 }
6058 range
6059 };
6060
6061 ranges.push(range.clone());
6062
6063 if !self.linked_edit_ranges.is_empty() {
6064 let start_anchor = snapshot.anchor_before(range.start);
6065 let end_anchor = snapshot.anchor_after(range.end);
6066 if let Some(ranges) = self
6067 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6068 {
6069 for (buffer, edits) in ranges {
6070 linked_edits
6071 .entry(buffer.clone())
6072 .or_default()
6073 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6074 }
6075 }
6076 }
6077 }
6078
6079 let common_prefix_len = old_text
6080 .chars()
6081 .zip(new_text.chars())
6082 .take_while(|(a, b)| a == b)
6083 .map(|(a, _)| a.len_utf8())
6084 .sum::<usize>();
6085
6086 cx.emit(EditorEvent::InputHandled {
6087 utf16_range_to_replace: None,
6088 text: new_text[common_prefix_len..].into(),
6089 });
6090
6091 self.transact(window, cx, |editor, window, cx| {
6092 if let Some(mut snippet) = snippet {
6093 snippet.text = new_text.to_string();
6094 editor
6095 .insert_snippet(&ranges, snippet, window, cx)
6096 .log_err();
6097 } else {
6098 editor.buffer.update(cx, |multi_buffer, cx| {
6099 let auto_indent = match completion.insert_text_mode {
6100 Some(InsertTextMode::AS_IS) => None,
6101 _ => editor.autoindent_mode.clone(),
6102 };
6103 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6104 multi_buffer.edit(edits, auto_indent, cx);
6105 });
6106 }
6107 for (buffer, edits) in linked_edits {
6108 buffer.update(cx, |buffer, cx| {
6109 let snapshot = buffer.snapshot();
6110 let edits = edits
6111 .into_iter()
6112 .map(|(range, text)| {
6113 use text::ToPoint as TP;
6114 let end_point = TP::to_point(&range.end, &snapshot);
6115 let start_point = TP::to_point(&range.start, &snapshot);
6116 (start_point..end_point, text)
6117 })
6118 .sorted_by_key(|(range, _)| range.start);
6119 buffer.edit(edits, None, cx);
6120 })
6121 }
6122
6123 editor.refresh_edit_prediction(true, false, window, cx);
6124 });
6125 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6126
6127 let show_new_completions_on_confirm = completion
6128 .confirm
6129 .as_ref()
6130 .is_some_and(|confirm| confirm(intent, window, cx));
6131 if show_new_completions_on_confirm {
6132 self.open_or_update_completions_menu(None, None, false, window, cx);
6133 }
6134
6135 let provider = self.completion_provider.as_ref()?;
6136 drop(completion);
6137 let apply_edits = provider.apply_additional_edits_for_completion(
6138 buffer_handle,
6139 completions_menu.completions.clone(),
6140 candidate_id,
6141 true,
6142 cx,
6143 );
6144
6145 let editor_settings = EditorSettings::get_global(cx);
6146 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6147 // After the code completion is finished, users often want to know what signatures are needed.
6148 // so we should automatically call signature_help
6149 self.show_signature_help(&ShowSignatureHelp, window, cx);
6150 }
6151
6152 Some(cx.foreground_executor().spawn(async move {
6153 apply_edits.await?;
6154 Ok(())
6155 }))
6156 }
6157
6158 pub fn toggle_code_actions(
6159 &mut self,
6160 action: &ToggleCodeActions,
6161 window: &mut Window,
6162 cx: &mut Context<Self>,
6163 ) {
6164 let quick_launch = action.quick_launch;
6165 let mut context_menu = self.context_menu.borrow_mut();
6166 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6167 if code_actions.deployed_from == action.deployed_from {
6168 // Toggle if we're selecting the same one
6169 *context_menu = None;
6170 cx.notify();
6171 return;
6172 } else {
6173 // Otherwise, clear it and start a new one
6174 *context_menu = None;
6175 cx.notify();
6176 }
6177 }
6178 drop(context_menu);
6179 let snapshot = self.snapshot(window, cx);
6180 let deployed_from = action.deployed_from.clone();
6181 let action = action.clone();
6182 self.completion_tasks.clear();
6183 self.discard_edit_prediction(false, cx);
6184
6185 let multibuffer_point = match &action.deployed_from {
6186 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6187 DisplayPoint::new(*row, 0).to_point(&snapshot)
6188 }
6189 _ => self
6190 .selections
6191 .newest::<Point>(&snapshot.display_snapshot)
6192 .head(),
6193 };
6194 let Some((buffer, buffer_row)) = snapshot
6195 .buffer_snapshot()
6196 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6197 .and_then(|(buffer_snapshot, range)| {
6198 self.buffer()
6199 .read(cx)
6200 .buffer(buffer_snapshot.remote_id())
6201 .map(|buffer| (buffer, range.start.row))
6202 })
6203 else {
6204 return;
6205 };
6206 let buffer_id = buffer.read(cx).remote_id();
6207 let tasks = self
6208 .tasks
6209 .get(&(buffer_id, buffer_row))
6210 .map(|t| Arc::new(t.to_owned()));
6211
6212 if !self.focus_handle.is_focused(window) {
6213 return;
6214 }
6215 let project = self.project.clone();
6216
6217 let code_actions_task = match deployed_from {
6218 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6219 _ => self.code_actions(buffer_row, window, cx),
6220 };
6221
6222 let runnable_task = match deployed_from {
6223 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6224 _ => {
6225 let mut task_context_task = Task::ready(None);
6226 if let Some(tasks) = &tasks
6227 && let Some(project) = project
6228 {
6229 task_context_task =
6230 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6231 }
6232
6233 cx.spawn_in(window, {
6234 let buffer = buffer.clone();
6235 async move |editor, cx| {
6236 let task_context = task_context_task.await;
6237
6238 let resolved_tasks =
6239 tasks
6240 .zip(task_context.clone())
6241 .map(|(tasks, task_context)| ResolvedTasks {
6242 templates: tasks.resolve(&task_context).collect(),
6243 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6244 multibuffer_point.row,
6245 tasks.column,
6246 )),
6247 });
6248 let debug_scenarios = editor
6249 .update(cx, |editor, cx| {
6250 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6251 })?
6252 .await;
6253 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6254 }
6255 })
6256 }
6257 };
6258
6259 cx.spawn_in(window, async move |editor, cx| {
6260 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6261 let code_actions = code_actions_task.await;
6262 let spawn_straight_away = quick_launch
6263 && resolved_tasks
6264 .as_ref()
6265 .is_some_and(|tasks| tasks.templates.len() == 1)
6266 && code_actions
6267 .as_ref()
6268 .is_none_or(|actions| actions.is_empty())
6269 && debug_scenarios.is_empty();
6270
6271 editor.update_in(cx, |editor, window, cx| {
6272 crate::hover_popover::hide_hover(editor, cx);
6273 let actions = CodeActionContents::new(
6274 resolved_tasks,
6275 code_actions,
6276 debug_scenarios,
6277 task_context.unwrap_or_default(),
6278 );
6279
6280 // Don't show the menu if there are no actions available
6281 if actions.is_empty() {
6282 cx.notify();
6283 return Task::ready(Ok(()));
6284 }
6285
6286 *editor.context_menu.borrow_mut() =
6287 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6288 buffer,
6289 actions,
6290 selected_item: Default::default(),
6291 scroll_handle: UniformListScrollHandle::default(),
6292 deployed_from,
6293 }));
6294 cx.notify();
6295 if spawn_straight_away
6296 && let Some(task) = editor.confirm_code_action(
6297 &ConfirmCodeAction { item_ix: Some(0) },
6298 window,
6299 cx,
6300 )
6301 {
6302 return task;
6303 }
6304
6305 Task::ready(Ok(()))
6306 })
6307 })
6308 .detach_and_log_err(cx);
6309 }
6310
6311 fn debug_scenarios(
6312 &mut self,
6313 resolved_tasks: &Option<ResolvedTasks>,
6314 buffer: &Entity<Buffer>,
6315 cx: &mut App,
6316 ) -> Task<Vec<task::DebugScenario>> {
6317 maybe!({
6318 let project = self.project()?;
6319 let dap_store = project.read(cx).dap_store();
6320 let mut scenarios = vec![];
6321 let resolved_tasks = resolved_tasks.as_ref()?;
6322 let buffer = buffer.read(cx);
6323 let language = buffer.language()?;
6324 let file = buffer.file();
6325 let debug_adapter = language_settings(language.name().into(), file, cx)
6326 .debuggers
6327 .first()
6328 .map(SharedString::from)
6329 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6330
6331 dap_store.update(cx, |dap_store, cx| {
6332 for (_, task) in &resolved_tasks.templates {
6333 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6334 task.original_task().clone(),
6335 debug_adapter.clone().into(),
6336 task.display_label().to_owned().into(),
6337 cx,
6338 );
6339 scenarios.push(maybe_scenario);
6340 }
6341 });
6342 Some(cx.background_spawn(async move {
6343 futures::future::join_all(scenarios)
6344 .await
6345 .into_iter()
6346 .flatten()
6347 .collect::<Vec<_>>()
6348 }))
6349 })
6350 .unwrap_or_else(|| Task::ready(vec![]))
6351 }
6352
6353 fn code_actions(
6354 &mut self,
6355 buffer_row: u32,
6356 window: &mut Window,
6357 cx: &mut Context<Self>,
6358 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6359 let mut task = self.code_actions_task.take();
6360 cx.spawn_in(window, async move |editor, cx| {
6361 while let Some(prev_task) = task {
6362 prev_task.await.log_err();
6363 task = editor
6364 .update(cx, |this, _| this.code_actions_task.take())
6365 .ok()?;
6366 }
6367
6368 editor
6369 .update(cx, |editor, cx| {
6370 editor
6371 .available_code_actions
6372 .clone()
6373 .and_then(|(location, code_actions)| {
6374 let snapshot = location.buffer.read(cx).snapshot();
6375 let point_range = location.range.to_point(&snapshot);
6376 let point_range = point_range.start.row..=point_range.end.row;
6377 if point_range.contains(&buffer_row) {
6378 Some(code_actions)
6379 } else {
6380 None
6381 }
6382 })
6383 })
6384 .ok()
6385 .flatten()
6386 })
6387 }
6388
6389 pub fn confirm_code_action(
6390 &mut self,
6391 action: &ConfirmCodeAction,
6392 window: &mut Window,
6393 cx: &mut Context<Self>,
6394 ) -> Option<Task<Result<()>>> {
6395 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6396
6397 let actions_menu =
6398 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6399 menu
6400 } else {
6401 return None;
6402 };
6403
6404 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6405 let action = actions_menu.actions.get(action_ix)?;
6406 let title = action.label();
6407 let buffer = actions_menu.buffer;
6408 let workspace = self.workspace()?;
6409
6410 match action {
6411 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6412 workspace.update(cx, |workspace, cx| {
6413 workspace.schedule_resolved_task(
6414 task_source_kind,
6415 resolved_task,
6416 false,
6417 window,
6418 cx,
6419 );
6420
6421 Some(Task::ready(Ok(())))
6422 })
6423 }
6424 CodeActionsItem::CodeAction {
6425 excerpt_id,
6426 action,
6427 provider,
6428 } => {
6429 let apply_code_action =
6430 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6431 let workspace = workspace.downgrade();
6432 Some(cx.spawn_in(window, async move |editor, cx| {
6433 let project_transaction = apply_code_action.await?;
6434 Self::open_project_transaction(
6435 &editor,
6436 workspace,
6437 project_transaction,
6438 title,
6439 cx,
6440 )
6441 .await
6442 }))
6443 }
6444 CodeActionsItem::DebugScenario(scenario) => {
6445 let context = actions_menu.actions.context;
6446
6447 workspace.update(cx, |workspace, cx| {
6448 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6449 workspace.start_debug_session(
6450 scenario,
6451 context,
6452 Some(buffer),
6453 None,
6454 window,
6455 cx,
6456 );
6457 });
6458 Some(Task::ready(Ok(())))
6459 }
6460 }
6461 }
6462
6463 pub async fn open_project_transaction(
6464 editor: &WeakEntity<Editor>,
6465 workspace: WeakEntity<Workspace>,
6466 transaction: ProjectTransaction,
6467 title: String,
6468 cx: &mut AsyncWindowContext,
6469 ) -> Result<()> {
6470 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6471 cx.update(|_, cx| {
6472 entries.sort_unstable_by_key(|(buffer, _)| {
6473 buffer.read(cx).file().map(|f| f.path().clone())
6474 });
6475 })?;
6476 if entries.is_empty() {
6477 return Ok(());
6478 }
6479
6480 // If the project transaction's edits are all contained within this editor, then
6481 // avoid opening a new editor to display them.
6482
6483 if let [(buffer, transaction)] = &*entries {
6484 let excerpt = editor.update(cx, |editor, cx| {
6485 editor
6486 .buffer()
6487 .read(cx)
6488 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6489 })?;
6490 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6491 && excerpted_buffer == *buffer
6492 {
6493 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6494 let excerpt_range = excerpt_range.to_offset(buffer);
6495 buffer
6496 .edited_ranges_for_transaction::<usize>(transaction)
6497 .all(|range| {
6498 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6499 })
6500 })?;
6501
6502 if all_edits_within_excerpt {
6503 return Ok(());
6504 }
6505 }
6506 }
6507
6508 let mut ranges_to_highlight = Vec::new();
6509 let excerpt_buffer = cx.new(|cx| {
6510 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6511 for (buffer_handle, transaction) in &entries {
6512 let edited_ranges = buffer_handle
6513 .read(cx)
6514 .edited_ranges_for_transaction::<Point>(transaction)
6515 .collect::<Vec<_>>();
6516 let (ranges, _) = multibuffer.set_excerpts_for_path(
6517 PathKey::for_buffer(buffer_handle, cx),
6518 buffer_handle.clone(),
6519 edited_ranges,
6520 multibuffer_context_lines(cx),
6521 cx,
6522 );
6523
6524 ranges_to_highlight.extend(ranges);
6525 }
6526 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6527 multibuffer
6528 })?;
6529
6530 workspace.update_in(cx, |workspace, window, cx| {
6531 let project = workspace.project().clone();
6532 let editor =
6533 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6534 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6535 editor.update(cx, |editor, cx| {
6536 editor.highlight_background::<Self>(
6537 &ranges_to_highlight,
6538 |theme| theme.colors().editor_highlighted_line_background,
6539 cx,
6540 );
6541 });
6542 })?;
6543
6544 Ok(())
6545 }
6546
6547 pub fn clear_code_action_providers(&mut self) {
6548 self.code_action_providers.clear();
6549 self.available_code_actions.take();
6550 }
6551
6552 pub fn add_code_action_provider(
6553 &mut self,
6554 provider: Rc<dyn CodeActionProvider>,
6555 window: &mut Window,
6556 cx: &mut Context<Self>,
6557 ) {
6558 if self
6559 .code_action_providers
6560 .iter()
6561 .any(|existing_provider| existing_provider.id() == provider.id())
6562 {
6563 return;
6564 }
6565
6566 self.code_action_providers.push(provider);
6567 self.refresh_code_actions(window, cx);
6568 }
6569
6570 pub fn remove_code_action_provider(
6571 &mut self,
6572 id: Arc<str>,
6573 window: &mut Window,
6574 cx: &mut Context<Self>,
6575 ) {
6576 self.code_action_providers
6577 .retain(|provider| provider.id() != id);
6578 self.refresh_code_actions(window, cx);
6579 }
6580
6581 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6582 !self.code_action_providers.is_empty()
6583 && EditorSettings::get_global(cx).toolbar.code_actions
6584 }
6585
6586 pub fn has_available_code_actions(&self) -> bool {
6587 self.available_code_actions
6588 .as_ref()
6589 .is_some_and(|(_, actions)| !actions.is_empty())
6590 }
6591
6592 fn render_inline_code_actions(
6593 &self,
6594 icon_size: ui::IconSize,
6595 display_row: DisplayRow,
6596 is_active: bool,
6597 cx: &mut Context<Self>,
6598 ) -> AnyElement {
6599 let show_tooltip = !self.context_menu_visible();
6600 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6601 .icon_size(icon_size)
6602 .shape(ui::IconButtonShape::Square)
6603 .icon_color(ui::Color::Hidden)
6604 .toggle_state(is_active)
6605 .when(show_tooltip, |this| {
6606 this.tooltip({
6607 let focus_handle = self.focus_handle.clone();
6608 move |_window, cx| {
6609 Tooltip::for_action_in(
6610 "Toggle Code Actions",
6611 &ToggleCodeActions {
6612 deployed_from: None,
6613 quick_launch: false,
6614 },
6615 &focus_handle,
6616 cx,
6617 )
6618 }
6619 })
6620 })
6621 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6622 window.focus(&editor.focus_handle(cx));
6623 editor.toggle_code_actions(
6624 &crate::actions::ToggleCodeActions {
6625 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6626 display_row,
6627 )),
6628 quick_launch: false,
6629 },
6630 window,
6631 cx,
6632 );
6633 }))
6634 .into_any_element()
6635 }
6636
6637 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6638 &self.context_menu
6639 }
6640
6641 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6642 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6643 cx.background_executor()
6644 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6645 .await;
6646
6647 let (start_buffer, start, _, end, newest_selection) = this
6648 .update(cx, |this, cx| {
6649 let newest_selection = this.selections.newest_anchor().clone();
6650 if newest_selection.head().diff_base_anchor.is_some() {
6651 return None;
6652 }
6653 let display_snapshot = this.display_snapshot(cx);
6654 let newest_selection_adjusted =
6655 this.selections.newest_adjusted(&display_snapshot);
6656 let buffer = this.buffer.read(cx);
6657
6658 let (start_buffer, start) =
6659 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6660 let (end_buffer, end) =
6661 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6662
6663 Some((start_buffer, start, end_buffer, end, newest_selection))
6664 })?
6665 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6666 .context(
6667 "Expected selection to lie in a single buffer when refreshing code actions",
6668 )?;
6669 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6670 let providers = this.code_action_providers.clone();
6671 let tasks = this
6672 .code_action_providers
6673 .iter()
6674 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6675 .collect::<Vec<_>>();
6676 (providers, tasks)
6677 })?;
6678
6679 let mut actions = Vec::new();
6680 for (provider, provider_actions) in
6681 providers.into_iter().zip(future::join_all(tasks).await)
6682 {
6683 if let Some(provider_actions) = provider_actions.log_err() {
6684 actions.extend(provider_actions.into_iter().map(|action| {
6685 AvailableCodeAction {
6686 excerpt_id: newest_selection.start.excerpt_id,
6687 action,
6688 provider: provider.clone(),
6689 }
6690 }));
6691 }
6692 }
6693
6694 this.update(cx, |this, cx| {
6695 this.available_code_actions = if actions.is_empty() {
6696 None
6697 } else {
6698 Some((
6699 Location {
6700 buffer: start_buffer,
6701 range: start..end,
6702 },
6703 actions.into(),
6704 ))
6705 };
6706 cx.notify();
6707 })
6708 }));
6709 }
6710
6711 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6712 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6713 self.show_git_blame_inline = false;
6714
6715 self.show_git_blame_inline_delay_task =
6716 Some(cx.spawn_in(window, async move |this, cx| {
6717 cx.background_executor().timer(delay).await;
6718
6719 this.update(cx, |this, cx| {
6720 this.show_git_blame_inline = true;
6721 cx.notify();
6722 })
6723 .log_err();
6724 }));
6725 }
6726 }
6727
6728 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6729 let snapshot = self.snapshot(window, cx);
6730 let cursor = self
6731 .selections
6732 .newest::<Point>(&snapshot.display_snapshot)
6733 .head();
6734 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6735 else {
6736 return;
6737 };
6738
6739 let Some(blame) = self.blame.as_ref() else {
6740 return;
6741 };
6742
6743 let row_info = RowInfo {
6744 buffer_id: Some(buffer.remote_id()),
6745 buffer_row: Some(point.row),
6746 ..Default::default()
6747 };
6748 let Some((buffer, blame_entry)) = blame
6749 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6750 .flatten()
6751 else {
6752 return;
6753 };
6754
6755 let anchor = self.selections.newest_anchor().head();
6756 let position = self.to_pixel_point(anchor, &snapshot, window);
6757 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6758 self.show_blame_popover(
6759 buffer,
6760 &blame_entry,
6761 position + last_bounds.origin,
6762 true,
6763 cx,
6764 );
6765 };
6766 }
6767
6768 fn show_blame_popover(
6769 &mut self,
6770 buffer: BufferId,
6771 blame_entry: &BlameEntry,
6772 position: gpui::Point<Pixels>,
6773 ignore_timeout: bool,
6774 cx: &mut Context<Self>,
6775 ) {
6776 if let Some(state) = &mut self.inline_blame_popover {
6777 state.hide_task.take();
6778 } else {
6779 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6780 let blame_entry = blame_entry.clone();
6781 let show_task = cx.spawn(async move |editor, cx| {
6782 if !ignore_timeout {
6783 cx.background_executor()
6784 .timer(std::time::Duration::from_millis(blame_popover_delay))
6785 .await;
6786 }
6787 editor
6788 .update(cx, |editor, cx| {
6789 editor.inline_blame_popover_show_task.take();
6790 let Some(blame) = editor.blame.as_ref() else {
6791 return;
6792 };
6793 let blame = blame.read(cx);
6794 let details = blame.details_for_entry(buffer, &blame_entry);
6795 let markdown = cx.new(|cx| {
6796 Markdown::new(
6797 details
6798 .as_ref()
6799 .map(|message| message.message.clone())
6800 .unwrap_or_default(),
6801 None,
6802 None,
6803 cx,
6804 )
6805 });
6806 editor.inline_blame_popover = Some(InlineBlamePopover {
6807 position,
6808 hide_task: None,
6809 popover_bounds: None,
6810 popover_state: InlineBlamePopoverState {
6811 scroll_handle: ScrollHandle::new(),
6812 commit_message: details,
6813 markdown,
6814 },
6815 keyboard_grace: ignore_timeout,
6816 });
6817 cx.notify();
6818 })
6819 .ok();
6820 });
6821 self.inline_blame_popover_show_task = Some(show_task);
6822 }
6823 }
6824
6825 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6826 self.inline_blame_popover_show_task.take();
6827 if let Some(state) = &mut self.inline_blame_popover {
6828 let hide_task = cx.spawn(async move |editor, cx| {
6829 if !ignore_timeout {
6830 cx.background_executor()
6831 .timer(std::time::Duration::from_millis(100))
6832 .await;
6833 }
6834 editor
6835 .update(cx, |editor, cx| {
6836 editor.inline_blame_popover.take();
6837 cx.notify();
6838 })
6839 .ok();
6840 });
6841 state.hide_task = Some(hide_task);
6842 true
6843 } else {
6844 false
6845 }
6846 }
6847
6848 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6849 if self.pending_rename.is_some() {
6850 return None;
6851 }
6852
6853 let provider = self.semantics_provider.clone()?;
6854 let buffer = self.buffer.read(cx);
6855 let newest_selection = self.selections.newest_anchor().clone();
6856 let cursor_position = newest_selection.head();
6857 let (cursor_buffer, cursor_buffer_position) =
6858 buffer.text_anchor_for_position(cursor_position, cx)?;
6859 let (tail_buffer, tail_buffer_position) =
6860 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6861 if cursor_buffer != tail_buffer {
6862 return None;
6863 }
6864
6865 let snapshot = cursor_buffer.read(cx).snapshot();
6866 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6867 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6868 if start_word_range != end_word_range {
6869 self.document_highlights_task.take();
6870 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6871 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6872 return None;
6873 }
6874
6875 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6876 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6877 cx.background_executor()
6878 .timer(Duration::from_millis(debounce))
6879 .await;
6880
6881 let highlights = if let Some(highlights) = cx
6882 .update(|cx| {
6883 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6884 })
6885 .ok()
6886 .flatten()
6887 {
6888 highlights.await.log_err()
6889 } else {
6890 None
6891 };
6892
6893 if let Some(highlights) = highlights {
6894 this.update(cx, |this, cx| {
6895 if this.pending_rename.is_some() {
6896 return;
6897 }
6898
6899 let buffer = this.buffer.read(cx);
6900 if buffer
6901 .text_anchor_for_position(cursor_position, cx)
6902 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6903 {
6904 return;
6905 }
6906
6907 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6908 let mut write_ranges = Vec::new();
6909 let mut read_ranges = Vec::new();
6910 for highlight in highlights {
6911 let buffer_id = cursor_buffer.read(cx).remote_id();
6912 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6913 {
6914 let start = highlight
6915 .range
6916 .start
6917 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6918 let end = highlight
6919 .range
6920 .end
6921 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6922 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6923 continue;
6924 }
6925
6926 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
6927 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6928 write_ranges.push(range);
6929 } else {
6930 read_ranges.push(range);
6931 }
6932 }
6933 }
6934
6935 this.highlight_background::<DocumentHighlightRead>(
6936 &read_ranges,
6937 |theme| theme.colors().editor_document_highlight_read_background,
6938 cx,
6939 );
6940 this.highlight_background::<DocumentHighlightWrite>(
6941 &write_ranges,
6942 |theme| theme.colors().editor_document_highlight_write_background,
6943 cx,
6944 );
6945 cx.notify();
6946 })
6947 .log_err();
6948 }
6949 }));
6950 None
6951 }
6952
6953 fn prepare_highlight_query_from_selection(
6954 &mut self,
6955 window: &Window,
6956 cx: &mut Context<Editor>,
6957 ) -> Option<(String, Range<Anchor>)> {
6958 if matches!(self.mode, EditorMode::SingleLine) {
6959 return None;
6960 }
6961 if !EditorSettings::get_global(cx).selection_highlight {
6962 return None;
6963 }
6964 if self.selections.count() != 1 || self.selections.line_mode() {
6965 return None;
6966 }
6967 let snapshot = self.snapshot(window, cx);
6968 let selection = self.selections.newest::<Point>(&snapshot);
6969 // If the selection spans multiple rows OR it is empty
6970 if selection.start.row != selection.end.row
6971 || selection.start.column == selection.end.column
6972 {
6973 return None;
6974 }
6975 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6976 let query = snapshot
6977 .buffer_snapshot()
6978 .text_for_range(selection_anchor_range.clone())
6979 .collect::<String>();
6980 if query.trim().is_empty() {
6981 return None;
6982 }
6983 Some((query, selection_anchor_range))
6984 }
6985
6986 fn update_selection_occurrence_highlights(
6987 &mut self,
6988 query_text: String,
6989 query_range: Range<Anchor>,
6990 multi_buffer_range_to_query: Range<Point>,
6991 use_debounce: bool,
6992 window: &mut Window,
6993 cx: &mut Context<Editor>,
6994 ) -> Task<()> {
6995 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6996 cx.spawn_in(window, async move |editor, cx| {
6997 if use_debounce {
6998 cx.background_executor()
6999 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7000 .await;
7001 }
7002 let match_task = cx.background_spawn(async move {
7003 let buffer_ranges = multi_buffer_snapshot
7004 .range_to_buffer_ranges(multi_buffer_range_to_query)
7005 .into_iter()
7006 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7007 let mut match_ranges = Vec::new();
7008 let Ok(regex) = project::search::SearchQuery::text(
7009 query_text.clone(),
7010 false,
7011 false,
7012 false,
7013 Default::default(),
7014 Default::default(),
7015 false,
7016 None,
7017 ) else {
7018 return Vec::default();
7019 };
7020 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7021 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7022 match_ranges.extend(
7023 regex
7024 .search(
7025 buffer_snapshot,
7026 Some(search_range.start.0..search_range.end.0),
7027 )
7028 .await
7029 .into_iter()
7030 .filter_map(|match_range| {
7031 let match_start = buffer_snapshot
7032 .anchor_after(search_range.start + match_range.start);
7033 let match_end = buffer_snapshot
7034 .anchor_before(search_range.start + match_range.end);
7035 let match_anchor_range =
7036 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7037 (match_anchor_range != query_range).then_some(match_anchor_range)
7038 }),
7039 );
7040 }
7041 match_ranges
7042 });
7043 let match_ranges = match_task.await;
7044 editor
7045 .update_in(cx, |editor, _, cx| {
7046 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7047 if !match_ranges.is_empty() {
7048 editor.highlight_background::<SelectedTextHighlight>(
7049 &match_ranges,
7050 |theme| theme.colors().editor_document_highlight_bracket_background,
7051 cx,
7052 )
7053 }
7054 })
7055 .log_err();
7056 })
7057 }
7058
7059 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7060 struct NewlineFold;
7061 let type_id = std::any::TypeId::of::<NewlineFold>();
7062 if !self.mode.is_single_line() {
7063 return;
7064 }
7065 let snapshot = self.snapshot(window, cx);
7066 if snapshot.buffer_snapshot().max_point().row == 0 {
7067 return;
7068 }
7069 let task = cx.background_spawn(async move {
7070 let new_newlines = snapshot
7071 .buffer_chars_at(MultiBufferOffset(0))
7072 .filter_map(|(c, i)| {
7073 if c == '\n' {
7074 Some(
7075 snapshot.buffer_snapshot().anchor_after(i)
7076 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7077 )
7078 } else {
7079 None
7080 }
7081 })
7082 .collect::<Vec<_>>();
7083 let existing_newlines = snapshot
7084 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7085 .filter_map(|fold| {
7086 if fold.placeholder.type_tag == Some(type_id) {
7087 Some(fold.range.start..fold.range.end)
7088 } else {
7089 None
7090 }
7091 })
7092 .collect::<Vec<_>>();
7093
7094 (new_newlines, existing_newlines)
7095 });
7096 self.folding_newlines = cx.spawn(async move |this, cx| {
7097 let (new_newlines, existing_newlines) = task.await;
7098 if new_newlines == existing_newlines {
7099 return;
7100 }
7101 let placeholder = FoldPlaceholder {
7102 render: Arc::new(move |_, _, cx| {
7103 div()
7104 .bg(cx.theme().status().hint_background)
7105 .border_b_1()
7106 .size_full()
7107 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7108 .border_color(cx.theme().status().hint)
7109 .child("\\n")
7110 .into_any()
7111 }),
7112 constrain_width: false,
7113 merge_adjacent: false,
7114 type_tag: Some(type_id),
7115 };
7116 let creases = new_newlines
7117 .into_iter()
7118 .map(|range| Crease::simple(range, placeholder.clone()))
7119 .collect();
7120 this.update(cx, |this, cx| {
7121 this.display_map.update(cx, |display_map, cx| {
7122 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7123 display_map.fold(creases, cx);
7124 });
7125 })
7126 .ok();
7127 });
7128 }
7129
7130 fn refresh_selected_text_highlights(
7131 &mut self,
7132 on_buffer_edit: bool,
7133 window: &mut Window,
7134 cx: &mut Context<Editor>,
7135 ) {
7136 let Some((query_text, query_range)) =
7137 self.prepare_highlight_query_from_selection(window, cx)
7138 else {
7139 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7140 self.quick_selection_highlight_task.take();
7141 self.debounced_selection_highlight_task.take();
7142 return;
7143 };
7144 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7145 if on_buffer_edit
7146 || self
7147 .quick_selection_highlight_task
7148 .as_ref()
7149 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7150 {
7151 let multi_buffer_visible_start = self
7152 .scroll_manager
7153 .anchor()
7154 .anchor
7155 .to_point(&multi_buffer_snapshot);
7156 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7157 multi_buffer_visible_start
7158 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7159 Bias::Left,
7160 );
7161 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7162 self.quick_selection_highlight_task = Some((
7163 query_range.clone(),
7164 self.update_selection_occurrence_highlights(
7165 query_text.clone(),
7166 query_range.clone(),
7167 multi_buffer_visible_range,
7168 false,
7169 window,
7170 cx,
7171 ),
7172 ));
7173 }
7174 if on_buffer_edit
7175 || self
7176 .debounced_selection_highlight_task
7177 .as_ref()
7178 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7179 {
7180 let multi_buffer_start = multi_buffer_snapshot
7181 .anchor_before(MultiBufferOffset(0))
7182 .to_point(&multi_buffer_snapshot);
7183 let multi_buffer_end = multi_buffer_snapshot
7184 .anchor_after(multi_buffer_snapshot.len())
7185 .to_point(&multi_buffer_snapshot);
7186 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7187 self.debounced_selection_highlight_task = Some((
7188 query_range.clone(),
7189 self.update_selection_occurrence_highlights(
7190 query_text,
7191 query_range,
7192 multi_buffer_full_range,
7193 true,
7194 window,
7195 cx,
7196 ),
7197 ));
7198 }
7199 }
7200
7201 pub fn refresh_edit_prediction(
7202 &mut self,
7203 debounce: bool,
7204 user_requested: bool,
7205 window: &mut Window,
7206 cx: &mut Context<Self>,
7207 ) -> Option<()> {
7208 if DisableAiSettings::get_global(cx).disable_ai {
7209 return None;
7210 }
7211
7212 let provider = self.edit_prediction_provider()?;
7213 let cursor = self.selections.newest_anchor().head();
7214 let (buffer, cursor_buffer_position) =
7215 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7216
7217 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7218 self.discard_edit_prediction(false, cx);
7219 return None;
7220 }
7221
7222 self.update_visible_edit_prediction(window, cx);
7223
7224 if !user_requested
7225 && (!self.should_show_edit_predictions()
7226 || !self.is_focused(window)
7227 || buffer.read(cx).is_empty())
7228 {
7229 self.discard_edit_prediction(false, cx);
7230 return None;
7231 }
7232
7233 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7234 Some(())
7235 }
7236
7237 fn show_edit_predictions_in_menu(&self) -> bool {
7238 match self.edit_prediction_settings {
7239 EditPredictionSettings::Disabled => false,
7240 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7241 }
7242 }
7243
7244 pub fn edit_predictions_enabled(&self) -> bool {
7245 match self.edit_prediction_settings {
7246 EditPredictionSettings::Disabled => false,
7247 EditPredictionSettings::Enabled { .. } => true,
7248 }
7249 }
7250
7251 fn edit_prediction_requires_modifier(&self) -> bool {
7252 match self.edit_prediction_settings {
7253 EditPredictionSettings::Disabled => false,
7254 EditPredictionSettings::Enabled {
7255 preview_requires_modifier,
7256 ..
7257 } => preview_requires_modifier,
7258 }
7259 }
7260
7261 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7262 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7263 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7264 self.discard_edit_prediction(false, cx);
7265 } else {
7266 let selection = self.selections.newest_anchor();
7267 let cursor = selection.head();
7268
7269 if let Some((buffer, cursor_buffer_position)) =
7270 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7271 {
7272 self.edit_prediction_settings =
7273 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7274 }
7275 }
7276 }
7277
7278 fn edit_prediction_settings_at_position(
7279 &self,
7280 buffer: &Entity<Buffer>,
7281 buffer_position: language::Anchor,
7282 cx: &App,
7283 ) -> EditPredictionSettings {
7284 if !self.mode.is_full()
7285 || !self.show_edit_predictions_override.unwrap_or(true)
7286 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7287 {
7288 return EditPredictionSettings::Disabled;
7289 }
7290
7291 let buffer = buffer.read(cx);
7292
7293 let file = buffer.file();
7294
7295 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7296 return EditPredictionSettings::Disabled;
7297 };
7298
7299 let by_provider = matches!(
7300 self.menu_edit_predictions_policy,
7301 MenuEditPredictionsPolicy::ByProvider
7302 );
7303
7304 let show_in_menu = by_provider
7305 && self
7306 .edit_prediction_provider
7307 .as_ref()
7308 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7309
7310 let preview_requires_modifier =
7311 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7312
7313 EditPredictionSettings::Enabled {
7314 show_in_menu,
7315 preview_requires_modifier,
7316 }
7317 }
7318
7319 fn should_show_edit_predictions(&self) -> bool {
7320 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7321 }
7322
7323 pub fn edit_prediction_preview_is_active(&self) -> bool {
7324 matches!(
7325 self.edit_prediction_preview,
7326 EditPredictionPreview::Active { .. }
7327 )
7328 }
7329
7330 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7331 let cursor = self.selections.newest_anchor().head();
7332 if let Some((buffer, cursor_position)) =
7333 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7334 {
7335 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7336 } else {
7337 false
7338 }
7339 }
7340
7341 pub fn supports_minimap(&self, cx: &App) -> bool {
7342 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7343 }
7344
7345 fn edit_predictions_enabled_in_buffer(
7346 &self,
7347 buffer: &Entity<Buffer>,
7348 buffer_position: language::Anchor,
7349 cx: &App,
7350 ) -> bool {
7351 maybe!({
7352 if self.read_only(cx) {
7353 return Some(false);
7354 }
7355 let provider = self.edit_prediction_provider()?;
7356 if !provider.is_enabled(buffer, buffer_position, cx) {
7357 return Some(false);
7358 }
7359 let buffer = buffer.read(cx);
7360 let Some(file) = buffer.file() else {
7361 return Some(true);
7362 };
7363 let settings = all_language_settings(Some(file), cx);
7364 Some(settings.edit_predictions_enabled_for_file(file, cx))
7365 })
7366 .unwrap_or(false)
7367 }
7368
7369 fn cycle_edit_prediction(
7370 &mut self,
7371 direction: Direction,
7372 window: &mut Window,
7373 cx: &mut Context<Self>,
7374 ) -> Option<()> {
7375 let provider = self.edit_prediction_provider()?;
7376 let cursor = self.selections.newest_anchor().head();
7377 let (buffer, cursor_buffer_position) =
7378 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7379 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7380 return None;
7381 }
7382
7383 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7384 self.update_visible_edit_prediction(window, cx);
7385
7386 Some(())
7387 }
7388
7389 pub fn show_edit_prediction(
7390 &mut self,
7391 _: &ShowEditPrediction,
7392 window: &mut Window,
7393 cx: &mut Context<Self>,
7394 ) {
7395 if !self.has_active_edit_prediction() {
7396 self.refresh_edit_prediction(false, true, window, cx);
7397 return;
7398 }
7399
7400 self.update_visible_edit_prediction(window, cx);
7401 }
7402
7403 pub fn display_cursor_names(
7404 &mut self,
7405 _: &DisplayCursorNames,
7406 window: &mut Window,
7407 cx: &mut Context<Self>,
7408 ) {
7409 self.show_cursor_names(window, cx);
7410 }
7411
7412 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7413 self.show_cursor_names = true;
7414 cx.notify();
7415 cx.spawn_in(window, async move |this, cx| {
7416 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7417 this.update(cx, |this, cx| {
7418 this.show_cursor_names = false;
7419 cx.notify()
7420 })
7421 .ok()
7422 })
7423 .detach();
7424 }
7425
7426 pub fn next_edit_prediction(
7427 &mut self,
7428 _: &NextEditPrediction,
7429 window: &mut Window,
7430 cx: &mut Context<Self>,
7431 ) {
7432 if self.has_active_edit_prediction() {
7433 self.cycle_edit_prediction(Direction::Next, window, cx);
7434 } else {
7435 let is_copilot_disabled = self
7436 .refresh_edit_prediction(false, true, window, cx)
7437 .is_none();
7438 if is_copilot_disabled {
7439 cx.propagate();
7440 }
7441 }
7442 }
7443
7444 pub fn previous_edit_prediction(
7445 &mut self,
7446 _: &PreviousEditPrediction,
7447 window: &mut Window,
7448 cx: &mut Context<Self>,
7449 ) {
7450 if self.has_active_edit_prediction() {
7451 self.cycle_edit_prediction(Direction::Prev, window, cx);
7452 } else {
7453 let is_copilot_disabled = self
7454 .refresh_edit_prediction(false, true, window, cx)
7455 .is_none();
7456 if is_copilot_disabled {
7457 cx.propagate();
7458 }
7459 }
7460 }
7461
7462 pub fn accept_edit_prediction(
7463 &mut self,
7464 _: &AcceptEditPrediction,
7465 window: &mut Window,
7466 cx: &mut Context<Self>,
7467 ) {
7468 if self.show_edit_predictions_in_menu() {
7469 self.hide_context_menu(window, cx);
7470 }
7471
7472 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7473 return;
7474 };
7475
7476 match &active_edit_prediction.completion {
7477 EditPrediction::MoveWithin { target, .. } => {
7478 let target = *target;
7479
7480 if let Some(position_map) = &self.last_position_map {
7481 if position_map
7482 .visible_row_range
7483 .contains(&target.to_display_point(&position_map.snapshot).row())
7484 || !self.edit_prediction_requires_modifier()
7485 {
7486 self.unfold_ranges(&[target..target], true, false, cx);
7487 // Note that this is also done in vim's handler of the Tab action.
7488 self.change_selections(
7489 SelectionEffects::scroll(Autoscroll::newest()),
7490 window,
7491 cx,
7492 |selections| {
7493 selections.select_anchor_ranges([target..target]);
7494 },
7495 );
7496 self.clear_row_highlights::<EditPredictionPreview>();
7497
7498 self.edit_prediction_preview
7499 .set_previous_scroll_position(None);
7500 } else {
7501 self.edit_prediction_preview
7502 .set_previous_scroll_position(Some(
7503 position_map.snapshot.scroll_anchor,
7504 ));
7505
7506 self.highlight_rows::<EditPredictionPreview>(
7507 target..target,
7508 cx.theme().colors().editor_highlighted_line_background,
7509 RowHighlightOptions {
7510 autoscroll: true,
7511 ..Default::default()
7512 },
7513 cx,
7514 );
7515 self.request_autoscroll(Autoscroll::fit(), cx);
7516 }
7517 }
7518 }
7519 EditPrediction::MoveOutside { snapshot, target } => {
7520 if let Some(workspace) = self.workspace() {
7521 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7522 .detach_and_log_err(cx);
7523 }
7524 }
7525 EditPrediction::Edit { edits, .. } => {
7526 self.report_edit_prediction_event(
7527 active_edit_prediction.completion_id.clone(),
7528 true,
7529 cx,
7530 );
7531
7532 if let Some(provider) = self.edit_prediction_provider() {
7533 provider.accept(cx);
7534 }
7535
7536 // Store the transaction ID and selections before applying the edit
7537 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7538
7539 let snapshot = self.buffer.read(cx).snapshot(cx);
7540 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7541
7542 self.buffer.update(cx, |buffer, cx| {
7543 buffer.edit(edits.iter().cloned(), None, cx)
7544 });
7545
7546 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7547 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7548 });
7549
7550 let selections = self.selections.disjoint_anchors_arc();
7551 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7552 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7553 if has_new_transaction {
7554 self.selection_history
7555 .insert_transaction(transaction_id_now, selections);
7556 }
7557 }
7558
7559 self.update_visible_edit_prediction(window, cx);
7560 if self.active_edit_prediction.is_none() {
7561 self.refresh_edit_prediction(true, true, window, cx);
7562 }
7563
7564 cx.notify();
7565 }
7566 }
7567
7568 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7569 }
7570
7571 pub fn accept_partial_edit_prediction(
7572 &mut self,
7573 _: &AcceptPartialEditPrediction,
7574 window: &mut Window,
7575 cx: &mut Context<Self>,
7576 ) {
7577 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7578 return;
7579 };
7580 if self.selections.count() != 1 {
7581 return;
7582 }
7583
7584 match &active_edit_prediction.completion {
7585 EditPrediction::MoveWithin { target, .. } => {
7586 let target = *target;
7587 self.change_selections(
7588 SelectionEffects::scroll(Autoscroll::newest()),
7589 window,
7590 cx,
7591 |selections| {
7592 selections.select_anchor_ranges([target..target]);
7593 },
7594 );
7595 }
7596 EditPrediction::MoveOutside { snapshot, target } => {
7597 if let Some(workspace) = self.workspace() {
7598 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7599 .detach_and_log_err(cx);
7600 }
7601 }
7602 EditPrediction::Edit { edits, .. } => {
7603 self.report_edit_prediction_event(
7604 active_edit_prediction.completion_id.clone(),
7605 true,
7606 cx,
7607 );
7608
7609 // Find an insertion that starts at the cursor position.
7610 let snapshot = self.buffer.read(cx).snapshot(cx);
7611 let cursor_offset = self
7612 .selections
7613 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7614 .head();
7615 let insertion = edits.iter().find_map(|(range, text)| {
7616 let range = range.to_offset(&snapshot);
7617 if range.is_empty() && range.start == cursor_offset {
7618 Some(text)
7619 } else {
7620 None
7621 }
7622 });
7623
7624 if let Some(text) = insertion {
7625 let mut partial_completion = text
7626 .chars()
7627 .by_ref()
7628 .take_while(|c| c.is_alphabetic())
7629 .collect::<String>();
7630 if partial_completion.is_empty() {
7631 partial_completion = text
7632 .chars()
7633 .by_ref()
7634 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7635 .collect::<String>();
7636 }
7637
7638 cx.emit(EditorEvent::InputHandled {
7639 utf16_range_to_replace: None,
7640 text: partial_completion.clone().into(),
7641 });
7642
7643 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7644
7645 self.refresh_edit_prediction(true, true, window, cx);
7646 cx.notify();
7647 } else {
7648 self.accept_edit_prediction(&Default::default(), window, cx);
7649 }
7650 }
7651 }
7652 }
7653
7654 fn discard_edit_prediction(
7655 &mut self,
7656 should_report_edit_prediction_event: bool,
7657 cx: &mut Context<Self>,
7658 ) -> bool {
7659 if should_report_edit_prediction_event {
7660 let completion_id = self
7661 .active_edit_prediction
7662 .as_ref()
7663 .and_then(|active_completion| active_completion.completion_id.clone());
7664
7665 self.report_edit_prediction_event(completion_id, false, cx);
7666 }
7667
7668 if let Some(provider) = self.edit_prediction_provider() {
7669 provider.discard(cx);
7670 }
7671
7672 self.take_active_edit_prediction(cx)
7673 }
7674
7675 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7676 let Some(provider) = self.edit_prediction_provider() else {
7677 return;
7678 };
7679
7680 let Some((_, buffer, _)) = self
7681 .buffer
7682 .read(cx)
7683 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7684 else {
7685 return;
7686 };
7687
7688 let extension = buffer
7689 .read(cx)
7690 .file()
7691 .and_then(|file| Some(file.path().extension()?.to_string()));
7692
7693 let event_type = match accepted {
7694 true => "Edit Prediction Accepted",
7695 false => "Edit Prediction Discarded",
7696 };
7697 telemetry::event!(
7698 event_type,
7699 provider = provider.name(),
7700 prediction_id = id,
7701 suggestion_accepted = accepted,
7702 file_extension = extension,
7703 );
7704 }
7705
7706 fn open_editor_at_anchor(
7707 snapshot: &language::BufferSnapshot,
7708 target: language::Anchor,
7709 workspace: &Entity<Workspace>,
7710 window: &mut Window,
7711 cx: &mut App,
7712 ) -> Task<Result<()>> {
7713 workspace.update(cx, |workspace, cx| {
7714 let path = snapshot.file().map(|file| file.full_path(cx));
7715 let Some(path) =
7716 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7717 else {
7718 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7719 };
7720 let target = text::ToPoint::to_point(&target, snapshot);
7721 let item = workspace.open_path(path, None, true, window, cx);
7722 window.spawn(cx, async move |cx| {
7723 let Some(editor) = item.await?.downcast::<Editor>() else {
7724 return Ok(());
7725 };
7726 editor
7727 .update_in(cx, |editor, window, cx| {
7728 editor.go_to_singleton_buffer_point(target, window, cx);
7729 })
7730 .ok();
7731 anyhow::Ok(())
7732 })
7733 })
7734 }
7735
7736 pub fn has_active_edit_prediction(&self) -> bool {
7737 self.active_edit_prediction.is_some()
7738 }
7739
7740 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7741 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7742 return false;
7743 };
7744
7745 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7746 self.clear_highlights::<EditPredictionHighlight>(cx);
7747 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7748 true
7749 }
7750
7751 /// Returns true when we're displaying the edit prediction popover below the cursor
7752 /// like we are not previewing and the LSP autocomplete menu is visible
7753 /// or we are in `when_holding_modifier` mode.
7754 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7755 if self.edit_prediction_preview_is_active()
7756 || !self.show_edit_predictions_in_menu()
7757 || !self.edit_predictions_enabled()
7758 {
7759 return false;
7760 }
7761
7762 if self.has_visible_completions_menu() {
7763 return true;
7764 }
7765
7766 has_completion && self.edit_prediction_requires_modifier()
7767 }
7768
7769 fn handle_modifiers_changed(
7770 &mut self,
7771 modifiers: Modifiers,
7772 position_map: &PositionMap,
7773 window: &mut Window,
7774 cx: &mut Context<Self>,
7775 ) {
7776 // Ensure that the edit prediction preview is updated, even when not
7777 // enabled, if there's an active edit prediction preview.
7778 if self.show_edit_predictions_in_menu()
7779 || matches!(
7780 self.edit_prediction_preview,
7781 EditPredictionPreview::Active { .. }
7782 )
7783 {
7784 self.update_edit_prediction_preview(&modifiers, window, cx);
7785 }
7786
7787 self.update_selection_mode(&modifiers, position_map, window, cx);
7788
7789 let mouse_position = window.mouse_position();
7790 if !position_map.text_hitbox.is_hovered(window) {
7791 return;
7792 }
7793
7794 self.update_hovered_link(
7795 position_map.point_for_position(mouse_position),
7796 &position_map.snapshot,
7797 modifiers,
7798 window,
7799 cx,
7800 )
7801 }
7802
7803 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7804 match EditorSettings::get_global(cx).multi_cursor_modifier {
7805 MultiCursorModifier::Alt => modifiers.secondary(),
7806 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7807 }
7808 }
7809
7810 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7811 match EditorSettings::get_global(cx).multi_cursor_modifier {
7812 MultiCursorModifier::Alt => modifiers.alt,
7813 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7814 }
7815 }
7816
7817 fn columnar_selection_mode(
7818 modifiers: &Modifiers,
7819 cx: &mut Context<Self>,
7820 ) -> Option<ColumnarMode> {
7821 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7822 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7823 Some(ColumnarMode::FromMouse)
7824 } else if Self::is_alt_pressed(modifiers, cx) {
7825 Some(ColumnarMode::FromSelection)
7826 } else {
7827 None
7828 }
7829 } else {
7830 None
7831 }
7832 }
7833
7834 fn update_selection_mode(
7835 &mut self,
7836 modifiers: &Modifiers,
7837 position_map: &PositionMap,
7838 window: &mut Window,
7839 cx: &mut Context<Self>,
7840 ) {
7841 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7842 return;
7843 };
7844 if self.selections.pending_anchor().is_none() {
7845 return;
7846 }
7847
7848 let mouse_position = window.mouse_position();
7849 let point_for_position = position_map.point_for_position(mouse_position);
7850 let position = point_for_position.previous_valid;
7851
7852 self.select(
7853 SelectPhase::BeginColumnar {
7854 position,
7855 reset: false,
7856 mode,
7857 goal_column: point_for_position.exact_unclipped.column(),
7858 },
7859 window,
7860 cx,
7861 );
7862 }
7863
7864 fn update_edit_prediction_preview(
7865 &mut self,
7866 modifiers: &Modifiers,
7867 window: &mut Window,
7868 cx: &mut Context<Self>,
7869 ) {
7870 let mut modifiers_held = false;
7871 if let Some(accept_keystroke) = self
7872 .accept_edit_prediction_keybind(false, window, cx)
7873 .keystroke()
7874 {
7875 modifiers_held = modifiers_held
7876 || (accept_keystroke.modifiers() == modifiers
7877 && accept_keystroke.modifiers().modified());
7878 };
7879 if let Some(accept_partial_keystroke) = self
7880 .accept_edit_prediction_keybind(true, window, cx)
7881 .keystroke()
7882 {
7883 modifiers_held = modifiers_held
7884 || (accept_partial_keystroke.modifiers() == modifiers
7885 && accept_partial_keystroke.modifiers().modified());
7886 }
7887
7888 if modifiers_held {
7889 if matches!(
7890 self.edit_prediction_preview,
7891 EditPredictionPreview::Inactive { .. }
7892 ) {
7893 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7894 provider.provider.did_show(cx)
7895 }
7896
7897 self.edit_prediction_preview = EditPredictionPreview::Active {
7898 previous_scroll_position: None,
7899 since: Instant::now(),
7900 };
7901
7902 self.update_visible_edit_prediction(window, cx);
7903 cx.notify();
7904 }
7905 } else if let EditPredictionPreview::Active {
7906 previous_scroll_position,
7907 since,
7908 } = self.edit_prediction_preview
7909 {
7910 if let (Some(previous_scroll_position), Some(position_map)) =
7911 (previous_scroll_position, self.last_position_map.as_ref())
7912 {
7913 self.set_scroll_position(
7914 previous_scroll_position
7915 .scroll_position(&position_map.snapshot.display_snapshot),
7916 window,
7917 cx,
7918 );
7919 }
7920
7921 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7922 released_too_fast: since.elapsed() < Duration::from_millis(200),
7923 };
7924 self.clear_row_highlights::<EditPredictionPreview>();
7925 self.update_visible_edit_prediction(window, cx);
7926 cx.notify();
7927 }
7928 }
7929
7930 fn update_visible_edit_prediction(
7931 &mut self,
7932 _window: &mut Window,
7933 cx: &mut Context<Self>,
7934 ) -> Option<()> {
7935 if DisableAiSettings::get_global(cx).disable_ai {
7936 return None;
7937 }
7938
7939 if self.ime_transaction.is_some() {
7940 self.discard_edit_prediction(false, cx);
7941 return None;
7942 }
7943
7944 let selection = self.selections.newest_anchor();
7945 let cursor = selection.head();
7946 let multibuffer = self.buffer.read(cx).snapshot(cx);
7947 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7948 let excerpt_id = cursor.excerpt_id;
7949
7950 let show_in_menu = self.show_edit_predictions_in_menu();
7951 let completions_menu_has_precedence = !show_in_menu
7952 && (self.context_menu.borrow().is_some()
7953 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7954
7955 if completions_menu_has_precedence
7956 || !offset_selection.is_empty()
7957 || self
7958 .active_edit_prediction
7959 .as_ref()
7960 .is_some_and(|completion| {
7961 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7962 return false;
7963 };
7964 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7965 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7966 !invalidation_range.contains(&offset_selection.head())
7967 })
7968 {
7969 self.discard_edit_prediction(false, cx);
7970 return None;
7971 }
7972
7973 self.take_active_edit_prediction(cx);
7974 let Some(provider) = self.edit_prediction_provider() else {
7975 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7976 return None;
7977 };
7978
7979 let (buffer, cursor_buffer_position) =
7980 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7981
7982 self.edit_prediction_settings =
7983 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7984
7985 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7986
7987 if self.edit_prediction_indent_conflict {
7988 let cursor_point = cursor.to_point(&multibuffer);
7989
7990 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7991
7992 if let Some((_, indent)) = indents.iter().next()
7993 && indent.len == cursor_point.column
7994 {
7995 self.edit_prediction_indent_conflict = false;
7996 }
7997 }
7998
7999 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8000
8001 let (completion_id, edits, edit_preview) = match edit_prediction {
8002 edit_prediction::EditPrediction::Local {
8003 id,
8004 edits,
8005 edit_preview,
8006 } => (id, edits, edit_preview),
8007 edit_prediction::EditPrediction::Jump {
8008 id,
8009 snapshot,
8010 target,
8011 } => {
8012 self.stale_edit_prediction_in_menu = None;
8013 self.active_edit_prediction = Some(EditPredictionState {
8014 inlay_ids: vec![],
8015 completion: EditPrediction::MoveOutside { snapshot, target },
8016 completion_id: id,
8017 invalidation_range: None,
8018 });
8019 cx.notify();
8020 return Some(());
8021 }
8022 };
8023
8024 let edits = edits
8025 .into_iter()
8026 .flat_map(|(range, new_text)| {
8027 Some((
8028 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8029 new_text,
8030 ))
8031 })
8032 .collect::<Vec<_>>();
8033 if edits.is_empty() {
8034 return None;
8035 }
8036
8037 let first_edit_start = edits.first().unwrap().0.start;
8038 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8039 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8040
8041 let last_edit_end = edits.last().unwrap().0.end;
8042 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8043 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8044
8045 let cursor_row = cursor.to_point(&multibuffer).row;
8046
8047 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8048
8049 let mut inlay_ids = Vec::new();
8050 let invalidation_row_range;
8051 let move_invalidation_row_range = if cursor_row < edit_start_row {
8052 Some(cursor_row..edit_end_row)
8053 } else if cursor_row > edit_end_row {
8054 Some(edit_start_row..cursor_row)
8055 } else {
8056 None
8057 };
8058 let supports_jump = self
8059 .edit_prediction_provider
8060 .as_ref()
8061 .map(|provider| provider.provider.supports_jump_to_edit())
8062 .unwrap_or(true);
8063
8064 let is_move = supports_jump
8065 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8066 let completion = if is_move {
8067 invalidation_row_range =
8068 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8069 let target = first_edit_start;
8070 EditPrediction::MoveWithin { target, snapshot }
8071 } else {
8072 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8073 && !self.edit_predictions_hidden_for_vim_mode;
8074
8075 if show_completions_in_buffer {
8076 if let Some(provider) = &self.edit_prediction_provider {
8077 provider.provider.did_show(cx);
8078 }
8079 if edits
8080 .iter()
8081 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8082 {
8083 let mut inlays = Vec::new();
8084 for (range, new_text) in &edits {
8085 let inlay = Inlay::edit_prediction(
8086 post_inc(&mut self.next_inlay_id),
8087 range.start,
8088 new_text.as_ref(),
8089 );
8090 inlay_ids.push(inlay.id);
8091 inlays.push(inlay);
8092 }
8093
8094 self.splice_inlays(&[], inlays, cx);
8095 } else {
8096 let background_color = cx.theme().status().deleted_background;
8097 self.highlight_text::<EditPredictionHighlight>(
8098 edits.iter().map(|(range, _)| range.clone()).collect(),
8099 HighlightStyle {
8100 background_color: Some(background_color),
8101 ..Default::default()
8102 },
8103 cx,
8104 );
8105 }
8106 }
8107
8108 invalidation_row_range = edit_start_row..edit_end_row;
8109
8110 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8111 if provider.show_tab_accept_marker() {
8112 EditDisplayMode::TabAccept
8113 } else {
8114 EditDisplayMode::Inline
8115 }
8116 } else {
8117 EditDisplayMode::DiffPopover
8118 };
8119
8120 EditPrediction::Edit {
8121 edits,
8122 edit_preview,
8123 display_mode,
8124 snapshot,
8125 }
8126 };
8127
8128 let invalidation_range = multibuffer
8129 .anchor_before(Point::new(invalidation_row_range.start, 0))
8130 ..multibuffer.anchor_after(Point::new(
8131 invalidation_row_range.end,
8132 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8133 ));
8134
8135 self.stale_edit_prediction_in_menu = None;
8136 self.active_edit_prediction = Some(EditPredictionState {
8137 inlay_ids,
8138 completion,
8139 completion_id,
8140 invalidation_range: Some(invalidation_range),
8141 });
8142
8143 cx.notify();
8144
8145 Some(())
8146 }
8147
8148 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8149 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8150 }
8151
8152 fn clear_tasks(&mut self) {
8153 self.tasks.clear()
8154 }
8155
8156 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8157 if self.tasks.insert(key, value).is_some() {
8158 // This case should hopefully be rare, but just in case...
8159 log::error!(
8160 "multiple different run targets found on a single line, only the last target will be rendered"
8161 )
8162 }
8163 }
8164
8165 /// Get all display points of breakpoints that will be rendered within editor
8166 ///
8167 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8168 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8169 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8170 fn active_breakpoints(
8171 &self,
8172 range: Range<DisplayRow>,
8173 window: &mut Window,
8174 cx: &mut Context<Self>,
8175 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8176 let mut breakpoint_display_points = HashMap::default();
8177
8178 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8179 return breakpoint_display_points;
8180 };
8181
8182 let snapshot = self.snapshot(window, cx);
8183
8184 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8185 let Some(project) = self.project() else {
8186 return breakpoint_display_points;
8187 };
8188
8189 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8190 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8191
8192 for (buffer_snapshot, range, excerpt_id) in
8193 multi_buffer_snapshot.range_to_buffer_ranges(range)
8194 {
8195 let Some(buffer) = project
8196 .read(cx)
8197 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8198 else {
8199 continue;
8200 };
8201 let breakpoints = breakpoint_store.read(cx).breakpoints(
8202 &buffer,
8203 Some(
8204 buffer_snapshot.anchor_before(range.start)
8205 ..buffer_snapshot.anchor_after(range.end),
8206 ),
8207 buffer_snapshot,
8208 cx,
8209 );
8210 for (breakpoint, state) in breakpoints {
8211 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8212 let position = multi_buffer_anchor
8213 .to_point(&multi_buffer_snapshot)
8214 .to_display_point(&snapshot);
8215
8216 breakpoint_display_points.insert(
8217 position.row(),
8218 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8219 );
8220 }
8221 }
8222
8223 breakpoint_display_points
8224 }
8225
8226 fn breakpoint_context_menu(
8227 &self,
8228 anchor: Anchor,
8229 window: &mut Window,
8230 cx: &mut Context<Self>,
8231 ) -> Entity<ui::ContextMenu> {
8232 let weak_editor = cx.weak_entity();
8233 let focus_handle = self.focus_handle(cx);
8234
8235 let row = self
8236 .buffer
8237 .read(cx)
8238 .snapshot(cx)
8239 .summary_for_anchor::<Point>(&anchor)
8240 .row;
8241
8242 let breakpoint = self
8243 .breakpoint_at_row(row, window, cx)
8244 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8245
8246 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8247 "Edit Log Breakpoint"
8248 } else {
8249 "Set Log Breakpoint"
8250 };
8251
8252 let condition_breakpoint_msg = if breakpoint
8253 .as_ref()
8254 .is_some_and(|bp| bp.1.condition.is_some())
8255 {
8256 "Edit Condition Breakpoint"
8257 } else {
8258 "Set Condition Breakpoint"
8259 };
8260
8261 let hit_condition_breakpoint_msg = if breakpoint
8262 .as_ref()
8263 .is_some_and(|bp| bp.1.hit_condition.is_some())
8264 {
8265 "Edit Hit Condition Breakpoint"
8266 } else {
8267 "Set Hit Condition Breakpoint"
8268 };
8269
8270 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8271 "Unset Breakpoint"
8272 } else {
8273 "Set Breakpoint"
8274 };
8275
8276 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8277
8278 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8279 BreakpointState::Enabled => Some("Disable"),
8280 BreakpointState::Disabled => Some("Enable"),
8281 });
8282
8283 let (anchor, breakpoint) =
8284 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8285
8286 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8287 menu.on_blur_subscription(Subscription::new(|| {}))
8288 .context(focus_handle)
8289 .when(run_to_cursor, |this| {
8290 let weak_editor = weak_editor.clone();
8291 this.entry("Run to cursor", None, move |window, cx| {
8292 weak_editor
8293 .update(cx, |editor, cx| {
8294 editor.change_selections(
8295 SelectionEffects::no_scroll(),
8296 window,
8297 cx,
8298 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8299 );
8300 })
8301 .ok();
8302
8303 window.dispatch_action(Box::new(RunToCursor), cx);
8304 })
8305 .separator()
8306 })
8307 .when_some(toggle_state_msg, |this, msg| {
8308 this.entry(msg, None, {
8309 let weak_editor = weak_editor.clone();
8310 let breakpoint = breakpoint.clone();
8311 move |_window, cx| {
8312 weak_editor
8313 .update(cx, |this, cx| {
8314 this.edit_breakpoint_at_anchor(
8315 anchor,
8316 breakpoint.as_ref().clone(),
8317 BreakpointEditAction::InvertState,
8318 cx,
8319 );
8320 })
8321 .log_err();
8322 }
8323 })
8324 })
8325 .entry(set_breakpoint_msg, None, {
8326 let weak_editor = weak_editor.clone();
8327 let breakpoint = breakpoint.clone();
8328 move |_window, cx| {
8329 weak_editor
8330 .update(cx, |this, cx| {
8331 this.edit_breakpoint_at_anchor(
8332 anchor,
8333 breakpoint.as_ref().clone(),
8334 BreakpointEditAction::Toggle,
8335 cx,
8336 );
8337 })
8338 .log_err();
8339 }
8340 })
8341 .entry(log_breakpoint_msg, None, {
8342 let breakpoint = breakpoint.clone();
8343 let weak_editor = weak_editor.clone();
8344 move |window, cx| {
8345 weak_editor
8346 .update(cx, |this, cx| {
8347 this.add_edit_breakpoint_block(
8348 anchor,
8349 breakpoint.as_ref(),
8350 BreakpointPromptEditAction::Log,
8351 window,
8352 cx,
8353 );
8354 })
8355 .log_err();
8356 }
8357 })
8358 .entry(condition_breakpoint_msg, None, {
8359 let breakpoint = breakpoint.clone();
8360 let weak_editor = weak_editor.clone();
8361 move |window, cx| {
8362 weak_editor
8363 .update(cx, |this, cx| {
8364 this.add_edit_breakpoint_block(
8365 anchor,
8366 breakpoint.as_ref(),
8367 BreakpointPromptEditAction::Condition,
8368 window,
8369 cx,
8370 );
8371 })
8372 .log_err();
8373 }
8374 })
8375 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8376 weak_editor
8377 .update(cx, |this, cx| {
8378 this.add_edit_breakpoint_block(
8379 anchor,
8380 breakpoint.as_ref(),
8381 BreakpointPromptEditAction::HitCondition,
8382 window,
8383 cx,
8384 );
8385 })
8386 .log_err();
8387 })
8388 })
8389 }
8390
8391 fn render_breakpoint(
8392 &self,
8393 position: Anchor,
8394 row: DisplayRow,
8395 breakpoint: &Breakpoint,
8396 state: Option<BreakpointSessionState>,
8397 cx: &mut Context<Self>,
8398 ) -> IconButton {
8399 let is_rejected = state.is_some_and(|s| !s.verified);
8400 // Is it a breakpoint that shows up when hovering over gutter?
8401 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8402 (false, false),
8403 |PhantomBreakpointIndicator {
8404 is_active,
8405 display_row,
8406 collides_with_existing_breakpoint,
8407 }| {
8408 (
8409 is_active && display_row == row,
8410 collides_with_existing_breakpoint,
8411 )
8412 },
8413 );
8414
8415 let (color, icon) = {
8416 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8417 (false, false) => ui::IconName::DebugBreakpoint,
8418 (true, false) => ui::IconName::DebugLogBreakpoint,
8419 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8420 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8421 };
8422
8423 let color = if is_phantom {
8424 Color::Hint
8425 } else if is_rejected {
8426 Color::Disabled
8427 } else {
8428 Color::Debugger
8429 };
8430
8431 (color, icon)
8432 };
8433
8434 let breakpoint = Arc::from(breakpoint.clone());
8435
8436 let alt_as_text = gpui::Keystroke {
8437 modifiers: Modifiers::secondary_key(),
8438 ..Default::default()
8439 };
8440 let primary_action_text = if breakpoint.is_disabled() {
8441 "Enable breakpoint"
8442 } else if is_phantom && !collides_with_existing {
8443 "Set breakpoint"
8444 } else {
8445 "Unset breakpoint"
8446 };
8447 let focus_handle = self.focus_handle.clone();
8448
8449 let meta = if is_rejected {
8450 SharedString::from("No executable code is associated with this line.")
8451 } else if collides_with_existing && !breakpoint.is_disabled() {
8452 SharedString::from(format!(
8453 "{alt_as_text}-click to disable,\nright-click for more options."
8454 ))
8455 } else {
8456 SharedString::from("Right-click for more options.")
8457 };
8458 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8459 .icon_size(IconSize::XSmall)
8460 .size(ui::ButtonSize::None)
8461 .when(is_rejected, |this| {
8462 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8463 })
8464 .icon_color(color)
8465 .style(ButtonStyle::Transparent)
8466 .on_click(cx.listener({
8467 move |editor, event: &ClickEvent, window, cx| {
8468 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8469 BreakpointEditAction::InvertState
8470 } else {
8471 BreakpointEditAction::Toggle
8472 };
8473
8474 window.focus(&editor.focus_handle(cx));
8475 editor.edit_breakpoint_at_anchor(
8476 position,
8477 breakpoint.as_ref().clone(),
8478 edit_action,
8479 cx,
8480 );
8481 }
8482 }))
8483 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8484 editor.set_breakpoint_context_menu(
8485 row,
8486 Some(position),
8487 event.position(),
8488 window,
8489 cx,
8490 );
8491 }))
8492 .tooltip(move |_window, cx| {
8493 Tooltip::with_meta_in(
8494 primary_action_text,
8495 Some(&ToggleBreakpoint),
8496 meta.clone(),
8497 &focus_handle,
8498 cx,
8499 )
8500 })
8501 }
8502
8503 fn build_tasks_context(
8504 project: &Entity<Project>,
8505 buffer: &Entity<Buffer>,
8506 buffer_row: u32,
8507 tasks: &Arc<RunnableTasks>,
8508 cx: &mut Context<Self>,
8509 ) -> Task<Option<task::TaskContext>> {
8510 let position = Point::new(buffer_row, tasks.column);
8511 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8512 let location = Location {
8513 buffer: buffer.clone(),
8514 range: range_start..range_start,
8515 };
8516 // Fill in the environmental variables from the tree-sitter captures
8517 let mut captured_task_variables = TaskVariables::default();
8518 for (capture_name, value) in tasks.extra_variables.clone() {
8519 captured_task_variables.insert(
8520 task::VariableName::Custom(capture_name.into()),
8521 value.clone(),
8522 );
8523 }
8524 project.update(cx, |project, cx| {
8525 project.task_store().update(cx, |task_store, cx| {
8526 task_store.task_context_for_location(captured_task_variables, location, cx)
8527 })
8528 })
8529 }
8530
8531 pub fn spawn_nearest_task(
8532 &mut self,
8533 action: &SpawnNearestTask,
8534 window: &mut Window,
8535 cx: &mut Context<Self>,
8536 ) {
8537 let Some((workspace, _)) = self.workspace.clone() else {
8538 return;
8539 };
8540 let Some(project) = self.project.clone() else {
8541 return;
8542 };
8543
8544 // Try to find a closest, enclosing node using tree-sitter that has a task
8545 let Some((buffer, buffer_row, tasks)) = self
8546 .find_enclosing_node_task(cx)
8547 // Or find the task that's closest in row-distance.
8548 .or_else(|| self.find_closest_task(cx))
8549 else {
8550 return;
8551 };
8552
8553 let reveal_strategy = action.reveal;
8554 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8555 cx.spawn_in(window, async move |_, cx| {
8556 let context = task_context.await?;
8557 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8558
8559 let resolved = &mut resolved_task.resolved;
8560 resolved.reveal = reveal_strategy;
8561
8562 workspace
8563 .update_in(cx, |workspace, window, cx| {
8564 workspace.schedule_resolved_task(
8565 task_source_kind,
8566 resolved_task,
8567 false,
8568 window,
8569 cx,
8570 );
8571 })
8572 .ok()
8573 })
8574 .detach();
8575 }
8576
8577 fn find_closest_task(
8578 &mut self,
8579 cx: &mut Context<Self>,
8580 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8581 let cursor_row = self
8582 .selections
8583 .newest_adjusted(&self.display_snapshot(cx))
8584 .head()
8585 .row;
8586
8587 let ((buffer_id, row), tasks) = self
8588 .tasks
8589 .iter()
8590 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8591
8592 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8593 let tasks = Arc::new(tasks.to_owned());
8594 Some((buffer, *row, tasks))
8595 }
8596
8597 fn find_enclosing_node_task(
8598 &mut self,
8599 cx: &mut Context<Self>,
8600 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8601 let snapshot = self.buffer.read(cx).snapshot(cx);
8602 let offset = self
8603 .selections
8604 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8605 .head();
8606 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8607 let offset = excerpt.map_offset_to_buffer(offset);
8608 let buffer_id = excerpt.buffer().remote_id();
8609
8610 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8611 let mut cursor = layer.node().walk();
8612
8613 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8614 if cursor.node().end_byte() == offset.0 {
8615 cursor.goto_next_sibling();
8616 }
8617 }
8618
8619 // Ascend to the smallest ancestor that contains the range and has a task.
8620 loop {
8621 let node = cursor.node();
8622 let node_range = node.byte_range();
8623 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8624
8625 // Check if this node contains our offset
8626 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8627 // If it contains offset, check for task
8628 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8629 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8630 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8631 }
8632 }
8633
8634 if !cursor.goto_parent() {
8635 break;
8636 }
8637 }
8638 None
8639 }
8640
8641 fn render_run_indicator(
8642 &self,
8643 _style: &EditorStyle,
8644 is_active: bool,
8645 row: DisplayRow,
8646 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8647 cx: &mut Context<Self>,
8648 ) -> IconButton {
8649 let color = Color::Muted;
8650 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8651
8652 IconButton::new(
8653 ("run_indicator", row.0 as usize),
8654 ui::IconName::PlayOutlined,
8655 )
8656 .shape(ui::IconButtonShape::Square)
8657 .icon_size(IconSize::XSmall)
8658 .icon_color(color)
8659 .toggle_state(is_active)
8660 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8661 let quick_launch = match e {
8662 ClickEvent::Keyboard(_) => true,
8663 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8664 };
8665
8666 window.focus(&editor.focus_handle(cx));
8667 editor.toggle_code_actions(
8668 &ToggleCodeActions {
8669 deployed_from: Some(CodeActionSource::RunMenu(row)),
8670 quick_launch,
8671 },
8672 window,
8673 cx,
8674 );
8675 }))
8676 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8677 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8678 }))
8679 }
8680
8681 pub fn context_menu_visible(&self) -> bool {
8682 !self.edit_prediction_preview_is_active()
8683 && self
8684 .context_menu
8685 .borrow()
8686 .as_ref()
8687 .is_some_and(|menu| menu.visible())
8688 }
8689
8690 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8691 self.context_menu
8692 .borrow()
8693 .as_ref()
8694 .map(|menu| menu.origin())
8695 }
8696
8697 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8698 self.context_menu_options = Some(options);
8699 }
8700
8701 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8702 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8703
8704 fn render_edit_prediction_popover(
8705 &mut self,
8706 text_bounds: &Bounds<Pixels>,
8707 content_origin: gpui::Point<Pixels>,
8708 right_margin: Pixels,
8709 editor_snapshot: &EditorSnapshot,
8710 visible_row_range: Range<DisplayRow>,
8711 scroll_top: ScrollOffset,
8712 scroll_bottom: ScrollOffset,
8713 line_layouts: &[LineWithInvisibles],
8714 line_height: Pixels,
8715 scroll_position: gpui::Point<ScrollOffset>,
8716 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8717 newest_selection_head: Option<DisplayPoint>,
8718 editor_width: Pixels,
8719 style: &EditorStyle,
8720 window: &mut Window,
8721 cx: &mut App,
8722 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8723 if self.mode().is_minimap() {
8724 return None;
8725 }
8726 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8727
8728 if self.edit_prediction_visible_in_cursor_popover(true) {
8729 return None;
8730 }
8731
8732 match &active_edit_prediction.completion {
8733 EditPrediction::MoveWithin { target, .. } => {
8734 let target_display_point = target.to_display_point(editor_snapshot);
8735
8736 if self.edit_prediction_requires_modifier() {
8737 if !self.edit_prediction_preview_is_active() {
8738 return None;
8739 }
8740
8741 self.render_edit_prediction_modifier_jump_popover(
8742 text_bounds,
8743 content_origin,
8744 visible_row_range,
8745 line_layouts,
8746 line_height,
8747 scroll_pixel_position,
8748 newest_selection_head,
8749 target_display_point,
8750 window,
8751 cx,
8752 )
8753 } else {
8754 self.render_edit_prediction_eager_jump_popover(
8755 text_bounds,
8756 content_origin,
8757 editor_snapshot,
8758 visible_row_range,
8759 scroll_top,
8760 scroll_bottom,
8761 line_height,
8762 scroll_pixel_position,
8763 target_display_point,
8764 editor_width,
8765 window,
8766 cx,
8767 )
8768 }
8769 }
8770 EditPrediction::Edit {
8771 display_mode: EditDisplayMode::Inline,
8772 ..
8773 } => None,
8774 EditPrediction::Edit {
8775 display_mode: EditDisplayMode::TabAccept,
8776 edits,
8777 ..
8778 } => {
8779 let range = &edits.first()?.0;
8780 let target_display_point = range.end.to_display_point(editor_snapshot);
8781
8782 self.render_edit_prediction_end_of_line_popover(
8783 "Accept",
8784 editor_snapshot,
8785 visible_row_range,
8786 target_display_point,
8787 line_height,
8788 scroll_pixel_position,
8789 content_origin,
8790 editor_width,
8791 window,
8792 cx,
8793 )
8794 }
8795 EditPrediction::Edit {
8796 edits,
8797 edit_preview,
8798 display_mode: EditDisplayMode::DiffPopover,
8799 snapshot,
8800 } => self.render_edit_prediction_diff_popover(
8801 text_bounds,
8802 content_origin,
8803 right_margin,
8804 editor_snapshot,
8805 visible_row_range,
8806 line_layouts,
8807 line_height,
8808 scroll_position,
8809 scroll_pixel_position,
8810 newest_selection_head,
8811 editor_width,
8812 style,
8813 edits,
8814 edit_preview,
8815 snapshot,
8816 window,
8817 cx,
8818 ),
8819 EditPrediction::MoveOutside { snapshot, .. } => {
8820 let mut element = self
8821 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8822 .into_any();
8823
8824 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8825 let origin_x = text_bounds.size.width - size.width - px(30.);
8826 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8827 element.prepaint_at(origin, window, cx);
8828
8829 Some((element, origin))
8830 }
8831 }
8832 }
8833
8834 fn render_edit_prediction_modifier_jump_popover(
8835 &mut self,
8836 text_bounds: &Bounds<Pixels>,
8837 content_origin: gpui::Point<Pixels>,
8838 visible_row_range: Range<DisplayRow>,
8839 line_layouts: &[LineWithInvisibles],
8840 line_height: Pixels,
8841 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8842 newest_selection_head: Option<DisplayPoint>,
8843 target_display_point: DisplayPoint,
8844 window: &mut Window,
8845 cx: &mut App,
8846 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8847 let scrolled_content_origin =
8848 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8849
8850 const SCROLL_PADDING_Y: Pixels = px(12.);
8851
8852 if target_display_point.row() < visible_row_range.start {
8853 return self.render_edit_prediction_scroll_popover(
8854 |_| SCROLL_PADDING_Y,
8855 IconName::ArrowUp,
8856 visible_row_range,
8857 line_layouts,
8858 newest_selection_head,
8859 scrolled_content_origin,
8860 window,
8861 cx,
8862 );
8863 } else if target_display_point.row() >= visible_row_range.end {
8864 return self.render_edit_prediction_scroll_popover(
8865 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8866 IconName::ArrowDown,
8867 visible_row_range,
8868 line_layouts,
8869 newest_selection_head,
8870 scrolled_content_origin,
8871 window,
8872 cx,
8873 );
8874 }
8875
8876 const POLE_WIDTH: Pixels = px(2.);
8877
8878 let line_layout =
8879 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8880 let target_column = target_display_point.column() as usize;
8881
8882 let target_x = line_layout.x_for_index(target_column);
8883 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8884 - scroll_pixel_position.y;
8885
8886 let flag_on_right = target_x < text_bounds.size.width / 2.;
8887
8888 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8889 border_color.l += 0.001;
8890
8891 let mut element = v_flex()
8892 .items_end()
8893 .when(flag_on_right, |el| el.items_start())
8894 .child(if flag_on_right {
8895 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8896 .rounded_bl(px(0.))
8897 .rounded_tl(px(0.))
8898 .border_l_2()
8899 .border_color(border_color)
8900 } else {
8901 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8902 .rounded_br(px(0.))
8903 .rounded_tr(px(0.))
8904 .border_r_2()
8905 .border_color(border_color)
8906 })
8907 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8908 .into_any();
8909
8910 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8911
8912 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8913 - point(
8914 if flag_on_right {
8915 POLE_WIDTH
8916 } else {
8917 size.width - POLE_WIDTH
8918 },
8919 size.height - line_height,
8920 );
8921
8922 origin.x = origin.x.max(content_origin.x);
8923
8924 element.prepaint_at(origin, window, cx);
8925
8926 Some((element, origin))
8927 }
8928
8929 fn render_edit_prediction_scroll_popover(
8930 &mut self,
8931 to_y: impl Fn(Size<Pixels>) -> Pixels,
8932 scroll_icon: IconName,
8933 visible_row_range: Range<DisplayRow>,
8934 line_layouts: &[LineWithInvisibles],
8935 newest_selection_head: Option<DisplayPoint>,
8936 scrolled_content_origin: gpui::Point<Pixels>,
8937 window: &mut Window,
8938 cx: &mut App,
8939 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8940 let mut element = self
8941 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8942 .into_any();
8943
8944 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8945
8946 let cursor = newest_selection_head?;
8947 let cursor_row_layout =
8948 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8949 let cursor_column = cursor.column() as usize;
8950
8951 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8952
8953 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8954
8955 element.prepaint_at(origin, window, cx);
8956 Some((element, origin))
8957 }
8958
8959 fn render_edit_prediction_eager_jump_popover(
8960 &mut self,
8961 text_bounds: &Bounds<Pixels>,
8962 content_origin: gpui::Point<Pixels>,
8963 editor_snapshot: &EditorSnapshot,
8964 visible_row_range: Range<DisplayRow>,
8965 scroll_top: ScrollOffset,
8966 scroll_bottom: ScrollOffset,
8967 line_height: Pixels,
8968 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8969 target_display_point: DisplayPoint,
8970 editor_width: Pixels,
8971 window: &mut Window,
8972 cx: &mut App,
8973 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8974 if target_display_point.row().as_f64() < scroll_top {
8975 let mut element = self
8976 .render_edit_prediction_line_popover(
8977 "Jump to Edit",
8978 Some(IconName::ArrowUp),
8979 window,
8980 cx,
8981 )
8982 .into_any();
8983
8984 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8985 let offset = point(
8986 (text_bounds.size.width - size.width) / 2.,
8987 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8988 );
8989
8990 let origin = text_bounds.origin + offset;
8991 element.prepaint_at(origin, window, cx);
8992 Some((element, origin))
8993 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8994 let mut element = self
8995 .render_edit_prediction_line_popover(
8996 "Jump to Edit",
8997 Some(IconName::ArrowDown),
8998 window,
8999 cx,
9000 )
9001 .into_any();
9002
9003 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9004 let offset = point(
9005 (text_bounds.size.width - size.width) / 2.,
9006 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9007 );
9008
9009 let origin = text_bounds.origin + offset;
9010 element.prepaint_at(origin, window, cx);
9011 Some((element, origin))
9012 } else {
9013 self.render_edit_prediction_end_of_line_popover(
9014 "Jump to Edit",
9015 editor_snapshot,
9016 visible_row_range,
9017 target_display_point,
9018 line_height,
9019 scroll_pixel_position,
9020 content_origin,
9021 editor_width,
9022 window,
9023 cx,
9024 )
9025 }
9026 }
9027
9028 fn render_edit_prediction_end_of_line_popover(
9029 self: &mut Editor,
9030 label: &'static str,
9031 editor_snapshot: &EditorSnapshot,
9032 visible_row_range: Range<DisplayRow>,
9033 target_display_point: DisplayPoint,
9034 line_height: Pixels,
9035 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9036 content_origin: gpui::Point<Pixels>,
9037 editor_width: Pixels,
9038 window: &mut Window,
9039 cx: &mut App,
9040 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9041 let target_line_end = DisplayPoint::new(
9042 target_display_point.row(),
9043 editor_snapshot.line_len(target_display_point.row()),
9044 );
9045
9046 let mut element = self
9047 .render_edit_prediction_line_popover(label, None, window, cx)
9048 .into_any();
9049
9050 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9051
9052 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9053
9054 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9055 let mut origin = start_point
9056 + line_origin
9057 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9058 origin.x = origin.x.max(content_origin.x);
9059
9060 let max_x = content_origin.x + editor_width - size.width;
9061
9062 if origin.x > max_x {
9063 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9064
9065 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9066 origin.y += offset;
9067 IconName::ArrowUp
9068 } else {
9069 origin.y -= offset;
9070 IconName::ArrowDown
9071 };
9072
9073 element = self
9074 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9075 .into_any();
9076
9077 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9078
9079 origin.x = content_origin.x + editor_width - size.width - px(2.);
9080 }
9081
9082 element.prepaint_at(origin, window, cx);
9083 Some((element, origin))
9084 }
9085
9086 fn render_edit_prediction_diff_popover(
9087 self: &Editor,
9088 text_bounds: &Bounds<Pixels>,
9089 content_origin: gpui::Point<Pixels>,
9090 right_margin: Pixels,
9091 editor_snapshot: &EditorSnapshot,
9092 visible_row_range: Range<DisplayRow>,
9093 line_layouts: &[LineWithInvisibles],
9094 line_height: Pixels,
9095 scroll_position: gpui::Point<ScrollOffset>,
9096 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9097 newest_selection_head: Option<DisplayPoint>,
9098 editor_width: Pixels,
9099 style: &EditorStyle,
9100 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9101 edit_preview: &Option<language::EditPreview>,
9102 snapshot: &language::BufferSnapshot,
9103 window: &mut Window,
9104 cx: &mut App,
9105 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9106 let edit_start = edits
9107 .first()
9108 .unwrap()
9109 .0
9110 .start
9111 .to_display_point(editor_snapshot);
9112 let edit_end = edits
9113 .last()
9114 .unwrap()
9115 .0
9116 .end
9117 .to_display_point(editor_snapshot);
9118
9119 let is_visible = visible_row_range.contains(&edit_start.row())
9120 || visible_row_range.contains(&edit_end.row());
9121 if !is_visible {
9122 return None;
9123 }
9124
9125 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9126 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9127 } else {
9128 // Fallback for providers without edit_preview
9129 crate::edit_prediction_fallback_text(edits, cx)
9130 };
9131
9132 let styled_text = highlighted_edits.to_styled_text(&style.text);
9133 let line_count = highlighted_edits.text.lines().count();
9134
9135 const BORDER_WIDTH: Pixels = px(1.);
9136
9137 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9138 let has_keybind = keybind.is_some();
9139
9140 let mut element = h_flex()
9141 .items_start()
9142 .child(
9143 h_flex()
9144 .bg(cx.theme().colors().editor_background)
9145 .border(BORDER_WIDTH)
9146 .shadow_xs()
9147 .border_color(cx.theme().colors().border)
9148 .rounded_l_lg()
9149 .when(line_count > 1, |el| el.rounded_br_lg())
9150 .pr_1()
9151 .child(styled_text),
9152 )
9153 .child(
9154 h_flex()
9155 .h(line_height + BORDER_WIDTH * 2.)
9156 .px_1p5()
9157 .gap_1()
9158 // Workaround: For some reason, there's a gap if we don't do this
9159 .ml(-BORDER_WIDTH)
9160 .shadow(vec![gpui::BoxShadow {
9161 color: gpui::black().opacity(0.05),
9162 offset: point(px(1.), px(1.)),
9163 blur_radius: px(2.),
9164 spread_radius: px(0.),
9165 }])
9166 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9167 .border(BORDER_WIDTH)
9168 .border_color(cx.theme().colors().border)
9169 .rounded_r_lg()
9170 .id("edit_prediction_diff_popover_keybind")
9171 .when(!has_keybind, |el| {
9172 let status_colors = cx.theme().status();
9173
9174 el.bg(status_colors.error_background)
9175 .border_color(status_colors.error.opacity(0.6))
9176 .child(Icon::new(IconName::Info).color(Color::Error))
9177 .cursor_default()
9178 .hoverable_tooltip(move |_window, cx| {
9179 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9180 })
9181 })
9182 .children(keybind),
9183 )
9184 .into_any();
9185
9186 let longest_row =
9187 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9188 let longest_line_width = if visible_row_range.contains(&longest_row) {
9189 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9190 } else {
9191 layout_line(
9192 longest_row,
9193 editor_snapshot,
9194 style,
9195 editor_width,
9196 |_| false,
9197 window,
9198 cx,
9199 )
9200 .width
9201 };
9202
9203 let viewport_bounds =
9204 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9205 right: -right_margin,
9206 ..Default::default()
9207 });
9208
9209 let x_after_longest = Pixels::from(
9210 ScrollPixelOffset::from(
9211 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9212 ) - scroll_pixel_position.x,
9213 );
9214
9215 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9216
9217 // Fully visible if it can be displayed within the window (allow overlapping other
9218 // panes). However, this is only allowed if the popover starts within text_bounds.
9219 let can_position_to_the_right = x_after_longest < text_bounds.right()
9220 && x_after_longest + element_bounds.width < viewport_bounds.right();
9221
9222 let mut origin = if can_position_to_the_right {
9223 point(
9224 x_after_longest,
9225 text_bounds.origin.y
9226 + Pixels::from(
9227 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9228 - scroll_pixel_position.y,
9229 ),
9230 )
9231 } else {
9232 let cursor_row = newest_selection_head.map(|head| head.row());
9233 let above_edit = edit_start
9234 .row()
9235 .0
9236 .checked_sub(line_count as u32)
9237 .map(DisplayRow);
9238 let below_edit = Some(edit_end.row() + 1);
9239 let above_cursor =
9240 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9241 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9242
9243 // Place the edit popover adjacent to the edit if there is a location
9244 // available that is onscreen and does not obscure the cursor. Otherwise,
9245 // place it adjacent to the cursor.
9246 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9247 .into_iter()
9248 .flatten()
9249 .find(|&start_row| {
9250 let end_row = start_row + line_count as u32;
9251 visible_row_range.contains(&start_row)
9252 && visible_row_range.contains(&end_row)
9253 && cursor_row
9254 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9255 })?;
9256
9257 content_origin
9258 + point(
9259 Pixels::from(-scroll_pixel_position.x),
9260 Pixels::from(
9261 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9262 ),
9263 )
9264 };
9265
9266 origin.x -= BORDER_WIDTH;
9267
9268 window.defer_draw(element, origin, 1);
9269
9270 // Do not return an element, since it will already be drawn due to defer_draw.
9271 None
9272 }
9273
9274 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9275 px(30.)
9276 }
9277
9278 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9279 if self.read_only(cx) {
9280 cx.theme().players().read_only()
9281 } else {
9282 self.style.as_ref().unwrap().local_player
9283 }
9284 }
9285
9286 fn render_edit_prediction_accept_keybind(
9287 &self,
9288 window: &mut Window,
9289 cx: &mut App,
9290 ) -> Option<AnyElement> {
9291 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9292 let accept_keystroke = accept_binding.keystroke()?;
9293
9294 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9295
9296 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9297 Color::Accent
9298 } else {
9299 Color::Muted
9300 };
9301
9302 h_flex()
9303 .px_0p5()
9304 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9305 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9306 .text_size(TextSize::XSmall.rems(cx))
9307 .child(h_flex().children(ui::render_modifiers(
9308 accept_keystroke.modifiers(),
9309 PlatformStyle::platform(),
9310 Some(modifiers_color),
9311 Some(IconSize::XSmall.rems().into()),
9312 true,
9313 )))
9314 .when(is_platform_style_mac, |parent| {
9315 parent.child(accept_keystroke.key().to_string())
9316 })
9317 .when(!is_platform_style_mac, |parent| {
9318 parent.child(
9319 Key::new(
9320 util::capitalize(accept_keystroke.key()),
9321 Some(Color::Default),
9322 )
9323 .size(Some(IconSize::XSmall.rems().into())),
9324 )
9325 })
9326 .into_any()
9327 .into()
9328 }
9329
9330 fn render_edit_prediction_line_popover(
9331 &self,
9332 label: impl Into<SharedString>,
9333 icon: Option<IconName>,
9334 window: &mut Window,
9335 cx: &mut App,
9336 ) -> Stateful<Div> {
9337 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9338
9339 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9340 let has_keybind = keybind.is_some();
9341
9342 h_flex()
9343 .id("ep-line-popover")
9344 .py_0p5()
9345 .pl_1()
9346 .pr(padding_right)
9347 .gap_1()
9348 .rounded_md()
9349 .border_1()
9350 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9351 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9352 .shadow_xs()
9353 .when(!has_keybind, |el| {
9354 let status_colors = cx.theme().status();
9355
9356 el.bg(status_colors.error_background)
9357 .border_color(status_colors.error.opacity(0.6))
9358 .pl_2()
9359 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9360 .cursor_default()
9361 .hoverable_tooltip(move |_window, cx| {
9362 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9363 })
9364 })
9365 .children(keybind)
9366 .child(
9367 Label::new(label)
9368 .size(LabelSize::Small)
9369 .when(!has_keybind, |el| {
9370 el.color(cx.theme().status().error.into()).strikethrough()
9371 }),
9372 )
9373 .when(!has_keybind, |el| {
9374 el.child(
9375 h_flex().ml_1().child(
9376 Icon::new(IconName::Info)
9377 .size(IconSize::Small)
9378 .color(cx.theme().status().error.into()),
9379 ),
9380 )
9381 })
9382 .when_some(icon, |element, icon| {
9383 element.child(
9384 div()
9385 .mt(px(1.5))
9386 .child(Icon::new(icon).size(IconSize::Small)),
9387 )
9388 })
9389 }
9390
9391 fn render_edit_prediction_jump_outside_popover(
9392 &self,
9393 snapshot: &BufferSnapshot,
9394 window: &mut Window,
9395 cx: &mut App,
9396 ) -> Stateful<Div> {
9397 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9398 let has_keybind = keybind.is_some();
9399
9400 let file_name = snapshot
9401 .file()
9402 .map(|file| SharedString::new(file.file_name(cx)))
9403 .unwrap_or(SharedString::new_static("untitled"));
9404
9405 h_flex()
9406 .id("ep-jump-outside-popover")
9407 .py_1()
9408 .px_2()
9409 .gap_1()
9410 .rounded_md()
9411 .border_1()
9412 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9413 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9414 .shadow_xs()
9415 .when(!has_keybind, |el| {
9416 let status_colors = cx.theme().status();
9417
9418 el.bg(status_colors.error_background)
9419 .border_color(status_colors.error.opacity(0.6))
9420 .pl_2()
9421 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9422 .cursor_default()
9423 .hoverable_tooltip(move |_window, cx| {
9424 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9425 })
9426 })
9427 .children(keybind)
9428 .child(
9429 Label::new(file_name)
9430 .size(LabelSize::Small)
9431 .buffer_font(cx)
9432 .when(!has_keybind, |el| {
9433 el.color(cx.theme().status().error.into()).strikethrough()
9434 }),
9435 )
9436 .when(!has_keybind, |el| {
9437 el.child(
9438 h_flex().ml_1().child(
9439 Icon::new(IconName::Info)
9440 .size(IconSize::Small)
9441 .color(cx.theme().status().error.into()),
9442 ),
9443 )
9444 })
9445 .child(
9446 div()
9447 .mt(px(1.5))
9448 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9449 )
9450 }
9451
9452 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9453 let accent_color = cx.theme().colors().text_accent;
9454 let editor_bg_color = cx.theme().colors().editor_background;
9455 editor_bg_color.blend(accent_color.opacity(0.1))
9456 }
9457
9458 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9459 let accent_color = cx.theme().colors().text_accent;
9460 let editor_bg_color = cx.theme().colors().editor_background;
9461 editor_bg_color.blend(accent_color.opacity(0.6))
9462 }
9463 fn get_prediction_provider_icon_name(
9464 provider: &Option<RegisteredEditPredictionProvider>,
9465 ) -> IconName {
9466 match provider {
9467 Some(provider) => match provider.provider.name() {
9468 "copilot" => IconName::Copilot,
9469 "supermaven" => IconName::Supermaven,
9470 _ => IconName::ZedPredict,
9471 },
9472 None => IconName::ZedPredict,
9473 }
9474 }
9475
9476 fn render_edit_prediction_cursor_popover(
9477 &self,
9478 min_width: Pixels,
9479 max_width: Pixels,
9480 cursor_point: Point,
9481 style: &EditorStyle,
9482 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9483 _window: &Window,
9484 cx: &mut Context<Editor>,
9485 ) -> Option<AnyElement> {
9486 let provider = self.edit_prediction_provider.as_ref()?;
9487 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9488
9489 let is_refreshing = provider.provider.is_refreshing(cx);
9490
9491 fn pending_completion_container(icon: IconName) -> Div {
9492 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9493 }
9494
9495 let completion = match &self.active_edit_prediction {
9496 Some(prediction) => {
9497 if !self.has_visible_completions_menu() {
9498 const RADIUS: Pixels = px(6.);
9499 const BORDER_WIDTH: Pixels = px(1.);
9500
9501 return Some(
9502 h_flex()
9503 .elevation_2(cx)
9504 .border(BORDER_WIDTH)
9505 .border_color(cx.theme().colors().border)
9506 .when(accept_keystroke.is_none(), |el| {
9507 el.border_color(cx.theme().status().error)
9508 })
9509 .rounded(RADIUS)
9510 .rounded_tl(px(0.))
9511 .overflow_hidden()
9512 .child(div().px_1p5().child(match &prediction.completion {
9513 EditPrediction::MoveWithin { target, snapshot } => {
9514 use text::ToPoint as _;
9515 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9516 {
9517 Icon::new(IconName::ZedPredictDown)
9518 } else {
9519 Icon::new(IconName::ZedPredictUp)
9520 }
9521 }
9522 EditPrediction::MoveOutside { .. } => {
9523 // TODO [zeta2] custom icon for external jump?
9524 Icon::new(provider_icon)
9525 }
9526 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9527 }))
9528 .child(
9529 h_flex()
9530 .gap_1()
9531 .py_1()
9532 .px_2()
9533 .rounded_r(RADIUS - BORDER_WIDTH)
9534 .border_l_1()
9535 .border_color(cx.theme().colors().border)
9536 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9537 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9538 el.child(
9539 Label::new("Hold")
9540 .size(LabelSize::Small)
9541 .when(accept_keystroke.is_none(), |el| {
9542 el.strikethrough()
9543 })
9544 .line_height_style(LineHeightStyle::UiLabel),
9545 )
9546 })
9547 .id("edit_prediction_cursor_popover_keybind")
9548 .when(accept_keystroke.is_none(), |el| {
9549 let status_colors = cx.theme().status();
9550
9551 el.bg(status_colors.error_background)
9552 .border_color(status_colors.error.opacity(0.6))
9553 .child(Icon::new(IconName::Info).color(Color::Error))
9554 .cursor_default()
9555 .hoverable_tooltip(move |_window, cx| {
9556 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9557 .into()
9558 })
9559 })
9560 .when_some(
9561 accept_keystroke.as_ref(),
9562 |el, accept_keystroke| {
9563 el.child(h_flex().children(ui::render_modifiers(
9564 accept_keystroke.modifiers(),
9565 PlatformStyle::platform(),
9566 Some(Color::Default),
9567 Some(IconSize::XSmall.rems().into()),
9568 false,
9569 )))
9570 },
9571 ),
9572 )
9573 .into_any(),
9574 );
9575 }
9576
9577 self.render_edit_prediction_cursor_popover_preview(
9578 prediction,
9579 cursor_point,
9580 style,
9581 cx,
9582 )?
9583 }
9584
9585 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9586 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9587 stale_completion,
9588 cursor_point,
9589 style,
9590 cx,
9591 )?,
9592
9593 None => pending_completion_container(provider_icon)
9594 .child(Label::new("...").size(LabelSize::Small)),
9595 },
9596
9597 None => pending_completion_container(provider_icon)
9598 .child(Label::new("...").size(LabelSize::Small)),
9599 };
9600
9601 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9602 completion
9603 .with_animation(
9604 "loading-completion",
9605 Animation::new(Duration::from_secs(2))
9606 .repeat()
9607 .with_easing(pulsating_between(0.4, 0.8)),
9608 |label, delta| label.opacity(delta),
9609 )
9610 .into_any_element()
9611 } else {
9612 completion.into_any_element()
9613 };
9614
9615 let has_completion = self.active_edit_prediction.is_some();
9616
9617 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9618 Some(
9619 h_flex()
9620 .min_w(min_width)
9621 .max_w(max_width)
9622 .flex_1()
9623 .elevation_2(cx)
9624 .border_color(cx.theme().colors().border)
9625 .child(
9626 div()
9627 .flex_1()
9628 .py_1()
9629 .px_2()
9630 .overflow_hidden()
9631 .child(completion),
9632 )
9633 .when_some(accept_keystroke, |el, accept_keystroke| {
9634 if !accept_keystroke.modifiers().modified() {
9635 return el;
9636 }
9637
9638 el.child(
9639 h_flex()
9640 .h_full()
9641 .border_l_1()
9642 .rounded_r_lg()
9643 .border_color(cx.theme().colors().border)
9644 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9645 .gap_1()
9646 .py_1()
9647 .px_2()
9648 .child(
9649 h_flex()
9650 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9651 .when(is_platform_style_mac, |parent| parent.gap_1())
9652 .child(h_flex().children(ui::render_modifiers(
9653 accept_keystroke.modifiers(),
9654 PlatformStyle::platform(),
9655 Some(if !has_completion {
9656 Color::Muted
9657 } else {
9658 Color::Default
9659 }),
9660 None,
9661 false,
9662 ))),
9663 )
9664 .child(Label::new("Preview").into_any_element())
9665 .opacity(if has_completion { 1.0 } else { 0.4 }),
9666 )
9667 })
9668 .into_any(),
9669 )
9670 }
9671
9672 fn render_edit_prediction_cursor_popover_preview(
9673 &self,
9674 completion: &EditPredictionState,
9675 cursor_point: Point,
9676 style: &EditorStyle,
9677 cx: &mut Context<Editor>,
9678 ) -> Option<Div> {
9679 use text::ToPoint as _;
9680
9681 fn render_relative_row_jump(
9682 prefix: impl Into<String>,
9683 current_row: u32,
9684 target_row: u32,
9685 ) -> Div {
9686 let (row_diff, arrow) = if target_row < current_row {
9687 (current_row - target_row, IconName::ArrowUp)
9688 } else {
9689 (target_row - current_row, IconName::ArrowDown)
9690 };
9691
9692 h_flex()
9693 .child(
9694 Label::new(format!("{}{}", prefix.into(), row_diff))
9695 .color(Color::Muted)
9696 .size(LabelSize::Small),
9697 )
9698 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9699 }
9700
9701 let supports_jump = self
9702 .edit_prediction_provider
9703 .as_ref()
9704 .map(|provider| provider.provider.supports_jump_to_edit())
9705 .unwrap_or(true);
9706
9707 match &completion.completion {
9708 EditPrediction::MoveWithin {
9709 target, snapshot, ..
9710 } => {
9711 if !supports_jump {
9712 return None;
9713 }
9714
9715 Some(
9716 h_flex()
9717 .px_2()
9718 .gap_2()
9719 .flex_1()
9720 .child(
9721 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9722 Icon::new(IconName::ZedPredictDown)
9723 } else {
9724 Icon::new(IconName::ZedPredictUp)
9725 },
9726 )
9727 .child(Label::new("Jump to Edit")),
9728 )
9729 }
9730 EditPrediction::MoveOutside { snapshot, .. } => {
9731 let file_name = snapshot
9732 .file()
9733 .map(|file| file.file_name(cx))
9734 .unwrap_or("untitled");
9735 Some(
9736 h_flex()
9737 .px_2()
9738 .gap_2()
9739 .flex_1()
9740 .child(Icon::new(IconName::ZedPredict))
9741 .child(Label::new(format!("Jump to {file_name}"))),
9742 )
9743 }
9744 EditPrediction::Edit {
9745 edits,
9746 edit_preview,
9747 snapshot,
9748 display_mode: _,
9749 } => {
9750 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9751
9752 let (highlighted_edits, has_more_lines) =
9753 if let Some(edit_preview) = edit_preview.as_ref() {
9754 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9755 .first_line_preview()
9756 } else {
9757 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9758 };
9759
9760 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9761 .with_default_highlights(&style.text, highlighted_edits.highlights);
9762
9763 let preview = h_flex()
9764 .gap_1()
9765 .min_w_16()
9766 .child(styled_text)
9767 .when(has_more_lines, |parent| parent.child("…"));
9768
9769 let left = if supports_jump && first_edit_row != cursor_point.row {
9770 render_relative_row_jump("", cursor_point.row, first_edit_row)
9771 .into_any_element()
9772 } else {
9773 let icon_name =
9774 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9775 Icon::new(icon_name).into_any_element()
9776 };
9777
9778 Some(
9779 h_flex()
9780 .h_full()
9781 .flex_1()
9782 .gap_2()
9783 .pr_1()
9784 .overflow_x_hidden()
9785 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9786 .child(left)
9787 .child(preview),
9788 )
9789 }
9790 }
9791 }
9792
9793 pub fn render_context_menu(
9794 &self,
9795 style: &EditorStyle,
9796 max_height_in_lines: u32,
9797 window: &mut Window,
9798 cx: &mut Context<Editor>,
9799 ) -> Option<AnyElement> {
9800 let menu = self.context_menu.borrow();
9801 let menu = menu.as_ref()?;
9802 if !menu.visible() {
9803 return None;
9804 };
9805 Some(menu.render(style, max_height_in_lines, window, cx))
9806 }
9807
9808 fn render_context_menu_aside(
9809 &mut self,
9810 max_size: Size<Pixels>,
9811 window: &mut Window,
9812 cx: &mut Context<Editor>,
9813 ) -> Option<AnyElement> {
9814 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9815 if menu.visible() {
9816 menu.render_aside(max_size, window, cx)
9817 } else {
9818 None
9819 }
9820 })
9821 }
9822
9823 fn hide_context_menu(
9824 &mut self,
9825 window: &mut Window,
9826 cx: &mut Context<Self>,
9827 ) -> Option<CodeContextMenu> {
9828 cx.notify();
9829 self.completion_tasks.clear();
9830 let context_menu = self.context_menu.borrow_mut().take();
9831 self.stale_edit_prediction_in_menu.take();
9832 self.update_visible_edit_prediction(window, cx);
9833 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9834 && let Some(completion_provider) = &self.completion_provider
9835 {
9836 completion_provider.selection_changed(None, window, cx);
9837 }
9838 context_menu
9839 }
9840
9841 fn show_snippet_choices(
9842 &mut self,
9843 choices: &Vec<String>,
9844 selection: Range<Anchor>,
9845 cx: &mut Context<Self>,
9846 ) {
9847 let Some((_, buffer, _)) = self
9848 .buffer()
9849 .read(cx)
9850 .excerpt_containing(selection.start, cx)
9851 else {
9852 return;
9853 };
9854 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9855 else {
9856 return;
9857 };
9858 if buffer != end_buffer {
9859 log::error!("expected anchor range to have matching buffer IDs");
9860 return;
9861 }
9862
9863 let id = post_inc(&mut self.next_completion_id);
9864 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9865 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9866 CompletionsMenu::new_snippet_choices(
9867 id,
9868 true,
9869 choices,
9870 selection,
9871 buffer,
9872 snippet_sort_order,
9873 ),
9874 ));
9875 }
9876
9877 pub fn insert_snippet(
9878 &mut self,
9879 insertion_ranges: &[Range<MultiBufferOffset>],
9880 snippet: Snippet,
9881 window: &mut Window,
9882 cx: &mut Context<Self>,
9883 ) -> Result<()> {
9884 struct Tabstop<T> {
9885 is_end_tabstop: bool,
9886 ranges: Vec<Range<T>>,
9887 choices: Option<Vec<String>>,
9888 }
9889
9890 let tabstops = self.buffer.update(cx, |buffer, cx| {
9891 let snippet_text: Arc<str> = snippet.text.clone().into();
9892 let edits = insertion_ranges
9893 .iter()
9894 .cloned()
9895 .map(|range| (range, snippet_text.clone()));
9896 let autoindent_mode = AutoindentMode::Block {
9897 original_indent_columns: Vec::new(),
9898 };
9899 buffer.edit(edits, Some(autoindent_mode), cx);
9900
9901 let snapshot = &*buffer.read(cx);
9902 let snippet = &snippet;
9903 snippet
9904 .tabstops
9905 .iter()
9906 .map(|tabstop| {
9907 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9908 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9909 });
9910 let mut tabstop_ranges = tabstop
9911 .ranges
9912 .iter()
9913 .flat_map(|tabstop_range| {
9914 let mut delta = 0_isize;
9915 insertion_ranges.iter().map(move |insertion_range| {
9916 let insertion_start = insertion_range.start + delta;
9917 delta += snippet.text.len() as isize
9918 - (insertion_range.end - insertion_range.start) as isize;
9919
9920 let start =
9921 (insertion_start + tabstop_range.start).min(snapshot.len());
9922 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
9923 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9924 })
9925 })
9926 .collect::<Vec<_>>();
9927 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9928
9929 Tabstop {
9930 is_end_tabstop,
9931 ranges: tabstop_ranges,
9932 choices: tabstop.choices.clone(),
9933 }
9934 })
9935 .collect::<Vec<_>>()
9936 });
9937 if let Some(tabstop) = tabstops.first() {
9938 self.change_selections(Default::default(), window, cx, |s| {
9939 // Reverse order so that the first range is the newest created selection.
9940 // Completions will use it and autoscroll will prioritize it.
9941 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9942 });
9943
9944 if let Some(choices) = &tabstop.choices
9945 && let Some(selection) = tabstop.ranges.first()
9946 {
9947 self.show_snippet_choices(choices, selection.clone(), cx)
9948 }
9949
9950 // If we're already at the last tabstop and it's at the end of the snippet,
9951 // we're done, we don't need to keep the state around.
9952 if !tabstop.is_end_tabstop {
9953 let choices = tabstops
9954 .iter()
9955 .map(|tabstop| tabstop.choices.clone())
9956 .collect();
9957
9958 let ranges = tabstops
9959 .into_iter()
9960 .map(|tabstop| tabstop.ranges)
9961 .collect::<Vec<_>>();
9962
9963 self.snippet_stack.push(SnippetState {
9964 active_index: 0,
9965 ranges,
9966 choices,
9967 });
9968 }
9969
9970 // Check whether the just-entered snippet ends with an auto-closable bracket.
9971 if self.autoclose_regions.is_empty() {
9972 let snapshot = self.buffer.read(cx).snapshot(cx);
9973 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9974 let selection_head = selection.head();
9975 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9976 continue;
9977 };
9978
9979 let mut bracket_pair = None;
9980 let max_lookup_length = scope
9981 .brackets()
9982 .map(|(pair, _)| {
9983 pair.start
9984 .as_str()
9985 .chars()
9986 .count()
9987 .max(pair.end.as_str().chars().count())
9988 })
9989 .max();
9990 if let Some(max_lookup_length) = max_lookup_length {
9991 let next_text = snapshot
9992 .chars_at(selection_head)
9993 .take(max_lookup_length)
9994 .collect::<String>();
9995 let prev_text = snapshot
9996 .reversed_chars_at(selection_head)
9997 .take(max_lookup_length)
9998 .collect::<String>();
9999
10000 for (pair, enabled) in scope.brackets() {
10001 if enabled
10002 && pair.close
10003 && prev_text.starts_with(pair.start.as_str())
10004 && next_text.starts_with(pair.end.as_str())
10005 {
10006 bracket_pair = Some(pair.clone());
10007 break;
10008 }
10009 }
10010 }
10011
10012 if let Some(pair) = bracket_pair {
10013 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10014 let autoclose_enabled =
10015 self.use_autoclose && snapshot_settings.use_autoclose;
10016 if autoclose_enabled {
10017 let start = snapshot.anchor_after(selection_head);
10018 let end = snapshot.anchor_after(selection_head);
10019 self.autoclose_regions.push(AutocloseRegion {
10020 selection_id: selection.id,
10021 range: start..end,
10022 pair,
10023 });
10024 }
10025 }
10026 }
10027 }
10028 }
10029 Ok(())
10030 }
10031
10032 pub fn move_to_next_snippet_tabstop(
10033 &mut self,
10034 window: &mut Window,
10035 cx: &mut Context<Self>,
10036 ) -> bool {
10037 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10038 }
10039
10040 pub fn move_to_prev_snippet_tabstop(
10041 &mut self,
10042 window: &mut Window,
10043 cx: &mut Context<Self>,
10044 ) -> bool {
10045 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10046 }
10047
10048 pub fn move_to_snippet_tabstop(
10049 &mut self,
10050 bias: Bias,
10051 window: &mut Window,
10052 cx: &mut Context<Self>,
10053 ) -> bool {
10054 if let Some(mut snippet) = self.snippet_stack.pop() {
10055 match bias {
10056 Bias::Left => {
10057 if snippet.active_index > 0 {
10058 snippet.active_index -= 1;
10059 } else {
10060 self.snippet_stack.push(snippet);
10061 return false;
10062 }
10063 }
10064 Bias::Right => {
10065 if snippet.active_index + 1 < snippet.ranges.len() {
10066 snippet.active_index += 1;
10067 } else {
10068 self.snippet_stack.push(snippet);
10069 return false;
10070 }
10071 }
10072 }
10073 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10074 self.change_selections(Default::default(), window, cx, |s| {
10075 // Reverse order so that the first range is the newest created selection.
10076 // Completions will use it and autoscroll will prioritize it.
10077 s.select_ranges(current_ranges.iter().rev().cloned())
10078 });
10079
10080 if let Some(choices) = &snippet.choices[snippet.active_index]
10081 && let Some(selection) = current_ranges.first()
10082 {
10083 self.show_snippet_choices(choices, selection.clone(), cx);
10084 }
10085
10086 // If snippet state is not at the last tabstop, push it back on the stack
10087 if snippet.active_index + 1 < snippet.ranges.len() {
10088 self.snippet_stack.push(snippet);
10089 }
10090 return true;
10091 }
10092 }
10093
10094 false
10095 }
10096
10097 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10098 self.transact(window, cx, |this, window, cx| {
10099 this.select_all(&SelectAll, window, cx);
10100 this.insert("", window, cx);
10101 });
10102 }
10103
10104 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10105 if self.read_only(cx) {
10106 return;
10107 }
10108 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10109 self.transact(window, cx, |this, window, cx| {
10110 this.select_autoclose_pair(window, cx);
10111
10112 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10113
10114 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10115 if !this.linked_edit_ranges.is_empty() {
10116 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10117 let snapshot = this.buffer.read(cx).snapshot(cx);
10118
10119 for selection in selections.iter() {
10120 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10121 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10122 if selection_start.buffer_id != selection_end.buffer_id {
10123 continue;
10124 }
10125 if let Some(ranges) =
10126 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10127 {
10128 for (buffer, entries) in ranges {
10129 linked_ranges.entry(buffer).or_default().extend(entries);
10130 }
10131 }
10132 }
10133 }
10134
10135 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10136 for selection in &mut selections {
10137 if selection.is_empty() {
10138 let old_head = selection.head();
10139 let mut new_head =
10140 movement::left(&display_map, old_head.to_display_point(&display_map))
10141 .to_point(&display_map);
10142 if let Some((buffer, line_buffer_range)) = display_map
10143 .buffer_snapshot()
10144 .buffer_line_for_row(MultiBufferRow(old_head.row))
10145 {
10146 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10147 let indent_len = match indent_size.kind {
10148 IndentKind::Space => {
10149 buffer.settings_at(line_buffer_range.start, cx).tab_size
10150 }
10151 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10152 };
10153 if old_head.column <= indent_size.len && old_head.column > 0 {
10154 let indent_len = indent_len.get();
10155 new_head = cmp::min(
10156 new_head,
10157 MultiBufferPoint::new(
10158 old_head.row,
10159 ((old_head.column - 1) / indent_len) * indent_len,
10160 ),
10161 );
10162 }
10163 }
10164
10165 selection.set_head(new_head, SelectionGoal::None);
10166 }
10167 }
10168
10169 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10170 this.insert("", window, cx);
10171 let empty_str: Arc<str> = Arc::from("");
10172 for (buffer, edits) in linked_ranges {
10173 let snapshot = buffer.read(cx).snapshot();
10174 use text::ToPoint as TP;
10175
10176 let edits = edits
10177 .into_iter()
10178 .map(|range| {
10179 let end_point = TP::to_point(&range.end, &snapshot);
10180 let mut start_point = TP::to_point(&range.start, &snapshot);
10181
10182 if end_point == start_point {
10183 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10184 .saturating_sub(1);
10185 start_point =
10186 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10187 };
10188
10189 (start_point..end_point, empty_str.clone())
10190 })
10191 .sorted_by_key(|(range, _)| range.start)
10192 .collect::<Vec<_>>();
10193 buffer.update(cx, |this, cx| {
10194 this.edit(edits, None, cx);
10195 })
10196 }
10197 this.refresh_edit_prediction(true, false, window, cx);
10198 refresh_linked_ranges(this, window, cx);
10199 });
10200 }
10201
10202 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10203 if self.read_only(cx) {
10204 return;
10205 }
10206 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10207 self.transact(window, cx, |this, window, cx| {
10208 this.change_selections(Default::default(), window, cx, |s| {
10209 s.move_with(|map, selection| {
10210 if selection.is_empty() {
10211 let cursor = movement::right(map, selection.head());
10212 selection.end = cursor;
10213 selection.reversed = true;
10214 selection.goal = SelectionGoal::None;
10215 }
10216 })
10217 });
10218 this.insert("", window, cx);
10219 this.refresh_edit_prediction(true, false, window, cx);
10220 });
10221 }
10222
10223 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10224 if self.mode.is_single_line() {
10225 cx.propagate();
10226 return;
10227 }
10228
10229 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10230 if self.move_to_prev_snippet_tabstop(window, cx) {
10231 return;
10232 }
10233 self.outdent(&Outdent, window, cx);
10234 }
10235
10236 pub fn next_snippet_tabstop(
10237 &mut self,
10238 _: &NextSnippetTabstop,
10239 window: &mut Window,
10240 cx: &mut Context<Self>,
10241 ) {
10242 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10243 cx.propagate();
10244 return;
10245 }
10246
10247 if self.move_to_next_snippet_tabstop(window, cx) {
10248 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10249 return;
10250 }
10251 cx.propagate();
10252 }
10253
10254 pub fn previous_snippet_tabstop(
10255 &mut self,
10256 _: &PreviousSnippetTabstop,
10257 window: &mut Window,
10258 cx: &mut Context<Self>,
10259 ) {
10260 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10261 cx.propagate();
10262 return;
10263 }
10264
10265 if self.move_to_prev_snippet_tabstop(window, cx) {
10266 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10267 return;
10268 }
10269 cx.propagate();
10270 }
10271
10272 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10273 if self.mode.is_single_line() {
10274 cx.propagate();
10275 return;
10276 }
10277
10278 if self.move_to_next_snippet_tabstop(window, cx) {
10279 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10280 return;
10281 }
10282 if self.read_only(cx) {
10283 return;
10284 }
10285 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10286 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10287 let buffer = self.buffer.read(cx);
10288 let snapshot = buffer.snapshot(cx);
10289 let rows_iter = selections.iter().map(|s| s.head().row);
10290 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10291
10292 let has_some_cursor_in_whitespace = selections
10293 .iter()
10294 .filter(|selection| selection.is_empty())
10295 .any(|selection| {
10296 let cursor = selection.head();
10297 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10298 cursor.column < current_indent.len
10299 });
10300
10301 let mut edits = Vec::new();
10302 let mut prev_edited_row = 0;
10303 let mut row_delta = 0;
10304 for selection in &mut selections {
10305 if selection.start.row != prev_edited_row {
10306 row_delta = 0;
10307 }
10308 prev_edited_row = selection.end.row;
10309
10310 // If the selection is non-empty, then increase the indentation of the selected lines.
10311 if !selection.is_empty() {
10312 row_delta =
10313 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10314 continue;
10315 }
10316
10317 let cursor = selection.head();
10318 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10319 if let Some(suggested_indent) =
10320 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10321 {
10322 // Don't do anything if already at suggested indent
10323 // and there is any other cursor which is not
10324 if has_some_cursor_in_whitespace
10325 && cursor.column == current_indent.len
10326 && current_indent.len == suggested_indent.len
10327 {
10328 continue;
10329 }
10330
10331 // Adjust line and move cursor to suggested indent
10332 // if cursor is not at suggested indent
10333 if cursor.column < suggested_indent.len
10334 && cursor.column <= current_indent.len
10335 && current_indent.len <= suggested_indent.len
10336 {
10337 selection.start = Point::new(cursor.row, suggested_indent.len);
10338 selection.end = selection.start;
10339 if row_delta == 0 {
10340 edits.extend(Buffer::edit_for_indent_size_adjustment(
10341 cursor.row,
10342 current_indent,
10343 suggested_indent,
10344 ));
10345 row_delta = suggested_indent.len - current_indent.len;
10346 }
10347 continue;
10348 }
10349
10350 // If current indent is more than suggested indent
10351 // only move cursor to current indent and skip indent
10352 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10353 selection.start = Point::new(cursor.row, current_indent.len);
10354 selection.end = selection.start;
10355 continue;
10356 }
10357 }
10358
10359 // Otherwise, insert a hard or soft tab.
10360 let settings = buffer.language_settings_at(cursor, cx);
10361 let tab_size = if settings.hard_tabs {
10362 IndentSize::tab()
10363 } else {
10364 let tab_size = settings.tab_size.get();
10365 let indent_remainder = snapshot
10366 .text_for_range(Point::new(cursor.row, 0)..cursor)
10367 .flat_map(str::chars)
10368 .fold(row_delta % tab_size, |counter: u32, c| {
10369 if c == '\t' {
10370 0
10371 } else {
10372 (counter + 1) % tab_size
10373 }
10374 });
10375
10376 let chars_to_next_tab_stop = tab_size - indent_remainder;
10377 IndentSize::spaces(chars_to_next_tab_stop)
10378 };
10379 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10380 selection.end = selection.start;
10381 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10382 row_delta += tab_size.len;
10383 }
10384
10385 self.transact(window, cx, |this, window, cx| {
10386 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10387 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10388 this.refresh_edit_prediction(true, false, window, cx);
10389 });
10390 }
10391
10392 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10393 if self.read_only(cx) {
10394 return;
10395 }
10396 if self.mode.is_single_line() {
10397 cx.propagate();
10398 return;
10399 }
10400
10401 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10402 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10403 let mut prev_edited_row = 0;
10404 let mut row_delta = 0;
10405 let mut edits = Vec::new();
10406 let buffer = self.buffer.read(cx);
10407 let snapshot = buffer.snapshot(cx);
10408 for selection in &mut selections {
10409 if selection.start.row != prev_edited_row {
10410 row_delta = 0;
10411 }
10412 prev_edited_row = selection.end.row;
10413
10414 row_delta =
10415 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10416 }
10417
10418 self.transact(window, cx, |this, window, cx| {
10419 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10420 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10421 });
10422 }
10423
10424 fn indent_selection(
10425 buffer: &MultiBuffer,
10426 snapshot: &MultiBufferSnapshot,
10427 selection: &mut Selection<Point>,
10428 edits: &mut Vec<(Range<Point>, String)>,
10429 delta_for_start_row: u32,
10430 cx: &App,
10431 ) -> u32 {
10432 let settings = buffer.language_settings_at(selection.start, cx);
10433 let tab_size = settings.tab_size.get();
10434 let indent_kind = if settings.hard_tabs {
10435 IndentKind::Tab
10436 } else {
10437 IndentKind::Space
10438 };
10439 let mut start_row = selection.start.row;
10440 let mut end_row = selection.end.row + 1;
10441
10442 // If a selection ends at the beginning of a line, don't indent
10443 // that last line.
10444 if selection.end.column == 0 && selection.end.row > selection.start.row {
10445 end_row -= 1;
10446 }
10447
10448 // Avoid re-indenting a row that has already been indented by a
10449 // previous selection, but still update this selection's column
10450 // to reflect that indentation.
10451 if delta_for_start_row > 0 {
10452 start_row += 1;
10453 selection.start.column += delta_for_start_row;
10454 if selection.end.row == selection.start.row {
10455 selection.end.column += delta_for_start_row;
10456 }
10457 }
10458
10459 let mut delta_for_end_row = 0;
10460 let has_multiple_rows = start_row + 1 != end_row;
10461 for row in start_row..end_row {
10462 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10463 let indent_delta = match (current_indent.kind, indent_kind) {
10464 (IndentKind::Space, IndentKind::Space) => {
10465 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10466 IndentSize::spaces(columns_to_next_tab_stop)
10467 }
10468 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10469 (_, IndentKind::Tab) => IndentSize::tab(),
10470 };
10471
10472 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10473 0
10474 } else {
10475 selection.start.column
10476 };
10477 let row_start = Point::new(row, start);
10478 edits.push((
10479 row_start..row_start,
10480 indent_delta.chars().collect::<String>(),
10481 ));
10482
10483 // Update this selection's endpoints to reflect the indentation.
10484 if row == selection.start.row {
10485 selection.start.column += indent_delta.len;
10486 }
10487 if row == selection.end.row {
10488 selection.end.column += indent_delta.len;
10489 delta_for_end_row = indent_delta.len;
10490 }
10491 }
10492
10493 if selection.start.row == selection.end.row {
10494 delta_for_start_row + delta_for_end_row
10495 } else {
10496 delta_for_end_row
10497 }
10498 }
10499
10500 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10501 if self.read_only(cx) {
10502 return;
10503 }
10504 if self.mode.is_single_line() {
10505 cx.propagate();
10506 return;
10507 }
10508
10509 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10510 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10511 let selections = self.selections.all::<Point>(&display_map);
10512 let mut deletion_ranges = Vec::new();
10513 let mut last_outdent = None;
10514 {
10515 let buffer = self.buffer.read(cx);
10516 let snapshot = buffer.snapshot(cx);
10517 for selection in &selections {
10518 let settings = buffer.language_settings_at(selection.start, cx);
10519 let tab_size = settings.tab_size.get();
10520 let mut rows = selection.spanned_rows(false, &display_map);
10521
10522 // Avoid re-outdenting a row that has already been outdented by a
10523 // previous selection.
10524 if let Some(last_row) = last_outdent
10525 && last_row == rows.start
10526 {
10527 rows.start = rows.start.next_row();
10528 }
10529 let has_multiple_rows = rows.len() > 1;
10530 for row in rows.iter_rows() {
10531 let indent_size = snapshot.indent_size_for_line(row);
10532 if indent_size.len > 0 {
10533 let deletion_len = match indent_size.kind {
10534 IndentKind::Space => {
10535 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10536 if columns_to_prev_tab_stop == 0 {
10537 tab_size
10538 } else {
10539 columns_to_prev_tab_stop
10540 }
10541 }
10542 IndentKind::Tab => 1,
10543 };
10544 let start = if has_multiple_rows
10545 || deletion_len > selection.start.column
10546 || indent_size.len < selection.start.column
10547 {
10548 0
10549 } else {
10550 selection.start.column - deletion_len
10551 };
10552 deletion_ranges.push(
10553 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10554 );
10555 last_outdent = Some(row);
10556 }
10557 }
10558 }
10559 }
10560
10561 self.transact(window, cx, |this, window, cx| {
10562 this.buffer.update(cx, |buffer, cx| {
10563 let empty_str: Arc<str> = Arc::default();
10564 buffer.edit(
10565 deletion_ranges
10566 .into_iter()
10567 .map(|range| (range, empty_str.clone())),
10568 None,
10569 cx,
10570 );
10571 });
10572 let selections = this
10573 .selections
10574 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10575 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10576 });
10577 }
10578
10579 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10580 if self.read_only(cx) {
10581 return;
10582 }
10583 if self.mode.is_single_line() {
10584 cx.propagate();
10585 return;
10586 }
10587
10588 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10589 let selections = self
10590 .selections
10591 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10592 .into_iter()
10593 .map(|s| s.range());
10594
10595 self.transact(window, cx, |this, window, cx| {
10596 this.buffer.update(cx, |buffer, cx| {
10597 buffer.autoindent_ranges(selections, cx);
10598 });
10599 let selections = this
10600 .selections
10601 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10602 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10603 });
10604 }
10605
10606 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10607 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10608 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10609 let selections = self.selections.all::<Point>(&display_map);
10610
10611 let mut new_cursors = Vec::new();
10612 let mut edit_ranges = Vec::new();
10613 let mut selections = selections.iter().peekable();
10614 while let Some(selection) = selections.next() {
10615 let mut rows = selection.spanned_rows(false, &display_map);
10616
10617 // Accumulate contiguous regions of rows that we want to delete.
10618 while let Some(next_selection) = selections.peek() {
10619 let next_rows = next_selection.spanned_rows(false, &display_map);
10620 if next_rows.start <= rows.end {
10621 rows.end = next_rows.end;
10622 selections.next().unwrap();
10623 } else {
10624 break;
10625 }
10626 }
10627
10628 let buffer = display_map.buffer_snapshot();
10629 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10630 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10631 // If there's a line after the range, delete the \n from the end of the row range
10632 (
10633 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10634 rows.end,
10635 )
10636 } else {
10637 // If there isn't a line after the range, delete the \n from the line before the
10638 // start of the row range
10639 edit_start = edit_start.saturating_sub_usize(1);
10640 (buffer.len(), rows.start.previous_row())
10641 };
10642
10643 let text_layout_details = self.text_layout_details(window);
10644 let x = display_map.x_for_display_point(
10645 selection.head().to_display_point(&display_map),
10646 &text_layout_details,
10647 );
10648 let row = Point::new(target_row.0, 0)
10649 .to_display_point(&display_map)
10650 .row();
10651 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10652
10653 new_cursors.push((
10654 selection.id,
10655 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10656 SelectionGoal::None,
10657 ));
10658 edit_ranges.push(edit_start..edit_end);
10659 }
10660
10661 self.transact(window, cx, |this, window, cx| {
10662 let buffer = this.buffer.update(cx, |buffer, cx| {
10663 let empty_str: Arc<str> = Arc::default();
10664 buffer.edit(
10665 edit_ranges
10666 .into_iter()
10667 .map(|range| (range, empty_str.clone())),
10668 None,
10669 cx,
10670 );
10671 buffer.snapshot(cx)
10672 });
10673 let new_selections = new_cursors
10674 .into_iter()
10675 .map(|(id, cursor, goal)| {
10676 let cursor = cursor.to_point(&buffer);
10677 Selection {
10678 id,
10679 start: cursor,
10680 end: cursor,
10681 reversed: false,
10682 goal,
10683 }
10684 })
10685 .collect();
10686
10687 this.change_selections(Default::default(), window, cx, |s| {
10688 s.select(new_selections);
10689 });
10690 });
10691 }
10692
10693 pub fn join_lines_impl(
10694 &mut self,
10695 insert_whitespace: bool,
10696 window: &mut Window,
10697 cx: &mut Context<Self>,
10698 ) {
10699 if self.read_only(cx) {
10700 return;
10701 }
10702 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10703 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10704 let start = MultiBufferRow(selection.start.row);
10705 // Treat single line selections as if they include the next line. Otherwise this action
10706 // would do nothing for single line selections individual cursors.
10707 let end = if selection.start.row == selection.end.row {
10708 MultiBufferRow(selection.start.row + 1)
10709 } else {
10710 MultiBufferRow(selection.end.row)
10711 };
10712
10713 if let Some(last_row_range) = row_ranges.last_mut()
10714 && start <= last_row_range.end
10715 {
10716 last_row_range.end = end;
10717 continue;
10718 }
10719 row_ranges.push(start..end);
10720 }
10721
10722 let snapshot = self.buffer.read(cx).snapshot(cx);
10723 let mut cursor_positions = Vec::new();
10724 for row_range in &row_ranges {
10725 let anchor = snapshot.anchor_before(Point::new(
10726 row_range.end.previous_row().0,
10727 snapshot.line_len(row_range.end.previous_row()),
10728 ));
10729 cursor_positions.push(anchor..anchor);
10730 }
10731
10732 self.transact(window, cx, |this, window, cx| {
10733 for row_range in row_ranges.into_iter().rev() {
10734 for row in row_range.iter_rows().rev() {
10735 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10736 let next_line_row = row.next_row();
10737 let indent = snapshot.indent_size_for_line(next_line_row);
10738 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10739
10740 let replace =
10741 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10742 " "
10743 } else {
10744 ""
10745 };
10746
10747 this.buffer.update(cx, |buffer, cx| {
10748 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10749 });
10750 }
10751 }
10752
10753 this.change_selections(Default::default(), window, cx, |s| {
10754 s.select_anchor_ranges(cursor_positions)
10755 });
10756 });
10757 }
10758
10759 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10761 self.join_lines_impl(true, window, cx);
10762 }
10763
10764 pub fn sort_lines_case_sensitive(
10765 &mut self,
10766 _: &SortLinesCaseSensitive,
10767 window: &mut Window,
10768 cx: &mut Context<Self>,
10769 ) {
10770 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10771 }
10772
10773 pub fn sort_lines_by_length(
10774 &mut self,
10775 _: &SortLinesByLength,
10776 window: &mut Window,
10777 cx: &mut Context<Self>,
10778 ) {
10779 self.manipulate_immutable_lines(window, cx, |lines| {
10780 lines.sort_by_key(|&line| line.chars().count())
10781 })
10782 }
10783
10784 pub fn sort_lines_case_insensitive(
10785 &mut self,
10786 _: &SortLinesCaseInsensitive,
10787 window: &mut Window,
10788 cx: &mut Context<Self>,
10789 ) {
10790 self.manipulate_immutable_lines(window, cx, |lines| {
10791 lines.sort_by_key(|line| line.to_lowercase())
10792 })
10793 }
10794
10795 pub fn unique_lines_case_insensitive(
10796 &mut self,
10797 _: &UniqueLinesCaseInsensitive,
10798 window: &mut Window,
10799 cx: &mut Context<Self>,
10800 ) {
10801 self.manipulate_immutable_lines(window, cx, |lines| {
10802 let mut seen = HashSet::default();
10803 lines.retain(|line| seen.insert(line.to_lowercase()));
10804 })
10805 }
10806
10807 pub fn unique_lines_case_sensitive(
10808 &mut self,
10809 _: &UniqueLinesCaseSensitive,
10810 window: &mut Window,
10811 cx: &mut Context<Self>,
10812 ) {
10813 self.manipulate_immutable_lines(window, cx, |lines| {
10814 let mut seen = HashSet::default();
10815 lines.retain(|line| seen.insert(*line));
10816 })
10817 }
10818
10819 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10820 let snapshot = self.buffer.read(cx).snapshot(cx);
10821 for selection in self.selections.disjoint_anchors_arc().iter() {
10822 if snapshot
10823 .language_at(selection.start)
10824 .and_then(|lang| lang.config().wrap_characters.as_ref())
10825 .is_some()
10826 {
10827 return true;
10828 }
10829 }
10830 false
10831 }
10832
10833 fn wrap_selections_in_tag(
10834 &mut self,
10835 _: &WrapSelectionsInTag,
10836 window: &mut Window,
10837 cx: &mut Context<Self>,
10838 ) {
10839 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10840
10841 let snapshot = self.buffer.read(cx).snapshot(cx);
10842
10843 let mut edits = Vec::new();
10844 let mut boundaries = Vec::new();
10845
10846 for selection in self
10847 .selections
10848 .all_adjusted(&self.display_snapshot(cx))
10849 .iter()
10850 {
10851 let Some(wrap_config) = snapshot
10852 .language_at(selection.start)
10853 .and_then(|lang| lang.config().wrap_characters.clone())
10854 else {
10855 continue;
10856 };
10857
10858 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10859 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10860
10861 let start_before = snapshot.anchor_before(selection.start);
10862 let end_after = snapshot.anchor_after(selection.end);
10863
10864 edits.push((start_before..start_before, open_tag));
10865 edits.push((end_after..end_after, close_tag));
10866
10867 boundaries.push((
10868 start_before,
10869 end_after,
10870 wrap_config.start_prefix.len(),
10871 wrap_config.end_suffix.len(),
10872 ));
10873 }
10874
10875 if edits.is_empty() {
10876 return;
10877 }
10878
10879 self.transact(window, cx, |this, window, cx| {
10880 let buffer = this.buffer.update(cx, |buffer, cx| {
10881 buffer.edit(edits, None, cx);
10882 buffer.snapshot(cx)
10883 });
10884
10885 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10886 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10887 boundaries.into_iter()
10888 {
10889 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10890 let close_offset = end_after
10891 .to_offset(&buffer)
10892 .saturating_sub_usize(end_suffix_len);
10893 new_selections.push(open_offset..open_offset);
10894 new_selections.push(close_offset..close_offset);
10895 }
10896
10897 this.change_selections(Default::default(), window, cx, |s| {
10898 s.select_ranges(new_selections);
10899 });
10900
10901 this.request_autoscroll(Autoscroll::fit(), cx);
10902 });
10903 }
10904
10905 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10906 let Some(project) = self.project.clone() else {
10907 return;
10908 };
10909 self.reload(project, window, cx)
10910 .detach_and_notify_err(window, cx);
10911 }
10912
10913 pub fn restore_file(
10914 &mut self,
10915 _: &::git::RestoreFile,
10916 window: &mut Window,
10917 cx: &mut Context<Self>,
10918 ) {
10919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10920 let mut buffer_ids = HashSet::default();
10921 let snapshot = self.buffer().read(cx).snapshot(cx);
10922 for selection in self
10923 .selections
10924 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10925 {
10926 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10927 }
10928
10929 let buffer = self.buffer().read(cx);
10930 let ranges = buffer_ids
10931 .into_iter()
10932 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10933 .collect::<Vec<_>>();
10934
10935 self.restore_hunks_in_ranges(ranges, window, cx);
10936 }
10937
10938 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10940 let selections = self
10941 .selections
10942 .all(&self.display_snapshot(cx))
10943 .into_iter()
10944 .map(|s| s.range())
10945 .collect();
10946 self.restore_hunks_in_ranges(selections, window, cx);
10947 }
10948
10949 pub fn restore_hunks_in_ranges(
10950 &mut self,
10951 ranges: Vec<Range<Point>>,
10952 window: &mut Window,
10953 cx: &mut Context<Editor>,
10954 ) {
10955 let mut revert_changes = HashMap::default();
10956 let chunk_by = self
10957 .snapshot(window, cx)
10958 .hunks_for_ranges(ranges)
10959 .into_iter()
10960 .chunk_by(|hunk| hunk.buffer_id);
10961 for (buffer_id, hunks) in &chunk_by {
10962 let hunks = hunks.collect::<Vec<_>>();
10963 for hunk in &hunks {
10964 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10965 }
10966 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10967 }
10968 drop(chunk_by);
10969 if !revert_changes.is_empty() {
10970 self.transact(window, cx, |editor, window, cx| {
10971 editor.restore(revert_changes, window, cx);
10972 });
10973 }
10974 }
10975
10976 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10977 if let Some(status) = self
10978 .addons
10979 .iter()
10980 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10981 {
10982 return Some(status);
10983 }
10984 self.project
10985 .as_ref()?
10986 .read(cx)
10987 .status_for_buffer_id(buffer_id, cx)
10988 }
10989
10990 pub fn open_active_item_in_terminal(
10991 &mut self,
10992 _: &OpenInTerminal,
10993 window: &mut Window,
10994 cx: &mut Context<Self>,
10995 ) {
10996 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10997 let project_path = buffer.read(cx).project_path(cx)?;
10998 let project = self.project()?.read(cx);
10999 let entry = project.entry_for_path(&project_path, cx)?;
11000 let parent = match &entry.canonical_path {
11001 Some(canonical_path) => canonical_path.to_path_buf(),
11002 None => project.absolute_path(&project_path, cx)?,
11003 }
11004 .parent()?
11005 .to_path_buf();
11006 Some(parent)
11007 }) {
11008 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11009 }
11010 }
11011
11012 fn set_breakpoint_context_menu(
11013 &mut self,
11014 display_row: DisplayRow,
11015 position: Option<Anchor>,
11016 clicked_point: gpui::Point<Pixels>,
11017 window: &mut Window,
11018 cx: &mut Context<Self>,
11019 ) {
11020 let source = self
11021 .buffer
11022 .read(cx)
11023 .snapshot(cx)
11024 .anchor_before(Point::new(display_row.0, 0u32));
11025
11026 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11027
11028 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11029 self,
11030 source,
11031 clicked_point,
11032 context_menu,
11033 window,
11034 cx,
11035 );
11036 }
11037
11038 fn add_edit_breakpoint_block(
11039 &mut self,
11040 anchor: Anchor,
11041 breakpoint: &Breakpoint,
11042 edit_action: BreakpointPromptEditAction,
11043 window: &mut Window,
11044 cx: &mut Context<Self>,
11045 ) {
11046 let weak_editor = cx.weak_entity();
11047 let bp_prompt = cx.new(|cx| {
11048 BreakpointPromptEditor::new(
11049 weak_editor,
11050 anchor,
11051 breakpoint.clone(),
11052 edit_action,
11053 window,
11054 cx,
11055 )
11056 });
11057
11058 let height = bp_prompt.update(cx, |this, cx| {
11059 this.prompt
11060 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11061 });
11062 let cloned_prompt = bp_prompt.clone();
11063 let blocks = vec![BlockProperties {
11064 style: BlockStyle::Sticky,
11065 placement: BlockPlacement::Above(anchor),
11066 height: Some(height),
11067 render: Arc::new(move |cx| {
11068 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11069 cloned_prompt.clone().into_any_element()
11070 }),
11071 priority: 0,
11072 }];
11073
11074 let focus_handle = bp_prompt.focus_handle(cx);
11075 window.focus(&focus_handle);
11076
11077 let block_ids = self.insert_blocks(blocks, None, cx);
11078 bp_prompt.update(cx, |prompt, _| {
11079 prompt.add_block_ids(block_ids);
11080 });
11081 }
11082
11083 pub(crate) fn breakpoint_at_row(
11084 &self,
11085 row: u32,
11086 window: &mut Window,
11087 cx: &mut Context<Self>,
11088 ) -> Option<(Anchor, Breakpoint)> {
11089 let snapshot = self.snapshot(window, cx);
11090 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11091
11092 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11093 }
11094
11095 pub(crate) fn breakpoint_at_anchor(
11096 &self,
11097 breakpoint_position: Anchor,
11098 snapshot: &EditorSnapshot,
11099 cx: &mut Context<Self>,
11100 ) -> Option<(Anchor, Breakpoint)> {
11101 let buffer = self
11102 .buffer
11103 .read(cx)
11104 .buffer_for_anchor(breakpoint_position, cx)?;
11105
11106 let enclosing_excerpt = breakpoint_position.excerpt_id;
11107 let buffer_snapshot = buffer.read(cx).snapshot();
11108
11109 let row = buffer_snapshot
11110 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11111 .row;
11112
11113 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11114 let anchor_end = snapshot
11115 .buffer_snapshot()
11116 .anchor_after(Point::new(row, line_len));
11117
11118 self.breakpoint_store
11119 .as_ref()?
11120 .read_with(cx, |breakpoint_store, cx| {
11121 breakpoint_store
11122 .breakpoints(
11123 &buffer,
11124 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11125 &buffer_snapshot,
11126 cx,
11127 )
11128 .next()
11129 .and_then(|(bp, _)| {
11130 let breakpoint_row = buffer_snapshot
11131 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11132 .row;
11133
11134 if breakpoint_row == row {
11135 snapshot
11136 .buffer_snapshot()
11137 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11138 .map(|position| (position, bp.bp.clone()))
11139 } else {
11140 None
11141 }
11142 })
11143 })
11144 }
11145
11146 pub fn edit_log_breakpoint(
11147 &mut self,
11148 _: &EditLogBreakpoint,
11149 window: &mut Window,
11150 cx: &mut Context<Self>,
11151 ) {
11152 if self.breakpoint_store.is_none() {
11153 return;
11154 }
11155
11156 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11157 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11158 message: None,
11159 state: BreakpointState::Enabled,
11160 condition: None,
11161 hit_condition: None,
11162 });
11163
11164 self.add_edit_breakpoint_block(
11165 anchor,
11166 &breakpoint,
11167 BreakpointPromptEditAction::Log,
11168 window,
11169 cx,
11170 );
11171 }
11172 }
11173
11174 fn breakpoints_at_cursors(
11175 &self,
11176 window: &mut Window,
11177 cx: &mut Context<Self>,
11178 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11179 let snapshot = self.snapshot(window, cx);
11180 let cursors = self
11181 .selections
11182 .disjoint_anchors_arc()
11183 .iter()
11184 .map(|selection| {
11185 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11186
11187 let breakpoint_position = self
11188 .breakpoint_at_row(cursor_position.row, window, cx)
11189 .map(|bp| bp.0)
11190 .unwrap_or_else(|| {
11191 snapshot
11192 .display_snapshot
11193 .buffer_snapshot()
11194 .anchor_after(Point::new(cursor_position.row, 0))
11195 });
11196
11197 let breakpoint = self
11198 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11199 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11200
11201 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11202 })
11203 // 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.
11204 .collect::<HashMap<Anchor, _>>();
11205
11206 cursors.into_iter().collect()
11207 }
11208
11209 pub fn enable_breakpoint(
11210 &mut self,
11211 _: &crate::actions::EnableBreakpoint,
11212 window: &mut Window,
11213 cx: &mut Context<Self>,
11214 ) {
11215 if self.breakpoint_store.is_none() {
11216 return;
11217 }
11218
11219 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11220 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11221 continue;
11222 };
11223 self.edit_breakpoint_at_anchor(
11224 anchor,
11225 breakpoint,
11226 BreakpointEditAction::InvertState,
11227 cx,
11228 );
11229 }
11230 }
11231
11232 pub fn disable_breakpoint(
11233 &mut self,
11234 _: &crate::actions::DisableBreakpoint,
11235 window: &mut Window,
11236 cx: &mut Context<Self>,
11237 ) {
11238 if self.breakpoint_store.is_none() {
11239 return;
11240 }
11241
11242 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11243 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11244 continue;
11245 };
11246 self.edit_breakpoint_at_anchor(
11247 anchor,
11248 breakpoint,
11249 BreakpointEditAction::InvertState,
11250 cx,
11251 );
11252 }
11253 }
11254
11255 pub fn toggle_breakpoint(
11256 &mut self,
11257 _: &crate::actions::ToggleBreakpoint,
11258 window: &mut Window,
11259 cx: &mut Context<Self>,
11260 ) {
11261 if self.breakpoint_store.is_none() {
11262 return;
11263 }
11264
11265 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11266 if let Some(breakpoint) = breakpoint {
11267 self.edit_breakpoint_at_anchor(
11268 anchor,
11269 breakpoint,
11270 BreakpointEditAction::Toggle,
11271 cx,
11272 );
11273 } else {
11274 self.edit_breakpoint_at_anchor(
11275 anchor,
11276 Breakpoint::new_standard(),
11277 BreakpointEditAction::Toggle,
11278 cx,
11279 );
11280 }
11281 }
11282 }
11283
11284 pub fn edit_breakpoint_at_anchor(
11285 &mut self,
11286 breakpoint_position: Anchor,
11287 breakpoint: Breakpoint,
11288 edit_action: BreakpointEditAction,
11289 cx: &mut Context<Self>,
11290 ) {
11291 let Some(breakpoint_store) = &self.breakpoint_store else {
11292 return;
11293 };
11294
11295 let Some(buffer) = self
11296 .buffer
11297 .read(cx)
11298 .buffer_for_anchor(breakpoint_position, cx)
11299 else {
11300 return;
11301 };
11302
11303 breakpoint_store.update(cx, |breakpoint_store, cx| {
11304 breakpoint_store.toggle_breakpoint(
11305 buffer,
11306 BreakpointWithPosition {
11307 position: breakpoint_position.text_anchor,
11308 bp: breakpoint,
11309 },
11310 edit_action,
11311 cx,
11312 );
11313 });
11314
11315 cx.notify();
11316 }
11317
11318 #[cfg(any(test, feature = "test-support"))]
11319 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11320 self.breakpoint_store.clone()
11321 }
11322
11323 pub fn prepare_restore_change(
11324 &self,
11325 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11326 hunk: &MultiBufferDiffHunk,
11327 cx: &mut App,
11328 ) -> Option<()> {
11329 if hunk.is_created_file() {
11330 return None;
11331 }
11332 let buffer = self.buffer.read(cx);
11333 let diff = buffer.diff_for(hunk.buffer_id)?;
11334 let buffer = buffer.buffer(hunk.buffer_id)?;
11335 let buffer = buffer.read(cx);
11336 let original_text = diff
11337 .read(cx)
11338 .base_text()
11339 .as_rope()
11340 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11341 let buffer_snapshot = buffer.snapshot();
11342 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11343 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11344 probe
11345 .0
11346 .start
11347 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11348 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11349 }) {
11350 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11351 Some(())
11352 } else {
11353 None
11354 }
11355 }
11356
11357 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11358 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11359 }
11360
11361 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11362 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11363 }
11364
11365 fn manipulate_lines<M>(
11366 &mut self,
11367 window: &mut Window,
11368 cx: &mut Context<Self>,
11369 mut manipulate: M,
11370 ) where
11371 M: FnMut(&str) -> LineManipulationResult,
11372 {
11373 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11374
11375 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11376 let buffer = self.buffer.read(cx).snapshot(cx);
11377
11378 let mut edits = Vec::new();
11379
11380 let selections = self.selections.all::<Point>(&display_map);
11381 let mut selections = selections.iter().peekable();
11382 let mut contiguous_row_selections = Vec::new();
11383 let mut new_selections = Vec::new();
11384 let mut added_lines = 0;
11385 let mut removed_lines = 0;
11386
11387 while let Some(selection) = selections.next() {
11388 let (start_row, end_row) = consume_contiguous_rows(
11389 &mut contiguous_row_selections,
11390 selection,
11391 &display_map,
11392 &mut selections,
11393 );
11394
11395 let start_point = Point::new(start_row.0, 0);
11396 let end_point = Point::new(
11397 end_row.previous_row().0,
11398 buffer.line_len(end_row.previous_row()),
11399 );
11400 let text = buffer
11401 .text_for_range(start_point..end_point)
11402 .collect::<String>();
11403
11404 let LineManipulationResult {
11405 new_text,
11406 line_count_before,
11407 line_count_after,
11408 } = manipulate(&text);
11409
11410 edits.push((start_point..end_point, new_text));
11411
11412 // Selections must change based on added and removed line count
11413 let start_row =
11414 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11415 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11416 new_selections.push(Selection {
11417 id: selection.id,
11418 start: start_row,
11419 end: end_row,
11420 goal: SelectionGoal::None,
11421 reversed: selection.reversed,
11422 });
11423
11424 if line_count_after > line_count_before {
11425 added_lines += line_count_after - line_count_before;
11426 } else if line_count_before > line_count_after {
11427 removed_lines += line_count_before - line_count_after;
11428 }
11429 }
11430
11431 self.transact(window, cx, |this, window, cx| {
11432 let buffer = this.buffer.update(cx, |buffer, cx| {
11433 buffer.edit(edits, None, cx);
11434 buffer.snapshot(cx)
11435 });
11436
11437 // Recalculate offsets on newly edited buffer
11438 let new_selections = new_selections
11439 .iter()
11440 .map(|s| {
11441 let start_point = Point::new(s.start.0, 0);
11442 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11443 Selection {
11444 id: s.id,
11445 start: buffer.point_to_offset(start_point),
11446 end: buffer.point_to_offset(end_point),
11447 goal: s.goal,
11448 reversed: s.reversed,
11449 }
11450 })
11451 .collect();
11452
11453 this.change_selections(Default::default(), window, cx, |s| {
11454 s.select(new_selections);
11455 });
11456
11457 this.request_autoscroll(Autoscroll::fit(), cx);
11458 });
11459 }
11460
11461 fn manipulate_immutable_lines<Fn>(
11462 &mut self,
11463 window: &mut Window,
11464 cx: &mut Context<Self>,
11465 mut callback: Fn,
11466 ) where
11467 Fn: FnMut(&mut Vec<&str>),
11468 {
11469 self.manipulate_lines(window, cx, |text| {
11470 let mut lines: Vec<&str> = text.split('\n').collect();
11471 let line_count_before = lines.len();
11472
11473 callback(&mut lines);
11474
11475 LineManipulationResult {
11476 new_text: lines.join("\n"),
11477 line_count_before,
11478 line_count_after: lines.len(),
11479 }
11480 });
11481 }
11482
11483 fn manipulate_mutable_lines<Fn>(
11484 &mut self,
11485 window: &mut Window,
11486 cx: &mut Context<Self>,
11487 mut callback: Fn,
11488 ) where
11489 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11490 {
11491 self.manipulate_lines(window, cx, |text| {
11492 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11493 let line_count_before = lines.len();
11494
11495 callback(&mut lines);
11496
11497 LineManipulationResult {
11498 new_text: lines.join("\n"),
11499 line_count_before,
11500 line_count_after: lines.len(),
11501 }
11502 });
11503 }
11504
11505 pub fn convert_indentation_to_spaces(
11506 &mut self,
11507 _: &ConvertIndentationToSpaces,
11508 window: &mut Window,
11509 cx: &mut Context<Self>,
11510 ) {
11511 let settings = self.buffer.read(cx).language_settings(cx);
11512 let tab_size = settings.tab_size.get() as usize;
11513
11514 self.manipulate_mutable_lines(window, cx, |lines| {
11515 // Allocates a reasonably sized scratch buffer once for the whole loop
11516 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11517 // Avoids recomputing spaces that could be inserted many times
11518 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11519 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11520 .collect();
11521
11522 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11523 let mut chars = line.as_ref().chars();
11524 let mut col = 0;
11525 let mut changed = false;
11526
11527 for ch in chars.by_ref() {
11528 match ch {
11529 ' ' => {
11530 reindented_line.push(' ');
11531 col += 1;
11532 }
11533 '\t' => {
11534 // \t are converted to spaces depending on the current column
11535 let spaces_len = tab_size - (col % tab_size);
11536 reindented_line.extend(&space_cache[spaces_len - 1]);
11537 col += spaces_len;
11538 changed = true;
11539 }
11540 _ => {
11541 // If we dont append before break, the character is consumed
11542 reindented_line.push(ch);
11543 break;
11544 }
11545 }
11546 }
11547
11548 if !changed {
11549 reindented_line.clear();
11550 continue;
11551 }
11552 // Append the rest of the line and replace old reference with new one
11553 reindented_line.extend(chars);
11554 *line = Cow::Owned(reindented_line.clone());
11555 reindented_line.clear();
11556 }
11557 });
11558 }
11559
11560 pub fn convert_indentation_to_tabs(
11561 &mut self,
11562 _: &ConvertIndentationToTabs,
11563 window: &mut Window,
11564 cx: &mut Context<Self>,
11565 ) {
11566 let settings = self.buffer.read(cx).language_settings(cx);
11567 let tab_size = settings.tab_size.get() as usize;
11568
11569 self.manipulate_mutable_lines(window, cx, |lines| {
11570 // Allocates a reasonably sized buffer once for the whole loop
11571 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11572 // Avoids recomputing spaces that could be inserted many times
11573 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11574 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11575 .collect();
11576
11577 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11578 let mut chars = line.chars();
11579 let mut spaces_count = 0;
11580 let mut first_non_indent_char = None;
11581 let mut changed = false;
11582
11583 for ch in chars.by_ref() {
11584 match ch {
11585 ' ' => {
11586 // Keep track of spaces. Append \t when we reach tab_size
11587 spaces_count += 1;
11588 changed = true;
11589 if spaces_count == tab_size {
11590 reindented_line.push('\t');
11591 spaces_count = 0;
11592 }
11593 }
11594 '\t' => {
11595 reindented_line.push('\t');
11596 spaces_count = 0;
11597 }
11598 _ => {
11599 // Dont append it yet, we might have remaining spaces
11600 first_non_indent_char = Some(ch);
11601 break;
11602 }
11603 }
11604 }
11605
11606 if !changed {
11607 reindented_line.clear();
11608 continue;
11609 }
11610 // Remaining spaces that didn't make a full tab stop
11611 if spaces_count > 0 {
11612 reindented_line.extend(&space_cache[spaces_count - 1]);
11613 }
11614 // If we consume an extra character that was not indentation, add it back
11615 if let Some(extra_char) = first_non_indent_char {
11616 reindented_line.push(extra_char);
11617 }
11618 // Append the rest of the line and replace old reference with new one
11619 reindented_line.extend(chars);
11620 *line = Cow::Owned(reindented_line.clone());
11621 reindented_line.clear();
11622 }
11623 });
11624 }
11625
11626 pub fn convert_to_upper_case(
11627 &mut self,
11628 _: &ConvertToUpperCase,
11629 window: &mut Window,
11630 cx: &mut Context<Self>,
11631 ) {
11632 self.manipulate_text(window, cx, |text| text.to_uppercase())
11633 }
11634
11635 pub fn convert_to_lower_case(
11636 &mut self,
11637 _: &ConvertToLowerCase,
11638 window: &mut Window,
11639 cx: &mut Context<Self>,
11640 ) {
11641 self.manipulate_text(window, cx, |text| text.to_lowercase())
11642 }
11643
11644 pub fn convert_to_title_case(
11645 &mut self,
11646 _: &ConvertToTitleCase,
11647 window: &mut Window,
11648 cx: &mut Context<Self>,
11649 ) {
11650 self.manipulate_text(window, cx, |text| {
11651 text.split('\n')
11652 .map(|line| line.to_case(Case::Title))
11653 .join("\n")
11654 })
11655 }
11656
11657 pub fn convert_to_snake_case(
11658 &mut self,
11659 _: &ConvertToSnakeCase,
11660 window: &mut Window,
11661 cx: &mut Context<Self>,
11662 ) {
11663 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11664 }
11665
11666 pub fn convert_to_kebab_case(
11667 &mut self,
11668 _: &ConvertToKebabCase,
11669 window: &mut Window,
11670 cx: &mut Context<Self>,
11671 ) {
11672 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11673 }
11674
11675 pub fn convert_to_upper_camel_case(
11676 &mut self,
11677 _: &ConvertToUpperCamelCase,
11678 window: &mut Window,
11679 cx: &mut Context<Self>,
11680 ) {
11681 self.manipulate_text(window, cx, |text| {
11682 text.split('\n')
11683 .map(|line| line.to_case(Case::UpperCamel))
11684 .join("\n")
11685 })
11686 }
11687
11688 pub fn convert_to_lower_camel_case(
11689 &mut self,
11690 _: &ConvertToLowerCamelCase,
11691 window: &mut Window,
11692 cx: &mut Context<Self>,
11693 ) {
11694 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11695 }
11696
11697 pub fn convert_to_opposite_case(
11698 &mut self,
11699 _: &ConvertToOppositeCase,
11700 window: &mut Window,
11701 cx: &mut Context<Self>,
11702 ) {
11703 self.manipulate_text(window, cx, |text| {
11704 text.chars()
11705 .fold(String::with_capacity(text.len()), |mut t, c| {
11706 if c.is_uppercase() {
11707 t.extend(c.to_lowercase());
11708 } else {
11709 t.extend(c.to_uppercase());
11710 }
11711 t
11712 })
11713 })
11714 }
11715
11716 pub fn convert_to_sentence_case(
11717 &mut self,
11718 _: &ConvertToSentenceCase,
11719 window: &mut Window,
11720 cx: &mut Context<Self>,
11721 ) {
11722 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11723 }
11724
11725 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11726 self.manipulate_text(window, cx, |text| {
11727 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11728 if has_upper_case_characters {
11729 text.to_lowercase()
11730 } else {
11731 text.to_uppercase()
11732 }
11733 })
11734 }
11735
11736 pub fn convert_to_rot13(
11737 &mut self,
11738 _: &ConvertToRot13,
11739 window: &mut Window,
11740 cx: &mut Context<Self>,
11741 ) {
11742 self.manipulate_text(window, cx, |text| {
11743 text.chars()
11744 .map(|c| match c {
11745 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11746 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11747 _ => c,
11748 })
11749 .collect()
11750 })
11751 }
11752
11753 pub fn convert_to_rot47(
11754 &mut self,
11755 _: &ConvertToRot47,
11756 window: &mut Window,
11757 cx: &mut Context<Self>,
11758 ) {
11759 self.manipulate_text(window, cx, |text| {
11760 text.chars()
11761 .map(|c| {
11762 let code_point = c as u32;
11763 if code_point >= 33 && code_point <= 126 {
11764 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11765 }
11766 c
11767 })
11768 .collect()
11769 })
11770 }
11771
11772 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11773 where
11774 Fn: FnMut(&str) -> String,
11775 {
11776 let buffer = self.buffer.read(cx).snapshot(cx);
11777
11778 let mut new_selections = Vec::new();
11779 let mut edits = Vec::new();
11780 let mut selection_adjustment = 0isize;
11781
11782 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11783 let selection_is_empty = selection.is_empty();
11784
11785 let (start, end) = if selection_is_empty {
11786 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11787 (word_range.start, word_range.end)
11788 } else {
11789 (
11790 buffer.point_to_offset(selection.start),
11791 buffer.point_to_offset(selection.end),
11792 )
11793 };
11794
11795 let text = buffer.text_for_range(start..end).collect::<String>();
11796 let old_length = text.len() as isize;
11797 let text = callback(&text);
11798
11799 new_selections.push(Selection {
11800 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11801 end: MultiBufferOffset(
11802 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11803 ),
11804 goal: SelectionGoal::None,
11805 id: selection.id,
11806 reversed: selection.reversed,
11807 });
11808
11809 selection_adjustment += old_length - text.len() as isize;
11810
11811 edits.push((start..end, text));
11812 }
11813
11814 self.transact(window, cx, |this, window, cx| {
11815 this.buffer.update(cx, |buffer, cx| {
11816 buffer.edit(edits, None, cx);
11817 });
11818
11819 this.change_selections(Default::default(), window, cx, |s| {
11820 s.select(new_selections);
11821 });
11822
11823 this.request_autoscroll(Autoscroll::fit(), cx);
11824 });
11825 }
11826
11827 pub fn move_selection_on_drop(
11828 &mut self,
11829 selection: &Selection<Anchor>,
11830 target: DisplayPoint,
11831 is_cut: bool,
11832 window: &mut Window,
11833 cx: &mut Context<Self>,
11834 ) {
11835 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11836 let buffer = display_map.buffer_snapshot();
11837 let mut edits = Vec::new();
11838 let insert_point = display_map
11839 .clip_point(target, Bias::Left)
11840 .to_point(&display_map);
11841 let text = buffer
11842 .text_for_range(selection.start..selection.end)
11843 .collect::<String>();
11844 if is_cut {
11845 edits.push(((selection.start..selection.end), String::new()));
11846 }
11847 let insert_anchor = buffer.anchor_before(insert_point);
11848 edits.push(((insert_anchor..insert_anchor), text));
11849 let last_edit_start = insert_anchor.bias_left(buffer);
11850 let last_edit_end = insert_anchor.bias_right(buffer);
11851 self.transact(window, cx, |this, window, cx| {
11852 this.buffer.update(cx, |buffer, cx| {
11853 buffer.edit(edits, None, cx);
11854 });
11855 this.change_selections(Default::default(), window, cx, |s| {
11856 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11857 });
11858 });
11859 }
11860
11861 pub fn clear_selection_drag_state(&mut self) {
11862 self.selection_drag_state = SelectionDragState::None;
11863 }
11864
11865 pub fn duplicate(
11866 &mut self,
11867 upwards: bool,
11868 whole_lines: bool,
11869 window: &mut Window,
11870 cx: &mut Context<Self>,
11871 ) {
11872 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11873
11874 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11875 let buffer = display_map.buffer_snapshot();
11876 let selections = self.selections.all::<Point>(&display_map);
11877
11878 let mut edits = Vec::new();
11879 let mut selections_iter = selections.iter().peekable();
11880 while let Some(selection) = selections_iter.next() {
11881 let mut rows = selection.spanned_rows(false, &display_map);
11882 // duplicate line-wise
11883 if whole_lines || selection.start == selection.end {
11884 // Avoid duplicating the same lines twice.
11885 while let Some(next_selection) = selections_iter.peek() {
11886 let next_rows = next_selection.spanned_rows(false, &display_map);
11887 if next_rows.start < rows.end {
11888 rows.end = next_rows.end;
11889 selections_iter.next().unwrap();
11890 } else {
11891 break;
11892 }
11893 }
11894
11895 // Copy the text from the selected row region and splice it either at the start
11896 // or end of the region.
11897 let start = Point::new(rows.start.0, 0);
11898 let end = Point::new(
11899 rows.end.previous_row().0,
11900 buffer.line_len(rows.end.previous_row()),
11901 );
11902
11903 let mut text = buffer.text_for_range(start..end).collect::<String>();
11904
11905 let insert_location = if upwards {
11906 // When duplicating upward, we need to insert before the current line.
11907 // If we're on the last line and it doesn't end with a newline,
11908 // we need to add a newline before the duplicated content.
11909 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11910 && buffer.max_point().column > 0
11911 && !text.ends_with('\n');
11912
11913 if needs_leading_newline {
11914 text.insert(0, '\n');
11915 end
11916 } else {
11917 text.push('\n');
11918 Point::new(rows.start.0, 0)
11919 }
11920 } else {
11921 text.push('\n');
11922 start
11923 };
11924 edits.push((insert_location..insert_location, text));
11925 } else {
11926 // duplicate character-wise
11927 let start = selection.start;
11928 let end = selection.end;
11929 let text = buffer.text_for_range(start..end).collect::<String>();
11930 edits.push((selection.end..selection.end, text));
11931 }
11932 }
11933
11934 self.transact(window, cx, |this, window, cx| {
11935 this.buffer.update(cx, |buffer, cx| {
11936 buffer.edit(edits, None, cx);
11937 });
11938
11939 // When duplicating upward with whole lines, move the cursor to the duplicated line
11940 if upwards && whole_lines {
11941 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11942
11943 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11944 let mut new_ranges = Vec::new();
11945 let selections = s.all::<Point>(&display_map);
11946 let mut selections_iter = selections.iter().peekable();
11947
11948 while let Some(first_selection) = selections_iter.next() {
11949 // Group contiguous selections together to find the total row span
11950 let mut group_selections = vec![first_selection];
11951 let mut rows = first_selection.spanned_rows(false, &display_map);
11952
11953 while let Some(next_selection) = selections_iter.peek() {
11954 let next_rows = next_selection.spanned_rows(false, &display_map);
11955 if next_rows.start < rows.end {
11956 rows.end = next_rows.end;
11957 group_selections.push(selections_iter.next().unwrap());
11958 } else {
11959 break;
11960 }
11961 }
11962
11963 let row_count = rows.end.0 - rows.start.0;
11964
11965 // Move all selections in this group up by the total number of duplicated rows
11966 for selection in group_selections {
11967 let new_start = Point::new(
11968 selection.start.row.saturating_sub(row_count),
11969 selection.start.column,
11970 );
11971
11972 let new_end = Point::new(
11973 selection.end.row.saturating_sub(row_count),
11974 selection.end.column,
11975 );
11976
11977 new_ranges.push(new_start..new_end);
11978 }
11979 }
11980
11981 s.select_ranges(new_ranges);
11982 });
11983 }
11984
11985 this.request_autoscroll(Autoscroll::fit(), cx);
11986 });
11987 }
11988
11989 pub fn duplicate_line_up(
11990 &mut self,
11991 _: &DuplicateLineUp,
11992 window: &mut Window,
11993 cx: &mut Context<Self>,
11994 ) {
11995 self.duplicate(true, true, window, cx);
11996 }
11997
11998 pub fn duplicate_line_down(
11999 &mut self,
12000 _: &DuplicateLineDown,
12001 window: &mut Window,
12002 cx: &mut Context<Self>,
12003 ) {
12004 self.duplicate(false, true, window, cx);
12005 }
12006
12007 pub fn duplicate_selection(
12008 &mut self,
12009 _: &DuplicateSelection,
12010 window: &mut Window,
12011 cx: &mut Context<Self>,
12012 ) {
12013 self.duplicate(false, false, window, cx);
12014 }
12015
12016 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12017 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12018 if self.mode.is_single_line() {
12019 cx.propagate();
12020 return;
12021 }
12022
12023 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12024 let buffer = self.buffer.read(cx).snapshot(cx);
12025
12026 let mut edits = Vec::new();
12027 let mut unfold_ranges = Vec::new();
12028 let mut refold_creases = Vec::new();
12029
12030 let selections = self.selections.all::<Point>(&display_map);
12031 let mut selections = selections.iter().peekable();
12032 let mut contiguous_row_selections = Vec::new();
12033 let mut new_selections = Vec::new();
12034
12035 while let Some(selection) = selections.next() {
12036 // Find all the selections that span a contiguous row range
12037 let (start_row, end_row) = consume_contiguous_rows(
12038 &mut contiguous_row_selections,
12039 selection,
12040 &display_map,
12041 &mut selections,
12042 );
12043
12044 // Move the text spanned by the row range to be before the line preceding the row range
12045 if start_row.0 > 0 {
12046 let range_to_move = Point::new(
12047 start_row.previous_row().0,
12048 buffer.line_len(start_row.previous_row()),
12049 )
12050 ..Point::new(
12051 end_row.previous_row().0,
12052 buffer.line_len(end_row.previous_row()),
12053 );
12054 let insertion_point = display_map
12055 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12056 .0;
12057
12058 // Don't move lines across excerpts
12059 if buffer
12060 .excerpt_containing(insertion_point..range_to_move.end)
12061 .is_some()
12062 {
12063 let text = buffer
12064 .text_for_range(range_to_move.clone())
12065 .flat_map(|s| s.chars())
12066 .skip(1)
12067 .chain(['\n'])
12068 .collect::<String>();
12069
12070 edits.push((
12071 buffer.anchor_after(range_to_move.start)
12072 ..buffer.anchor_before(range_to_move.end),
12073 String::new(),
12074 ));
12075 let insertion_anchor = buffer.anchor_after(insertion_point);
12076 edits.push((insertion_anchor..insertion_anchor, text));
12077
12078 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12079
12080 // Move selections up
12081 new_selections.extend(contiguous_row_selections.drain(..).map(
12082 |mut selection| {
12083 selection.start.row -= row_delta;
12084 selection.end.row -= row_delta;
12085 selection
12086 },
12087 ));
12088
12089 // Move folds up
12090 unfold_ranges.push(range_to_move.clone());
12091 for fold in display_map.folds_in_range(
12092 buffer.anchor_before(range_to_move.start)
12093 ..buffer.anchor_after(range_to_move.end),
12094 ) {
12095 let mut start = fold.range.start.to_point(&buffer);
12096 let mut end = fold.range.end.to_point(&buffer);
12097 start.row -= row_delta;
12098 end.row -= row_delta;
12099 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12100 }
12101 }
12102 }
12103
12104 // If we didn't move line(s), preserve the existing selections
12105 new_selections.append(&mut contiguous_row_selections);
12106 }
12107
12108 self.transact(window, cx, |this, window, cx| {
12109 this.unfold_ranges(&unfold_ranges, true, true, cx);
12110 this.buffer.update(cx, |buffer, cx| {
12111 for (range, text) in edits {
12112 buffer.edit([(range, text)], None, cx);
12113 }
12114 });
12115 this.fold_creases(refold_creases, true, window, cx);
12116 this.change_selections(Default::default(), window, cx, |s| {
12117 s.select(new_selections);
12118 })
12119 });
12120 }
12121
12122 pub fn move_line_down(
12123 &mut self,
12124 _: &MoveLineDown,
12125 window: &mut Window,
12126 cx: &mut Context<Self>,
12127 ) {
12128 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12129 if self.mode.is_single_line() {
12130 cx.propagate();
12131 return;
12132 }
12133
12134 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12135 let buffer = self.buffer.read(cx).snapshot(cx);
12136
12137 let mut edits = Vec::new();
12138 let mut unfold_ranges = Vec::new();
12139 let mut refold_creases = Vec::new();
12140
12141 let selections = self.selections.all::<Point>(&display_map);
12142 let mut selections = selections.iter().peekable();
12143 let mut contiguous_row_selections = Vec::new();
12144 let mut new_selections = Vec::new();
12145
12146 while let Some(selection) = selections.next() {
12147 // Find all the selections that span a contiguous row range
12148 let (start_row, end_row) = consume_contiguous_rows(
12149 &mut contiguous_row_selections,
12150 selection,
12151 &display_map,
12152 &mut selections,
12153 );
12154
12155 // Move the text spanned by the row range to be after the last line of the row range
12156 if end_row.0 <= buffer.max_point().row {
12157 let range_to_move =
12158 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12159 let insertion_point = display_map
12160 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12161 .0;
12162
12163 // Don't move lines across excerpt boundaries
12164 if buffer
12165 .excerpt_containing(range_to_move.start..insertion_point)
12166 .is_some()
12167 {
12168 let mut text = String::from("\n");
12169 text.extend(buffer.text_for_range(range_to_move.clone()));
12170 text.pop(); // Drop trailing newline
12171 edits.push((
12172 buffer.anchor_after(range_to_move.start)
12173 ..buffer.anchor_before(range_to_move.end),
12174 String::new(),
12175 ));
12176 let insertion_anchor = buffer.anchor_after(insertion_point);
12177 edits.push((insertion_anchor..insertion_anchor, text));
12178
12179 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12180
12181 // Move selections down
12182 new_selections.extend(contiguous_row_selections.drain(..).map(
12183 |mut selection| {
12184 selection.start.row += row_delta;
12185 selection.end.row += row_delta;
12186 selection
12187 },
12188 ));
12189
12190 // Move folds down
12191 unfold_ranges.push(range_to_move.clone());
12192 for fold in display_map.folds_in_range(
12193 buffer.anchor_before(range_to_move.start)
12194 ..buffer.anchor_after(range_to_move.end),
12195 ) {
12196 let mut start = fold.range.start.to_point(&buffer);
12197 let mut end = fold.range.end.to_point(&buffer);
12198 start.row += row_delta;
12199 end.row += row_delta;
12200 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12201 }
12202 }
12203 }
12204
12205 // If we didn't move line(s), preserve the existing selections
12206 new_selections.append(&mut contiguous_row_selections);
12207 }
12208
12209 self.transact(window, cx, |this, window, cx| {
12210 this.unfold_ranges(&unfold_ranges, true, true, cx);
12211 this.buffer.update(cx, |buffer, cx| {
12212 for (range, text) in edits {
12213 buffer.edit([(range, text)], None, cx);
12214 }
12215 });
12216 this.fold_creases(refold_creases, true, window, cx);
12217 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12218 });
12219 }
12220
12221 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12222 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12223 let text_layout_details = &self.text_layout_details(window);
12224 self.transact(window, cx, |this, window, cx| {
12225 let edits = this.change_selections(Default::default(), window, cx, |s| {
12226 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12227 s.move_with(|display_map, selection| {
12228 if !selection.is_empty() {
12229 return;
12230 }
12231
12232 let mut head = selection.head();
12233 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12234 if head.column() == display_map.line_len(head.row()) {
12235 transpose_offset = display_map
12236 .buffer_snapshot()
12237 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12238 }
12239
12240 if transpose_offset == MultiBufferOffset(0) {
12241 return;
12242 }
12243
12244 *head.column_mut() += 1;
12245 head = display_map.clip_point(head, Bias::Right);
12246 let goal = SelectionGoal::HorizontalPosition(
12247 display_map
12248 .x_for_display_point(head, text_layout_details)
12249 .into(),
12250 );
12251 selection.collapse_to(head, goal);
12252
12253 let transpose_start = display_map
12254 .buffer_snapshot()
12255 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12256 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12257 let transpose_end = display_map
12258 .buffer_snapshot()
12259 .clip_offset(transpose_offset + 1usize, Bias::Right);
12260 if let Some(ch) = display_map
12261 .buffer_snapshot()
12262 .chars_at(transpose_start)
12263 .next()
12264 {
12265 edits.push((transpose_start..transpose_offset, String::new()));
12266 edits.push((transpose_end..transpose_end, ch.to_string()));
12267 }
12268 }
12269 });
12270 edits
12271 });
12272 this.buffer
12273 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12274 let selections = this
12275 .selections
12276 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12277 this.change_selections(Default::default(), window, cx, |s| {
12278 s.select(selections);
12279 });
12280 });
12281 }
12282
12283 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12284 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12285 if self.mode.is_single_line() {
12286 cx.propagate();
12287 return;
12288 }
12289
12290 self.rewrap_impl(RewrapOptions::default(), cx)
12291 }
12292
12293 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12294 let buffer = self.buffer.read(cx).snapshot(cx);
12295 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12296
12297 #[derive(Clone, Debug, PartialEq)]
12298 enum CommentFormat {
12299 /// single line comment, with prefix for line
12300 Line(String),
12301 /// single line within a block comment, with prefix for line
12302 BlockLine(String),
12303 /// a single line of a block comment that includes the initial delimiter
12304 BlockCommentWithStart(BlockCommentConfig),
12305 /// a single line of a block comment that includes the ending delimiter
12306 BlockCommentWithEnd(BlockCommentConfig),
12307 }
12308
12309 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12310 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12311 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12312 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12313 .peekable();
12314
12315 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12316 row
12317 } else {
12318 return Vec::new();
12319 };
12320
12321 let language_settings = buffer.language_settings_at(selection.head(), cx);
12322 let language_scope = buffer.language_scope_at(selection.head());
12323
12324 let indent_and_prefix_for_row =
12325 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12326 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12327 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12328 &language_scope
12329 {
12330 let indent_end = Point::new(row, indent.len);
12331 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12332 let line_text_after_indent = buffer
12333 .text_for_range(indent_end..line_end)
12334 .collect::<String>();
12335
12336 let is_within_comment_override = buffer
12337 .language_scope_at(indent_end)
12338 .is_some_and(|scope| scope.override_name() == Some("comment"));
12339 let comment_delimiters = if is_within_comment_override {
12340 // we are within a comment syntax node, but we don't
12341 // yet know what kind of comment: block, doc or line
12342 match (
12343 language_scope.documentation_comment(),
12344 language_scope.block_comment(),
12345 ) {
12346 (Some(config), _) | (_, Some(config))
12347 if buffer.contains_str_at(indent_end, &config.start) =>
12348 {
12349 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12350 }
12351 (Some(config), _) | (_, Some(config))
12352 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12353 {
12354 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12355 }
12356 (Some(config), _) | (_, Some(config))
12357 if buffer.contains_str_at(indent_end, &config.prefix) =>
12358 {
12359 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12360 }
12361 (_, _) => language_scope
12362 .line_comment_prefixes()
12363 .iter()
12364 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12365 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12366 }
12367 } else {
12368 // we not in an overridden comment node, but we may
12369 // be within a non-overridden line comment node
12370 language_scope
12371 .line_comment_prefixes()
12372 .iter()
12373 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12374 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12375 };
12376
12377 let rewrap_prefix = language_scope
12378 .rewrap_prefixes()
12379 .iter()
12380 .find_map(|prefix_regex| {
12381 prefix_regex.find(&line_text_after_indent).map(|mat| {
12382 if mat.start() == 0 {
12383 Some(mat.as_str().to_string())
12384 } else {
12385 None
12386 }
12387 })
12388 })
12389 .flatten();
12390 (comment_delimiters, rewrap_prefix)
12391 } else {
12392 (None, None)
12393 };
12394 (indent, comment_prefix, rewrap_prefix)
12395 };
12396
12397 let mut ranges = Vec::new();
12398 let from_empty_selection = selection.is_empty();
12399
12400 let mut current_range_start = first_row;
12401 let mut prev_row = first_row;
12402 let (
12403 mut current_range_indent,
12404 mut current_range_comment_delimiters,
12405 mut current_range_rewrap_prefix,
12406 ) = indent_and_prefix_for_row(first_row);
12407
12408 for row in non_blank_rows_iter.skip(1) {
12409 let has_paragraph_break = row > prev_row + 1;
12410
12411 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12412 indent_and_prefix_for_row(row);
12413
12414 let has_indent_change = row_indent != current_range_indent;
12415 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12416
12417 let has_boundary_change = has_comment_change
12418 || row_rewrap_prefix.is_some()
12419 || (has_indent_change && current_range_comment_delimiters.is_some());
12420
12421 if has_paragraph_break || has_boundary_change {
12422 ranges.push((
12423 language_settings.clone(),
12424 Point::new(current_range_start, 0)
12425 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12426 current_range_indent,
12427 current_range_comment_delimiters.clone(),
12428 current_range_rewrap_prefix.clone(),
12429 from_empty_selection,
12430 ));
12431 current_range_start = row;
12432 current_range_indent = row_indent;
12433 current_range_comment_delimiters = row_comment_delimiters;
12434 current_range_rewrap_prefix = row_rewrap_prefix;
12435 }
12436 prev_row = row;
12437 }
12438
12439 ranges.push((
12440 language_settings.clone(),
12441 Point::new(current_range_start, 0)
12442 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12443 current_range_indent,
12444 current_range_comment_delimiters,
12445 current_range_rewrap_prefix,
12446 from_empty_selection,
12447 ));
12448
12449 ranges
12450 });
12451
12452 let mut edits = Vec::new();
12453 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12454
12455 for (
12456 language_settings,
12457 wrap_range,
12458 mut indent_size,
12459 comment_prefix,
12460 rewrap_prefix,
12461 from_empty_selection,
12462 ) in wrap_ranges
12463 {
12464 let mut start_row = wrap_range.start.row;
12465 let mut end_row = wrap_range.end.row;
12466
12467 // Skip selections that overlap with a range that has already been rewrapped.
12468 let selection_range = start_row..end_row;
12469 if rewrapped_row_ranges
12470 .iter()
12471 .any(|range| range.overlaps(&selection_range))
12472 {
12473 continue;
12474 }
12475
12476 let tab_size = language_settings.tab_size;
12477
12478 let (line_prefix, inside_comment) = match &comment_prefix {
12479 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12480 (Some(prefix.as_str()), true)
12481 }
12482 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12483 (Some(prefix.as_ref()), true)
12484 }
12485 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12486 start: _,
12487 end: _,
12488 prefix,
12489 tab_size,
12490 })) => {
12491 indent_size.len += tab_size;
12492 (Some(prefix.as_ref()), true)
12493 }
12494 None => (None, false),
12495 };
12496 let indent_prefix = indent_size.chars().collect::<String>();
12497 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12498
12499 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12500 RewrapBehavior::InComments => inside_comment,
12501 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12502 RewrapBehavior::Anywhere => true,
12503 };
12504
12505 let should_rewrap = options.override_language_settings
12506 || allow_rewrap_based_on_language
12507 || self.hard_wrap.is_some();
12508 if !should_rewrap {
12509 continue;
12510 }
12511
12512 if from_empty_selection {
12513 'expand_upwards: while start_row > 0 {
12514 let prev_row = start_row - 1;
12515 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12516 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12517 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12518 {
12519 start_row = prev_row;
12520 } else {
12521 break 'expand_upwards;
12522 }
12523 }
12524
12525 'expand_downwards: while end_row < buffer.max_point().row {
12526 let next_row = end_row + 1;
12527 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12528 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12529 && !buffer.is_line_blank(MultiBufferRow(next_row))
12530 {
12531 end_row = next_row;
12532 } else {
12533 break 'expand_downwards;
12534 }
12535 }
12536 }
12537
12538 let start = Point::new(start_row, 0);
12539 let start_offset = ToOffset::to_offset(&start, &buffer);
12540 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12541 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12542 let mut first_line_delimiter = None;
12543 let mut last_line_delimiter = None;
12544 let Some(lines_without_prefixes) = selection_text
12545 .lines()
12546 .enumerate()
12547 .map(|(ix, line)| {
12548 let line_trimmed = line.trim_start();
12549 if rewrap_prefix.is_some() && ix > 0 {
12550 Ok(line_trimmed)
12551 } else if let Some(
12552 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12553 start,
12554 prefix,
12555 end,
12556 tab_size,
12557 })
12558 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12559 start,
12560 prefix,
12561 end,
12562 tab_size,
12563 }),
12564 ) = &comment_prefix
12565 {
12566 let line_trimmed = line_trimmed
12567 .strip_prefix(start.as_ref())
12568 .map(|s| {
12569 let mut indent_size = indent_size;
12570 indent_size.len -= tab_size;
12571 let indent_prefix: String = indent_size.chars().collect();
12572 first_line_delimiter = Some((indent_prefix, start));
12573 s.trim_start()
12574 })
12575 .unwrap_or(line_trimmed);
12576 let line_trimmed = line_trimmed
12577 .strip_suffix(end.as_ref())
12578 .map(|s| {
12579 last_line_delimiter = Some(end);
12580 s.trim_end()
12581 })
12582 .unwrap_or(line_trimmed);
12583 let line_trimmed = line_trimmed
12584 .strip_prefix(prefix.as_ref())
12585 .unwrap_or(line_trimmed);
12586 Ok(line_trimmed)
12587 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12588 line_trimmed.strip_prefix(prefix).with_context(|| {
12589 format!("line did not start with prefix {prefix:?}: {line:?}")
12590 })
12591 } else {
12592 line_trimmed
12593 .strip_prefix(&line_prefix.trim_start())
12594 .with_context(|| {
12595 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12596 })
12597 }
12598 })
12599 .collect::<Result<Vec<_>, _>>()
12600 .log_err()
12601 else {
12602 continue;
12603 };
12604
12605 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12606 buffer
12607 .language_settings_at(Point::new(start_row, 0), cx)
12608 .preferred_line_length as usize
12609 });
12610
12611 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12612 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12613 } else {
12614 line_prefix.clone()
12615 };
12616
12617 let wrapped_text = {
12618 let mut wrapped_text = wrap_with_prefix(
12619 line_prefix,
12620 subsequent_lines_prefix,
12621 lines_without_prefixes.join("\n"),
12622 wrap_column,
12623 tab_size,
12624 options.preserve_existing_whitespace,
12625 );
12626
12627 if let Some((indent, delimiter)) = first_line_delimiter {
12628 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12629 }
12630 if let Some(last_line) = last_line_delimiter {
12631 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12632 }
12633
12634 wrapped_text
12635 };
12636
12637 // TODO: should always use char-based diff while still supporting cursor behavior that
12638 // matches vim.
12639 let mut diff_options = DiffOptions::default();
12640 if options.override_language_settings {
12641 diff_options.max_word_diff_len = 0;
12642 diff_options.max_word_diff_line_count = 0;
12643 } else {
12644 diff_options.max_word_diff_len = usize::MAX;
12645 diff_options.max_word_diff_line_count = usize::MAX;
12646 }
12647
12648 for (old_range, new_text) in
12649 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12650 {
12651 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12652 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12653 edits.push((edit_start..edit_end, new_text));
12654 }
12655
12656 rewrapped_row_ranges.push(start_row..=end_row);
12657 }
12658
12659 self.buffer
12660 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12661 }
12662
12663 pub fn cut_common(
12664 &mut self,
12665 cut_no_selection_line: bool,
12666 window: &mut Window,
12667 cx: &mut Context<Self>,
12668 ) -> ClipboardItem {
12669 let mut text = String::new();
12670 let buffer = self.buffer.read(cx).snapshot(cx);
12671 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12672 let mut clipboard_selections = Vec::with_capacity(selections.len());
12673 {
12674 let max_point = buffer.max_point();
12675 let mut is_first = true;
12676 let mut prev_selection_was_entire_line = false;
12677 for selection in &mut selections {
12678 let is_entire_line =
12679 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12680 if is_entire_line {
12681 selection.start = Point::new(selection.start.row, 0);
12682 if !selection.is_empty() && selection.end.column == 0 {
12683 selection.end = cmp::min(max_point, selection.end);
12684 } else {
12685 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12686 }
12687 selection.goal = SelectionGoal::None;
12688 }
12689 if is_first {
12690 is_first = false;
12691 } else if !prev_selection_was_entire_line {
12692 text += "\n";
12693 }
12694 prev_selection_was_entire_line = is_entire_line;
12695 let mut len = 0;
12696 for chunk in buffer.text_for_range(selection.start..selection.end) {
12697 text.push_str(chunk);
12698 len += chunk.len();
12699 }
12700 clipboard_selections.push(ClipboardSelection {
12701 len,
12702 is_entire_line,
12703 first_line_indent: buffer
12704 .indent_size_for_line(MultiBufferRow(selection.start.row))
12705 .len,
12706 });
12707 }
12708 }
12709
12710 self.transact(window, cx, |this, window, cx| {
12711 this.change_selections(Default::default(), window, cx, |s| {
12712 s.select(selections);
12713 });
12714 this.insert("", window, cx);
12715 });
12716 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12717 }
12718
12719 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12720 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12721 let item = self.cut_common(true, window, cx);
12722 cx.write_to_clipboard(item);
12723 }
12724
12725 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12726 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12727 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12728 s.move_with(|snapshot, sel| {
12729 if sel.is_empty() {
12730 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12731 }
12732 if sel.is_empty() {
12733 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12734 }
12735 });
12736 });
12737 let item = self.cut_common(false, window, cx);
12738 cx.set_global(KillRing(item))
12739 }
12740
12741 pub fn kill_ring_yank(
12742 &mut self,
12743 _: &KillRingYank,
12744 window: &mut Window,
12745 cx: &mut Context<Self>,
12746 ) {
12747 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12748 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12749 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12750 (kill_ring.text().to_string(), kill_ring.metadata_json())
12751 } else {
12752 return;
12753 }
12754 } else {
12755 return;
12756 };
12757 self.do_paste(&text, metadata, false, window, cx);
12758 }
12759
12760 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12761 self.do_copy(true, cx);
12762 }
12763
12764 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12765 self.do_copy(false, cx);
12766 }
12767
12768 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12769 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12770 let buffer = self.buffer.read(cx).read(cx);
12771 let mut text = String::new();
12772
12773 let mut clipboard_selections = Vec::with_capacity(selections.len());
12774 {
12775 let max_point = buffer.max_point();
12776 let mut is_first = true;
12777 let mut prev_selection_was_entire_line = false;
12778 for selection in &selections {
12779 let mut start = selection.start;
12780 let mut end = selection.end;
12781 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12782 let mut add_trailing_newline = false;
12783 if is_entire_line {
12784 start = Point::new(start.row, 0);
12785 let next_line_start = Point::new(end.row + 1, 0);
12786 if next_line_start <= max_point {
12787 end = next_line_start;
12788 } else {
12789 // We're on the last line without a trailing newline.
12790 // Copy to the end of the line and add a newline afterwards.
12791 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12792 add_trailing_newline = true;
12793 }
12794 }
12795
12796 let mut trimmed_selections = Vec::new();
12797 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12798 let row = MultiBufferRow(start.row);
12799 let first_indent = buffer.indent_size_for_line(row);
12800 if first_indent.len == 0 || start.column > first_indent.len {
12801 trimmed_selections.push(start..end);
12802 } else {
12803 trimmed_selections.push(
12804 Point::new(row.0, first_indent.len)
12805 ..Point::new(row.0, buffer.line_len(row)),
12806 );
12807 for row in start.row + 1..=end.row {
12808 let mut line_len = buffer.line_len(MultiBufferRow(row));
12809 if row == end.row {
12810 line_len = end.column;
12811 }
12812 if line_len == 0 {
12813 trimmed_selections
12814 .push(Point::new(row, 0)..Point::new(row, line_len));
12815 continue;
12816 }
12817 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12818 if row_indent_size.len >= first_indent.len {
12819 trimmed_selections.push(
12820 Point::new(row, first_indent.len)..Point::new(row, line_len),
12821 );
12822 } else {
12823 trimmed_selections.clear();
12824 trimmed_selections.push(start..end);
12825 break;
12826 }
12827 }
12828 }
12829 } else {
12830 trimmed_selections.push(start..end);
12831 }
12832
12833 for trimmed_range in trimmed_selections {
12834 if is_first {
12835 is_first = false;
12836 } else if !prev_selection_was_entire_line {
12837 text += "\n";
12838 }
12839 prev_selection_was_entire_line = is_entire_line;
12840 let mut len = 0;
12841 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12842 text.push_str(chunk);
12843 len += chunk.len();
12844 }
12845 if add_trailing_newline {
12846 text.push('\n');
12847 len += 1;
12848 }
12849 clipboard_selections.push(ClipboardSelection {
12850 len,
12851 is_entire_line,
12852 first_line_indent: buffer
12853 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12854 .len,
12855 });
12856 }
12857 }
12858 }
12859
12860 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12861 text,
12862 clipboard_selections,
12863 ));
12864 }
12865
12866 pub fn do_paste(
12867 &mut self,
12868 text: &String,
12869 clipboard_selections: Option<Vec<ClipboardSelection>>,
12870 handle_entire_lines: bool,
12871 window: &mut Window,
12872 cx: &mut Context<Self>,
12873 ) {
12874 if self.read_only(cx) {
12875 return;
12876 }
12877
12878 let clipboard_text = Cow::Borrowed(text.as_str());
12879
12880 self.transact(window, cx, |this, window, cx| {
12881 let had_active_edit_prediction = this.has_active_edit_prediction();
12882 let display_map = this.display_snapshot(cx);
12883 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
12884 let cursor_offset = this
12885 .selections
12886 .last::<MultiBufferOffset>(&display_map)
12887 .head();
12888
12889 if let Some(mut clipboard_selections) = clipboard_selections {
12890 let all_selections_were_entire_line =
12891 clipboard_selections.iter().all(|s| s.is_entire_line);
12892 let first_selection_indent_column =
12893 clipboard_selections.first().map(|s| s.first_line_indent);
12894 if clipboard_selections.len() != old_selections.len() {
12895 clipboard_selections.drain(..);
12896 }
12897 let mut auto_indent_on_paste = true;
12898
12899 this.buffer.update(cx, |buffer, cx| {
12900 let snapshot = buffer.read(cx);
12901 auto_indent_on_paste = snapshot
12902 .language_settings_at(cursor_offset, cx)
12903 .auto_indent_on_paste;
12904
12905 let mut start_offset = 0;
12906 let mut edits = Vec::new();
12907 let mut original_indent_columns = Vec::new();
12908 for (ix, selection) in old_selections.iter().enumerate() {
12909 let to_insert;
12910 let entire_line;
12911 let original_indent_column;
12912 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12913 let end_offset = start_offset + clipboard_selection.len;
12914 to_insert = &clipboard_text[start_offset..end_offset];
12915 entire_line = clipboard_selection.is_entire_line;
12916 start_offset = if entire_line {
12917 end_offset
12918 } else {
12919 end_offset + 1
12920 };
12921 original_indent_column = Some(clipboard_selection.first_line_indent);
12922 } else {
12923 to_insert = &*clipboard_text;
12924 entire_line = all_selections_were_entire_line;
12925 original_indent_column = first_selection_indent_column
12926 }
12927
12928 let (range, to_insert) =
12929 if selection.is_empty() && handle_entire_lines && entire_line {
12930 // If the corresponding selection was empty when this slice of the
12931 // clipboard text was written, then the entire line containing the
12932 // selection was copied. If this selection is also currently empty,
12933 // then paste the line before the current line of the buffer.
12934 let column = selection.start.to_point(&snapshot).column as usize;
12935 let line_start = selection.start - column;
12936 (line_start..line_start, Cow::Borrowed(to_insert))
12937 } else {
12938 let language = snapshot.language_at(selection.head());
12939 let range = selection.range();
12940 if let Some(language) = language
12941 && language.name() == "Markdown".into()
12942 {
12943 edit_for_markdown_paste(
12944 &snapshot,
12945 range,
12946 to_insert,
12947 url::Url::parse(to_insert).ok(),
12948 )
12949 } else {
12950 (range, Cow::Borrowed(to_insert))
12951 }
12952 };
12953
12954 edits.push((range, to_insert));
12955 original_indent_columns.push(original_indent_column);
12956 }
12957 drop(snapshot);
12958
12959 buffer.edit(
12960 edits,
12961 if auto_indent_on_paste {
12962 Some(AutoindentMode::Block {
12963 original_indent_columns,
12964 })
12965 } else {
12966 None
12967 },
12968 cx,
12969 );
12970 });
12971
12972 let selections = this
12973 .selections
12974 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12975 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12976 } else {
12977 let url = url::Url::parse(&clipboard_text).ok();
12978
12979 let auto_indent_mode = if !clipboard_text.is_empty() {
12980 Some(AutoindentMode::Block {
12981 original_indent_columns: Vec::new(),
12982 })
12983 } else {
12984 None
12985 };
12986
12987 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12988 let snapshot = buffer.snapshot(cx);
12989
12990 let anchors = old_selections
12991 .iter()
12992 .map(|s| {
12993 let anchor = snapshot.anchor_after(s.head());
12994 s.map(|_| anchor)
12995 })
12996 .collect::<Vec<_>>();
12997
12998 let mut edits = Vec::new();
12999
13000 for selection in old_selections.iter() {
13001 let language = snapshot.language_at(selection.head());
13002 let range = selection.range();
13003
13004 let (edit_range, edit_text) = if let Some(language) = language
13005 && language.name() == "Markdown".into()
13006 {
13007 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13008 } else {
13009 (range, clipboard_text.clone())
13010 };
13011
13012 edits.push((edit_range, edit_text));
13013 }
13014
13015 drop(snapshot);
13016 buffer.edit(edits, auto_indent_mode, cx);
13017
13018 anchors
13019 });
13020
13021 this.change_selections(Default::default(), window, cx, |s| {
13022 s.select_anchors(selection_anchors);
13023 });
13024 }
13025
13026 // 🤔 | .. | show_in_menu |
13027 // | .. | true true
13028 // | had_edit_prediction | false true
13029
13030 let trigger_in_words =
13031 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13032
13033 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13034 });
13035 }
13036
13037 pub fn diff_clipboard_with_selection(
13038 &mut self,
13039 _: &DiffClipboardWithSelection,
13040 window: &mut Window,
13041 cx: &mut Context<Self>,
13042 ) {
13043 let selections = self
13044 .selections
13045 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13046
13047 if selections.is_empty() {
13048 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13049 return;
13050 };
13051
13052 let clipboard_text = match cx.read_from_clipboard() {
13053 Some(item) => match item.entries().first() {
13054 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13055 _ => None,
13056 },
13057 None => None,
13058 };
13059
13060 let Some(clipboard_text) = clipboard_text else {
13061 log::warn!("Clipboard doesn't contain text.");
13062 return;
13063 };
13064
13065 window.dispatch_action(
13066 Box::new(DiffClipboardWithSelectionData {
13067 clipboard_text,
13068 editor: cx.entity(),
13069 }),
13070 cx,
13071 );
13072 }
13073
13074 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13075 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13076 if let Some(item) = cx.read_from_clipboard() {
13077 let entries = item.entries();
13078
13079 match entries.first() {
13080 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13081 // of all the pasted entries.
13082 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13083 .do_paste(
13084 clipboard_string.text(),
13085 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13086 true,
13087 window,
13088 cx,
13089 ),
13090 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13091 }
13092 }
13093 }
13094
13095 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13096 if self.read_only(cx) {
13097 return;
13098 }
13099
13100 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13101
13102 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13103 if let Some((selections, _)) =
13104 self.selection_history.transaction(transaction_id).cloned()
13105 {
13106 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13107 s.select_anchors(selections.to_vec());
13108 });
13109 } else {
13110 log::error!(
13111 "No entry in selection_history found for undo. \
13112 This may correspond to a bug where undo does not update the selection. \
13113 If this is occurring, please add details to \
13114 https://github.com/zed-industries/zed/issues/22692"
13115 );
13116 }
13117 self.request_autoscroll(Autoscroll::fit(), cx);
13118 self.unmark_text(window, cx);
13119 self.refresh_edit_prediction(true, false, window, cx);
13120 cx.emit(EditorEvent::Edited { transaction_id });
13121 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13122 }
13123 }
13124
13125 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13126 if self.read_only(cx) {
13127 return;
13128 }
13129
13130 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13131
13132 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13133 if let Some((_, Some(selections))) =
13134 self.selection_history.transaction(transaction_id).cloned()
13135 {
13136 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13137 s.select_anchors(selections.to_vec());
13138 });
13139 } else {
13140 log::error!(
13141 "No entry in selection_history found for redo. \
13142 This may correspond to a bug where undo does not update the selection. \
13143 If this is occurring, please add details to \
13144 https://github.com/zed-industries/zed/issues/22692"
13145 );
13146 }
13147 self.request_autoscroll(Autoscroll::fit(), cx);
13148 self.unmark_text(window, cx);
13149 self.refresh_edit_prediction(true, false, window, cx);
13150 cx.emit(EditorEvent::Edited { transaction_id });
13151 }
13152 }
13153
13154 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13155 self.buffer
13156 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13157 }
13158
13159 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13160 self.buffer
13161 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13162 }
13163
13164 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13165 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13166 self.change_selections(Default::default(), window, cx, |s| {
13167 s.move_with(|map, selection| {
13168 let cursor = if selection.is_empty() {
13169 movement::left(map, selection.start)
13170 } else {
13171 selection.start
13172 };
13173 selection.collapse_to(cursor, SelectionGoal::None);
13174 });
13175 })
13176 }
13177
13178 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13180 self.change_selections(Default::default(), window, cx, |s| {
13181 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13182 })
13183 }
13184
13185 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13187 self.change_selections(Default::default(), window, cx, |s| {
13188 s.move_with(|map, selection| {
13189 let cursor = if selection.is_empty() {
13190 movement::right(map, selection.end)
13191 } else {
13192 selection.end
13193 };
13194 selection.collapse_to(cursor, SelectionGoal::None)
13195 });
13196 })
13197 }
13198
13199 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13201 self.change_selections(Default::default(), window, cx, |s| {
13202 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13203 });
13204 }
13205
13206 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13207 if self.take_rename(true, window, cx).is_some() {
13208 return;
13209 }
13210
13211 if self.mode.is_single_line() {
13212 cx.propagate();
13213 return;
13214 }
13215
13216 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13217
13218 let text_layout_details = &self.text_layout_details(window);
13219 let selection_count = self.selections.count();
13220 let first_selection = self.selections.first_anchor();
13221
13222 self.change_selections(Default::default(), window, cx, |s| {
13223 s.move_with(|map, selection| {
13224 if !selection.is_empty() {
13225 selection.goal = SelectionGoal::None;
13226 }
13227 let (cursor, goal) = movement::up(
13228 map,
13229 selection.start,
13230 selection.goal,
13231 false,
13232 text_layout_details,
13233 );
13234 selection.collapse_to(cursor, goal);
13235 });
13236 });
13237
13238 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13239 {
13240 cx.propagate();
13241 }
13242 }
13243
13244 pub fn move_up_by_lines(
13245 &mut self,
13246 action: &MoveUpByLines,
13247 window: &mut Window,
13248 cx: &mut Context<Self>,
13249 ) {
13250 if self.take_rename(true, window, cx).is_some() {
13251 return;
13252 }
13253
13254 if self.mode.is_single_line() {
13255 cx.propagate();
13256 return;
13257 }
13258
13259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13260
13261 let text_layout_details = &self.text_layout_details(window);
13262
13263 self.change_selections(Default::default(), window, cx, |s| {
13264 s.move_with(|map, selection| {
13265 if !selection.is_empty() {
13266 selection.goal = SelectionGoal::None;
13267 }
13268 let (cursor, goal) = movement::up_by_rows(
13269 map,
13270 selection.start,
13271 action.lines,
13272 selection.goal,
13273 false,
13274 text_layout_details,
13275 );
13276 selection.collapse_to(cursor, goal);
13277 });
13278 })
13279 }
13280
13281 pub fn move_down_by_lines(
13282 &mut self,
13283 action: &MoveDownByLines,
13284 window: &mut Window,
13285 cx: &mut Context<Self>,
13286 ) {
13287 if self.take_rename(true, window, cx).is_some() {
13288 return;
13289 }
13290
13291 if self.mode.is_single_line() {
13292 cx.propagate();
13293 return;
13294 }
13295
13296 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13297
13298 let text_layout_details = &self.text_layout_details(window);
13299
13300 self.change_selections(Default::default(), window, cx, |s| {
13301 s.move_with(|map, selection| {
13302 if !selection.is_empty() {
13303 selection.goal = SelectionGoal::None;
13304 }
13305 let (cursor, goal) = movement::down_by_rows(
13306 map,
13307 selection.start,
13308 action.lines,
13309 selection.goal,
13310 false,
13311 text_layout_details,
13312 );
13313 selection.collapse_to(cursor, goal);
13314 });
13315 })
13316 }
13317
13318 pub fn select_down_by_lines(
13319 &mut self,
13320 action: &SelectDownByLines,
13321 window: &mut Window,
13322 cx: &mut Context<Self>,
13323 ) {
13324 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13325 let text_layout_details = &self.text_layout_details(window);
13326 self.change_selections(Default::default(), window, cx, |s| {
13327 s.move_heads_with(|map, head, goal| {
13328 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13329 })
13330 })
13331 }
13332
13333 pub fn select_up_by_lines(
13334 &mut self,
13335 action: &SelectUpByLines,
13336 window: &mut Window,
13337 cx: &mut Context<Self>,
13338 ) {
13339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13340 let text_layout_details = &self.text_layout_details(window);
13341 self.change_selections(Default::default(), window, cx, |s| {
13342 s.move_heads_with(|map, head, goal| {
13343 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13344 })
13345 })
13346 }
13347
13348 pub fn select_page_up(
13349 &mut self,
13350 _: &SelectPageUp,
13351 window: &mut Window,
13352 cx: &mut Context<Self>,
13353 ) {
13354 let Some(row_count) = self.visible_row_count() else {
13355 return;
13356 };
13357
13358 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13359
13360 let text_layout_details = &self.text_layout_details(window);
13361
13362 self.change_selections(Default::default(), window, cx, |s| {
13363 s.move_heads_with(|map, head, goal| {
13364 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13365 })
13366 })
13367 }
13368
13369 pub fn move_page_up(
13370 &mut self,
13371 action: &MovePageUp,
13372 window: &mut Window,
13373 cx: &mut Context<Self>,
13374 ) {
13375 if self.take_rename(true, window, cx).is_some() {
13376 return;
13377 }
13378
13379 if self
13380 .context_menu
13381 .borrow_mut()
13382 .as_mut()
13383 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13384 .unwrap_or(false)
13385 {
13386 return;
13387 }
13388
13389 if matches!(self.mode, EditorMode::SingleLine) {
13390 cx.propagate();
13391 return;
13392 }
13393
13394 let Some(row_count) = self.visible_row_count() else {
13395 return;
13396 };
13397
13398 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13399
13400 let effects = if action.center_cursor {
13401 SelectionEffects::scroll(Autoscroll::center())
13402 } else {
13403 SelectionEffects::default()
13404 };
13405
13406 let text_layout_details = &self.text_layout_details(window);
13407
13408 self.change_selections(effects, window, cx, |s| {
13409 s.move_with(|map, selection| {
13410 if !selection.is_empty() {
13411 selection.goal = SelectionGoal::None;
13412 }
13413 let (cursor, goal) = movement::up_by_rows(
13414 map,
13415 selection.end,
13416 row_count,
13417 selection.goal,
13418 false,
13419 text_layout_details,
13420 );
13421 selection.collapse_to(cursor, goal);
13422 });
13423 });
13424 }
13425
13426 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13427 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13428 let text_layout_details = &self.text_layout_details(window);
13429 self.change_selections(Default::default(), window, cx, |s| {
13430 s.move_heads_with(|map, head, goal| {
13431 movement::up(map, head, goal, false, text_layout_details)
13432 })
13433 })
13434 }
13435
13436 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13437 self.take_rename(true, window, cx);
13438
13439 if self.mode.is_single_line() {
13440 cx.propagate();
13441 return;
13442 }
13443
13444 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13445
13446 let text_layout_details = &self.text_layout_details(window);
13447 let selection_count = self.selections.count();
13448 let first_selection = self.selections.first_anchor();
13449
13450 self.change_selections(Default::default(), window, cx, |s| {
13451 s.move_with(|map, selection| {
13452 if !selection.is_empty() {
13453 selection.goal = SelectionGoal::None;
13454 }
13455 let (cursor, goal) = movement::down(
13456 map,
13457 selection.end,
13458 selection.goal,
13459 false,
13460 text_layout_details,
13461 );
13462 selection.collapse_to(cursor, goal);
13463 });
13464 });
13465
13466 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13467 {
13468 cx.propagate();
13469 }
13470 }
13471
13472 pub fn select_page_down(
13473 &mut self,
13474 _: &SelectPageDown,
13475 window: &mut Window,
13476 cx: &mut Context<Self>,
13477 ) {
13478 let Some(row_count) = self.visible_row_count() else {
13479 return;
13480 };
13481
13482 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13483
13484 let text_layout_details = &self.text_layout_details(window);
13485
13486 self.change_selections(Default::default(), window, cx, |s| {
13487 s.move_heads_with(|map, head, goal| {
13488 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13489 })
13490 })
13491 }
13492
13493 pub fn move_page_down(
13494 &mut self,
13495 action: &MovePageDown,
13496 window: &mut Window,
13497 cx: &mut Context<Self>,
13498 ) {
13499 if self.take_rename(true, window, cx).is_some() {
13500 return;
13501 }
13502
13503 if self
13504 .context_menu
13505 .borrow_mut()
13506 .as_mut()
13507 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13508 .unwrap_or(false)
13509 {
13510 return;
13511 }
13512
13513 if matches!(self.mode, EditorMode::SingleLine) {
13514 cx.propagate();
13515 return;
13516 }
13517
13518 let Some(row_count) = self.visible_row_count() else {
13519 return;
13520 };
13521
13522 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13523
13524 let effects = if action.center_cursor {
13525 SelectionEffects::scroll(Autoscroll::center())
13526 } else {
13527 SelectionEffects::default()
13528 };
13529
13530 let text_layout_details = &self.text_layout_details(window);
13531 self.change_selections(effects, window, cx, |s| {
13532 s.move_with(|map, selection| {
13533 if !selection.is_empty() {
13534 selection.goal = SelectionGoal::None;
13535 }
13536 let (cursor, goal) = movement::down_by_rows(
13537 map,
13538 selection.end,
13539 row_count,
13540 selection.goal,
13541 false,
13542 text_layout_details,
13543 );
13544 selection.collapse_to(cursor, goal);
13545 });
13546 });
13547 }
13548
13549 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13550 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13551 let text_layout_details = &self.text_layout_details(window);
13552 self.change_selections(Default::default(), window, cx, |s| {
13553 s.move_heads_with(|map, head, goal| {
13554 movement::down(map, head, goal, false, text_layout_details)
13555 })
13556 });
13557 }
13558
13559 pub fn context_menu_first(
13560 &mut self,
13561 _: &ContextMenuFirst,
13562 window: &mut Window,
13563 cx: &mut Context<Self>,
13564 ) {
13565 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13566 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13567 }
13568 }
13569
13570 pub fn context_menu_prev(
13571 &mut self,
13572 _: &ContextMenuPrevious,
13573 window: &mut Window,
13574 cx: &mut Context<Self>,
13575 ) {
13576 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13577 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13578 }
13579 }
13580
13581 pub fn context_menu_next(
13582 &mut self,
13583 _: &ContextMenuNext,
13584 window: &mut Window,
13585 cx: &mut Context<Self>,
13586 ) {
13587 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13588 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13589 }
13590 }
13591
13592 pub fn context_menu_last(
13593 &mut self,
13594 _: &ContextMenuLast,
13595 window: &mut Window,
13596 cx: &mut Context<Self>,
13597 ) {
13598 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13599 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13600 }
13601 }
13602
13603 pub fn signature_help_prev(
13604 &mut self,
13605 _: &SignatureHelpPrevious,
13606 _: &mut Window,
13607 cx: &mut Context<Self>,
13608 ) {
13609 if let Some(popover) = self.signature_help_state.popover_mut() {
13610 if popover.current_signature == 0 {
13611 popover.current_signature = popover.signatures.len() - 1;
13612 } else {
13613 popover.current_signature -= 1;
13614 }
13615 cx.notify();
13616 }
13617 }
13618
13619 pub fn signature_help_next(
13620 &mut self,
13621 _: &SignatureHelpNext,
13622 _: &mut Window,
13623 cx: &mut Context<Self>,
13624 ) {
13625 if let Some(popover) = self.signature_help_state.popover_mut() {
13626 if popover.current_signature + 1 == popover.signatures.len() {
13627 popover.current_signature = 0;
13628 } else {
13629 popover.current_signature += 1;
13630 }
13631 cx.notify();
13632 }
13633 }
13634
13635 pub fn move_to_previous_word_start(
13636 &mut self,
13637 _: &MoveToPreviousWordStart,
13638 window: &mut Window,
13639 cx: &mut Context<Self>,
13640 ) {
13641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13642 self.change_selections(Default::default(), window, cx, |s| {
13643 s.move_cursors_with(|map, head, _| {
13644 (
13645 movement::previous_word_start(map, head),
13646 SelectionGoal::None,
13647 )
13648 });
13649 })
13650 }
13651
13652 pub fn move_to_previous_subword_start(
13653 &mut self,
13654 _: &MoveToPreviousSubwordStart,
13655 window: &mut Window,
13656 cx: &mut Context<Self>,
13657 ) {
13658 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13659 self.change_selections(Default::default(), window, cx, |s| {
13660 s.move_cursors_with(|map, head, _| {
13661 (
13662 movement::previous_subword_start(map, head),
13663 SelectionGoal::None,
13664 )
13665 });
13666 })
13667 }
13668
13669 pub fn select_to_previous_word_start(
13670 &mut self,
13671 _: &SelectToPreviousWordStart,
13672 window: &mut Window,
13673 cx: &mut Context<Self>,
13674 ) {
13675 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13676 self.change_selections(Default::default(), window, cx, |s| {
13677 s.move_heads_with(|map, head, _| {
13678 (
13679 movement::previous_word_start(map, head),
13680 SelectionGoal::None,
13681 )
13682 });
13683 })
13684 }
13685
13686 pub fn select_to_previous_subword_start(
13687 &mut self,
13688 _: &SelectToPreviousSubwordStart,
13689 window: &mut Window,
13690 cx: &mut Context<Self>,
13691 ) {
13692 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13693 self.change_selections(Default::default(), window, cx, |s| {
13694 s.move_heads_with(|map, head, _| {
13695 (
13696 movement::previous_subword_start(map, head),
13697 SelectionGoal::None,
13698 )
13699 });
13700 })
13701 }
13702
13703 pub fn delete_to_previous_word_start(
13704 &mut self,
13705 action: &DeleteToPreviousWordStart,
13706 window: &mut Window,
13707 cx: &mut Context<Self>,
13708 ) {
13709 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13710 self.transact(window, cx, |this, window, cx| {
13711 this.select_autoclose_pair(window, cx);
13712 this.change_selections(Default::default(), window, cx, |s| {
13713 s.move_with(|map, selection| {
13714 if selection.is_empty() {
13715 let mut cursor = if action.ignore_newlines {
13716 movement::previous_word_start(map, selection.head())
13717 } else {
13718 movement::previous_word_start_or_newline(map, selection.head())
13719 };
13720 cursor = movement::adjust_greedy_deletion(
13721 map,
13722 selection.head(),
13723 cursor,
13724 action.ignore_brackets,
13725 );
13726 selection.set_head(cursor, SelectionGoal::None);
13727 }
13728 });
13729 });
13730 this.insert("", window, cx);
13731 });
13732 }
13733
13734 pub fn delete_to_previous_subword_start(
13735 &mut self,
13736 _: &DeleteToPreviousSubwordStart,
13737 window: &mut Window,
13738 cx: &mut Context<Self>,
13739 ) {
13740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13741 self.transact(window, cx, |this, window, cx| {
13742 this.select_autoclose_pair(window, cx);
13743 this.change_selections(Default::default(), window, cx, |s| {
13744 s.move_with(|map, selection| {
13745 if selection.is_empty() {
13746 let mut cursor = movement::previous_subword_start(map, selection.head());
13747 cursor =
13748 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13749 selection.set_head(cursor, SelectionGoal::None);
13750 }
13751 });
13752 });
13753 this.insert("", window, cx);
13754 });
13755 }
13756
13757 pub fn move_to_next_word_end(
13758 &mut self,
13759 _: &MoveToNextWordEnd,
13760 window: &mut Window,
13761 cx: &mut Context<Self>,
13762 ) {
13763 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13764 self.change_selections(Default::default(), window, cx, |s| {
13765 s.move_cursors_with(|map, head, _| {
13766 (movement::next_word_end(map, head), SelectionGoal::None)
13767 });
13768 })
13769 }
13770
13771 pub fn move_to_next_subword_end(
13772 &mut self,
13773 _: &MoveToNextSubwordEnd,
13774 window: &mut Window,
13775 cx: &mut Context<Self>,
13776 ) {
13777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13778 self.change_selections(Default::default(), window, cx, |s| {
13779 s.move_cursors_with(|map, head, _| {
13780 (movement::next_subword_end(map, head), SelectionGoal::None)
13781 });
13782 })
13783 }
13784
13785 pub fn select_to_next_word_end(
13786 &mut self,
13787 _: &SelectToNextWordEnd,
13788 window: &mut Window,
13789 cx: &mut Context<Self>,
13790 ) {
13791 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13792 self.change_selections(Default::default(), window, cx, |s| {
13793 s.move_heads_with(|map, head, _| {
13794 (movement::next_word_end(map, head), SelectionGoal::None)
13795 });
13796 })
13797 }
13798
13799 pub fn select_to_next_subword_end(
13800 &mut self,
13801 _: &SelectToNextSubwordEnd,
13802 window: &mut Window,
13803 cx: &mut Context<Self>,
13804 ) {
13805 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13806 self.change_selections(Default::default(), window, cx, |s| {
13807 s.move_heads_with(|map, head, _| {
13808 (movement::next_subword_end(map, head), SelectionGoal::None)
13809 });
13810 })
13811 }
13812
13813 pub fn delete_to_next_word_end(
13814 &mut self,
13815 action: &DeleteToNextWordEnd,
13816 window: &mut Window,
13817 cx: &mut Context<Self>,
13818 ) {
13819 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13820 self.transact(window, cx, |this, window, cx| {
13821 this.change_selections(Default::default(), window, cx, |s| {
13822 s.move_with(|map, selection| {
13823 if selection.is_empty() {
13824 let mut cursor = if action.ignore_newlines {
13825 movement::next_word_end(map, selection.head())
13826 } else {
13827 movement::next_word_end_or_newline(map, selection.head())
13828 };
13829 cursor = movement::adjust_greedy_deletion(
13830 map,
13831 selection.head(),
13832 cursor,
13833 action.ignore_brackets,
13834 );
13835 selection.set_head(cursor, SelectionGoal::None);
13836 }
13837 });
13838 });
13839 this.insert("", window, cx);
13840 });
13841 }
13842
13843 pub fn delete_to_next_subword_end(
13844 &mut self,
13845 _: &DeleteToNextSubwordEnd,
13846 window: &mut Window,
13847 cx: &mut Context<Self>,
13848 ) {
13849 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13850 self.transact(window, cx, |this, window, cx| {
13851 this.change_selections(Default::default(), window, cx, |s| {
13852 s.move_with(|map, selection| {
13853 if selection.is_empty() {
13854 let mut cursor = movement::next_subword_end(map, selection.head());
13855 cursor =
13856 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13857 selection.set_head(cursor, SelectionGoal::None);
13858 }
13859 });
13860 });
13861 this.insert("", window, cx);
13862 });
13863 }
13864
13865 pub fn move_to_beginning_of_line(
13866 &mut self,
13867 action: &MoveToBeginningOfLine,
13868 window: &mut Window,
13869 cx: &mut Context<Self>,
13870 ) {
13871 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13872 self.change_selections(Default::default(), window, cx, |s| {
13873 s.move_cursors_with(|map, head, _| {
13874 (
13875 movement::indented_line_beginning(
13876 map,
13877 head,
13878 action.stop_at_soft_wraps,
13879 action.stop_at_indent,
13880 ),
13881 SelectionGoal::None,
13882 )
13883 });
13884 })
13885 }
13886
13887 pub fn select_to_beginning_of_line(
13888 &mut self,
13889 action: &SelectToBeginningOfLine,
13890 window: &mut Window,
13891 cx: &mut Context<Self>,
13892 ) {
13893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13894 self.change_selections(Default::default(), window, cx, |s| {
13895 s.move_heads_with(|map, head, _| {
13896 (
13897 movement::indented_line_beginning(
13898 map,
13899 head,
13900 action.stop_at_soft_wraps,
13901 action.stop_at_indent,
13902 ),
13903 SelectionGoal::None,
13904 )
13905 });
13906 });
13907 }
13908
13909 pub fn delete_to_beginning_of_line(
13910 &mut self,
13911 action: &DeleteToBeginningOfLine,
13912 window: &mut Window,
13913 cx: &mut Context<Self>,
13914 ) {
13915 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13916 self.transact(window, cx, |this, window, cx| {
13917 this.change_selections(Default::default(), window, cx, |s| {
13918 s.move_with(|_, selection| {
13919 selection.reversed = true;
13920 });
13921 });
13922
13923 this.select_to_beginning_of_line(
13924 &SelectToBeginningOfLine {
13925 stop_at_soft_wraps: false,
13926 stop_at_indent: action.stop_at_indent,
13927 },
13928 window,
13929 cx,
13930 );
13931 this.backspace(&Backspace, window, cx);
13932 });
13933 }
13934
13935 pub fn move_to_end_of_line(
13936 &mut self,
13937 action: &MoveToEndOfLine,
13938 window: &mut Window,
13939 cx: &mut Context<Self>,
13940 ) {
13941 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13942 self.change_selections(Default::default(), window, cx, |s| {
13943 s.move_cursors_with(|map, head, _| {
13944 (
13945 movement::line_end(map, head, action.stop_at_soft_wraps),
13946 SelectionGoal::None,
13947 )
13948 });
13949 })
13950 }
13951
13952 pub fn select_to_end_of_line(
13953 &mut self,
13954 action: &SelectToEndOfLine,
13955 window: &mut Window,
13956 cx: &mut Context<Self>,
13957 ) {
13958 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13959 self.change_selections(Default::default(), window, cx, |s| {
13960 s.move_heads_with(|map, head, _| {
13961 (
13962 movement::line_end(map, head, action.stop_at_soft_wraps),
13963 SelectionGoal::None,
13964 )
13965 });
13966 })
13967 }
13968
13969 pub fn delete_to_end_of_line(
13970 &mut self,
13971 _: &DeleteToEndOfLine,
13972 window: &mut Window,
13973 cx: &mut Context<Self>,
13974 ) {
13975 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13976 self.transact(window, cx, |this, window, cx| {
13977 this.select_to_end_of_line(
13978 &SelectToEndOfLine {
13979 stop_at_soft_wraps: false,
13980 },
13981 window,
13982 cx,
13983 );
13984 this.delete(&Delete, window, cx);
13985 });
13986 }
13987
13988 pub fn cut_to_end_of_line(
13989 &mut self,
13990 action: &CutToEndOfLine,
13991 window: &mut Window,
13992 cx: &mut Context<Self>,
13993 ) {
13994 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13995 self.transact(window, cx, |this, window, cx| {
13996 this.select_to_end_of_line(
13997 &SelectToEndOfLine {
13998 stop_at_soft_wraps: false,
13999 },
14000 window,
14001 cx,
14002 );
14003 if !action.stop_at_newlines {
14004 this.change_selections(Default::default(), window, cx, |s| {
14005 s.move_with(|_, sel| {
14006 if sel.is_empty() {
14007 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14008 }
14009 });
14010 });
14011 }
14012 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14013 let item = this.cut_common(false, window, cx);
14014 cx.write_to_clipboard(item);
14015 });
14016 }
14017
14018 pub fn move_to_start_of_paragraph(
14019 &mut self,
14020 _: &MoveToStartOfParagraph,
14021 window: &mut Window,
14022 cx: &mut Context<Self>,
14023 ) {
14024 if matches!(self.mode, EditorMode::SingleLine) {
14025 cx.propagate();
14026 return;
14027 }
14028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14029 self.change_selections(Default::default(), window, cx, |s| {
14030 s.move_with(|map, selection| {
14031 selection.collapse_to(
14032 movement::start_of_paragraph(map, selection.head(), 1),
14033 SelectionGoal::None,
14034 )
14035 });
14036 })
14037 }
14038
14039 pub fn move_to_end_of_paragraph(
14040 &mut self,
14041 _: &MoveToEndOfParagraph,
14042 window: &mut Window,
14043 cx: &mut Context<Self>,
14044 ) {
14045 if matches!(self.mode, EditorMode::SingleLine) {
14046 cx.propagate();
14047 return;
14048 }
14049 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14050 self.change_selections(Default::default(), window, cx, |s| {
14051 s.move_with(|map, selection| {
14052 selection.collapse_to(
14053 movement::end_of_paragraph(map, selection.head(), 1),
14054 SelectionGoal::None,
14055 )
14056 });
14057 })
14058 }
14059
14060 pub fn select_to_start_of_paragraph(
14061 &mut self,
14062 _: &SelectToStartOfParagraph,
14063 window: &mut Window,
14064 cx: &mut Context<Self>,
14065 ) {
14066 if matches!(self.mode, EditorMode::SingleLine) {
14067 cx.propagate();
14068 return;
14069 }
14070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14071 self.change_selections(Default::default(), window, cx, |s| {
14072 s.move_heads_with(|map, head, _| {
14073 (
14074 movement::start_of_paragraph(map, head, 1),
14075 SelectionGoal::None,
14076 )
14077 });
14078 })
14079 }
14080
14081 pub fn select_to_end_of_paragraph(
14082 &mut self,
14083 _: &SelectToEndOfParagraph,
14084 window: &mut Window,
14085 cx: &mut Context<Self>,
14086 ) {
14087 if matches!(self.mode, EditorMode::SingleLine) {
14088 cx.propagate();
14089 return;
14090 }
14091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14092 self.change_selections(Default::default(), window, cx, |s| {
14093 s.move_heads_with(|map, head, _| {
14094 (
14095 movement::end_of_paragraph(map, head, 1),
14096 SelectionGoal::None,
14097 )
14098 });
14099 })
14100 }
14101
14102 pub fn move_to_start_of_excerpt(
14103 &mut self,
14104 _: &MoveToStartOfExcerpt,
14105 window: &mut Window,
14106 cx: &mut Context<Self>,
14107 ) {
14108 if matches!(self.mode, EditorMode::SingleLine) {
14109 cx.propagate();
14110 return;
14111 }
14112 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14113 self.change_selections(Default::default(), window, cx, |s| {
14114 s.move_with(|map, selection| {
14115 selection.collapse_to(
14116 movement::start_of_excerpt(
14117 map,
14118 selection.head(),
14119 workspace::searchable::Direction::Prev,
14120 ),
14121 SelectionGoal::None,
14122 )
14123 });
14124 })
14125 }
14126
14127 pub fn move_to_start_of_next_excerpt(
14128 &mut self,
14129 _: &MoveToStartOfNextExcerpt,
14130 window: &mut Window,
14131 cx: &mut Context<Self>,
14132 ) {
14133 if matches!(self.mode, EditorMode::SingleLine) {
14134 cx.propagate();
14135 return;
14136 }
14137
14138 self.change_selections(Default::default(), window, cx, |s| {
14139 s.move_with(|map, selection| {
14140 selection.collapse_to(
14141 movement::start_of_excerpt(
14142 map,
14143 selection.head(),
14144 workspace::searchable::Direction::Next,
14145 ),
14146 SelectionGoal::None,
14147 )
14148 });
14149 })
14150 }
14151
14152 pub fn move_to_end_of_excerpt(
14153 &mut self,
14154 _: &MoveToEndOfExcerpt,
14155 window: &mut Window,
14156 cx: &mut Context<Self>,
14157 ) {
14158 if matches!(self.mode, EditorMode::SingleLine) {
14159 cx.propagate();
14160 return;
14161 }
14162 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14163 self.change_selections(Default::default(), window, cx, |s| {
14164 s.move_with(|map, selection| {
14165 selection.collapse_to(
14166 movement::end_of_excerpt(
14167 map,
14168 selection.head(),
14169 workspace::searchable::Direction::Next,
14170 ),
14171 SelectionGoal::None,
14172 )
14173 });
14174 })
14175 }
14176
14177 pub fn move_to_end_of_previous_excerpt(
14178 &mut self,
14179 _: &MoveToEndOfPreviousExcerpt,
14180 window: &mut Window,
14181 cx: &mut Context<Self>,
14182 ) {
14183 if matches!(self.mode, EditorMode::SingleLine) {
14184 cx.propagate();
14185 return;
14186 }
14187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14188 self.change_selections(Default::default(), window, cx, |s| {
14189 s.move_with(|map, selection| {
14190 selection.collapse_to(
14191 movement::end_of_excerpt(
14192 map,
14193 selection.head(),
14194 workspace::searchable::Direction::Prev,
14195 ),
14196 SelectionGoal::None,
14197 )
14198 });
14199 })
14200 }
14201
14202 pub fn select_to_start_of_excerpt(
14203 &mut self,
14204 _: &SelectToStartOfExcerpt,
14205 window: &mut Window,
14206 cx: &mut Context<Self>,
14207 ) {
14208 if matches!(self.mode, EditorMode::SingleLine) {
14209 cx.propagate();
14210 return;
14211 }
14212 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14213 self.change_selections(Default::default(), window, cx, |s| {
14214 s.move_heads_with(|map, head, _| {
14215 (
14216 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14217 SelectionGoal::None,
14218 )
14219 });
14220 })
14221 }
14222
14223 pub fn select_to_start_of_next_excerpt(
14224 &mut self,
14225 _: &SelectToStartOfNextExcerpt,
14226 window: &mut Window,
14227 cx: &mut Context<Self>,
14228 ) {
14229 if matches!(self.mode, EditorMode::SingleLine) {
14230 cx.propagate();
14231 return;
14232 }
14233 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14234 self.change_selections(Default::default(), window, cx, |s| {
14235 s.move_heads_with(|map, head, _| {
14236 (
14237 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14238 SelectionGoal::None,
14239 )
14240 });
14241 })
14242 }
14243
14244 pub fn select_to_end_of_excerpt(
14245 &mut self,
14246 _: &SelectToEndOfExcerpt,
14247 window: &mut Window,
14248 cx: &mut Context<Self>,
14249 ) {
14250 if matches!(self.mode, EditorMode::SingleLine) {
14251 cx.propagate();
14252 return;
14253 }
14254 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14255 self.change_selections(Default::default(), window, cx, |s| {
14256 s.move_heads_with(|map, head, _| {
14257 (
14258 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14259 SelectionGoal::None,
14260 )
14261 });
14262 })
14263 }
14264
14265 pub fn select_to_end_of_previous_excerpt(
14266 &mut self,
14267 _: &SelectToEndOfPreviousExcerpt,
14268 window: &mut Window,
14269 cx: &mut Context<Self>,
14270 ) {
14271 if matches!(self.mode, EditorMode::SingleLine) {
14272 cx.propagate();
14273 return;
14274 }
14275 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14276 self.change_selections(Default::default(), window, cx, |s| {
14277 s.move_heads_with(|map, head, _| {
14278 (
14279 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14280 SelectionGoal::None,
14281 )
14282 });
14283 })
14284 }
14285
14286 pub fn move_to_beginning(
14287 &mut self,
14288 _: &MoveToBeginning,
14289 window: &mut Window,
14290 cx: &mut Context<Self>,
14291 ) {
14292 if matches!(self.mode, EditorMode::SingleLine) {
14293 cx.propagate();
14294 return;
14295 }
14296 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14297 self.change_selections(Default::default(), window, cx, |s| {
14298 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14299 });
14300 }
14301
14302 pub fn select_to_beginning(
14303 &mut self,
14304 _: &SelectToBeginning,
14305 window: &mut Window,
14306 cx: &mut Context<Self>,
14307 ) {
14308 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14309 selection.set_head(Point::zero(), SelectionGoal::None);
14310 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14311 self.change_selections(Default::default(), window, cx, |s| {
14312 s.select(vec![selection]);
14313 });
14314 }
14315
14316 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14317 if matches!(self.mode, EditorMode::SingleLine) {
14318 cx.propagate();
14319 return;
14320 }
14321 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14322 let cursor = self.buffer.read(cx).read(cx).len();
14323 self.change_selections(Default::default(), window, cx, |s| {
14324 s.select_ranges(vec![cursor..cursor])
14325 });
14326 }
14327
14328 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14329 self.nav_history = nav_history;
14330 }
14331
14332 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14333 self.nav_history.as_ref()
14334 }
14335
14336 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14337 self.push_to_nav_history(
14338 self.selections.newest_anchor().head(),
14339 None,
14340 false,
14341 true,
14342 cx,
14343 );
14344 }
14345
14346 fn push_to_nav_history(
14347 &mut self,
14348 cursor_anchor: Anchor,
14349 new_position: Option<Point>,
14350 is_deactivate: bool,
14351 always: bool,
14352 cx: &mut Context<Self>,
14353 ) {
14354 if let Some(nav_history) = self.nav_history.as_mut() {
14355 let buffer = self.buffer.read(cx).read(cx);
14356 let cursor_position = cursor_anchor.to_point(&buffer);
14357 let scroll_state = self.scroll_manager.anchor();
14358 let scroll_top_row = scroll_state.top_row(&buffer);
14359 drop(buffer);
14360
14361 if let Some(new_position) = new_position {
14362 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14363 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14364 return;
14365 }
14366 }
14367
14368 nav_history.push(
14369 Some(NavigationData {
14370 cursor_anchor,
14371 cursor_position,
14372 scroll_anchor: scroll_state,
14373 scroll_top_row,
14374 }),
14375 cx,
14376 );
14377 cx.emit(EditorEvent::PushedToNavHistory {
14378 anchor: cursor_anchor,
14379 is_deactivate,
14380 })
14381 }
14382 }
14383
14384 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14385 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14386 let buffer = self.buffer.read(cx).snapshot(cx);
14387 let mut selection = self
14388 .selections
14389 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14390 selection.set_head(buffer.len(), SelectionGoal::None);
14391 self.change_selections(Default::default(), window, cx, |s| {
14392 s.select(vec![selection]);
14393 });
14394 }
14395
14396 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14397 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14398 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14399 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14400 });
14401 }
14402
14403 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14404 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14405 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14406 let mut selections = self.selections.all::<Point>(&display_map);
14407 let max_point = display_map.buffer_snapshot().max_point();
14408 for selection in &mut selections {
14409 let rows = selection.spanned_rows(true, &display_map);
14410 selection.start = Point::new(rows.start.0, 0);
14411 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14412 selection.reversed = false;
14413 }
14414 self.change_selections(Default::default(), window, cx, |s| {
14415 s.select(selections);
14416 });
14417 }
14418
14419 pub fn split_selection_into_lines(
14420 &mut self,
14421 action: &SplitSelectionIntoLines,
14422 window: &mut Window,
14423 cx: &mut Context<Self>,
14424 ) {
14425 let selections = self
14426 .selections
14427 .all::<Point>(&self.display_snapshot(cx))
14428 .into_iter()
14429 .map(|selection| selection.start..selection.end)
14430 .collect::<Vec<_>>();
14431 self.unfold_ranges(&selections, true, true, cx);
14432
14433 let mut new_selection_ranges = Vec::new();
14434 {
14435 let buffer = self.buffer.read(cx).read(cx);
14436 for selection in selections {
14437 for row in selection.start.row..selection.end.row {
14438 let line_start = Point::new(row, 0);
14439 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14440
14441 if action.keep_selections {
14442 // Keep the selection range for each line
14443 let selection_start = if row == selection.start.row {
14444 selection.start
14445 } else {
14446 line_start
14447 };
14448 new_selection_ranges.push(selection_start..line_end);
14449 } else {
14450 // Collapse to cursor at end of line
14451 new_selection_ranges.push(line_end..line_end);
14452 }
14453 }
14454
14455 let is_multiline_selection = selection.start.row != selection.end.row;
14456 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14457 // so this action feels more ergonomic when paired with other selection operations
14458 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14459 if !should_skip_last {
14460 if action.keep_selections {
14461 if is_multiline_selection {
14462 let line_start = Point::new(selection.end.row, 0);
14463 new_selection_ranges.push(line_start..selection.end);
14464 } else {
14465 new_selection_ranges.push(selection.start..selection.end);
14466 }
14467 } else {
14468 new_selection_ranges.push(selection.end..selection.end);
14469 }
14470 }
14471 }
14472 }
14473 self.change_selections(Default::default(), window, cx, |s| {
14474 s.select_ranges(new_selection_ranges);
14475 });
14476 }
14477
14478 pub fn add_selection_above(
14479 &mut self,
14480 action: &AddSelectionAbove,
14481 window: &mut Window,
14482 cx: &mut Context<Self>,
14483 ) {
14484 self.add_selection(true, action.skip_soft_wrap, window, cx);
14485 }
14486
14487 pub fn add_selection_below(
14488 &mut self,
14489 action: &AddSelectionBelow,
14490 window: &mut Window,
14491 cx: &mut Context<Self>,
14492 ) {
14493 self.add_selection(false, action.skip_soft_wrap, window, cx);
14494 }
14495
14496 fn add_selection(
14497 &mut self,
14498 above: bool,
14499 skip_soft_wrap: bool,
14500 window: &mut Window,
14501 cx: &mut Context<Self>,
14502 ) {
14503 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14504
14505 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14506 let all_selections = self.selections.all::<Point>(&display_map);
14507 let text_layout_details = self.text_layout_details(window);
14508
14509 let (mut columnar_selections, new_selections_to_columnarize) = {
14510 if let Some(state) = self.add_selections_state.as_ref() {
14511 let columnar_selection_ids: HashSet<_> = state
14512 .groups
14513 .iter()
14514 .flat_map(|group| group.stack.iter())
14515 .copied()
14516 .collect();
14517
14518 all_selections
14519 .into_iter()
14520 .partition(|s| columnar_selection_ids.contains(&s.id))
14521 } else {
14522 (Vec::new(), all_selections)
14523 }
14524 };
14525
14526 let mut state = self
14527 .add_selections_state
14528 .take()
14529 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14530
14531 for selection in new_selections_to_columnarize {
14532 let range = selection.display_range(&display_map).sorted();
14533 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14534 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14535 let positions = start_x.min(end_x)..start_x.max(end_x);
14536 let mut stack = Vec::new();
14537 for row in range.start.row().0..=range.end.row().0 {
14538 if let Some(selection) = self.selections.build_columnar_selection(
14539 &display_map,
14540 DisplayRow(row),
14541 &positions,
14542 selection.reversed,
14543 &text_layout_details,
14544 ) {
14545 stack.push(selection.id);
14546 columnar_selections.push(selection);
14547 }
14548 }
14549 if !stack.is_empty() {
14550 if above {
14551 stack.reverse();
14552 }
14553 state.groups.push(AddSelectionsGroup { above, stack });
14554 }
14555 }
14556
14557 let mut final_selections = Vec::new();
14558 let end_row = if above {
14559 DisplayRow(0)
14560 } else {
14561 display_map.max_point().row()
14562 };
14563
14564 let mut last_added_item_per_group = HashMap::default();
14565 for group in state.groups.iter_mut() {
14566 if let Some(last_id) = group.stack.last() {
14567 last_added_item_per_group.insert(*last_id, group);
14568 }
14569 }
14570
14571 for selection in columnar_selections {
14572 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14573 if above == group.above {
14574 let range = selection.display_range(&display_map).sorted();
14575 debug_assert_eq!(range.start.row(), range.end.row());
14576 let mut row = range.start.row();
14577 let positions =
14578 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14579 Pixels::from(start)..Pixels::from(end)
14580 } else {
14581 let start_x =
14582 display_map.x_for_display_point(range.start, &text_layout_details);
14583 let end_x =
14584 display_map.x_for_display_point(range.end, &text_layout_details);
14585 start_x.min(end_x)..start_x.max(end_x)
14586 };
14587
14588 let mut maybe_new_selection = None;
14589 let direction = if above { -1 } else { 1 };
14590
14591 while row != end_row {
14592 if skip_soft_wrap {
14593 row = display_map
14594 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14595 .row();
14596 } else if above {
14597 row.0 -= 1;
14598 } else {
14599 row.0 += 1;
14600 }
14601
14602 if let Some(new_selection) = self.selections.build_columnar_selection(
14603 &display_map,
14604 row,
14605 &positions,
14606 selection.reversed,
14607 &text_layout_details,
14608 ) {
14609 maybe_new_selection = Some(new_selection);
14610 break;
14611 }
14612 }
14613
14614 if let Some(new_selection) = maybe_new_selection {
14615 group.stack.push(new_selection.id);
14616 if above {
14617 final_selections.push(new_selection);
14618 final_selections.push(selection);
14619 } else {
14620 final_selections.push(selection);
14621 final_selections.push(new_selection);
14622 }
14623 } else {
14624 final_selections.push(selection);
14625 }
14626 } else {
14627 group.stack.pop();
14628 }
14629 } else {
14630 final_selections.push(selection);
14631 }
14632 }
14633
14634 self.change_selections(Default::default(), window, cx, |s| {
14635 s.select(final_selections);
14636 });
14637
14638 let final_selection_ids: HashSet<_> = self
14639 .selections
14640 .all::<Point>(&display_map)
14641 .iter()
14642 .map(|s| s.id)
14643 .collect();
14644 state.groups.retain_mut(|group| {
14645 // selections might get merged above so we remove invalid items from stacks
14646 group.stack.retain(|id| final_selection_ids.contains(id));
14647
14648 // single selection in stack can be treated as initial state
14649 group.stack.len() > 1
14650 });
14651
14652 if !state.groups.is_empty() {
14653 self.add_selections_state = Some(state);
14654 }
14655 }
14656
14657 fn select_match_ranges(
14658 &mut self,
14659 range: Range<MultiBufferOffset>,
14660 reversed: bool,
14661 replace_newest: bool,
14662 auto_scroll: Option<Autoscroll>,
14663 window: &mut Window,
14664 cx: &mut Context<Editor>,
14665 ) {
14666 self.unfold_ranges(
14667 std::slice::from_ref(&range),
14668 false,
14669 auto_scroll.is_some(),
14670 cx,
14671 );
14672 let effects = if let Some(scroll) = auto_scroll {
14673 SelectionEffects::scroll(scroll)
14674 } else {
14675 SelectionEffects::no_scroll()
14676 };
14677 self.change_selections(effects, window, cx, |s| {
14678 if replace_newest {
14679 s.delete(s.newest_anchor().id);
14680 }
14681 if reversed {
14682 s.insert_range(range.end..range.start);
14683 } else {
14684 s.insert_range(range);
14685 }
14686 });
14687 }
14688
14689 pub fn select_next_match_internal(
14690 &mut self,
14691 display_map: &DisplaySnapshot,
14692 replace_newest: bool,
14693 autoscroll: Option<Autoscroll>,
14694 window: &mut Window,
14695 cx: &mut Context<Self>,
14696 ) -> Result<()> {
14697 let buffer = display_map.buffer_snapshot();
14698 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14699 if let Some(mut select_next_state) = self.select_next_state.take() {
14700 let query = &select_next_state.query;
14701 if !select_next_state.done {
14702 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14703 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14704 let mut next_selected_range = None;
14705
14706 let bytes_after_last_selection =
14707 buffer.bytes_in_range(last_selection.end..buffer.len());
14708 let bytes_before_first_selection =
14709 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14710 let query_matches = query
14711 .stream_find_iter(bytes_after_last_selection)
14712 .map(|result| (last_selection.end, result))
14713 .chain(
14714 query
14715 .stream_find_iter(bytes_before_first_selection)
14716 .map(|result| (MultiBufferOffset(0), result)),
14717 );
14718
14719 for (start_offset, query_match) in query_matches {
14720 let query_match = query_match.unwrap(); // can only fail due to I/O
14721 let offset_range =
14722 start_offset + query_match.start()..start_offset + query_match.end();
14723
14724 if !select_next_state.wordwise
14725 || (!buffer.is_inside_word(offset_range.start, None)
14726 && !buffer.is_inside_word(offset_range.end, None))
14727 {
14728 let idx = selections
14729 .partition_point(|selection| selection.end <= offset_range.start);
14730 let overlaps = selections
14731 .get(idx)
14732 .map_or(false, |selection| selection.start < offset_range.end);
14733
14734 if !overlaps {
14735 next_selected_range = Some(offset_range);
14736 break;
14737 }
14738 }
14739 }
14740
14741 if let Some(next_selected_range) = next_selected_range {
14742 self.select_match_ranges(
14743 next_selected_range,
14744 last_selection.reversed,
14745 replace_newest,
14746 autoscroll,
14747 window,
14748 cx,
14749 );
14750 } else {
14751 select_next_state.done = true;
14752 }
14753 }
14754
14755 self.select_next_state = Some(select_next_state);
14756 } else {
14757 let mut only_carets = true;
14758 let mut same_text_selected = true;
14759 let mut selected_text = None;
14760
14761 let mut selections_iter = selections.iter().peekable();
14762 while let Some(selection) = selections_iter.next() {
14763 if selection.start != selection.end {
14764 only_carets = false;
14765 }
14766
14767 if same_text_selected {
14768 if selected_text.is_none() {
14769 selected_text =
14770 Some(buffer.text_for_range(selection.range()).collect::<String>());
14771 }
14772
14773 if let Some(next_selection) = selections_iter.peek() {
14774 if next_selection.len() == selection.len() {
14775 let next_selected_text = buffer
14776 .text_for_range(next_selection.range())
14777 .collect::<String>();
14778 if Some(next_selected_text) != selected_text {
14779 same_text_selected = false;
14780 selected_text = None;
14781 }
14782 } else {
14783 same_text_selected = false;
14784 selected_text = None;
14785 }
14786 }
14787 }
14788 }
14789
14790 if only_carets {
14791 for selection in &mut selections {
14792 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14793 selection.start = word_range.start;
14794 selection.end = word_range.end;
14795 selection.goal = SelectionGoal::None;
14796 selection.reversed = false;
14797 self.select_match_ranges(
14798 selection.start..selection.end,
14799 selection.reversed,
14800 replace_newest,
14801 autoscroll,
14802 window,
14803 cx,
14804 );
14805 }
14806
14807 if selections.len() == 1 {
14808 let selection = selections
14809 .last()
14810 .expect("ensured that there's only one selection");
14811 let query = buffer
14812 .text_for_range(selection.start..selection.end)
14813 .collect::<String>();
14814 let is_empty = query.is_empty();
14815 let select_state = SelectNextState {
14816 query: self.build_query(&[query], cx)?,
14817 wordwise: true,
14818 done: is_empty,
14819 };
14820 self.select_next_state = Some(select_state);
14821 } else {
14822 self.select_next_state = None;
14823 }
14824 } else if let Some(selected_text) = selected_text {
14825 self.select_next_state = Some(SelectNextState {
14826 query: self.build_query(&[selected_text], cx)?,
14827 wordwise: false,
14828 done: false,
14829 });
14830 self.select_next_match_internal(
14831 display_map,
14832 replace_newest,
14833 autoscroll,
14834 window,
14835 cx,
14836 )?;
14837 }
14838 }
14839 Ok(())
14840 }
14841
14842 pub fn select_all_matches(
14843 &mut self,
14844 _action: &SelectAllMatches,
14845 window: &mut Window,
14846 cx: &mut Context<Self>,
14847 ) -> Result<()> {
14848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14849
14850 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14851
14852 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14853 let Some(select_next_state) = self.select_next_state.as_mut() else {
14854 return Ok(());
14855 };
14856 if select_next_state.done {
14857 return Ok(());
14858 }
14859
14860 let mut new_selections = Vec::new();
14861
14862 let reversed = self
14863 .selections
14864 .oldest::<MultiBufferOffset>(&display_map)
14865 .reversed;
14866 let buffer = display_map.buffer_snapshot();
14867 let query_matches = select_next_state
14868 .query
14869 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
14870
14871 for query_match in query_matches.into_iter() {
14872 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14873 let offset_range = if reversed {
14874 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
14875 } else {
14876 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
14877 };
14878
14879 if !select_next_state.wordwise
14880 || (!buffer.is_inside_word(offset_range.start, None)
14881 && !buffer.is_inside_word(offset_range.end, None))
14882 {
14883 new_selections.push(offset_range.start..offset_range.end);
14884 }
14885 }
14886
14887 select_next_state.done = true;
14888
14889 if new_selections.is_empty() {
14890 log::error!("bug: new_selections is empty in select_all_matches");
14891 return Ok(());
14892 }
14893
14894 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14895 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14896 selections.select_ranges(new_selections)
14897 });
14898
14899 Ok(())
14900 }
14901
14902 pub fn select_next(
14903 &mut self,
14904 action: &SelectNext,
14905 window: &mut Window,
14906 cx: &mut Context<Self>,
14907 ) -> Result<()> {
14908 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14909 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14910 self.select_next_match_internal(
14911 &display_map,
14912 action.replace_newest,
14913 Some(Autoscroll::newest()),
14914 window,
14915 cx,
14916 )?;
14917 Ok(())
14918 }
14919
14920 pub fn select_previous(
14921 &mut self,
14922 action: &SelectPrevious,
14923 window: &mut Window,
14924 cx: &mut Context<Self>,
14925 ) -> Result<()> {
14926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14927 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14928 let buffer = display_map.buffer_snapshot();
14929 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14930 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14931 let query = &select_prev_state.query;
14932 if !select_prev_state.done {
14933 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14934 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14935 let mut next_selected_range = None;
14936 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14937 let bytes_before_last_selection =
14938 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
14939 let bytes_after_first_selection =
14940 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14941 let query_matches = query
14942 .stream_find_iter(bytes_before_last_selection)
14943 .map(|result| (last_selection.start, result))
14944 .chain(
14945 query
14946 .stream_find_iter(bytes_after_first_selection)
14947 .map(|result| (buffer.len(), result)),
14948 );
14949 for (end_offset, query_match) in query_matches {
14950 let query_match = query_match.unwrap(); // can only fail due to I/O
14951 let offset_range =
14952 end_offset - query_match.end()..end_offset - query_match.start();
14953
14954 if !select_prev_state.wordwise
14955 || (!buffer.is_inside_word(offset_range.start, None)
14956 && !buffer.is_inside_word(offset_range.end, None))
14957 {
14958 next_selected_range = Some(offset_range);
14959 break;
14960 }
14961 }
14962
14963 if let Some(next_selected_range) = next_selected_range {
14964 self.select_match_ranges(
14965 next_selected_range,
14966 last_selection.reversed,
14967 action.replace_newest,
14968 Some(Autoscroll::newest()),
14969 window,
14970 cx,
14971 );
14972 } else {
14973 select_prev_state.done = true;
14974 }
14975 }
14976
14977 self.select_prev_state = Some(select_prev_state);
14978 } else {
14979 let mut only_carets = true;
14980 let mut same_text_selected = true;
14981 let mut selected_text = None;
14982
14983 let mut selections_iter = selections.iter().peekable();
14984 while let Some(selection) = selections_iter.next() {
14985 if selection.start != selection.end {
14986 only_carets = false;
14987 }
14988
14989 if same_text_selected {
14990 if selected_text.is_none() {
14991 selected_text =
14992 Some(buffer.text_for_range(selection.range()).collect::<String>());
14993 }
14994
14995 if let Some(next_selection) = selections_iter.peek() {
14996 if next_selection.len() == selection.len() {
14997 let next_selected_text = buffer
14998 .text_for_range(next_selection.range())
14999 .collect::<String>();
15000 if Some(next_selected_text) != selected_text {
15001 same_text_selected = false;
15002 selected_text = None;
15003 }
15004 } else {
15005 same_text_selected = false;
15006 selected_text = None;
15007 }
15008 }
15009 }
15010 }
15011
15012 if only_carets {
15013 for selection in &mut selections {
15014 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15015 selection.start = word_range.start;
15016 selection.end = word_range.end;
15017 selection.goal = SelectionGoal::None;
15018 selection.reversed = false;
15019 self.select_match_ranges(
15020 selection.start..selection.end,
15021 selection.reversed,
15022 action.replace_newest,
15023 Some(Autoscroll::newest()),
15024 window,
15025 cx,
15026 );
15027 }
15028 if selections.len() == 1 {
15029 let selection = selections
15030 .last()
15031 .expect("ensured that there's only one selection");
15032 let query = buffer
15033 .text_for_range(selection.start..selection.end)
15034 .collect::<String>();
15035 let is_empty = query.is_empty();
15036 let select_state = SelectNextState {
15037 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15038 wordwise: true,
15039 done: is_empty,
15040 };
15041 self.select_prev_state = Some(select_state);
15042 } else {
15043 self.select_prev_state = None;
15044 }
15045 } else if let Some(selected_text) = selected_text {
15046 self.select_prev_state = Some(SelectNextState {
15047 query: self
15048 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15049 wordwise: false,
15050 done: false,
15051 });
15052 self.select_previous(action, window, cx)?;
15053 }
15054 }
15055 Ok(())
15056 }
15057
15058 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15059 /// setting the case sensitivity based on the global
15060 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15061 /// editor's settings.
15062 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15063 where
15064 I: IntoIterator<Item = P>,
15065 P: AsRef<[u8]>,
15066 {
15067 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15068 || EditorSettings::get_global(cx).search.case_sensitive,
15069 |value| value,
15070 );
15071
15072 let mut builder = AhoCorasickBuilder::new();
15073 builder.ascii_case_insensitive(!case_sensitive);
15074 builder.build(patterns)
15075 }
15076
15077 pub fn find_next_match(
15078 &mut self,
15079 _: &FindNextMatch,
15080 window: &mut Window,
15081 cx: &mut Context<Self>,
15082 ) -> Result<()> {
15083 let selections = self.selections.disjoint_anchors_arc();
15084 match selections.first() {
15085 Some(first) if selections.len() >= 2 => {
15086 self.change_selections(Default::default(), window, cx, |s| {
15087 s.select_ranges([first.range()]);
15088 });
15089 }
15090 _ => self.select_next(
15091 &SelectNext {
15092 replace_newest: true,
15093 },
15094 window,
15095 cx,
15096 )?,
15097 }
15098 Ok(())
15099 }
15100
15101 pub fn find_previous_match(
15102 &mut self,
15103 _: &FindPreviousMatch,
15104 window: &mut Window,
15105 cx: &mut Context<Self>,
15106 ) -> Result<()> {
15107 let selections = self.selections.disjoint_anchors_arc();
15108 match selections.last() {
15109 Some(last) if selections.len() >= 2 => {
15110 self.change_selections(Default::default(), window, cx, |s| {
15111 s.select_ranges([last.range()]);
15112 });
15113 }
15114 _ => self.select_previous(
15115 &SelectPrevious {
15116 replace_newest: true,
15117 },
15118 window,
15119 cx,
15120 )?,
15121 }
15122 Ok(())
15123 }
15124
15125 pub fn toggle_comments(
15126 &mut self,
15127 action: &ToggleComments,
15128 window: &mut Window,
15129 cx: &mut Context<Self>,
15130 ) {
15131 if self.read_only(cx) {
15132 return;
15133 }
15134 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15135 let text_layout_details = &self.text_layout_details(window);
15136 self.transact(window, cx, |this, window, cx| {
15137 let mut selections = this
15138 .selections
15139 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15140 let mut edits = Vec::new();
15141 let mut selection_edit_ranges = Vec::new();
15142 let mut last_toggled_row = None;
15143 let snapshot = this.buffer.read(cx).read(cx);
15144 let empty_str: Arc<str> = Arc::default();
15145 let mut suffixes_inserted = Vec::new();
15146 let ignore_indent = action.ignore_indent;
15147
15148 fn comment_prefix_range(
15149 snapshot: &MultiBufferSnapshot,
15150 row: MultiBufferRow,
15151 comment_prefix: &str,
15152 comment_prefix_whitespace: &str,
15153 ignore_indent: bool,
15154 ) -> Range<Point> {
15155 let indent_size = if ignore_indent {
15156 0
15157 } else {
15158 snapshot.indent_size_for_line(row).len
15159 };
15160
15161 let start = Point::new(row.0, indent_size);
15162
15163 let mut line_bytes = snapshot
15164 .bytes_in_range(start..snapshot.max_point())
15165 .flatten()
15166 .copied();
15167
15168 // If this line currently begins with the line comment prefix, then record
15169 // the range containing the prefix.
15170 if line_bytes
15171 .by_ref()
15172 .take(comment_prefix.len())
15173 .eq(comment_prefix.bytes())
15174 {
15175 // Include any whitespace that matches the comment prefix.
15176 let matching_whitespace_len = line_bytes
15177 .zip(comment_prefix_whitespace.bytes())
15178 .take_while(|(a, b)| a == b)
15179 .count() as u32;
15180 let end = Point::new(
15181 start.row,
15182 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15183 );
15184 start..end
15185 } else {
15186 start..start
15187 }
15188 }
15189
15190 fn comment_suffix_range(
15191 snapshot: &MultiBufferSnapshot,
15192 row: MultiBufferRow,
15193 comment_suffix: &str,
15194 comment_suffix_has_leading_space: bool,
15195 ) -> Range<Point> {
15196 let end = Point::new(row.0, snapshot.line_len(row));
15197 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15198
15199 let mut line_end_bytes = snapshot
15200 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15201 .flatten()
15202 .copied();
15203
15204 let leading_space_len = if suffix_start_column > 0
15205 && line_end_bytes.next() == Some(b' ')
15206 && comment_suffix_has_leading_space
15207 {
15208 1
15209 } else {
15210 0
15211 };
15212
15213 // If this line currently begins with the line comment prefix, then record
15214 // the range containing the prefix.
15215 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15216 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15217 start..end
15218 } else {
15219 end..end
15220 }
15221 }
15222
15223 // TODO: Handle selections that cross excerpts
15224 for selection in &mut selections {
15225 let start_column = snapshot
15226 .indent_size_for_line(MultiBufferRow(selection.start.row))
15227 .len;
15228 let language = if let Some(language) =
15229 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15230 {
15231 language
15232 } else {
15233 continue;
15234 };
15235
15236 selection_edit_ranges.clear();
15237
15238 // If multiple selections contain a given row, avoid processing that
15239 // row more than once.
15240 let mut start_row = MultiBufferRow(selection.start.row);
15241 if last_toggled_row == Some(start_row) {
15242 start_row = start_row.next_row();
15243 }
15244 let end_row =
15245 if selection.end.row > selection.start.row && selection.end.column == 0 {
15246 MultiBufferRow(selection.end.row - 1)
15247 } else {
15248 MultiBufferRow(selection.end.row)
15249 };
15250 last_toggled_row = Some(end_row);
15251
15252 if start_row > end_row {
15253 continue;
15254 }
15255
15256 // If the language has line comments, toggle those.
15257 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15258
15259 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15260 if ignore_indent {
15261 full_comment_prefixes = full_comment_prefixes
15262 .into_iter()
15263 .map(|s| Arc::from(s.trim_end()))
15264 .collect();
15265 }
15266
15267 if !full_comment_prefixes.is_empty() {
15268 let first_prefix = full_comment_prefixes
15269 .first()
15270 .expect("prefixes is non-empty");
15271 let prefix_trimmed_lengths = full_comment_prefixes
15272 .iter()
15273 .map(|p| p.trim_end_matches(' ').len())
15274 .collect::<SmallVec<[usize; 4]>>();
15275
15276 let mut all_selection_lines_are_comments = true;
15277
15278 for row in start_row.0..=end_row.0 {
15279 let row = MultiBufferRow(row);
15280 if start_row < end_row && snapshot.is_line_blank(row) {
15281 continue;
15282 }
15283
15284 let prefix_range = full_comment_prefixes
15285 .iter()
15286 .zip(prefix_trimmed_lengths.iter().copied())
15287 .map(|(prefix, trimmed_prefix_len)| {
15288 comment_prefix_range(
15289 snapshot.deref(),
15290 row,
15291 &prefix[..trimmed_prefix_len],
15292 &prefix[trimmed_prefix_len..],
15293 ignore_indent,
15294 )
15295 })
15296 .max_by_key(|range| range.end.column - range.start.column)
15297 .expect("prefixes is non-empty");
15298
15299 if prefix_range.is_empty() {
15300 all_selection_lines_are_comments = false;
15301 }
15302
15303 selection_edit_ranges.push(prefix_range);
15304 }
15305
15306 if all_selection_lines_are_comments {
15307 edits.extend(
15308 selection_edit_ranges
15309 .iter()
15310 .cloned()
15311 .map(|range| (range, empty_str.clone())),
15312 );
15313 } else {
15314 let min_column = selection_edit_ranges
15315 .iter()
15316 .map(|range| range.start.column)
15317 .min()
15318 .unwrap_or(0);
15319 edits.extend(selection_edit_ranges.iter().map(|range| {
15320 let position = Point::new(range.start.row, min_column);
15321 (position..position, first_prefix.clone())
15322 }));
15323 }
15324 } else if let Some(BlockCommentConfig {
15325 start: full_comment_prefix,
15326 end: comment_suffix,
15327 ..
15328 }) = language.block_comment()
15329 {
15330 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15331 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15332 let prefix_range = comment_prefix_range(
15333 snapshot.deref(),
15334 start_row,
15335 comment_prefix,
15336 comment_prefix_whitespace,
15337 ignore_indent,
15338 );
15339 let suffix_range = comment_suffix_range(
15340 snapshot.deref(),
15341 end_row,
15342 comment_suffix.trim_start_matches(' '),
15343 comment_suffix.starts_with(' '),
15344 );
15345
15346 if prefix_range.is_empty() || suffix_range.is_empty() {
15347 edits.push((
15348 prefix_range.start..prefix_range.start,
15349 full_comment_prefix.clone(),
15350 ));
15351 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15352 suffixes_inserted.push((end_row, comment_suffix.len()));
15353 } else {
15354 edits.push((prefix_range, empty_str.clone()));
15355 edits.push((suffix_range, empty_str.clone()));
15356 }
15357 } else {
15358 continue;
15359 }
15360 }
15361
15362 drop(snapshot);
15363 this.buffer.update(cx, |buffer, cx| {
15364 buffer.edit(edits, None, cx);
15365 });
15366
15367 // Adjust selections so that they end before any comment suffixes that
15368 // were inserted.
15369 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15370 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15371 let snapshot = this.buffer.read(cx).read(cx);
15372 for selection in &mut selections {
15373 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15374 match row.cmp(&MultiBufferRow(selection.end.row)) {
15375 Ordering::Less => {
15376 suffixes_inserted.next();
15377 continue;
15378 }
15379 Ordering::Greater => break,
15380 Ordering::Equal => {
15381 if selection.end.column == snapshot.line_len(row) {
15382 if selection.is_empty() {
15383 selection.start.column -= suffix_len as u32;
15384 }
15385 selection.end.column -= suffix_len as u32;
15386 }
15387 break;
15388 }
15389 }
15390 }
15391 }
15392
15393 drop(snapshot);
15394 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15395
15396 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15397 let selections_on_single_row = selections.windows(2).all(|selections| {
15398 selections[0].start.row == selections[1].start.row
15399 && selections[0].end.row == selections[1].end.row
15400 && selections[0].start.row == selections[0].end.row
15401 });
15402 let selections_selecting = selections
15403 .iter()
15404 .any(|selection| selection.start != selection.end);
15405 let advance_downwards = action.advance_downwards
15406 && selections_on_single_row
15407 && !selections_selecting
15408 && !matches!(this.mode, EditorMode::SingleLine);
15409
15410 if advance_downwards {
15411 let snapshot = this.buffer.read(cx).snapshot(cx);
15412
15413 this.change_selections(Default::default(), window, cx, |s| {
15414 s.move_cursors_with(|display_snapshot, display_point, _| {
15415 let mut point = display_point.to_point(display_snapshot);
15416 point.row += 1;
15417 point = snapshot.clip_point(point, Bias::Left);
15418 let display_point = point.to_display_point(display_snapshot);
15419 let goal = SelectionGoal::HorizontalPosition(
15420 display_snapshot
15421 .x_for_display_point(display_point, text_layout_details)
15422 .into(),
15423 );
15424 (display_point, goal)
15425 })
15426 });
15427 }
15428 });
15429 }
15430
15431 pub fn select_enclosing_symbol(
15432 &mut self,
15433 _: &SelectEnclosingSymbol,
15434 window: &mut Window,
15435 cx: &mut Context<Self>,
15436 ) {
15437 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15438
15439 let buffer = self.buffer.read(cx).snapshot(cx);
15440 let old_selections = self
15441 .selections
15442 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15443 .into_boxed_slice();
15444
15445 fn update_selection(
15446 selection: &Selection<MultiBufferOffset>,
15447 buffer_snap: &MultiBufferSnapshot,
15448 ) -> Option<Selection<MultiBufferOffset>> {
15449 let cursor = selection.head();
15450 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15451 for symbol in symbols.iter().rev() {
15452 let start = symbol.range.start.to_offset(buffer_snap);
15453 let end = symbol.range.end.to_offset(buffer_snap);
15454 let new_range = start..end;
15455 if start < selection.start || end > selection.end {
15456 return Some(Selection {
15457 id: selection.id,
15458 start: new_range.start,
15459 end: new_range.end,
15460 goal: SelectionGoal::None,
15461 reversed: selection.reversed,
15462 });
15463 }
15464 }
15465 None
15466 }
15467
15468 let mut selected_larger_symbol = false;
15469 let new_selections = old_selections
15470 .iter()
15471 .map(|selection| match update_selection(selection, &buffer) {
15472 Some(new_selection) => {
15473 if new_selection.range() != selection.range() {
15474 selected_larger_symbol = true;
15475 }
15476 new_selection
15477 }
15478 None => selection.clone(),
15479 })
15480 .collect::<Vec<_>>();
15481
15482 if selected_larger_symbol {
15483 self.change_selections(Default::default(), window, cx, |s| {
15484 s.select(new_selections);
15485 });
15486 }
15487 }
15488
15489 pub fn select_larger_syntax_node(
15490 &mut self,
15491 _: &SelectLargerSyntaxNode,
15492 window: &mut Window,
15493 cx: &mut Context<Self>,
15494 ) {
15495 let Some(visible_row_count) = self.visible_row_count() else {
15496 return;
15497 };
15498 let old_selections: Box<[_]> = self
15499 .selections
15500 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15501 .into();
15502 if old_selections.is_empty() {
15503 return;
15504 }
15505
15506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15507
15508 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15509 let buffer = self.buffer.read(cx).snapshot(cx);
15510
15511 let mut selected_larger_node = false;
15512 let mut new_selections = old_selections
15513 .iter()
15514 .map(|selection| {
15515 let old_range = selection.start..selection.end;
15516
15517 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15518 // manually select word at selection
15519 if ["string_content", "inline"].contains(&node.kind()) {
15520 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15521 // ignore if word is already selected
15522 if !word_range.is_empty() && old_range != word_range {
15523 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15524 // only select word if start and end point belongs to same word
15525 if word_range == last_word_range {
15526 selected_larger_node = true;
15527 return Selection {
15528 id: selection.id,
15529 start: word_range.start,
15530 end: word_range.end,
15531 goal: SelectionGoal::None,
15532 reversed: selection.reversed,
15533 };
15534 }
15535 }
15536 }
15537 }
15538
15539 let mut new_range = old_range.clone();
15540 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15541 new_range = range;
15542 if !node.is_named() {
15543 continue;
15544 }
15545 if !display_map.intersects_fold(new_range.start)
15546 && !display_map.intersects_fold(new_range.end)
15547 {
15548 break;
15549 }
15550 }
15551
15552 selected_larger_node |= new_range != old_range;
15553 Selection {
15554 id: selection.id,
15555 start: new_range.start,
15556 end: new_range.end,
15557 goal: SelectionGoal::None,
15558 reversed: selection.reversed,
15559 }
15560 })
15561 .collect::<Vec<_>>();
15562
15563 if !selected_larger_node {
15564 return; // don't put this call in the history
15565 }
15566
15567 // scroll based on transformation done to the last selection created by the user
15568 let (last_old, last_new) = old_selections
15569 .last()
15570 .zip(new_selections.last().cloned())
15571 .expect("old_selections isn't empty");
15572
15573 // revert selection
15574 let is_selection_reversed = {
15575 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15576 new_selections.last_mut().expect("checked above").reversed =
15577 should_newest_selection_be_reversed;
15578 should_newest_selection_be_reversed
15579 };
15580
15581 if selected_larger_node {
15582 self.select_syntax_node_history.disable_clearing = true;
15583 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15584 s.select(new_selections.clone());
15585 });
15586 self.select_syntax_node_history.disable_clearing = false;
15587 }
15588
15589 let start_row = last_new.start.to_display_point(&display_map).row().0;
15590 let end_row = last_new.end.to_display_point(&display_map).row().0;
15591 let selection_height = end_row - start_row + 1;
15592 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15593
15594 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15595 let scroll_behavior = if fits_on_the_screen {
15596 self.request_autoscroll(Autoscroll::fit(), cx);
15597 SelectSyntaxNodeScrollBehavior::FitSelection
15598 } else if is_selection_reversed {
15599 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15600 SelectSyntaxNodeScrollBehavior::CursorTop
15601 } else {
15602 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15603 SelectSyntaxNodeScrollBehavior::CursorBottom
15604 };
15605
15606 self.select_syntax_node_history.push((
15607 old_selections,
15608 scroll_behavior,
15609 is_selection_reversed,
15610 ));
15611 }
15612
15613 pub fn select_smaller_syntax_node(
15614 &mut self,
15615 _: &SelectSmallerSyntaxNode,
15616 window: &mut Window,
15617 cx: &mut Context<Self>,
15618 ) {
15619 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15620
15621 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15622 self.select_syntax_node_history.pop()
15623 {
15624 if let Some(selection) = selections.last_mut() {
15625 selection.reversed = is_selection_reversed;
15626 }
15627
15628 self.select_syntax_node_history.disable_clearing = true;
15629 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15630 s.select(selections.to_vec());
15631 });
15632 self.select_syntax_node_history.disable_clearing = false;
15633
15634 match scroll_behavior {
15635 SelectSyntaxNodeScrollBehavior::CursorTop => {
15636 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15637 }
15638 SelectSyntaxNodeScrollBehavior::FitSelection => {
15639 self.request_autoscroll(Autoscroll::fit(), cx);
15640 }
15641 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15642 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15643 }
15644 }
15645 }
15646 }
15647
15648 pub fn unwrap_syntax_node(
15649 &mut self,
15650 _: &UnwrapSyntaxNode,
15651 window: &mut Window,
15652 cx: &mut Context<Self>,
15653 ) {
15654 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15655
15656 let buffer = self.buffer.read(cx).snapshot(cx);
15657 let selections = self
15658 .selections
15659 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15660 .into_iter()
15661 // subtracting the offset requires sorting
15662 .sorted_by_key(|i| i.start);
15663
15664 let full_edits = selections
15665 .into_iter()
15666 .filter_map(|selection| {
15667 let child = if selection.is_empty()
15668 && let Some((_, ancestor_range)) =
15669 buffer.syntax_ancestor(selection.start..selection.end)
15670 {
15671 ancestor_range
15672 } else {
15673 selection.range()
15674 };
15675
15676 let mut parent = child.clone();
15677 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15678 parent = ancestor_range;
15679 if parent.start < child.start || parent.end > child.end {
15680 break;
15681 }
15682 }
15683
15684 if parent == child {
15685 return None;
15686 }
15687 let text = buffer.text_for_range(child).collect::<String>();
15688 Some((selection.id, parent, text))
15689 })
15690 .collect::<Vec<_>>();
15691 if full_edits.is_empty() {
15692 return;
15693 }
15694
15695 self.transact(window, cx, |this, window, cx| {
15696 this.buffer.update(cx, |buffer, cx| {
15697 buffer.edit(
15698 full_edits
15699 .iter()
15700 .map(|(_, p, t)| (p.clone(), t.clone()))
15701 .collect::<Vec<_>>(),
15702 None,
15703 cx,
15704 );
15705 });
15706 this.change_selections(Default::default(), window, cx, |s| {
15707 let mut offset = 0;
15708 let mut selections = vec![];
15709 for (id, parent, text) in full_edits {
15710 let start = parent.start - offset;
15711 offset += (parent.end - parent.start) - text.len();
15712 selections.push(Selection {
15713 id,
15714 start,
15715 end: start + text.len(),
15716 reversed: false,
15717 goal: Default::default(),
15718 });
15719 }
15720 s.select(selections);
15721 });
15722 });
15723 }
15724
15725 pub fn select_next_syntax_node(
15726 &mut self,
15727 _: &SelectNextSyntaxNode,
15728 window: &mut Window,
15729 cx: &mut Context<Self>,
15730 ) {
15731 let old_selections: Box<[_]> = self
15732 .selections
15733 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15734 .into();
15735 if old_selections.is_empty() {
15736 return;
15737 }
15738
15739 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15740
15741 let buffer = self.buffer.read(cx).snapshot(cx);
15742 let mut selected_sibling = false;
15743
15744 let new_selections = old_selections
15745 .iter()
15746 .map(|selection| {
15747 let old_range = selection.start..selection.end;
15748
15749 let old_range =
15750 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15751 let excerpt = buffer.excerpt_containing(old_range.clone());
15752
15753 if let Some(mut excerpt) = excerpt
15754 && let Some(node) = excerpt
15755 .buffer()
15756 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15757 {
15758 let new_range = excerpt.map_range_from_buffer(
15759 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15760 );
15761 selected_sibling = true;
15762 Selection {
15763 id: selection.id,
15764 start: new_range.start,
15765 end: new_range.end,
15766 goal: SelectionGoal::None,
15767 reversed: selection.reversed,
15768 }
15769 } else {
15770 selection.clone()
15771 }
15772 })
15773 .collect::<Vec<_>>();
15774
15775 if selected_sibling {
15776 self.change_selections(
15777 SelectionEffects::scroll(Autoscroll::fit()),
15778 window,
15779 cx,
15780 |s| {
15781 s.select(new_selections);
15782 },
15783 );
15784 }
15785 }
15786
15787 pub fn select_prev_syntax_node(
15788 &mut self,
15789 _: &SelectPreviousSyntaxNode,
15790 window: &mut Window,
15791 cx: &mut Context<Self>,
15792 ) {
15793 let old_selections: Box<[_]> = self
15794 .selections
15795 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15796 .into();
15797 if old_selections.is_empty() {
15798 return;
15799 }
15800
15801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15802
15803 let buffer = self.buffer.read(cx).snapshot(cx);
15804 let mut selected_sibling = false;
15805
15806 let new_selections = old_selections
15807 .iter()
15808 .map(|selection| {
15809 let old_range = selection.start..selection.end;
15810 let old_range =
15811 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15812 let excerpt = buffer.excerpt_containing(old_range.clone());
15813
15814 if let Some(mut excerpt) = excerpt
15815 && let Some(node) = excerpt
15816 .buffer()
15817 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
15818 {
15819 let new_range = excerpt.map_range_from_buffer(
15820 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15821 );
15822 selected_sibling = true;
15823 Selection {
15824 id: selection.id,
15825 start: new_range.start,
15826 end: new_range.end,
15827 goal: SelectionGoal::None,
15828 reversed: selection.reversed,
15829 }
15830 } else {
15831 selection.clone()
15832 }
15833 })
15834 .collect::<Vec<_>>();
15835
15836 if selected_sibling {
15837 self.change_selections(
15838 SelectionEffects::scroll(Autoscroll::fit()),
15839 window,
15840 cx,
15841 |s| {
15842 s.select(new_selections);
15843 },
15844 );
15845 }
15846 }
15847
15848 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15849 if !EditorSettings::get_global(cx).gutter.runnables {
15850 self.clear_tasks();
15851 return Task::ready(());
15852 }
15853 let project = self.project().map(Entity::downgrade);
15854 let task_sources = self.lsp_task_sources(cx);
15855 let multi_buffer = self.buffer.downgrade();
15856 cx.spawn_in(window, async move |editor, cx| {
15857 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15858 let Some(project) = project.and_then(|p| p.upgrade()) else {
15859 return;
15860 };
15861 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15862 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15863 }) else {
15864 return;
15865 };
15866
15867 let hide_runnables = project
15868 .update(cx, |project, _| project.is_via_collab())
15869 .unwrap_or(true);
15870 if hide_runnables {
15871 return;
15872 }
15873 let new_rows =
15874 cx.background_spawn({
15875 let snapshot = display_snapshot.clone();
15876 async move {
15877 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15878 }
15879 })
15880 .await;
15881 let Ok(lsp_tasks) =
15882 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15883 else {
15884 return;
15885 };
15886 let lsp_tasks = lsp_tasks.await;
15887
15888 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15889 lsp_tasks
15890 .into_iter()
15891 .flat_map(|(kind, tasks)| {
15892 tasks.into_iter().filter_map(move |(location, task)| {
15893 Some((kind.clone(), location?, task))
15894 })
15895 })
15896 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15897 let buffer = location.target.buffer;
15898 let buffer_snapshot = buffer.read(cx).snapshot();
15899 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15900 |(excerpt_id, snapshot, _)| {
15901 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15902 display_snapshot
15903 .buffer_snapshot()
15904 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15905 } else {
15906 None
15907 }
15908 },
15909 );
15910 if let Some(offset) = offset {
15911 let task_buffer_range =
15912 location.target.range.to_point(&buffer_snapshot);
15913 let context_buffer_range =
15914 task_buffer_range.to_offset(&buffer_snapshot);
15915 let context_range = BufferOffset(context_buffer_range.start)
15916 ..BufferOffset(context_buffer_range.end);
15917
15918 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15919 .or_insert_with(|| RunnableTasks {
15920 templates: Vec::new(),
15921 offset,
15922 column: task_buffer_range.start.column,
15923 extra_variables: HashMap::default(),
15924 context_range,
15925 })
15926 .templates
15927 .push((kind, task.original_task().clone()));
15928 }
15929
15930 acc
15931 })
15932 }) else {
15933 return;
15934 };
15935
15936 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15937 buffer.language_settings(cx).tasks.prefer_lsp
15938 }) else {
15939 return;
15940 };
15941
15942 let rows = Self::runnable_rows(
15943 project,
15944 display_snapshot,
15945 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15946 new_rows,
15947 cx.clone(),
15948 )
15949 .await;
15950 editor
15951 .update(cx, |editor, _| {
15952 editor.clear_tasks();
15953 for (key, mut value) in rows {
15954 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15955 value.templates.extend(lsp_tasks.templates);
15956 }
15957
15958 editor.insert_tasks(key, value);
15959 }
15960 for (key, value) in lsp_tasks_by_rows {
15961 editor.insert_tasks(key, value);
15962 }
15963 })
15964 .ok();
15965 })
15966 }
15967 fn fetch_runnable_ranges(
15968 snapshot: &DisplaySnapshot,
15969 range: Range<Anchor>,
15970 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
15971 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15972 }
15973
15974 fn runnable_rows(
15975 project: Entity<Project>,
15976 snapshot: DisplaySnapshot,
15977 prefer_lsp: bool,
15978 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
15979 cx: AsyncWindowContext,
15980 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15981 cx.spawn(async move |cx| {
15982 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15983 for (run_range, mut runnable) in runnable_ranges {
15984 let Some(tasks) = cx
15985 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15986 .ok()
15987 else {
15988 continue;
15989 };
15990 let mut tasks = tasks.await;
15991
15992 if prefer_lsp {
15993 tasks.retain(|(task_kind, _)| {
15994 !matches!(task_kind, TaskSourceKind::Language { .. })
15995 });
15996 }
15997 if tasks.is_empty() {
15998 continue;
15999 }
16000
16001 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16002 let Some(row) = snapshot
16003 .buffer_snapshot()
16004 .buffer_line_for_row(MultiBufferRow(point.row))
16005 .map(|(_, range)| range.start.row)
16006 else {
16007 continue;
16008 };
16009
16010 let context_range =
16011 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16012 runnable_rows.push((
16013 (runnable.buffer_id, row),
16014 RunnableTasks {
16015 templates: tasks,
16016 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16017 context_range,
16018 column: point.column,
16019 extra_variables: runnable.extra_captures,
16020 },
16021 ));
16022 }
16023 runnable_rows
16024 })
16025 }
16026
16027 fn templates_with_tags(
16028 project: &Entity<Project>,
16029 runnable: &mut Runnable,
16030 cx: &mut App,
16031 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16032 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16033 let (worktree_id, file) = project
16034 .buffer_for_id(runnable.buffer, cx)
16035 .and_then(|buffer| buffer.read(cx).file())
16036 .map(|file| (file.worktree_id(cx), file.clone()))
16037 .unzip();
16038
16039 (
16040 project.task_store().read(cx).task_inventory().cloned(),
16041 worktree_id,
16042 file,
16043 )
16044 });
16045
16046 let tags = mem::take(&mut runnable.tags);
16047 let language = runnable.language.clone();
16048 cx.spawn(async move |cx| {
16049 let mut templates_with_tags = Vec::new();
16050 if let Some(inventory) = inventory {
16051 for RunnableTag(tag) in tags {
16052 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16053 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16054 }) else {
16055 return templates_with_tags;
16056 };
16057 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16058 move |(_, template)| {
16059 template.tags.iter().any(|source_tag| source_tag == &tag)
16060 },
16061 ));
16062 }
16063 }
16064 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16065
16066 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16067 // Strongest source wins; if we have worktree tag binding, prefer that to
16068 // global and language bindings;
16069 // if we have a global binding, prefer that to language binding.
16070 let first_mismatch = templates_with_tags
16071 .iter()
16072 .position(|(tag_source, _)| tag_source != leading_tag_source);
16073 if let Some(index) = first_mismatch {
16074 templates_with_tags.truncate(index);
16075 }
16076 }
16077
16078 templates_with_tags
16079 })
16080 }
16081
16082 pub fn move_to_enclosing_bracket(
16083 &mut self,
16084 _: &MoveToEnclosingBracket,
16085 window: &mut Window,
16086 cx: &mut Context<Self>,
16087 ) {
16088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16089 self.change_selections(Default::default(), window, cx, |s| {
16090 s.move_offsets_with(|snapshot, selection| {
16091 let Some(enclosing_bracket_ranges) =
16092 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16093 else {
16094 return;
16095 };
16096
16097 let mut best_length = usize::MAX;
16098 let mut best_inside = false;
16099 let mut best_in_bracket_range = false;
16100 let mut best_destination = None;
16101 for (open, close) in enclosing_bracket_ranges {
16102 let close = close.to_inclusive();
16103 let length = *close.end() - open.start;
16104 let inside = selection.start >= open.end && selection.end <= *close.start();
16105 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16106 || close.contains(&selection.head());
16107
16108 // If best is next to a bracket and current isn't, skip
16109 if !in_bracket_range && best_in_bracket_range {
16110 continue;
16111 }
16112
16113 // Prefer smaller lengths unless best is inside and current isn't
16114 if length > best_length && (best_inside || !inside) {
16115 continue;
16116 }
16117
16118 best_length = length;
16119 best_inside = inside;
16120 best_in_bracket_range = in_bracket_range;
16121 best_destination = Some(
16122 if close.contains(&selection.start) && close.contains(&selection.end) {
16123 if inside { open.end } else { open.start }
16124 } else if inside {
16125 *close.start()
16126 } else {
16127 *close.end()
16128 },
16129 );
16130 }
16131
16132 if let Some(destination) = best_destination {
16133 selection.collapse_to(destination, SelectionGoal::None);
16134 }
16135 })
16136 });
16137 }
16138
16139 pub fn undo_selection(
16140 &mut self,
16141 _: &UndoSelection,
16142 window: &mut Window,
16143 cx: &mut Context<Self>,
16144 ) {
16145 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16146 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16147 self.selection_history.mode = SelectionHistoryMode::Undoing;
16148 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16149 this.end_selection(window, cx);
16150 this.change_selections(
16151 SelectionEffects::scroll(Autoscroll::newest()),
16152 window,
16153 cx,
16154 |s| s.select_anchors(entry.selections.to_vec()),
16155 );
16156 });
16157 self.selection_history.mode = SelectionHistoryMode::Normal;
16158
16159 self.select_next_state = entry.select_next_state;
16160 self.select_prev_state = entry.select_prev_state;
16161 self.add_selections_state = entry.add_selections_state;
16162 }
16163 }
16164
16165 pub fn redo_selection(
16166 &mut self,
16167 _: &RedoSelection,
16168 window: &mut Window,
16169 cx: &mut Context<Self>,
16170 ) {
16171 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16172 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16173 self.selection_history.mode = SelectionHistoryMode::Redoing;
16174 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16175 this.end_selection(window, cx);
16176 this.change_selections(
16177 SelectionEffects::scroll(Autoscroll::newest()),
16178 window,
16179 cx,
16180 |s| s.select_anchors(entry.selections.to_vec()),
16181 );
16182 });
16183 self.selection_history.mode = SelectionHistoryMode::Normal;
16184
16185 self.select_next_state = entry.select_next_state;
16186 self.select_prev_state = entry.select_prev_state;
16187 self.add_selections_state = entry.add_selections_state;
16188 }
16189 }
16190
16191 pub fn expand_excerpts(
16192 &mut self,
16193 action: &ExpandExcerpts,
16194 _: &mut Window,
16195 cx: &mut Context<Self>,
16196 ) {
16197 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16198 }
16199
16200 pub fn expand_excerpts_down(
16201 &mut self,
16202 action: &ExpandExcerptsDown,
16203 _: &mut Window,
16204 cx: &mut Context<Self>,
16205 ) {
16206 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16207 }
16208
16209 pub fn expand_excerpts_up(
16210 &mut self,
16211 action: &ExpandExcerptsUp,
16212 _: &mut Window,
16213 cx: &mut Context<Self>,
16214 ) {
16215 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16216 }
16217
16218 pub fn expand_excerpts_for_direction(
16219 &mut self,
16220 lines: u32,
16221 direction: ExpandExcerptDirection,
16222
16223 cx: &mut Context<Self>,
16224 ) {
16225 let selections = self.selections.disjoint_anchors_arc();
16226
16227 let lines = if lines == 0 {
16228 EditorSettings::get_global(cx).expand_excerpt_lines
16229 } else {
16230 lines
16231 };
16232
16233 self.buffer.update(cx, |buffer, cx| {
16234 let snapshot = buffer.snapshot(cx);
16235 let mut excerpt_ids = selections
16236 .iter()
16237 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16238 .collect::<Vec<_>>();
16239 excerpt_ids.sort();
16240 excerpt_ids.dedup();
16241 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16242 })
16243 }
16244
16245 pub fn expand_excerpt(
16246 &mut self,
16247 excerpt: ExcerptId,
16248 direction: ExpandExcerptDirection,
16249 window: &mut Window,
16250 cx: &mut Context<Self>,
16251 ) {
16252 let current_scroll_position = self.scroll_position(cx);
16253 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16254 let mut scroll = None;
16255
16256 if direction == ExpandExcerptDirection::Down {
16257 let multi_buffer = self.buffer.read(cx);
16258 let snapshot = multi_buffer.snapshot(cx);
16259 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16260 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16261 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16262 {
16263 let buffer_snapshot = buffer.read(cx).snapshot();
16264 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16265 let last_row = buffer_snapshot.max_point().row;
16266 let lines_below = last_row.saturating_sub(excerpt_end_row);
16267 if lines_below >= lines_to_expand {
16268 scroll = Some(
16269 current_scroll_position
16270 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16271 );
16272 }
16273 }
16274 }
16275 if direction == ExpandExcerptDirection::Up
16276 && self
16277 .buffer
16278 .read(cx)
16279 .snapshot(cx)
16280 .excerpt_before(excerpt)
16281 .is_none()
16282 {
16283 scroll = Some(current_scroll_position);
16284 }
16285
16286 self.buffer.update(cx, |buffer, cx| {
16287 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16288 });
16289
16290 if let Some(new_scroll_position) = scroll {
16291 self.set_scroll_position(new_scroll_position, window, cx);
16292 }
16293 }
16294
16295 pub fn go_to_singleton_buffer_point(
16296 &mut self,
16297 point: Point,
16298 window: &mut Window,
16299 cx: &mut Context<Self>,
16300 ) {
16301 self.go_to_singleton_buffer_range(point..point, window, cx);
16302 }
16303
16304 pub fn go_to_singleton_buffer_range(
16305 &mut self,
16306 range: Range<Point>,
16307 window: &mut Window,
16308 cx: &mut Context<Self>,
16309 ) {
16310 let multibuffer = self.buffer().read(cx);
16311 let Some(buffer) = multibuffer.as_singleton() else {
16312 return;
16313 };
16314 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16315 return;
16316 };
16317 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16318 return;
16319 };
16320 self.change_selections(
16321 SelectionEffects::default().nav_history(true),
16322 window,
16323 cx,
16324 |s| s.select_anchor_ranges([start..end]),
16325 );
16326 }
16327
16328 pub fn go_to_diagnostic(
16329 &mut self,
16330 action: &GoToDiagnostic,
16331 window: &mut Window,
16332 cx: &mut Context<Self>,
16333 ) {
16334 if !self.diagnostics_enabled() {
16335 return;
16336 }
16337 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16338 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16339 }
16340
16341 pub fn go_to_prev_diagnostic(
16342 &mut self,
16343 action: &GoToPreviousDiagnostic,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) {
16347 if !self.diagnostics_enabled() {
16348 return;
16349 }
16350 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16351 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16352 }
16353
16354 pub fn go_to_diagnostic_impl(
16355 &mut self,
16356 direction: Direction,
16357 severity: GoToDiagnosticSeverityFilter,
16358 window: &mut Window,
16359 cx: &mut Context<Self>,
16360 ) {
16361 let buffer = self.buffer.read(cx).snapshot(cx);
16362 let selection = self
16363 .selections
16364 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16365
16366 let mut active_group_id = None;
16367 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16368 && active_group.active_range.start.to_offset(&buffer) == selection.start
16369 {
16370 active_group_id = Some(active_group.group_id);
16371 }
16372
16373 fn filtered<'a>(
16374 severity: GoToDiagnosticSeverityFilter,
16375 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16376 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16377 diagnostics
16378 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16379 .filter(|entry| entry.range.start != entry.range.end)
16380 .filter(|entry| !entry.diagnostic.is_unnecessary)
16381 }
16382
16383 let before = filtered(
16384 severity,
16385 buffer
16386 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16387 .filter(|entry| entry.range.start <= selection.start),
16388 );
16389 let after = filtered(
16390 severity,
16391 buffer
16392 .diagnostics_in_range(selection.start..buffer.len())
16393 .filter(|entry| entry.range.start >= selection.start),
16394 );
16395
16396 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16397 if direction == Direction::Prev {
16398 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16399 {
16400 for diagnostic in prev_diagnostics.into_iter().rev() {
16401 if diagnostic.range.start != selection.start
16402 || active_group_id
16403 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16404 {
16405 found = Some(diagnostic);
16406 break 'outer;
16407 }
16408 }
16409 }
16410 } else {
16411 for diagnostic in after.chain(before) {
16412 if diagnostic.range.start != selection.start
16413 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16414 {
16415 found = Some(diagnostic);
16416 break;
16417 }
16418 }
16419 }
16420 let Some(next_diagnostic) = found else {
16421 return;
16422 };
16423
16424 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16425 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16426 return;
16427 };
16428 let snapshot = self.snapshot(window, cx);
16429 if snapshot.intersects_fold(next_diagnostic.range.start) {
16430 self.unfold_ranges(
16431 std::slice::from_ref(&next_diagnostic.range),
16432 true,
16433 false,
16434 cx,
16435 );
16436 }
16437 self.change_selections(Default::default(), window, cx, |s| {
16438 s.select_ranges(vec![
16439 next_diagnostic.range.start..next_diagnostic.range.start,
16440 ])
16441 });
16442 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16443 self.refresh_edit_prediction(false, true, window, cx);
16444 }
16445
16446 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16447 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16448 let snapshot = self.snapshot(window, cx);
16449 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16450 self.go_to_hunk_before_or_after_position(
16451 &snapshot,
16452 selection.head(),
16453 Direction::Next,
16454 window,
16455 cx,
16456 );
16457 }
16458
16459 pub fn go_to_hunk_before_or_after_position(
16460 &mut self,
16461 snapshot: &EditorSnapshot,
16462 position: Point,
16463 direction: Direction,
16464 window: &mut Window,
16465 cx: &mut Context<Editor>,
16466 ) {
16467 let row = if direction == Direction::Next {
16468 self.hunk_after_position(snapshot, position)
16469 .map(|hunk| hunk.row_range.start)
16470 } else {
16471 self.hunk_before_position(snapshot, position)
16472 };
16473
16474 if let Some(row) = row {
16475 let destination = Point::new(row.0, 0);
16476 let autoscroll = Autoscroll::center();
16477
16478 self.unfold_ranges(&[destination..destination], false, false, cx);
16479 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16480 s.select_ranges([destination..destination]);
16481 });
16482 }
16483 }
16484
16485 fn hunk_after_position(
16486 &mut self,
16487 snapshot: &EditorSnapshot,
16488 position: Point,
16489 ) -> Option<MultiBufferDiffHunk> {
16490 snapshot
16491 .buffer_snapshot()
16492 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16493 .find(|hunk| hunk.row_range.start.0 > position.row)
16494 .or_else(|| {
16495 snapshot
16496 .buffer_snapshot()
16497 .diff_hunks_in_range(Point::zero()..position)
16498 .find(|hunk| hunk.row_range.end.0 < position.row)
16499 })
16500 }
16501
16502 fn go_to_prev_hunk(
16503 &mut self,
16504 _: &GoToPreviousHunk,
16505 window: &mut Window,
16506 cx: &mut Context<Self>,
16507 ) {
16508 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16509 let snapshot = self.snapshot(window, cx);
16510 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16511 self.go_to_hunk_before_or_after_position(
16512 &snapshot,
16513 selection.head(),
16514 Direction::Prev,
16515 window,
16516 cx,
16517 );
16518 }
16519
16520 fn hunk_before_position(
16521 &mut self,
16522 snapshot: &EditorSnapshot,
16523 position: Point,
16524 ) -> Option<MultiBufferRow> {
16525 snapshot
16526 .buffer_snapshot()
16527 .diff_hunk_before(position)
16528 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16529 }
16530
16531 fn go_to_next_change(
16532 &mut self,
16533 _: &GoToNextChange,
16534 window: &mut Window,
16535 cx: &mut Context<Self>,
16536 ) {
16537 if let Some(selections) = self
16538 .change_list
16539 .next_change(1, Direction::Next)
16540 .map(|s| s.to_vec())
16541 {
16542 self.change_selections(Default::default(), window, cx, |s| {
16543 let map = s.display_snapshot();
16544 s.select_display_ranges(selections.iter().map(|a| {
16545 let point = a.to_display_point(&map);
16546 point..point
16547 }))
16548 })
16549 }
16550 }
16551
16552 fn go_to_previous_change(
16553 &mut self,
16554 _: &GoToPreviousChange,
16555 window: &mut Window,
16556 cx: &mut Context<Self>,
16557 ) {
16558 if let Some(selections) = self
16559 .change_list
16560 .next_change(1, Direction::Prev)
16561 .map(|s| s.to_vec())
16562 {
16563 self.change_selections(Default::default(), window, cx, |s| {
16564 let map = s.display_snapshot();
16565 s.select_display_ranges(selections.iter().map(|a| {
16566 let point = a.to_display_point(&map);
16567 point..point
16568 }))
16569 })
16570 }
16571 }
16572
16573 pub fn go_to_next_document_highlight(
16574 &mut self,
16575 _: &GoToNextDocumentHighlight,
16576 window: &mut Window,
16577 cx: &mut Context<Self>,
16578 ) {
16579 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16580 }
16581
16582 pub fn go_to_prev_document_highlight(
16583 &mut self,
16584 _: &GoToPreviousDocumentHighlight,
16585 window: &mut Window,
16586 cx: &mut Context<Self>,
16587 ) {
16588 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16589 }
16590
16591 pub fn go_to_document_highlight_before_or_after_position(
16592 &mut self,
16593 direction: Direction,
16594 window: &mut Window,
16595 cx: &mut Context<Editor>,
16596 ) {
16597 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16598 let snapshot = self.snapshot(window, cx);
16599 let buffer = &snapshot.buffer_snapshot();
16600 let position = self
16601 .selections
16602 .newest::<Point>(&snapshot.display_snapshot)
16603 .head();
16604 let anchor_position = buffer.anchor_after(position);
16605
16606 // Get all document highlights (both read and write)
16607 let mut all_highlights = Vec::new();
16608
16609 if let Some((_, read_highlights)) = self
16610 .background_highlights
16611 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16612 {
16613 all_highlights.extend(read_highlights.iter());
16614 }
16615
16616 if let Some((_, write_highlights)) = self
16617 .background_highlights
16618 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16619 {
16620 all_highlights.extend(write_highlights.iter());
16621 }
16622
16623 if all_highlights.is_empty() {
16624 return;
16625 }
16626
16627 // Sort highlights by position
16628 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16629
16630 let target_highlight = match direction {
16631 Direction::Next => {
16632 // Find the first highlight after the current position
16633 all_highlights
16634 .iter()
16635 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16636 }
16637 Direction::Prev => {
16638 // Find the last highlight before the current position
16639 all_highlights
16640 .iter()
16641 .rev()
16642 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16643 }
16644 };
16645
16646 if let Some(highlight) = target_highlight {
16647 let destination = highlight.start.to_point(buffer);
16648 let autoscroll = Autoscroll::center();
16649
16650 self.unfold_ranges(&[destination..destination], false, false, cx);
16651 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16652 s.select_ranges([destination..destination]);
16653 });
16654 }
16655 }
16656
16657 fn go_to_line<T: 'static>(
16658 &mut self,
16659 position: Anchor,
16660 highlight_color: Option<Hsla>,
16661 window: &mut Window,
16662 cx: &mut Context<Self>,
16663 ) {
16664 let snapshot = self.snapshot(window, cx).display_snapshot;
16665 let position = position.to_point(&snapshot.buffer_snapshot());
16666 let start = snapshot
16667 .buffer_snapshot()
16668 .clip_point(Point::new(position.row, 0), Bias::Left);
16669 let end = start + Point::new(1, 0);
16670 let start = snapshot.buffer_snapshot().anchor_before(start);
16671 let end = snapshot.buffer_snapshot().anchor_before(end);
16672
16673 self.highlight_rows::<T>(
16674 start..end,
16675 highlight_color
16676 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16677 Default::default(),
16678 cx,
16679 );
16680
16681 if self.buffer.read(cx).is_singleton() {
16682 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16683 }
16684 }
16685
16686 pub fn go_to_definition(
16687 &mut self,
16688 _: &GoToDefinition,
16689 window: &mut Window,
16690 cx: &mut Context<Self>,
16691 ) -> Task<Result<Navigated>> {
16692 let definition =
16693 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16694 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16695 cx.spawn_in(window, async move |editor, cx| {
16696 if definition.await? == Navigated::Yes {
16697 return Ok(Navigated::Yes);
16698 }
16699 match fallback_strategy {
16700 GoToDefinitionFallback::None => Ok(Navigated::No),
16701 GoToDefinitionFallback::FindAllReferences => {
16702 match editor.update_in(cx, |editor, window, cx| {
16703 editor.find_all_references(&FindAllReferences, window, cx)
16704 })? {
16705 Some(references) => references.await,
16706 None => Ok(Navigated::No),
16707 }
16708 }
16709 }
16710 })
16711 }
16712
16713 pub fn go_to_declaration(
16714 &mut self,
16715 _: &GoToDeclaration,
16716 window: &mut Window,
16717 cx: &mut Context<Self>,
16718 ) -> Task<Result<Navigated>> {
16719 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16720 }
16721
16722 pub fn go_to_declaration_split(
16723 &mut self,
16724 _: &GoToDeclaration,
16725 window: &mut Window,
16726 cx: &mut Context<Self>,
16727 ) -> Task<Result<Navigated>> {
16728 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16729 }
16730
16731 pub fn go_to_implementation(
16732 &mut self,
16733 _: &GoToImplementation,
16734 window: &mut Window,
16735 cx: &mut Context<Self>,
16736 ) -> Task<Result<Navigated>> {
16737 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16738 }
16739
16740 pub fn go_to_implementation_split(
16741 &mut self,
16742 _: &GoToImplementationSplit,
16743 window: &mut Window,
16744 cx: &mut Context<Self>,
16745 ) -> Task<Result<Navigated>> {
16746 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16747 }
16748
16749 pub fn go_to_type_definition(
16750 &mut self,
16751 _: &GoToTypeDefinition,
16752 window: &mut Window,
16753 cx: &mut Context<Self>,
16754 ) -> Task<Result<Navigated>> {
16755 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16756 }
16757
16758 pub fn go_to_definition_split(
16759 &mut self,
16760 _: &GoToDefinitionSplit,
16761 window: &mut Window,
16762 cx: &mut Context<Self>,
16763 ) -> Task<Result<Navigated>> {
16764 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16765 }
16766
16767 pub fn go_to_type_definition_split(
16768 &mut self,
16769 _: &GoToTypeDefinitionSplit,
16770 window: &mut Window,
16771 cx: &mut Context<Self>,
16772 ) -> Task<Result<Navigated>> {
16773 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16774 }
16775
16776 fn go_to_definition_of_kind(
16777 &mut self,
16778 kind: GotoDefinitionKind,
16779 split: bool,
16780 window: &mut Window,
16781 cx: &mut Context<Self>,
16782 ) -> Task<Result<Navigated>> {
16783 let Some(provider) = self.semantics_provider.clone() else {
16784 return Task::ready(Ok(Navigated::No));
16785 };
16786 let head = self
16787 .selections
16788 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16789 .head();
16790 let buffer = self.buffer.read(cx);
16791 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16792 return Task::ready(Ok(Navigated::No));
16793 };
16794 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16795 return Task::ready(Ok(Navigated::No));
16796 };
16797
16798 cx.spawn_in(window, async move |editor, cx| {
16799 let Some(definitions) = definitions.await? else {
16800 return Ok(Navigated::No);
16801 };
16802 let navigated = editor
16803 .update_in(cx, |editor, window, cx| {
16804 editor.navigate_to_hover_links(
16805 Some(kind),
16806 definitions
16807 .into_iter()
16808 .filter(|location| {
16809 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16810 })
16811 .map(HoverLink::Text)
16812 .collect::<Vec<_>>(),
16813 split,
16814 window,
16815 cx,
16816 )
16817 })?
16818 .await?;
16819 anyhow::Ok(navigated)
16820 })
16821 }
16822
16823 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16824 let selection = self.selections.newest_anchor();
16825 let head = selection.head();
16826 let tail = selection.tail();
16827
16828 let Some((buffer, start_position)) =
16829 self.buffer.read(cx).text_anchor_for_position(head, cx)
16830 else {
16831 return;
16832 };
16833
16834 let end_position = if head != tail {
16835 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16836 return;
16837 };
16838 Some(pos)
16839 } else {
16840 None
16841 };
16842
16843 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16844 let url = if let Some(end_pos) = end_position {
16845 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16846 } else {
16847 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16848 };
16849
16850 if let Some(url) = url {
16851 cx.update(|window, cx| {
16852 if parse_zed_link(&url, cx).is_some() {
16853 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16854 } else {
16855 cx.open_url(&url);
16856 }
16857 })?;
16858 }
16859
16860 anyhow::Ok(())
16861 });
16862
16863 url_finder.detach();
16864 }
16865
16866 pub fn open_selected_filename(
16867 &mut self,
16868 _: &OpenSelectedFilename,
16869 window: &mut Window,
16870 cx: &mut Context<Self>,
16871 ) {
16872 let Some(workspace) = self.workspace() else {
16873 return;
16874 };
16875
16876 let position = self.selections.newest_anchor().head();
16877
16878 let Some((buffer, buffer_position)) =
16879 self.buffer.read(cx).text_anchor_for_position(position, cx)
16880 else {
16881 return;
16882 };
16883
16884 let project = self.project.clone();
16885
16886 cx.spawn_in(window, async move |_, cx| {
16887 let result = find_file(&buffer, project, buffer_position, cx).await;
16888
16889 if let Some((_, path)) = result {
16890 workspace
16891 .update_in(cx, |workspace, window, cx| {
16892 workspace.open_resolved_path(path, window, cx)
16893 })?
16894 .await?;
16895 }
16896 anyhow::Ok(())
16897 })
16898 .detach();
16899 }
16900
16901 pub(crate) fn navigate_to_hover_links(
16902 &mut self,
16903 kind: Option<GotoDefinitionKind>,
16904 definitions: Vec<HoverLink>,
16905 split: bool,
16906 window: &mut Window,
16907 cx: &mut Context<Editor>,
16908 ) -> Task<Result<Navigated>> {
16909 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16910 let mut first_url_or_file = None;
16911 let definitions: Vec<_> = definitions
16912 .into_iter()
16913 .filter_map(|def| match def {
16914 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16915 HoverLink::InlayHint(lsp_location, server_id) => {
16916 let computation =
16917 self.compute_target_location(lsp_location, server_id, window, cx);
16918 Some(cx.background_spawn(computation))
16919 }
16920 HoverLink::Url(url) => {
16921 first_url_or_file = Some(Either::Left(url));
16922 None
16923 }
16924 HoverLink::File(path) => {
16925 first_url_or_file = Some(Either::Right(path));
16926 None
16927 }
16928 })
16929 .collect();
16930
16931 let workspace = self.workspace();
16932
16933 cx.spawn_in(window, async move |editor, cx| {
16934 let locations: Vec<Location> = future::join_all(definitions)
16935 .await
16936 .into_iter()
16937 .filter_map(|location| location.transpose())
16938 .collect::<Result<_>>()
16939 .context("location tasks")?;
16940 let mut locations = cx.update(|_, cx| {
16941 locations
16942 .into_iter()
16943 .map(|location| {
16944 let buffer = location.buffer.read(cx);
16945 (location.buffer, location.range.to_point(buffer))
16946 })
16947 .into_group_map()
16948 })?;
16949 let mut num_locations = 0;
16950 for ranges in locations.values_mut() {
16951 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16952 ranges.dedup();
16953 num_locations += ranges.len();
16954 }
16955
16956 if num_locations > 1 {
16957 let Some(workspace) = workspace else {
16958 return Ok(Navigated::No);
16959 };
16960
16961 let tab_kind = match kind {
16962 Some(GotoDefinitionKind::Implementation) => "Implementations",
16963 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16964 Some(GotoDefinitionKind::Declaration) => "Declarations",
16965 Some(GotoDefinitionKind::Type) => "Types",
16966 };
16967 let title = editor
16968 .update_in(cx, |_, _, cx| {
16969 let target = locations
16970 .iter()
16971 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16972 .map(|(buffer, location)| {
16973 buffer
16974 .read(cx)
16975 .text_for_range(location.clone())
16976 .collect::<String>()
16977 })
16978 .filter(|text| !text.contains('\n'))
16979 .unique()
16980 .take(3)
16981 .join(", ");
16982 if target.is_empty() {
16983 tab_kind.to_owned()
16984 } else {
16985 format!("{tab_kind} for {target}")
16986 }
16987 })
16988 .context("buffer title")?;
16989
16990 let opened = workspace
16991 .update_in(cx, |workspace, window, cx| {
16992 Self::open_locations_in_multibuffer(
16993 workspace,
16994 locations,
16995 title,
16996 split,
16997 MultibufferSelectionMode::First,
16998 window,
16999 cx,
17000 )
17001 })
17002 .is_ok();
17003
17004 anyhow::Ok(Navigated::from_bool(opened))
17005 } else if num_locations == 0 {
17006 // If there is one url or file, open it directly
17007 match first_url_or_file {
17008 Some(Either::Left(url)) => {
17009 cx.update(|_, cx| cx.open_url(&url))?;
17010 Ok(Navigated::Yes)
17011 }
17012 Some(Either::Right(path)) => {
17013 let Some(workspace) = workspace else {
17014 return Ok(Navigated::No);
17015 };
17016
17017 workspace
17018 .update_in(cx, |workspace, window, cx| {
17019 workspace.open_resolved_path(path, window, cx)
17020 })?
17021 .await?;
17022 Ok(Navigated::Yes)
17023 }
17024 None => Ok(Navigated::No),
17025 }
17026 } else {
17027 let Some(workspace) = workspace else {
17028 return Ok(Navigated::No);
17029 };
17030
17031 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17032 let target_range = target_ranges.first().unwrap().clone();
17033
17034 editor.update_in(cx, |editor, window, cx| {
17035 let range = target_range.to_point(target_buffer.read(cx));
17036 let range = editor.range_for_match(&range);
17037 let range = collapse_multiline_range(range);
17038
17039 if !split
17040 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17041 {
17042 editor.go_to_singleton_buffer_range(range, window, cx);
17043 } else {
17044 let pane = workspace.read(cx).active_pane().clone();
17045 window.defer(cx, move |window, cx| {
17046 let target_editor: Entity<Self> =
17047 workspace.update(cx, |workspace, cx| {
17048 let pane = if split {
17049 workspace.adjacent_pane(window, cx)
17050 } else {
17051 workspace.active_pane().clone()
17052 };
17053
17054 workspace.open_project_item(
17055 pane,
17056 target_buffer.clone(),
17057 true,
17058 true,
17059 window,
17060 cx,
17061 )
17062 });
17063 target_editor.update(cx, |target_editor, cx| {
17064 // When selecting a definition in a different buffer, disable the nav history
17065 // to avoid creating a history entry at the previous cursor location.
17066 pane.update(cx, |pane, _| pane.disable_history());
17067 target_editor.go_to_singleton_buffer_range(range, window, cx);
17068 pane.update(cx, |pane, _| pane.enable_history());
17069 });
17070 });
17071 }
17072 Navigated::Yes
17073 })
17074 }
17075 })
17076 }
17077
17078 fn compute_target_location(
17079 &self,
17080 lsp_location: lsp::Location,
17081 server_id: LanguageServerId,
17082 window: &mut Window,
17083 cx: &mut Context<Self>,
17084 ) -> Task<anyhow::Result<Option<Location>>> {
17085 let Some(project) = self.project.clone() else {
17086 return Task::ready(Ok(None));
17087 };
17088
17089 cx.spawn_in(window, async move |editor, cx| {
17090 let location_task = editor.update(cx, |_, cx| {
17091 project.update(cx, |project, cx| {
17092 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17093 })
17094 })?;
17095 let location = Some({
17096 let target_buffer_handle = location_task.await.context("open local buffer")?;
17097 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17098 let target_start = target_buffer
17099 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17100 let target_end = target_buffer
17101 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17102 target_buffer.anchor_after(target_start)
17103 ..target_buffer.anchor_before(target_end)
17104 })?;
17105 Location {
17106 buffer: target_buffer_handle,
17107 range,
17108 }
17109 });
17110 Ok(location)
17111 })
17112 }
17113
17114 fn go_to_next_reference(
17115 &mut self,
17116 _: &GoToNextReference,
17117 window: &mut Window,
17118 cx: &mut Context<Self>,
17119 ) {
17120 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17121 if let Some(task) = task {
17122 task.detach();
17123 };
17124 }
17125
17126 fn go_to_prev_reference(
17127 &mut self,
17128 _: &GoToPreviousReference,
17129 window: &mut Window,
17130 cx: &mut Context<Self>,
17131 ) {
17132 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17133 if let Some(task) = task {
17134 task.detach();
17135 };
17136 }
17137
17138 pub fn go_to_reference_before_or_after_position(
17139 &mut self,
17140 direction: Direction,
17141 count: usize,
17142 window: &mut Window,
17143 cx: &mut Context<Self>,
17144 ) -> Option<Task<Result<()>>> {
17145 let selection = self.selections.newest_anchor();
17146 let head = selection.head();
17147
17148 let multi_buffer = self.buffer.read(cx);
17149
17150 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17151 let workspace = self.workspace()?;
17152 let project = workspace.read(cx).project().clone();
17153 let references =
17154 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17155 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17156 let Some(locations) = references.await? else {
17157 return Ok(());
17158 };
17159
17160 if locations.is_empty() {
17161 // totally normal - the cursor may be on something which is not
17162 // a symbol (e.g. a keyword)
17163 log::info!("no references found under cursor");
17164 return Ok(());
17165 }
17166
17167 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17168
17169 let (locations, current_location_index) =
17170 multi_buffer.update(cx, |multi_buffer, cx| {
17171 let mut locations = locations
17172 .into_iter()
17173 .filter_map(|loc| {
17174 let start = multi_buffer.buffer_anchor_to_anchor(
17175 &loc.buffer,
17176 loc.range.start,
17177 cx,
17178 )?;
17179 let end = multi_buffer.buffer_anchor_to_anchor(
17180 &loc.buffer,
17181 loc.range.end,
17182 cx,
17183 )?;
17184 Some(start..end)
17185 })
17186 .collect::<Vec<_>>();
17187
17188 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17189 // There is an O(n) implementation, but given this list will be
17190 // small (usually <100 items), the extra O(log(n)) factor isn't
17191 // worth the (surprisingly large amount of) extra complexity.
17192 locations
17193 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17194
17195 let head_offset = head.to_offset(&multi_buffer_snapshot);
17196
17197 let current_location_index = locations.iter().position(|loc| {
17198 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17199 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17200 });
17201
17202 (locations, current_location_index)
17203 })?;
17204
17205 let Some(current_location_index) = current_location_index else {
17206 // This indicates something has gone wrong, because we already
17207 // handle the "no references" case above
17208 log::error!(
17209 "failed to find current reference under cursor. Total references: {}",
17210 locations.len()
17211 );
17212 return Ok(());
17213 };
17214
17215 let destination_location_index = match direction {
17216 Direction::Next => (current_location_index + count) % locations.len(),
17217 Direction::Prev => {
17218 (current_location_index + locations.len() - count % locations.len())
17219 % locations.len()
17220 }
17221 };
17222
17223 // TODO(cameron): is this needed?
17224 // the thinking is to avoid "jumping to the current location" (avoid
17225 // polluting "jumplist" in vim terms)
17226 if current_location_index == destination_location_index {
17227 return Ok(());
17228 }
17229
17230 let Range { start, end } = locations[destination_location_index];
17231
17232 editor.update_in(cx, |editor, window, cx| {
17233 let effects = SelectionEffects::default();
17234
17235 editor.unfold_ranges(&[start..end], false, false, cx);
17236 editor.change_selections(effects, window, cx, |s| {
17237 s.select_ranges([start..start]);
17238 });
17239 })?;
17240
17241 Ok(())
17242 }))
17243 }
17244
17245 pub fn find_all_references(
17246 &mut self,
17247 _: &FindAllReferences,
17248 window: &mut Window,
17249 cx: &mut Context<Self>,
17250 ) -> Option<Task<Result<Navigated>>> {
17251 let selection = self
17252 .selections
17253 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17254 let multi_buffer = self.buffer.read(cx);
17255 let head = selection.head();
17256
17257 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17258 let head_anchor = multi_buffer_snapshot.anchor_at(
17259 head,
17260 if head < selection.tail() {
17261 Bias::Right
17262 } else {
17263 Bias::Left
17264 },
17265 );
17266
17267 match self
17268 .find_all_references_task_sources
17269 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17270 {
17271 Ok(_) => {
17272 log::info!(
17273 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17274 );
17275 return None;
17276 }
17277 Err(i) => {
17278 self.find_all_references_task_sources.insert(i, head_anchor);
17279 }
17280 }
17281
17282 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17283 let workspace = self.workspace()?;
17284 let project = workspace.read(cx).project().clone();
17285 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17286 Some(cx.spawn_in(window, async move |editor, cx| {
17287 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17288 if let Ok(i) = editor
17289 .find_all_references_task_sources
17290 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17291 {
17292 editor.find_all_references_task_sources.remove(i);
17293 }
17294 });
17295
17296 let Some(locations) = references.await? else {
17297 return anyhow::Ok(Navigated::No);
17298 };
17299 let mut locations = cx.update(|_, cx| {
17300 locations
17301 .into_iter()
17302 .map(|location| {
17303 let buffer = location.buffer.read(cx);
17304 (location.buffer, location.range.to_point(buffer))
17305 })
17306 .into_group_map()
17307 })?;
17308 if locations.is_empty() {
17309 return anyhow::Ok(Navigated::No);
17310 }
17311 for ranges in locations.values_mut() {
17312 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17313 ranges.dedup();
17314 }
17315
17316 workspace.update_in(cx, |workspace, window, cx| {
17317 let target = locations
17318 .iter()
17319 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17320 .map(|(buffer, location)| {
17321 buffer
17322 .read(cx)
17323 .text_for_range(location.clone())
17324 .collect::<String>()
17325 })
17326 .filter(|text| !text.contains('\n'))
17327 .unique()
17328 .take(3)
17329 .join(", ");
17330 let title = if target.is_empty() {
17331 "References".to_owned()
17332 } else {
17333 format!("References to {target}")
17334 };
17335 Self::open_locations_in_multibuffer(
17336 workspace,
17337 locations,
17338 title,
17339 false,
17340 MultibufferSelectionMode::First,
17341 window,
17342 cx,
17343 );
17344 Navigated::Yes
17345 })
17346 }))
17347 }
17348
17349 /// Opens a multibuffer with the given project locations in it
17350 pub fn open_locations_in_multibuffer(
17351 workspace: &mut Workspace,
17352 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17353 title: String,
17354 split: bool,
17355 multibuffer_selection_mode: MultibufferSelectionMode,
17356 window: &mut Window,
17357 cx: &mut Context<Workspace>,
17358 ) {
17359 if locations.is_empty() {
17360 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17361 return;
17362 }
17363
17364 let capability = workspace.project().read(cx).capability();
17365 let mut ranges = <Vec<Range<Anchor>>>::new();
17366
17367 // a key to find existing multibuffer editors with the same set of locations
17368 // to prevent us from opening more and more multibuffer tabs for searches and the like
17369 let mut key = (title.clone(), vec![]);
17370 let excerpt_buffer = cx.new(|cx| {
17371 let key = &mut key.1;
17372 let mut multibuffer = MultiBuffer::new(capability);
17373 for (buffer, mut ranges_for_buffer) in locations {
17374 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17375 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17376 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17377 PathKey::for_buffer(&buffer, cx),
17378 buffer.clone(),
17379 ranges_for_buffer,
17380 multibuffer_context_lines(cx),
17381 cx,
17382 );
17383 ranges.extend(new_ranges)
17384 }
17385
17386 multibuffer.with_title(title)
17387 });
17388 let existing = workspace.active_pane().update(cx, |pane, cx| {
17389 pane.items()
17390 .filter_map(|item| item.downcast::<Editor>())
17391 .find(|editor| {
17392 editor
17393 .read(cx)
17394 .lookup_key
17395 .as_ref()
17396 .and_then(|it| {
17397 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17398 })
17399 .is_some_and(|it| *it == key)
17400 })
17401 });
17402 let editor = existing.unwrap_or_else(|| {
17403 cx.new(|cx| {
17404 let mut editor = Editor::for_multibuffer(
17405 excerpt_buffer,
17406 Some(workspace.project().clone()),
17407 window,
17408 cx,
17409 );
17410 editor.lookup_key = Some(Box::new(key));
17411 editor
17412 })
17413 });
17414 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17415 MultibufferSelectionMode::First => {
17416 if let Some(first_range) = ranges.first() {
17417 editor.change_selections(
17418 SelectionEffects::no_scroll(),
17419 window,
17420 cx,
17421 |selections| {
17422 selections.clear_disjoint();
17423 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17424 },
17425 );
17426 }
17427 editor.highlight_background::<Self>(
17428 &ranges,
17429 |theme| theme.colors().editor_highlighted_line_background,
17430 cx,
17431 );
17432 }
17433 MultibufferSelectionMode::All => {
17434 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17435 selections.clear_disjoint();
17436 selections.select_anchor_ranges(ranges);
17437 });
17438 }
17439 });
17440
17441 let item = Box::new(editor);
17442 let item_id = item.item_id();
17443
17444 if split {
17445 let pane = workspace.adjacent_pane(window, cx);
17446 workspace.add_item(pane, item, None, true, true, window, cx);
17447 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17448 let (preview_item_id, preview_item_idx) =
17449 workspace.active_pane().read_with(cx, |pane, _| {
17450 (pane.preview_item_id(), pane.preview_item_idx())
17451 });
17452
17453 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17454
17455 if let Some(preview_item_id) = preview_item_id {
17456 workspace.active_pane().update(cx, |pane, cx| {
17457 pane.remove_item(preview_item_id, false, false, window, cx);
17458 });
17459 }
17460 } else {
17461 workspace.add_item_to_active_pane(item, None, true, window, cx);
17462 }
17463 workspace.active_pane().update(cx, |pane, cx| {
17464 pane.set_preview_item_id(Some(item_id), cx);
17465 });
17466 }
17467
17468 pub fn rename(
17469 &mut self,
17470 _: &Rename,
17471 window: &mut Window,
17472 cx: &mut Context<Self>,
17473 ) -> Option<Task<Result<()>>> {
17474 use language::ToOffset as _;
17475
17476 let provider = self.semantics_provider.clone()?;
17477 let selection = self.selections.newest_anchor().clone();
17478 let (cursor_buffer, cursor_buffer_position) = self
17479 .buffer
17480 .read(cx)
17481 .text_anchor_for_position(selection.head(), cx)?;
17482 let (tail_buffer, cursor_buffer_position_end) = self
17483 .buffer
17484 .read(cx)
17485 .text_anchor_for_position(selection.tail(), cx)?;
17486 if tail_buffer != cursor_buffer {
17487 return None;
17488 }
17489
17490 let snapshot = cursor_buffer.read(cx).snapshot();
17491 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17492 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17493 let prepare_rename = provider
17494 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17495 .unwrap_or_else(|| Task::ready(Ok(None)));
17496 drop(snapshot);
17497
17498 Some(cx.spawn_in(window, async move |this, cx| {
17499 let rename_range = if let Some(range) = prepare_rename.await? {
17500 Some(range)
17501 } else {
17502 this.update(cx, |this, cx| {
17503 let buffer = this.buffer.read(cx).snapshot(cx);
17504 let mut buffer_highlights = this
17505 .document_highlights_for_position(selection.head(), &buffer)
17506 .filter(|highlight| {
17507 highlight.start.excerpt_id == selection.head().excerpt_id
17508 && highlight.end.excerpt_id == selection.head().excerpt_id
17509 });
17510 buffer_highlights
17511 .next()
17512 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17513 })?
17514 };
17515 if let Some(rename_range) = rename_range {
17516 this.update_in(cx, |this, window, cx| {
17517 let snapshot = cursor_buffer.read(cx).snapshot();
17518 let rename_buffer_range = rename_range.to_offset(&snapshot);
17519 let cursor_offset_in_rename_range =
17520 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17521 let cursor_offset_in_rename_range_end =
17522 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17523
17524 this.take_rename(false, window, cx);
17525 let buffer = this.buffer.read(cx).read(cx);
17526 let cursor_offset = selection.head().to_offset(&buffer);
17527 let rename_start =
17528 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17529 let rename_end = rename_start + rename_buffer_range.len();
17530 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17531 let mut old_highlight_id = None;
17532 let old_name: Arc<str> = buffer
17533 .chunks(rename_start..rename_end, true)
17534 .map(|chunk| {
17535 if old_highlight_id.is_none() {
17536 old_highlight_id = chunk.syntax_highlight_id;
17537 }
17538 chunk.text
17539 })
17540 .collect::<String>()
17541 .into();
17542
17543 drop(buffer);
17544
17545 // Position the selection in the rename editor so that it matches the current selection.
17546 this.show_local_selections = false;
17547 let rename_editor = cx.new(|cx| {
17548 let mut editor = Editor::single_line(window, cx);
17549 editor.buffer.update(cx, |buffer, cx| {
17550 buffer.edit(
17551 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17552 None,
17553 cx,
17554 )
17555 });
17556 let cursor_offset_in_rename_range =
17557 MultiBufferOffset(cursor_offset_in_rename_range);
17558 let cursor_offset_in_rename_range_end =
17559 MultiBufferOffset(cursor_offset_in_rename_range_end);
17560 let rename_selection_range = match cursor_offset_in_rename_range
17561 .cmp(&cursor_offset_in_rename_range_end)
17562 {
17563 Ordering::Equal => {
17564 editor.select_all(&SelectAll, window, cx);
17565 return editor;
17566 }
17567 Ordering::Less => {
17568 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17569 }
17570 Ordering::Greater => {
17571 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17572 }
17573 };
17574 if rename_selection_range.end.0 > old_name.len() {
17575 editor.select_all(&SelectAll, window, cx);
17576 } else {
17577 editor.change_selections(Default::default(), window, cx, |s| {
17578 s.select_ranges([rename_selection_range]);
17579 });
17580 }
17581 editor
17582 });
17583 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17584 if e == &EditorEvent::Focused {
17585 cx.emit(EditorEvent::FocusedIn)
17586 }
17587 })
17588 .detach();
17589
17590 let write_highlights =
17591 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17592 let read_highlights =
17593 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17594 let ranges = write_highlights
17595 .iter()
17596 .flat_map(|(_, ranges)| ranges.iter())
17597 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17598 .cloned()
17599 .collect();
17600
17601 this.highlight_text::<Rename>(
17602 ranges,
17603 HighlightStyle {
17604 fade_out: Some(0.6),
17605 ..Default::default()
17606 },
17607 cx,
17608 );
17609 let rename_focus_handle = rename_editor.focus_handle(cx);
17610 window.focus(&rename_focus_handle);
17611 let block_id = this.insert_blocks(
17612 [BlockProperties {
17613 style: BlockStyle::Flex,
17614 placement: BlockPlacement::Below(range.start),
17615 height: Some(1),
17616 render: Arc::new({
17617 let rename_editor = rename_editor.clone();
17618 move |cx: &mut BlockContext| {
17619 let mut text_style = cx.editor_style.text.clone();
17620 if let Some(highlight_style) = old_highlight_id
17621 .and_then(|h| h.style(&cx.editor_style.syntax))
17622 {
17623 text_style = text_style.highlight(highlight_style);
17624 }
17625 div()
17626 .block_mouse_except_scroll()
17627 .pl(cx.anchor_x)
17628 .child(EditorElement::new(
17629 &rename_editor,
17630 EditorStyle {
17631 background: cx.theme().system().transparent,
17632 local_player: cx.editor_style.local_player,
17633 text: text_style,
17634 scrollbar_width: cx.editor_style.scrollbar_width,
17635 syntax: cx.editor_style.syntax.clone(),
17636 status: cx.editor_style.status.clone(),
17637 inlay_hints_style: HighlightStyle {
17638 font_weight: Some(FontWeight::BOLD),
17639 ..make_inlay_hints_style(cx.app)
17640 },
17641 edit_prediction_styles: make_suggestion_styles(
17642 cx.app,
17643 ),
17644 ..EditorStyle::default()
17645 },
17646 ))
17647 .into_any_element()
17648 }
17649 }),
17650 priority: 0,
17651 }],
17652 Some(Autoscroll::fit()),
17653 cx,
17654 )[0];
17655 this.pending_rename = Some(RenameState {
17656 range,
17657 old_name,
17658 editor: rename_editor,
17659 block_id,
17660 });
17661 })?;
17662 }
17663
17664 Ok(())
17665 }))
17666 }
17667
17668 pub fn confirm_rename(
17669 &mut self,
17670 _: &ConfirmRename,
17671 window: &mut Window,
17672 cx: &mut Context<Self>,
17673 ) -> Option<Task<Result<()>>> {
17674 let rename = self.take_rename(false, window, cx)?;
17675 let workspace = self.workspace()?.downgrade();
17676 let (buffer, start) = self
17677 .buffer
17678 .read(cx)
17679 .text_anchor_for_position(rename.range.start, cx)?;
17680 let (end_buffer, _) = self
17681 .buffer
17682 .read(cx)
17683 .text_anchor_for_position(rename.range.end, cx)?;
17684 if buffer != end_buffer {
17685 return None;
17686 }
17687
17688 let old_name = rename.old_name;
17689 let new_name = rename.editor.read(cx).text(cx);
17690
17691 let rename = self.semantics_provider.as_ref()?.perform_rename(
17692 &buffer,
17693 start,
17694 new_name.clone(),
17695 cx,
17696 )?;
17697
17698 Some(cx.spawn_in(window, async move |editor, cx| {
17699 let project_transaction = rename.await?;
17700 Self::open_project_transaction(
17701 &editor,
17702 workspace,
17703 project_transaction,
17704 format!("Rename: {} → {}", old_name, new_name),
17705 cx,
17706 )
17707 .await?;
17708
17709 editor.update(cx, |editor, cx| {
17710 editor.refresh_document_highlights(cx);
17711 })?;
17712 Ok(())
17713 }))
17714 }
17715
17716 fn take_rename(
17717 &mut self,
17718 moving_cursor: bool,
17719 window: &mut Window,
17720 cx: &mut Context<Self>,
17721 ) -> Option<RenameState> {
17722 let rename = self.pending_rename.take()?;
17723 if rename.editor.focus_handle(cx).is_focused(window) {
17724 window.focus(&self.focus_handle);
17725 }
17726
17727 self.remove_blocks(
17728 [rename.block_id].into_iter().collect(),
17729 Some(Autoscroll::fit()),
17730 cx,
17731 );
17732 self.clear_highlights::<Rename>(cx);
17733 self.show_local_selections = true;
17734
17735 if moving_cursor {
17736 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17737 editor
17738 .selections
17739 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
17740 .head()
17741 });
17742
17743 // Update the selection to match the position of the selection inside
17744 // the rename editor.
17745 let snapshot = self.buffer.read(cx).read(cx);
17746 let rename_range = rename.range.to_offset(&snapshot);
17747 let cursor_in_editor = snapshot
17748 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17749 .min(rename_range.end);
17750 drop(snapshot);
17751
17752 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17753 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17754 });
17755 } else {
17756 self.refresh_document_highlights(cx);
17757 }
17758
17759 Some(rename)
17760 }
17761
17762 pub fn pending_rename(&self) -> Option<&RenameState> {
17763 self.pending_rename.as_ref()
17764 }
17765
17766 fn format(
17767 &mut self,
17768 _: &Format,
17769 window: &mut Window,
17770 cx: &mut Context<Self>,
17771 ) -> Option<Task<Result<()>>> {
17772 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17773
17774 let project = match &self.project {
17775 Some(project) => project.clone(),
17776 None => return None,
17777 };
17778
17779 Some(self.perform_format(
17780 project,
17781 FormatTrigger::Manual,
17782 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17783 window,
17784 cx,
17785 ))
17786 }
17787
17788 fn format_selections(
17789 &mut self,
17790 _: &FormatSelections,
17791 window: &mut Window,
17792 cx: &mut Context<Self>,
17793 ) -> Option<Task<Result<()>>> {
17794 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17795
17796 let project = match &self.project {
17797 Some(project) => project.clone(),
17798 None => return None,
17799 };
17800
17801 let ranges = self
17802 .selections
17803 .all_adjusted(&self.display_snapshot(cx))
17804 .into_iter()
17805 .map(|selection| selection.range())
17806 .collect_vec();
17807
17808 Some(self.perform_format(
17809 project,
17810 FormatTrigger::Manual,
17811 FormatTarget::Ranges(ranges),
17812 window,
17813 cx,
17814 ))
17815 }
17816
17817 fn perform_format(
17818 &mut self,
17819 project: Entity<Project>,
17820 trigger: FormatTrigger,
17821 target: FormatTarget,
17822 window: &mut Window,
17823 cx: &mut Context<Self>,
17824 ) -> Task<Result<()>> {
17825 let buffer = self.buffer.clone();
17826 let (buffers, target) = match target {
17827 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17828 FormatTarget::Ranges(selection_ranges) => {
17829 let multi_buffer = buffer.read(cx);
17830 let snapshot = multi_buffer.read(cx);
17831 let mut buffers = HashSet::default();
17832 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17833 BTreeMap::new();
17834 for selection_range in selection_ranges {
17835 for (buffer, buffer_range, _) in
17836 snapshot.range_to_buffer_ranges(selection_range)
17837 {
17838 let buffer_id = buffer.remote_id();
17839 let start = buffer.anchor_before(buffer_range.start);
17840 let end = buffer.anchor_after(buffer_range.end);
17841 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17842 buffer_id_to_ranges
17843 .entry(buffer_id)
17844 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17845 .or_insert_with(|| vec![start..end]);
17846 }
17847 }
17848 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17849 }
17850 };
17851
17852 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17853 let selections_prev = transaction_id_prev
17854 .and_then(|transaction_id_prev| {
17855 // default to selections as they were after the last edit, if we have them,
17856 // instead of how they are now.
17857 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17858 // will take you back to where you made the last edit, instead of staying where you scrolled
17859 self.selection_history
17860 .transaction(transaction_id_prev)
17861 .map(|t| t.0.clone())
17862 })
17863 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17864
17865 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17866 let format = project.update(cx, |project, cx| {
17867 project.format(buffers, target, true, trigger, cx)
17868 });
17869
17870 cx.spawn_in(window, async move |editor, cx| {
17871 let transaction = futures::select_biased! {
17872 transaction = format.log_err().fuse() => transaction,
17873 () = timeout => {
17874 log::warn!("timed out waiting for formatting");
17875 None
17876 }
17877 };
17878
17879 buffer
17880 .update(cx, |buffer, cx| {
17881 if let Some(transaction) = transaction
17882 && !buffer.is_singleton()
17883 {
17884 buffer.push_transaction(&transaction.0, cx);
17885 }
17886 cx.notify();
17887 })
17888 .ok();
17889
17890 if let Some(transaction_id_now) =
17891 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17892 {
17893 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17894 if has_new_transaction {
17895 _ = editor.update(cx, |editor, _| {
17896 editor
17897 .selection_history
17898 .insert_transaction(transaction_id_now, selections_prev);
17899 });
17900 }
17901 }
17902
17903 Ok(())
17904 })
17905 }
17906
17907 fn organize_imports(
17908 &mut self,
17909 _: &OrganizeImports,
17910 window: &mut Window,
17911 cx: &mut Context<Self>,
17912 ) -> Option<Task<Result<()>>> {
17913 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17914 let project = match &self.project {
17915 Some(project) => project.clone(),
17916 None => return None,
17917 };
17918 Some(self.perform_code_action_kind(
17919 project,
17920 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17921 window,
17922 cx,
17923 ))
17924 }
17925
17926 fn perform_code_action_kind(
17927 &mut self,
17928 project: Entity<Project>,
17929 kind: CodeActionKind,
17930 window: &mut Window,
17931 cx: &mut Context<Self>,
17932 ) -> Task<Result<()>> {
17933 let buffer = self.buffer.clone();
17934 let buffers = buffer.read(cx).all_buffers();
17935 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17936 let apply_action = project.update(cx, |project, cx| {
17937 project.apply_code_action_kind(buffers, kind, true, cx)
17938 });
17939 cx.spawn_in(window, async move |_, cx| {
17940 let transaction = futures::select_biased! {
17941 () = timeout => {
17942 log::warn!("timed out waiting for executing code action");
17943 None
17944 }
17945 transaction = apply_action.log_err().fuse() => transaction,
17946 };
17947 buffer
17948 .update(cx, |buffer, cx| {
17949 // check if we need this
17950 if let Some(transaction) = transaction
17951 && !buffer.is_singleton()
17952 {
17953 buffer.push_transaction(&transaction.0, cx);
17954 }
17955 cx.notify();
17956 })
17957 .ok();
17958 Ok(())
17959 })
17960 }
17961
17962 pub fn restart_language_server(
17963 &mut self,
17964 _: &RestartLanguageServer,
17965 _: &mut Window,
17966 cx: &mut Context<Self>,
17967 ) {
17968 if let Some(project) = self.project.clone() {
17969 self.buffer.update(cx, |multi_buffer, cx| {
17970 project.update(cx, |project, cx| {
17971 project.restart_language_servers_for_buffers(
17972 multi_buffer.all_buffers().into_iter().collect(),
17973 HashSet::default(),
17974 cx,
17975 );
17976 });
17977 })
17978 }
17979 }
17980
17981 pub fn stop_language_server(
17982 &mut self,
17983 _: &StopLanguageServer,
17984 _: &mut Window,
17985 cx: &mut Context<Self>,
17986 ) {
17987 if let Some(project) = self.project.clone() {
17988 self.buffer.update(cx, |multi_buffer, cx| {
17989 project.update(cx, |project, cx| {
17990 project.stop_language_servers_for_buffers(
17991 multi_buffer.all_buffers().into_iter().collect(),
17992 HashSet::default(),
17993 cx,
17994 );
17995 });
17996 });
17997 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17998 }
17999 }
18000
18001 fn cancel_language_server_work(
18002 workspace: &mut Workspace,
18003 _: &actions::CancelLanguageServerWork,
18004 _: &mut Window,
18005 cx: &mut Context<Workspace>,
18006 ) {
18007 let project = workspace.project();
18008 let buffers = workspace
18009 .active_item(cx)
18010 .and_then(|item| item.act_as::<Editor>(cx))
18011 .map_or(HashSet::default(), |editor| {
18012 editor.read(cx).buffer.read(cx).all_buffers()
18013 });
18014 project.update(cx, |project, cx| {
18015 project.cancel_language_server_work_for_buffers(buffers, cx);
18016 });
18017 }
18018
18019 fn show_character_palette(
18020 &mut self,
18021 _: &ShowCharacterPalette,
18022 window: &mut Window,
18023 _: &mut Context<Self>,
18024 ) {
18025 window.show_character_palette();
18026 }
18027
18028 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18029 if !self.diagnostics_enabled() {
18030 return;
18031 }
18032
18033 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18034 let buffer = self.buffer.read(cx).snapshot(cx);
18035 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18036 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18037 let is_valid = buffer
18038 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18039 .any(|entry| {
18040 entry.diagnostic.is_primary
18041 && !entry.range.is_empty()
18042 && entry.range.start == primary_range_start
18043 && entry.diagnostic.message == active_diagnostics.active_message
18044 });
18045
18046 if !is_valid {
18047 self.dismiss_diagnostics(cx);
18048 }
18049 }
18050 }
18051
18052 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18053 match &self.active_diagnostics {
18054 ActiveDiagnostic::Group(group) => Some(group),
18055 _ => None,
18056 }
18057 }
18058
18059 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18060 if !self.diagnostics_enabled() {
18061 return;
18062 }
18063 self.dismiss_diagnostics(cx);
18064 self.active_diagnostics = ActiveDiagnostic::All;
18065 }
18066
18067 fn activate_diagnostics(
18068 &mut self,
18069 buffer_id: BufferId,
18070 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18071 window: &mut Window,
18072 cx: &mut Context<Self>,
18073 ) {
18074 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18075 return;
18076 }
18077 self.dismiss_diagnostics(cx);
18078 let snapshot = self.snapshot(window, cx);
18079 let buffer = self.buffer.read(cx).snapshot(cx);
18080 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18081 return;
18082 };
18083
18084 let diagnostic_group = buffer
18085 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18086 .collect::<Vec<_>>();
18087
18088 let language_registry = self
18089 .project()
18090 .map(|project| project.read(cx).languages().clone());
18091
18092 let blocks = renderer.render_group(
18093 diagnostic_group,
18094 buffer_id,
18095 snapshot,
18096 cx.weak_entity(),
18097 language_registry,
18098 cx,
18099 );
18100
18101 let blocks = self.display_map.update(cx, |display_map, cx| {
18102 display_map.insert_blocks(blocks, cx).into_iter().collect()
18103 });
18104 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18105 active_range: buffer.anchor_before(diagnostic.range.start)
18106 ..buffer.anchor_after(diagnostic.range.end),
18107 active_message: diagnostic.diagnostic.message.clone(),
18108 group_id: diagnostic.diagnostic.group_id,
18109 blocks,
18110 });
18111 cx.notify();
18112 }
18113
18114 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18115 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18116 return;
18117 };
18118
18119 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18120 if let ActiveDiagnostic::Group(group) = prev {
18121 self.display_map.update(cx, |display_map, cx| {
18122 display_map.remove_blocks(group.blocks, cx);
18123 });
18124 cx.notify();
18125 }
18126 }
18127
18128 /// Disable inline diagnostics rendering for this editor.
18129 pub fn disable_inline_diagnostics(&mut self) {
18130 self.inline_diagnostics_enabled = false;
18131 self.inline_diagnostics_update = Task::ready(());
18132 self.inline_diagnostics.clear();
18133 }
18134
18135 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18136 self.diagnostics_enabled = false;
18137 self.dismiss_diagnostics(cx);
18138 self.inline_diagnostics_update = Task::ready(());
18139 self.inline_diagnostics.clear();
18140 }
18141
18142 pub fn disable_word_completions(&mut self) {
18143 self.word_completions_enabled = false;
18144 }
18145
18146 pub fn diagnostics_enabled(&self) -> bool {
18147 self.diagnostics_enabled && self.mode.is_full()
18148 }
18149
18150 pub fn inline_diagnostics_enabled(&self) -> bool {
18151 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18152 }
18153
18154 pub fn show_inline_diagnostics(&self) -> bool {
18155 self.show_inline_diagnostics
18156 }
18157
18158 pub fn toggle_inline_diagnostics(
18159 &mut self,
18160 _: &ToggleInlineDiagnostics,
18161 window: &mut Window,
18162 cx: &mut Context<Editor>,
18163 ) {
18164 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18165 self.refresh_inline_diagnostics(false, window, cx);
18166 }
18167
18168 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18169 self.diagnostics_max_severity = severity;
18170 self.display_map.update(cx, |display_map, _| {
18171 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18172 });
18173 }
18174
18175 pub fn toggle_diagnostics(
18176 &mut self,
18177 _: &ToggleDiagnostics,
18178 window: &mut Window,
18179 cx: &mut Context<Editor>,
18180 ) {
18181 if !self.diagnostics_enabled() {
18182 return;
18183 }
18184
18185 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18186 EditorSettings::get_global(cx)
18187 .diagnostics_max_severity
18188 .filter(|severity| severity != &DiagnosticSeverity::Off)
18189 .unwrap_or(DiagnosticSeverity::Hint)
18190 } else {
18191 DiagnosticSeverity::Off
18192 };
18193 self.set_max_diagnostics_severity(new_severity, cx);
18194 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18195 self.active_diagnostics = ActiveDiagnostic::None;
18196 self.inline_diagnostics_update = Task::ready(());
18197 self.inline_diagnostics.clear();
18198 } else {
18199 self.refresh_inline_diagnostics(false, window, cx);
18200 }
18201
18202 cx.notify();
18203 }
18204
18205 pub fn toggle_minimap(
18206 &mut self,
18207 _: &ToggleMinimap,
18208 window: &mut Window,
18209 cx: &mut Context<Editor>,
18210 ) {
18211 if self.supports_minimap(cx) {
18212 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18213 }
18214 }
18215
18216 fn refresh_inline_diagnostics(
18217 &mut self,
18218 debounce: bool,
18219 window: &mut Window,
18220 cx: &mut Context<Self>,
18221 ) {
18222 let max_severity = ProjectSettings::get_global(cx)
18223 .diagnostics
18224 .inline
18225 .max_severity
18226 .unwrap_or(self.diagnostics_max_severity);
18227
18228 if !self.inline_diagnostics_enabled()
18229 || !self.diagnostics_enabled()
18230 || !self.show_inline_diagnostics
18231 || max_severity == DiagnosticSeverity::Off
18232 {
18233 self.inline_diagnostics_update = Task::ready(());
18234 self.inline_diagnostics.clear();
18235 return;
18236 }
18237
18238 let debounce_ms = ProjectSettings::get_global(cx)
18239 .diagnostics
18240 .inline
18241 .update_debounce_ms;
18242 let debounce = if debounce && debounce_ms > 0 {
18243 Some(Duration::from_millis(debounce_ms))
18244 } else {
18245 None
18246 };
18247 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18248 if let Some(debounce) = debounce {
18249 cx.background_executor().timer(debounce).await;
18250 }
18251 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18252 editor
18253 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18254 .ok()
18255 }) else {
18256 return;
18257 };
18258
18259 let new_inline_diagnostics = cx
18260 .background_spawn(async move {
18261 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18262 for diagnostic_entry in
18263 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18264 {
18265 let message = diagnostic_entry
18266 .diagnostic
18267 .message
18268 .split_once('\n')
18269 .map(|(line, _)| line)
18270 .map(SharedString::new)
18271 .unwrap_or_else(|| {
18272 SharedString::new(&*diagnostic_entry.diagnostic.message)
18273 });
18274 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18275 let (Ok(i) | Err(i)) = inline_diagnostics
18276 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18277 inline_diagnostics.insert(
18278 i,
18279 (
18280 start_anchor,
18281 InlineDiagnostic {
18282 message,
18283 group_id: diagnostic_entry.diagnostic.group_id,
18284 start: diagnostic_entry.range.start.to_point(&snapshot),
18285 is_primary: diagnostic_entry.diagnostic.is_primary,
18286 severity: diagnostic_entry.diagnostic.severity,
18287 },
18288 ),
18289 );
18290 }
18291 inline_diagnostics
18292 })
18293 .await;
18294
18295 editor
18296 .update(cx, |editor, cx| {
18297 editor.inline_diagnostics = new_inline_diagnostics;
18298 cx.notify();
18299 })
18300 .ok();
18301 });
18302 }
18303
18304 fn pull_diagnostics(
18305 &mut self,
18306 buffer_id: Option<BufferId>,
18307 window: &Window,
18308 cx: &mut Context<Self>,
18309 ) -> Option<()> {
18310 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18311 return None;
18312 }
18313 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18314 .diagnostics
18315 .lsp_pull_diagnostics;
18316 if !pull_diagnostics_settings.enabled {
18317 return None;
18318 }
18319 let project = self.project()?.downgrade();
18320 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18321 let mut buffers = self.buffer.read(cx).all_buffers();
18322 buffers.retain(|buffer| {
18323 let buffer_id_to_retain = buffer.read(cx).remote_id();
18324 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18325 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18326 });
18327 if buffers.is_empty() {
18328 self.pull_diagnostics_task = Task::ready(());
18329 return None;
18330 }
18331
18332 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18333 cx.background_executor().timer(debounce).await;
18334
18335 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18336 buffers
18337 .into_iter()
18338 .filter_map(|buffer| {
18339 project
18340 .update(cx, |project, cx| {
18341 project.lsp_store().update(cx, |lsp_store, cx| {
18342 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18343 })
18344 })
18345 .ok()
18346 })
18347 .collect::<FuturesUnordered<_>>()
18348 }) else {
18349 return;
18350 };
18351
18352 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18353 match pull_task {
18354 Ok(()) => {
18355 if editor
18356 .update_in(cx, |editor, window, cx| {
18357 editor.update_diagnostics_state(window, cx);
18358 })
18359 .is_err()
18360 {
18361 return;
18362 }
18363 }
18364 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18365 }
18366 }
18367 });
18368
18369 Some(())
18370 }
18371
18372 pub fn set_selections_from_remote(
18373 &mut self,
18374 selections: Vec<Selection<Anchor>>,
18375 pending_selection: Option<Selection<Anchor>>,
18376 window: &mut Window,
18377 cx: &mut Context<Self>,
18378 ) {
18379 let old_cursor_position = self.selections.newest_anchor().head();
18380 self.selections
18381 .change_with(&self.display_snapshot(cx), |s| {
18382 s.select_anchors(selections);
18383 if let Some(pending_selection) = pending_selection {
18384 s.set_pending(pending_selection, SelectMode::Character);
18385 } else {
18386 s.clear_pending();
18387 }
18388 });
18389 self.selections_did_change(
18390 false,
18391 &old_cursor_position,
18392 SelectionEffects::default(),
18393 window,
18394 cx,
18395 );
18396 }
18397
18398 pub fn transact(
18399 &mut self,
18400 window: &mut Window,
18401 cx: &mut Context<Self>,
18402 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18403 ) -> Option<TransactionId> {
18404 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18405 this.start_transaction_at(Instant::now(), window, cx);
18406 update(this, window, cx);
18407 this.end_transaction_at(Instant::now(), cx)
18408 })
18409 }
18410
18411 pub fn start_transaction_at(
18412 &mut self,
18413 now: Instant,
18414 window: &mut Window,
18415 cx: &mut Context<Self>,
18416 ) -> Option<TransactionId> {
18417 self.end_selection(window, cx);
18418 if let Some(tx_id) = self
18419 .buffer
18420 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18421 {
18422 self.selection_history
18423 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18424 cx.emit(EditorEvent::TransactionBegun {
18425 transaction_id: tx_id,
18426 });
18427 Some(tx_id)
18428 } else {
18429 None
18430 }
18431 }
18432
18433 pub fn end_transaction_at(
18434 &mut self,
18435 now: Instant,
18436 cx: &mut Context<Self>,
18437 ) -> Option<TransactionId> {
18438 if let Some(transaction_id) = self
18439 .buffer
18440 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18441 {
18442 if let Some((_, end_selections)) =
18443 self.selection_history.transaction_mut(transaction_id)
18444 {
18445 *end_selections = Some(self.selections.disjoint_anchors_arc());
18446 } else {
18447 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18448 }
18449
18450 cx.emit(EditorEvent::Edited { transaction_id });
18451 Some(transaction_id)
18452 } else {
18453 None
18454 }
18455 }
18456
18457 pub fn modify_transaction_selection_history(
18458 &mut self,
18459 transaction_id: TransactionId,
18460 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18461 ) -> bool {
18462 self.selection_history
18463 .transaction_mut(transaction_id)
18464 .map(modify)
18465 .is_some()
18466 }
18467
18468 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18469 if self.selection_mark_mode {
18470 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18471 s.move_with(|_, sel| {
18472 sel.collapse_to(sel.head(), SelectionGoal::None);
18473 });
18474 })
18475 }
18476 self.selection_mark_mode = true;
18477 cx.notify();
18478 }
18479
18480 pub fn swap_selection_ends(
18481 &mut self,
18482 _: &actions::SwapSelectionEnds,
18483 window: &mut Window,
18484 cx: &mut Context<Self>,
18485 ) {
18486 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18487 s.move_with(|_, sel| {
18488 if sel.start != sel.end {
18489 sel.reversed = !sel.reversed
18490 }
18491 });
18492 });
18493 self.request_autoscroll(Autoscroll::newest(), cx);
18494 cx.notify();
18495 }
18496
18497 pub fn toggle_focus(
18498 workspace: &mut Workspace,
18499 _: &actions::ToggleFocus,
18500 window: &mut Window,
18501 cx: &mut Context<Workspace>,
18502 ) {
18503 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18504 return;
18505 };
18506 workspace.activate_item(&item, true, true, window, cx);
18507 }
18508
18509 pub fn toggle_fold(
18510 &mut self,
18511 _: &actions::ToggleFold,
18512 window: &mut Window,
18513 cx: &mut Context<Self>,
18514 ) {
18515 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18516 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18517 let selection = self.selections.newest::<Point>(&display_map);
18518
18519 let range = if selection.is_empty() {
18520 let point = selection.head().to_display_point(&display_map);
18521 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18522 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18523 .to_point(&display_map);
18524 start..end
18525 } else {
18526 selection.range()
18527 };
18528 if display_map.folds_in_range(range).next().is_some() {
18529 self.unfold_lines(&Default::default(), window, cx)
18530 } else {
18531 self.fold(&Default::default(), window, cx)
18532 }
18533 } else {
18534 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18535 let buffer_ids: HashSet<_> = self
18536 .selections
18537 .disjoint_anchor_ranges()
18538 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18539 .collect();
18540
18541 let should_unfold = buffer_ids
18542 .iter()
18543 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18544
18545 for buffer_id in buffer_ids {
18546 if should_unfold {
18547 self.unfold_buffer(buffer_id, cx);
18548 } else {
18549 self.fold_buffer(buffer_id, cx);
18550 }
18551 }
18552 }
18553 }
18554
18555 pub fn toggle_fold_recursive(
18556 &mut self,
18557 _: &actions::ToggleFoldRecursive,
18558 window: &mut Window,
18559 cx: &mut Context<Self>,
18560 ) {
18561 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18562
18563 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18564 let range = if selection.is_empty() {
18565 let point = selection.head().to_display_point(&display_map);
18566 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18567 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18568 .to_point(&display_map);
18569 start..end
18570 } else {
18571 selection.range()
18572 };
18573 if display_map.folds_in_range(range).next().is_some() {
18574 self.unfold_recursive(&Default::default(), window, cx)
18575 } else {
18576 self.fold_recursive(&Default::default(), window, cx)
18577 }
18578 }
18579
18580 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18581 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18582 let mut to_fold = Vec::new();
18583 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18584 let selections = self.selections.all_adjusted(&display_map);
18585
18586 for selection in selections {
18587 let range = selection.range().sorted();
18588 let buffer_start_row = range.start.row;
18589
18590 if range.start.row != range.end.row {
18591 let mut found = false;
18592 let mut row = range.start.row;
18593 while row <= range.end.row {
18594 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18595 {
18596 found = true;
18597 row = crease.range().end.row + 1;
18598 to_fold.push(crease);
18599 } else {
18600 row += 1
18601 }
18602 }
18603 if found {
18604 continue;
18605 }
18606 }
18607
18608 for row in (0..=range.start.row).rev() {
18609 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18610 && crease.range().end.row >= buffer_start_row
18611 {
18612 to_fold.push(crease);
18613 if row <= range.start.row {
18614 break;
18615 }
18616 }
18617 }
18618 }
18619
18620 self.fold_creases(to_fold, true, window, cx);
18621 } else {
18622 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18623 let buffer_ids = self
18624 .selections
18625 .disjoint_anchor_ranges()
18626 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18627 .collect::<HashSet<_>>();
18628 for buffer_id in buffer_ids {
18629 self.fold_buffer(buffer_id, cx);
18630 }
18631 }
18632 }
18633
18634 pub fn toggle_fold_all(
18635 &mut self,
18636 _: &actions::ToggleFoldAll,
18637 window: &mut Window,
18638 cx: &mut Context<Self>,
18639 ) {
18640 if self.buffer.read(cx).is_singleton() {
18641 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18642 let has_folds = display_map
18643 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18644 .next()
18645 .is_some();
18646
18647 if has_folds {
18648 self.unfold_all(&actions::UnfoldAll, window, cx);
18649 } else {
18650 self.fold_all(&actions::FoldAll, window, cx);
18651 }
18652 } else {
18653 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18654 let should_unfold = buffer_ids
18655 .iter()
18656 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18657
18658 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18659 editor
18660 .update_in(cx, |editor, _, cx| {
18661 for buffer_id in buffer_ids {
18662 if should_unfold {
18663 editor.unfold_buffer(buffer_id, cx);
18664 } else {
18665 editor.fold_buffer(buffer_id, cx);
18666 }
18667 }
18668 })
18669 .ok();
18670 });
18671 }
18672 }
18673
18674 fn fold_at_level(
18675 &mut self,
18676 fold_at: &FoldAtLevel,
18677 window: &mut Window,
18678 cx: &mut Context<Self>,
18679 ) {
18680 if !self.buffer.read(cx).is_singleton() {
18681 return;
18682 }
18683
18684 let fold_at_level = fold_at.0;
18685 let snapshot = self.buffer.read(cx).snapshot(cx);
18686 let mut to_fold = Vec::new();
18687 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18688
18689 let row_ranges_to_keep: Vec<Range<u32>> = self
18690 .selections
18691 .all::<Point>(&self.display_snapshot(cx))
18692 .into_iter()
18693 .map(|sel| sel.start.row..sel.end.row)
18694 .collect();
18695
18696 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18697 while start_row < end_row {
18698 match self
18699 .snapshot(window, cx)
18700 .crease_for_buffer_row(MultiBufferRow(start_row))
18701 {
18702 Some(crease) => {
18703 let nested_start_row = crease.range().start.row + 1;
18704 let nested_end_row = crease.range().end.row;
18705
18706 if current_level < fold_at_level {
18707 stack.push((nested_start_row, nested_end_row, current_level + 1));
18708 } else if current_level == fold_at_level {
18709 // Fold iff there is no selection completely contained within the fold region
18710 if !row_ranges_to_keep.iter().any(|selection| {
18711 selection.end >= nested_start_row
18712 && selection.start <= nested_end_row
18713 }) {
18714 to_fold.push(crease);
18715 }
18716 }
18717
18718 start_row = nested_end_row + 1;
18719 }
18720 None => start_row += 1,
18721 }
18722 }
18723 }
18724
18725 self.fold_creases(to_fold, true, window, cx);
18726 }
18727
18728 pub fn fold_at_level_1(
18729 &mut self,
18730 _: &actions::FoldAtLevel1,
18731 window: &mut Window,
18732 cx: &mut Context<Self>,
18733 ) {
18734 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18735 }
18736
18737 pub fn fold_at_level_2(
18738 &mut self,
18739 _: &actions::FoldAtLevel2,
18740 window: &mut Window,
18741 cx: &mut Context<Self>,
18742 ) {
18743 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18744 }
18745
18746 pub fn fold_at_level_3(
18747 &mut self,
18748 _: &actions::FoldAtLevel3,
18749 window: &mut Window,
18750 cx: &mut Context<Self>,
18751 ) {
18752 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18753 }
18754
18755 pub fn fold_at_level_4(
18756 &mut self,
18757 _: &actions::FoldAtLevel4,
18758 window: &mut Window,
18759 cx: &mut Context<Self>,
18760 ) {
18761 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18762 }
18763
18764 pub fn fold_at_level_5(
18765 &mut self,
18766 _: &actions::FoldAtLevel5,
18767 window: &mut Window,
18768 cx: &mut Context<Self>,
18769 ) {
18770 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18771 }
18772
18773 pub fn fold_at_level_6(
18774 &mut self,
18775 _: &actions::FoldAtLevel6,
18776 window: &mut Window,
18777 cx: &mut Context<Self>,
18778 ) {
18779 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18780 }
18781
18782 pub fn fold_at_level_7(
18783 &mut self,
18784 _: &actions::FoldAtLevel7,
18785 window: &mut Window,
18786 cx: &mut Context<Self>,
18787 ) {
18788 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18789 }
18790
18791 pub fn fold_at_level_8(
18792 &mut self,
18793 _: &actions::FoldAtLevel8,
18794 window: &mut Window,
18795 cx: &mut Context<Self>,
18796 ) {
18797 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18798 }
18799
18800 pub fn fold_at_level_9(
18801 &mut self,
18802 _: &actions::FoldAtLevel9,
18803 window: &mut Window,
18804 cx: &mut Context<Self>,
18805 ) {
18806 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18807 }
18808
18809 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18810 if self.buffer.read(cx).is_singleton() {
18811 let mut fold_ranges = Vec::new();
18812 let snapshot = self.buffer.read(cx).snapshot(cx);
18813
18814 for row in 0..snapshot.max_row().0 {
18815 if let Some(foldable_range) = self
18816 .snapshot(window, cx)
18817 .crease_for_buffer_row(MultiBufferRow(row))
18818 {
18819 fold_ranges.push(foldable_range);
18820 }
18821 }
18822
18823 self.fold_creases(fold_ranges, true, window, cx);
18824 } else {
18825 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18826 editor
18827 .update_in(cx, |editor, _, cx| {
18828 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18829 editor.fold_buffer(buffer_id, cx);
18830 }
18831 })
18832 .ok();
18833 });
18834 }
18835 }
18836
18837 pub fn fold_function_bodies(
18838 &mut self,
18839 _: &actions::FoldFunctionBodies,
18840 window: &mut Window,
18841 cx: &mut Context<Self>,
18842 ) {
18843 let snapshot = self.buffer.read(cx).snapshot(cx);
18844
18845 let ranges = snapshot
18846 .text_object_ranges(
18847 MultiBufferOffset(0)..snapshot.len(),
18848 TreeSitterOptions::default(),
18849 )
18850 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18851 .collect::<Vec<_>>();
18852
18853 let creases = ranges
18854 .into_iter()
18855 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18856 .collect();
18857
18858 self.fold_creases(creases, true, window, cx);
18859 }
18860
18861 pub fn fold_recursive(
18862 &mut self,
18863 _: &actions::FoldRecursive,
18864 window: &mut Window,
18865 cx: &mut Context<Self>,
18866 ) {
18867 let mut to_fold = Vec::new();
18868 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18869 let selections = self.selections.all_adjusted(&display_map);
18870
18871 for selection in selections {
18872 let range = selection.range().sorted();
18873 let buffer_start_row = range.start.row;
18874
18875 if range.start.row != range.end.row {
18876 let mut found = false;
18877 for row in range.start.row..=range.end.row {
18878 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18879 found = true;
18880 to_fold.push(crease);
18881 }
18882 }
18883 if found {
18884 continue;
18885 }
18886 }
18887
18888 for row in (0..=range.start.row).rev() {
18889 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18890 if crease.range().end.row >= buffer_start_row {
18891 to_fold.push(crease);
18892 } else {
18893 break;
18894 }
18895 }
18896 }
18897 }
18898
18899 self.fold_creases(to_fold, true, window, cx);
18900 }
18901
18902 pub fn fold_at(
18903 &mut self,
18904 buffer_row: MultiBufferRow,
18905 window: &mut Window,
18906 cx: &mut Context<Self>,
18907 ) {
18908 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18909
18910 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18911 let autoscroll = self
18912 .selections
18913 .all::<Point>(&display_map)
18914 .iter()
18915 .any(|selection| crease.range().overlaps(&selection.range()));
18916
18917 self.fold_creases(vec![crease], autoscroll, window, cx);
18918 }
18919 }
18920
18921 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18922 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18923 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18924 let buffer = display_map.buffer_snapshot();
18925 let selections = self.selections.all::<Point>(&display_map);
18926 let ranges = selections
18927 .iter()
18928 .map(|s| {
18929 let range = s.display_range(&display_map).sorted();
18930 let mut start = range.start.to_point(&display_map);
18931 let mut end = range.end.to_point(&display_map);
18932 start.column = 0;
18933 end.column = buffer.line_len(MultiBufferRow(end.row));
18934 start..end
18935 })
18936 .collect::<Vec<_>>();
18937
18938 self.unfold_ranges(&ranges, true, true, cx);
18939 } else {
18940 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18941 let buffer_ids = self
18942 .selections
18943 .disjoint_anchor_ranges()
18944 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18945 .collect::<HashSet<_>>();
18946 for buffer_id in buffer_ids {
18947 self.unfold_buffer(buffer_id, cx);
18948 }
18949 }
18950 }
18951
18952 pub fn unfold_recursive(
18953 &mut self,
18954 _: &UnfoldRecursive,
18955 _window: &mut Window,
18956 cx: &mut Context<Self>,
18957 ) {
18958 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18959 let selections = self.selections.all::<Point>(&display_map);
18960 let ranges = selections
18961 .iter()
18962 .map(|s| {
18963 let mut range = s.display_range(&display_map).sorted();
18964 *range.start.column_mut() = 0;
18965 *range.end.column_mut() = display_map.line_len(range.end.row());
18966 let start = range.start.to_point(&display_map);
18967 let end = range.end.to_point(&display_map);
18968 start..end
18969 })
18970 .collect::<Vec<_>>();
18971
18972 self.unfold_ranges(&ranges, true, true, cx);
18973 }
18974
18975 pub fn unfold_at(
18976 &mut self,
18977 buffer_row: MultiBufferRow,
18978 _window: &mut Window,
18979 cx: &mut Context<Self>,
18980 ) {
18981 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18982
18983 let intersection_range = Point::new(buffer_row.0, 0)
18984 ..Point::new(
18985 buffer_row.0,
18986 display_map.buffer_snapshot().line_len(buffer_row),
18987 );
18988
18989 let autoscroll = self
18990 .selections
18991 .all::<Point>(&display_map)
18992 .iter()
18993 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18994
18995 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18996 }
18997
18998 pub fn unfold_all(
18999 &mut self,
19000 _: &actions::UnfoldAll,
19001 _window: &mut Window,
19002 cx: &mut Context<Self>,
19003 ) {
19004 if self.buffer.read(cx).is_singleton() {
19005 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19006 self.unfold_ranges(
19007 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19008 true,
19009 true,
19010 cx,
19011 );
19012 } else {
19013 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19014 editor
19015 .update(cx, |editor, cx| {
19016 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19017 editor.unfold_buffer(buffer_id, cx);
19018 }
19019 })
19020 .ok();
19021 });
19022 }
19023 }
19024
19025 pub fn fold_selected_ranges(
19026 &mut self,
19027 _: &FoldSelectedRanges,
19028 window: &mut Window,
19029 cx: &mut Context<Self>,
19030 ) {
19031 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19032 let selections = self.selections.all_adjusted(&display_map);
19033 let ranges = selections
19034 .into_iter()
19035 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19036 .collect::<Vec<_>>();
19037 self.fold_creases(ranges, true, window, cx);
19038 }
19039
19040 pub fn fold_ranges<T: ToOffset + Clone>(
19041 &mut self,
19042 ranges: Vec<Range<T>>,
19043 auto_scroll: bool,
19044 window: &mut Window,
19045 cx: &mut Context<Self>,
19046 ) {
19047 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19048 let ranges = ranges
19049 .into_iter()
19050 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19051 .collect::<Vec<_>>();
19052 self.fold_creases(ranges, auto_scroll, window, cx);
19053 }
19054
19055 pub fn fold_creases<T: ToOffset + Clone>(
19056 &mut self,
19057 creases: Vec<Crease<T>>,
19058 auto_scroll: bool,
19059 _window: &mut Window,
19060 cx: &mut Context<Self>,
19061 ) {
19062 if creases.is_empty() {
19063 return;
19064 }
19065
19066 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19067
19068 if auto_scroll {
19069 self.request_autoscroll(Autoscroll::fit(), cx);
19070 }
19071
19072 cx.notify();
19073
19074 self.scrollbar_marker_state.dirty = true;
19075 self.folds_did_change(cx);
19076 }
19077
19078 /// Removes any folds whose ranges intersect any of the given ranges.
19079 pub fn unfold_ranges<T: ToOffset + Clone>(
19080 &mut self,
19081 ranges: &[Range<T>],
19082 inclusive: bool,
19083 auto_scroll: bool,
19084 cx: &mut Context<Self>,
19085 ) {
19086 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19087 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19088 });
19089 self.folds_did_change(cx);
19090 }
19091
19092 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19093 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19094 return;
19095 }
19096
19097 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19098 self.display_map.update(cx, |display_map, cx| {
19099 display_map.fold_buffers([buffer_id], cx)
19100 });
19101
19102 let snapshot = self.display_snapshot(cx);
19103 self.selections.change_with(&snapshot, |selections| {
19104 selections.remove_selections_from_buffer(buffer_id);
19105 });
19106
19107 cx.emit(EditorEvent::BufferFoldToggled {
19108 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19109 folded: true,
19110 });
19111 cx.notify();
19112 }
19113
19114 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19115 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19116 return;
19117 }
19118 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19119 self.display_map.update(cx, |display_map, cx| {
19120 display_map.unfold_buffers([buffer_id], cx);
19121 });
19122 cx.emit(EditorEvent::BufferFoldToggled {
19123 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19124 folded: false,
19125 });
19126 cx.notify();
19127 }
19128
19129 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19130 self.display_map.read(cx).is_buffer_folded(buffer)
19131 }
19132
19133 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19134 self.display_map.read(cx).folded_buffers()
19135 }
19136
19137 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19138 self.display_map.update(cx, |display_map, cx| {
19139 display_map.disable_header_for_buffer(buffer_id, cx);
19140 });
19141 cx.notify();
19142 }
19143
19144 /// Removes any folds with the given ranges.
19145 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19146 &mut self,
19147 ranges: &[Range<T>],
19148 type_id: TypeId,
19149 auto_scroll: bool,
19150 cx: &mut Context<Self>,
19151 ) {
19152 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19153 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19154 });
19155 self.folds_did_change(cx);
19156 }
19157
19158 fn remove_folds_with<T: ToOffset + Clone>(
19159 &mut self,
19160 ranges: &[Range<T>],
19161 auto_scroll: bool,
19162 cx: &mut Context<Self>,
19163 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19164 ) {
19165 if ranges.is_empty() {
19166 return;
19167 }
19168
19169 let mut buffers_affected = HashSet::default();
19170 let multi_buffer = self.buffer().read(cx);
19171 for range in ranges {
19172 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19173 buffers_affected.insert(buffer.read(cx).remote_id());
19174 };
19175 }
19176
19177 self.display_map.update(cx, update);
19178
19179 if auto_scroll {
19180 self.request_autoscroll(Autoscroll::fit(), cx);
19181 }
19182
19183 cx.notify();
19184 self.scrollbar_marker_state.dirty = true;
19185 self.active_indent_guides_state.dirty = true;
19186 }
19187
19188 pub fn update_renderer_widths(
19189 &mut self,
19190 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19191 cx: &mut Context<Self>,
19192 ) -> bool {
19193 self.display_map
19194 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19195 }
19196
19197 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19198 self.display_map.read(cx).fold_placeholder.clone()
19199 }
19200
19201 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19202 self.buffer.update(cx, |buffer, cx| {
19203 buffer.set_all_diff_hunks_expanded(cx);
19204 });
19205 }
19206
19207 pub fn expand_all_diff_hunks(
19208 &mut self,
19209 _: &ExpandAllDiffHunks,
19210 _window: &mut Window,
19211 cx: &mut Context<Self>,
19212 ) {
19213 self.buffer.update(cx, |buffer, cx| {
19214 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19215 });
19216 }
19217
19218 pub fn collapse_all_diff_hunks(
19219 &mut self,
19220 _: &CollapseAllDiffHunks,
19221 _window: &mut Window,
19222 cx: &mut Context<Self>,
19223 ) {
19224 self.buffer.update(cx, |buffer, cx| {
19225 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19226 });
19227 }
19228
19229 pub fn toggle_selected_diff_hunks(
19230 &mut self,
19231 _: &ToggleSelectedDiffHunks,
19232 _window: &mut Window,
19233 cx: &mut Context<Self>,
19234 ) {
19235 let ranges: Vec<_> = self
19236 .selections
19237 .disjoint_anchors()
19238 .iter()
19239 .map(|s| s.range())
19240 .collect();
19241 self.toggle_diff_hunks_in_ranges(ranges, cx);
19242 }
19243
19244 pub fn diff_hunks_in_ranges<'a>(
19245 &'a self,
19246 ranges: &'a [Range<Anchor>],
19247 buffer: &'a MultiBufferSnapshot,
19248 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19249 ranges.iter().flat_map(move |range| {
19250 let end_excerpt_id = range.end.excerpt_id;
19251 let range = range.to_point(buffer);
19252 let mut peek_end = range.end;
19253 if range.end.row < buffer.max_row().0 {
19254 peek_end = Point::new(range.end.row + 1, 0);
19255 }
19256 buffer
19257 .diff_hunks_in_range(range.start..peek_end)
19258 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19259 })
19260 }
19261
19262 pub fn has_stageable_diff_hunks_in_ranges(
19263 &self,
19264 ranges: &[Range<Anchor>],
19265 snapshot: &MultiBufferSnapshot,
19266 ) -> bool {
19267 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19268 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19269 }
19270
19271 pub fn toggle_staged_selected_diff_hunks(
19272 &mut self,
19273 _: &::git::ToggleStaged,
19274 _: &mut Window,
19275 cx: &mut Context<Self>,
19276 ) {
19277 let snapshot = self.buffer.read(cx).snapshot(cx);
19278 let ranges: Vec<_> = self
19279 .selections
19280 .disjoint_anchors()
19281 .iter()
19282 .map(|s| s.range())
19283 .collect();
19284 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19285 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19286 }
19287
19288 pub fn set_render_diff_hunk_controls(
19289 &mut self,
19290 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19291 cx: &mut Context<Self>,
19292 ) {
19293 self.render_diff_hunk_controls = render_diff_hunk_controls;
19294 cx.notify();
19295 }
19296
19297 pub fn stage_and_next(
19298 &mut self,
19299 _: &::git::StageAndNext,
19300 window: &mut Window,
19301 cx: &mut Context<Self>,
19302 ) {
19303 self.do_stage_or_unstage_and_next(true, window, cx);
19304 }
19305
19306 pub fn unstage_and_next(
19307 &mut self,
19308 _: &::git::UnstageAndNext,
19309 window: &mut Window,
19310 cx: &mut Context<Self>,
19311 ) {
19312 self.do_stage_or_unstage_and_next(false, window, cx);
19313 }
19314
19315 pub fn stage_or_unstage_diff_hunks(
19316 &mut self,
19317 stage: bool,
19318 ranges: Vec<Range<Anchor>>,
19319 cx: &mut Context<Self>,
19320 ) {
19321 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19322 cx.spawn(async move |this, cx| {
19323 task.await?;
19324 this.update(cx, |this, cx| {
19325 let snapshot = this.buffer.read(cx).snapshot(cx);
19326 let chunk_by = this
19327 .diff_hunks_in_ranges(&ranges, &snapshot)
19328 .chunk_by(|hunk| hunk.buffer_id);
19329 for (buffer_id, hunks) in &chunk_by {
19330 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19331 }
19332 })
19333 })
19334 .detach_and_log_err(cx);
19335 }
19336
19337 fn save_buffers_for_ranges_if_needed(
19338 &mut self,
19339 ranges: &[Range<Anchor>],
19340 cx: &mut Context<Editor>,
19341 ) -> Task<Result<()>> {
19342 let multibuffer = self.buffer.read(cx);
19343 let snapshot = multibuffer.read(cx);
19344 let buffer_ids: HashSet<_> = ranges
19345 .iter()
19346 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19347 .collect();
19348 drop(snapshot);
19349
19350 let mut buffers = HashSet::default();
19351 for buffer_id in buffer_ids {
19352 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19353 let buffer = buffer_entity.read(cx);
19354 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19355 {
19356 buffers.insert(buffer_entity);
19357 }
19358 }
19359 }
19360
19361 if let Some(project) = &self.project {
19362 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19363 } else {
19364 Task::ready(Ok(()))
19365 }
19366 }
19367
19368 fn do_stage_or_unstage_and_next(
19369 &mut self,
19370 stage: bool,
19371 window: &mut Window,
19372 cx: &mut Context<Self>,
19373 ) {
19374 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19375
19376 if ranges.iter().any(|range| range.start != range.end) {
19377 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19378 return;
19379 }
19380
19381 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19382 let snapshot = self.snapshot(window, cx);
19383 let position = self
19384 .selections
19385 .newest::<Point>(&snapshot.display_snapshot)
19386 .head();
19387 let mut row = snapshot
19388 .buffer_snapshot()
19389 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19390 .find(|hunk| hunk.row_range.start.0 > position.row)
19391 .map(|hunk| hunk.row_range.start);
19392
19393 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19394 // Outside of the project diff editor, wrap around to the beginning.
19395 if !all_diff_hunks_expanded {
19396 row = row.or_else(|| {
19397 snapshot
19398 .buffer_snapshot()
19399 .diff_hunks_in_range(Point::zero()..position)
19400 .find(|hunk| hunk.row_range.end.0 < position.row)
19401 .map(|hunk| hunk.row_range.start)
19402 });
19403 }
19404
19405 if let Some(row) = row {
19406 let destination = Point::new(row.0, 0);
19407 let autoscroll = Autoscroll::center();
19408
19409 self.unfold_ranges(&[destination..destination], false, false, cx);
19410 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19411 s.select_ranges([destination..destination]);
19412 });
19413 }
19414 }
19415
19416 fn do_stage_or_unstage(
19417 &self,
19418 stage: bool,
19419 buffer_id: BufferId,
19420 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19421 cx: &mut App,
19422 ) -> Option<()> {
19423 let project = self.project()?;
19424 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19425 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19426 let buffer_snapshot = buffer.read(cx).snapshot();
19427 let file_exists = buffer_snapshot
19428 .file()
19429 .is_some_and(|file| file.disk_state().exists());
19430 diff.update(cx, |diff, cx| {
19431 diff.stage_or_unstage_hunks(
19432 stage,
19433 &hunks
19434 .map(|hunk| buffer_diff::DiffHunk {
19435 buffer_range: hunk.buffer_range,
19436 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19437 ..hunk.diff_base_byte_range.end.0,
19438 secondary_status: hunk.secondary_status,
19439 range: Point::zero()..Point::zero(), // unused
19440 })
19441 .collect::<Vec<_>>(),
19442 &buffer_snapshot,
19443 file_exists,
19444 cx,
19445 )
19446 });
19447 None
19448 }
19449
19450 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19451 let ranges: Vec<_> = self
19452 .selections
19453 .disjoint_anchors()
19454 .iter()
19455 .map(|s| s.range())
19456 .collect();
19457 self.buffer
19458 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19459 }
19460
19461 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19462 self.buffer.update(cx, |buffer, cx| {
19463 let ranges = vec![Anchor::min()..Anchor::max()];
19464 if !buffer.all_diff_hunks_expanded()
19465 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19466 {
19467 buffer.collapse_diff_hunks(ranges, cx);
19468 true
19469 } else {
19470 false
19471 }
19472 })
19473 }
19474
19475 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19476 if self.buffer.read(cx).all_diff_hunks_expanded() {
19477 return true;
19478 }
19479 let ranges = vec![Anchor::min()..Anchor::max()];
19480 self.buffer
19481 .read(cx)
19482 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19483 }
19484
19485 fn toggle_diff_hunks_in_ranges(
19486 &mut self,
19487 ranges: Vec<Range<Anchor>>,
19488 cx: &mut Context<Editor>,
19489 ) {
19490 self.buffer.update(cx, |buffer, cx| {
19491 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19492 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19493 })
19494 }
19495
19496 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19497 self.buffer.update(cx, |buffer, cx| {
19498 let snapshot = buffer.snapshot(cx);
19499 let excerpt_id = range.end.excerpt_id;
19500 let point_range = range.to_point(&snapshot);
19501 let expand = !buffer.single_hunk_is_expanded(range, cx);
19502 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19503 })
19504 }
19505
19506 pub(crate) fn apply_all_diff_hunks(
19507 &mut self,
19508 _: &ApplyAllDiffHunks,
19509 window: &mut Window,
19510 cx: &mut Context<Self>,
19511 ) {
19512 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19513
19514 let buffers = self.buffer.read(cx).all_buffers();
19515 for branch_buffer in buffers {
19516 branch_buffer.update(cx, |branch_buffer, cx| {
19517 branch_buffer.merge_into_base(Vec::new(), cx);
19518 });
19519 }
19520
19521 if let Some(project) = self.project.clone() {
19522 self.save(
19523 SaveOptions {
19524 format: true,
19525 autosave: false,
19526 },
19527 project,
19528 window,
19529 cx,
19530 )
19531 .detach_and_log_err(cx);
19532 }
19533 }
19534
19535 pub(crate) fn apply_selected_diff_hunks(
19536 &mut self,
19537 _: &ApplyDiffHunk,
19538 window: &mut Window,
19539 cx: &mut Context<Self>,
19540 ) {
19541 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19542 let snapshot = self.snapshot(window, cx);
19543 let hunks = snapshot.hunks_for_ranges(
19544 self.selections
19545 .all(&snapshot.display_snapshot)
19546 .into_iter()
19547 .map(|selection| selection.range()),
19548 );
19549 let mut ranges_by_buffer = HashMap::default();
19550 self.transact(window, cx, |editor, _window, cx| {
19551 for hunk in hunks {
19552 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19553 ranges_by_buffer
19554 .entry(buffer.clone())
19555 .or_insert_with(Vec::new)
19556 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19557 }
19558 }
19559
19560 for (buffer, ranges) in ranges_by_buffer {
19561 buffer.update(cx, |buffer, cx| {
19562 buffer.merge_into_base(ranges, cx);
19563 });
19564 }
19565 });
19566
19567 if let Some(project) = self.project.clone() {
19568 self.save(
19569 SaveOptions {
19570 format: true,
19571 autosave: false,
19572 },
19573 project,
19574 window,
19575 cx,
19576 )
19577 .detach_and_log_err(cx);
19578 }
19579 }
19580
19581 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19582 if hovered != self.gutter_hovered {
19583 self.gutter_hovered = hovered;
19584 cx.notify();
19585 }
19586 }
19587
19588 pub fn insert_blocks(
19589 &mut self,
19590 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19591 autoscroll: Option<Autoscroll>,
19592 cx: &mut Context<Self>,
19593 ) -> Vec<CustomBlockId> {
19594 let blocks = self
19595 .display_map
19596 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19597 if let Some(autoscroll) = autoscroll {
19598 self.request_autoscroll(autoscroll, cx);
19599 }
19600 cx.notify();
19601 blocks
19602 }
19603
19604 pub fn resize_blocks(
19605 &mut self,
19606 heights: HashMap<CustomBlockId, u32>,
19607 autoscroll: Option<Autoscroll>,
19608 cx: &mut Context<Self>,
19609 ) {
19610 self.display_map
19611 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19612 if let Some(autoscroll) = autoscroll {
19613 self.request_autoscroll(autoscroll, cx);
19614 }
19615 cx.notify();
19616 }
19617
19618 pub fn replace_blocks(
19619 &mut self,
19620 renderers: HashMap<CustomBlockId, RenderBlock>,
19621 autoscroll: Option<Autoscroll>,
19622 cx: &mut Context<Self>,
19623 ) {
19624 self.display_map
19625 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19626 if let Some(autoscroll) = autoscroll {
19627 self.request_autoscroll(autoscroll, cx);
19628 }
19629 cx.notify();
19630 }
19631
19632 pub fn remove_blocks(
19633 &mut self,
19634 block_ids: HashSet<CustomBlockId>,
19635 autoscroll: Option<Autoscroll>,
19636 cx: &mut Context<Self>,
19637 ) {
19638 self.display_map.update(cx, |display_map, cx| {
19639 display_map.remove_blocks(block_ids, cx)
19640 });
19641 if let Some(autoscroll) = autoscroll {
19642 self.request_autoscroll(autoscroll, cx);
19643 }
19644 cx.notify();
19645 }
19646
19647 pub fn row_for_block(
19648 &self,
19649 block_id: CustomBlockId,
19650 cx: &mut Context<Self>,
19651 ) -> Option<DisplayRow> {
19652 self.display_map
19653 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19654 }
19655
19656 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19657 self.focused_block = Some(focused_block);
19658 }
19659
19660 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19661 self.focused_block.take()
19662 }
19663
19664 pub fn insert_creases(
19665 &mut self,
19666 creases: impl IntoIterator<Item = Crease<Anchor>>,
19667 cx: &mut Context<Self>,
19668 ) -> Vec<CreaseId> {
19669 self.display_map
19670 .update(cx, |map, cx| map.insert_creases(creases, cx))
19671 }
19672
19673 pub fn remove_creases(
19674 &mut self,
19675 ids: impl IntoIterator<Item = CreaseId>,
19676 cx: &mut Context<Self>,
19677 ) -> Vec<(CreaseId, Range<Anchor>)> {
19678 self.display_map
19679 .update(cx, |map, cx| map.remove_creases(ids, cx))
19680 }
19681
19682 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19683 self.display_map
19684 .update(cx, |map, cx| map.snapshot(cx))
19685 .longest_row()
19686 }
19687
19688 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19689 self.display_map
19690 .update(cx, |map, cx| map.snapshot(cx))
19691 .max_point()
19692 }
19693
19694 pub fn text(&self, cx: &App) -> String {
19695 self.buffer.read(cx).read(cx).text()
19696 }
19697
19698 pub fn is_empty(&self, cx: &App) -> bool {
19699 self.buffer.read(cx).read(cx).is_empty()
19700 }
19701
19702 pub fn text_option(&self, cx: &App) -> Option<String> {
19703 let text = self.text(cx);
19704 let text = text.trim();
19705
19706 if text.is_empty() {
19707 return None;
19708 }
19709
19710 Some(text.to_string())
19711 }
19712
19713 pub fn set_text(
19714 &mut self,
19715 text: impl Into<Arc<str>>,
19716 window: &mut Window,
19717 cx: &mut Context<Self>,
19718 ) {
19719 self.transact(window, cx, |this, _, cx| {
19720 this.buffer
19721 .read(cx)
19722 .as_singleton()
19723 .expect("you can only call set_text on editors for singleton buffers")
19724 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19725 });
19726 }
19727
19728 pub fn display_text(&self, cx: &mut App) -> String {
19729 self.display_map
19730 .update(cx, |map, cx| map.snapshot(cx))
19731 .text()
19732 }
19733
19734 fn create_minimap(
19735 &self,
19736 minimap_settings: MinimapSettings,
19737 window: &mut Window,
19738 cx: &mut Context<Self>,
19739 ) -> Option<Entity<Self>> {
19740 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19741 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19742 }
19743
19744 fn initialize_new_minimap(
19745 &self,
19746 minimap_settings: MinimapSettings,
19747 window: &mut Window,
19748 cx: &mut Context<Self>,
19749 ) -> Entity<Self> {
19750 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19751
19752 let mut minimap = Editor::new_internal(
19753 EditorMode::Minimap {
19754 parent: cx.weak_entity(),
19755 },
19756 self.buffer.clone(),
19757 None,
19758 Some(self.display_map.clone()),
19759 window,
19760 cx,
19761 );
19762 minimap.scroll_manager.clone_state(&self.scroll_manager);
19763 minimap.set_text_style_refinement(TextStyleRefinement {
19764 font_size: Some(MINIMAP_FONT_SIZE),
19765 font_weight: Some(MINIMAP_FONT_WEIGHT),
19766 ..Default::default()
19767 });
19768 minimap.update_minimap_configuration(minimap_settings, cx);
19769 cx.new(|_| minimap)
19770 }
19771
19772 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19773 let current_line_highlight = minimap_settings
19774 .current_line_highlight
19775 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19776 self.set_current_line_highlight(Some(current_line_highlight));
19777 }
19778
19779 pub fn minimap(&self) -> Option<&Entity<Self>> {
19780 self.minimap
19781 .as_ref()
19782 .filter(|_| self.minimap_visibility.visible())
19783 }
19784
19785 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19786 let mut wrap_guides = smallvec![];
19787
19788 if self.show_wrap_guides == Some(false) {
19789 return wrap_guides;
19790 }
19791
19792 let settings = self.buffer.read(cx).language_settings(cx);
19793 if settings.show_wrap_guides {
19794 match self.soft_wrap_mode(cx) {
19795 SoftWrap::Column(soft_wrap) => {
19796 wrap_guides.push((soft_wrap as usize, true));
19797 }
19798 SoftWrap::Bounded(soft_wrap) => {
19799 wrap_guides.push((soft_wrap as usize, true));
19800 }
19801 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19802 }
19803 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19804 }
19805
19806 wrap_guides
19807 }
19808
19809 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19810 let settings = self.buffer.read(cx).language_settings(cx);
19811 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19812 match mode {
19813 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19814 SoftWrap::None
19815 }
19816 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19817 language_settings::SoftWrap::PreferredLineLength => {
19818 SoftWrap::Column(settings.preferred_line_length)
19819 }
19820 language_settings::SoftWrap::Bounded => {
19821 SoftWrap::Bounded(settings.preferred_line_length)
19822 }
19823 }
19824 }
19825
19826 pub fn set_soft_wrap_mode(
19827 &mut self,
19828 mode: language_settings::SoftWrap,
19829
19830 cx: &mut Context<Self>,
19831 ) {
19832 self.soft_wrap_mode_override = Some(mode);
19833 cx.notify();
19834 }
19835
19836 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19837 self.hard_wrap = hard_wrap;
19838 cx.notify();
19839 }
19840
19841 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19842 self.text_style_refinement = Some(style);
19843 }
19844
19845 /// called by the Element so we know what style we were most recently rendered with.
19846 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19847 // We intentionally do not inform the display map about the minimap style
19848 // so that wrapping is not recalculated and stays consistent for the editor
19849 // and its linked minimap.
19850 if !self.mode.is_minimap() {
19851 let font = style.text.font();
19852 let font_size = style.text.font_size.to_pixels(window.rem_size());
19853 let display_map = self
19854 .placeholder_display_map
19855 .as_ref()
19856 .filter(|_| self.is_empty(cx))
19857 .unwrap_or(&self.display_map);
19858
19859 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19860 }
19861 self.style = Some(style);
19862 }
19863
19864 pub fn style(&self) -> Option<&EditorStyle> {
19865 self.style.as_ref()
19866 }
19867
19868 // Called by the element. This method is not designed to be called outside of the editor
19869 // element's layout code because it does not notify when rewrapping is computed synchronously.
19870 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19871 if self.is_empty(cx) {
19872 self.placeholder_display_map
19873 .as_ref()
19874 .map_or(false, |display_map| {
19875 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19876 })
19877 } else {
19878 self.display_map
19879 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19880 }
19881 }
19882
19883 pub fn set_soft_wrap(&mut self) {
19884 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19885 }
19886
19887 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19888 if self.soft_wrap_mode_override.is_some() {
19889 self.soft_wrap_mode_override.take();
19890 } else {
19891 let soft_wrap = match self.soft_wrap_mode(cx) {
19892 SoftWrap::GitDiff => return,
19893 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19894 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19895 language_settings::SoftWrap::None
19896 }
19897 };
19898 self.soft_wrap_mode_override = Some(soft_wrap);
19899 }
19900 cx.notify();
19901 }
19902
19903 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19904 let Some(workspace) = self.workspace() else {
19905 return;
19906 };
19907 let fs = workspace.read(cx).app_state().fs.clone();
19908 let current_show = TabBarSettings::get_global(cx).show;
19909 update_settings_file(fs, cx, move |setting, _| {
19910 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19911 });
19912 }
19913
19914 pub fn toggle_indent_guides(
19915 &mut self,
19916 _: &ToggleIndentGuides,
19917 _: &mut Window,
19918 cx: &mut Context<Self>,
19919 ) {
19920 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19921 self.buffer
19922 .read(cx)
19923 .language_settings(cx)
19924 .indent_guides
19925 .enabled
19926 });
19927 self.show_indent_guides = Some(!currently_enabled);
19928 cx.notify();
19929 }
19930
19931 fn should_show_indent_guides(&self) -> Option<bool> {
19932 self.show_indent_guides
19933 }
19934
19935 pub fn toggle_line_numbers(
19936 &mut self,
19937 _: &ToggleLineNumbers,
19938 _: &mut Window,
19939 cx: &mut Context<Self>,
19940 ) {
19941 let mut editor_settings = EditorSettings::get_global(cx).clone();
19942 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19943 EditorSettings::override_global(editor_settings, cx);
19944 }
19945
19946 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19947 if let Some(show_line_numbers) = self.show_line_numbers {
19948 return show_line_numbers;
19949 }
19950 EditorSettings::get_global(cx).gutter.line_numbers
19951 }
19952
19953 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19954 match (
19955 self.use_relative_line_numbers,
19956 EditorSettings::get_global(cx).relative_line_numbers,
19957 ) {
19958 (None, setting) => setting,
19959 (Some(false), _) => RelativeLineNumbers::Disabled,
19960 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19961 (Some(true), _) => RelativeLineNumbers::Enabled,
19962 }
19963 }
19964
19965 pub fn toggle_relative_line_numbers(
19966 &mut self,
19967 _: &ToggleRelativeLineNumbers,
19968 _: &mut Window,
19969 cx: &mut Context<Self>,
19970 ) {
19971 let is_relative = self.relative_line_numbers(cx);
19972 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19973 }
19974
19975 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19976 self.use_relative_line_numbers = is_relative;
19977 cx.notify();
19978 }
19979
19980 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19981 self.show_gutter = show_gutter;
19982 cx.notify();
19983 }
19984
19985 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19986 self.show_scrollbars = ScrollbarAxes {
19987 horizontal: show,
19988 vertical: show,
19989 };
19990 cx.notify();
19991 }
19992
19993 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19994 self.show_scrollbars.vertical = show;
19995 cx.notify();
19996 }
19997
19998 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19999 self.show_scrollbars.horizontal = show;
20000 cx.notify();
20001 }
20002
20003 pub fn set_minimap_visibility(
20004 &mut self,
20005 minimap_visibility: MinimapVisibility,
20006 window: &mut Window,
20007 cx: &mut Context<Self>,
20008 ) {
20009 if self.minimap_visibility != minimap_visibility {
20010 if minimap_visibility.visible() && self.minimap.is_none() {
20011 let minimap_settings = EditorSettings::get_global(cx).minimap;
20012 self.minimap =
20013 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20014 }
20015 self.minimap_visibility = minimap_visibility;
20016 cx.notify();
20017 }
20018 }
20019
20020 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20021 self.set_show_scrollbars(false, cx);
20022 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20023 }
20024
20025 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20026 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20027 }
20028
20029 /// Normally the text in full mode and auto height editors is padded on the
20030 /// left side by roughly half a character width for improved hit testing.
20031 ///
20032 /// Use this method to disable this for cases where this is not wanted (e.g.
20033 /// if you want to align the editor text with some other text above or below)
20034 /// or if you want to add this padding to single-line editors.
20035 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20036 self.offset_content = offset_content;
20037 cx.notify();
20038 }
20039
20040 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20041 self.show_line_numbers = Some(show_line_numbers);
20042 cx.notify();
20043 }
20044
20045 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20046 self.disable_expand_excerpt_buttons = true;
20047 cx.notify();
20048 }
20049
20050 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20051 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20052 cx.notify();
20053 }
20054
20055 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20056 self.show_code_actions = Some(show_code_actions);
20057 cx.notify();
20058 }
20059
20060 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20061 self.show_runnables = Some(show_runnables);
20062 cx.notify();
20063 }
20064
20065 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20066 self.show_breakpoints = Some(show_breakpoints);
20067 cx.notify();
20068 }
20069
20070 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20071 if self.display_map.read(cx).masked != masked {
20072 self.display_map.update(cx, |map, _| map.masked = masked);
20073 }
20074 cx.notify()
20075 }
20076
20077 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20078 self.show_wrap_guides = Some(show_wrap_guides);
20079 cx.notify();
20080 }
20081
20082 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20083 self.show_indent_guides = Some(show_indent_guides);
20084 cx.notify();
20085 }
20086
20087 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20088 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20089 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20090 && let Some(dir) = file.abs_path(cx).parent()
20091 {
20092 return Some(dir.to_owned());
20093 }
20094 }
20095
20096 None
20097 }
20098
20099 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20100 self.active_excerpt(cx)?
20101 .1
20102 .read(cx)
20103 .file()
20104 .and_then(|f| f.as_local())
20105 }
20106
20107 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20108 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20109 let buffer = buffer.read(cx);
20110 if let Some(project_path) = buffer.project_path(cx) {
20111 let project = self.project()?.read(cx);
20112 project.absolute_path(&project_path, cx)
20113 } else {
20114 buffer
20115 .file()
20116 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20117 }
20118 })
20119 }
20120
20121 pub fn reveal_in_finder(
20122 &mut self,
20123 _: &RevealInFileManager,
20124 _window: &mut Window,
20125 cx: &mut Context<Self>,
20126 ) {
20127 if let Some(target) = self.target_file(cx) {
20128 cx.reveal_path(&target.abs_path(cx));
20129 }
20130 }
20131
20132 pub fn copy_path(
20133 &mut self,
20134 _: &zed_actions::workspace::CopyPath,
20135 _window: &mut Window,
20136 cx: &mut Context<Self>,
20137 ) {
20138 if let Some(path) = self.target_file_abs_path(cx)
20139 && let Some(path) = path.to_str()
20140 {
20141 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20142 } else {
20143 cx.propagate();
20144 }
20145 }
20146
20147 pub fn copy_relative_path(
20148 &mut self,
20149 _: &zed_actions::workspace::CopyRelativePath,
20150 _window: &mut Window,
20151 cx: &mut Context<Self>,
20152 ) {
20153 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20154 let project = self.project()?.read(cx);
20155 let path = buffer.read(cx).file()?.path();
20156 let path = path.display(project.path_style(cx));
20157 Some(path)
20158 }) {
20159 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20160 } else {
20161 cx.propagate();
20162 }
20163 }
20164
20165 /// Returns the project path for the editor's buffer, if any buffer is
20166 /// opened in the editor.
20167 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20168 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20169 buffer.read(cx).project_path(cx)
20170 } else {
20171 None
20172 }
20173 }
20174
20175 // Returns true if the editor handled a go-to-line request
20176 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20177 maybe!({
20178 let breakpoint_store = self.breakpoint_store.as_ref()?;
20179
20180 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20181 else {
20182 self.clear_row_highlights::<ActiveDebugLine>();
20183 return None;
20184 };
20185
20186 let position = active_stack_frame.position;
20187 let buffer_id = position.buffer_id?;
20188 let snapshot = self
20189 .project
20190 .as_ref()?
20191 .read(cx)
20192 .buffer_for_id(buffer_id, cx)?
20193 .read(cx)
20194 .snapshot();
20195
20196 let mut handled = false;
20197 for (id, ExcerptRange { context, .. }) in
20198 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20199 {
20200 if context.start.cmp(&position, &snapshot).is_ge()
20201 || context.end.cmp(&position, &snapshot).is_lt()
20202 {
20203 continue;
20204 }
20205 let snapshot = self.buffer.read(cx).snapshot(cx);
20206 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20207
20208 handled = true;
20209 self.clear_row_highlights::<ActiveDebugLine>();
20210
20211 self.go_to_line::<ActiveDebugLine>(
20212 multibuffer_anchor,
20213 Some(cx.theme().colors().editor_debugger_active_line_background),
20214 window,
20215 cx,
20216 );
20217
20218 cx.notify();
20219 }
20220
20221 handled.then_some(())
20222 })
20223 .is_some()
20224 }
20225
20226 pub fn copy_file_name_without_extension(
20227 &mut self,
20228 _: &CopyFileNameWithoutExtension,
20229 _: &mut Window,
20230 cx: &mut Context<Self>,
20231 ) {
20232 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20233 let file = buffer.read(cx).file()?;
20234 file.path().file_stem()
20235 }) {
20236 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20237 }
20238 }
20239
20240 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20241 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20242 let file = buffer.read(cx).file()?;
20243 Some(file.file_name(cx))
20244 }) {
20245 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20246 }
20247 }
20248
20249 pub fn toggle_git_blame(
20250 &mut self,
20251 _: &::git::Blame,
20252 window: &mut Window,
20253 cx: &mut Context<Self>,
20254 ) {
20255 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20256
20257 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20258 self.start_git_blame(true, window, cx);
20259 }
20260
20261 cx.notify();
20262 }
20263
20264 pub fn toggle_git_blame_inline(
20265 &mut self,
20266 _: &ToggleGitBlameInline,
20267 window: &mut Window,
20268 cx: &mut Context<Self>,
20269 ) {
20270 self.toggle_git_blame_inline_internal(true, window, cx);
20271 cx.notify();
20272 }
20273
20274 pub fn open_git_blame_commit(
20275 &mut self,
20276 _: &OpenGitBlameCommit,
20277 window: &mut Window,
20278 cx: &mut Context<Self>,
20279 ) {
20280 self.open_git_blame_commit_internal(window, cx);
20281 }
20282
20283 fn open_git_blame_commit_internal(
20284 &mut self,
20285 window: &mut Window,
20286 cx: &mut Context<Self>,
20287 ) -> Option<()> {
20288 let blame = self.blame.as_ref()?;
20289 let snapshot = self.snapshot(window, cx);
20290 let cursor = self
20291 .selections
20292 .newest::<Point>(&snapshot.display_snapshot)
20293 .head();
20294 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20295 let (_, blame_entry) = blame
20296 .update(cx, |blame, cx| {
20297 blame
20298 .blame_for_rows(
20299 &[RowInfo {
20300 buffer_id: Some(buffer.remote_id()),
20301 buffer_row: Some(point.row),
20302 ..Default::default()
20303 }],
20304 cx,
20305 )
20306 .next()
20307 })
20308 .flatten()?;
20309 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20310 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20311 let workspace = self.workspace()?.downgrade();
20312 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20313 None
20314 }
20315
20316 pub fn git_blame_inline_enabled(&self) -> bool {
20317 self.git_blame_inline_enabled
20318 }
20319
20320 pub fn toggle_selection_menu(
20321 &mut self,
20322 _: &ToggleSelectionMenu,
20323 _: &mut Window,
20324 cx: &mut Context<Self>,
20325 ) {
20326 self.show_selection_menu = self
20327 .show_selection_menu
20328 .map(|show_selections_menu| !show_selections_menu)
20329 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20330
20331 cx.notify();
20332 }
20333
20334 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20335 self.show_selection_menu
20336 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20337 }
20338
20339 fn start_git_blame(
20340 &mut self,
20341 user_triggered: bool,
20342 window: &mut Window,
20343 cx: &mut Context<Self>,
20344 ) {
20345 if let Some(project) = self.project() {
20346 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20347 && buffer.read(cx).file().is_none()
20348 {
20349 return;
20350 }
20351
20352 let focused = self.focus_handle(cx).contains_focused(window, cx);
20353
20354 let project = project.clone();
20355 let blame = cx
20356 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20357 self.blame_subscription =
20358 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20359 self.blame = Some(blame);
20360 }
20361 }
20362
20363 fn toggle_git_blame_inline_internal(
20364 &mut self,
20365 user_triggered: bool,
20366 window: &mut Window,
20367 cx: &mut Context<Self>,
20368 ) {
20369 if self.git_blame_inline_enabled {
20370 self.git_blame_inline_enabled = false;
20371 self.show_git_blame_inline = false;
20372 self.show_git_blame_inline_delay_task.take();
20373 } else {
20374 self.git_blame_inline_enabled = true;
20375 self.start_git_blame_inline(user_triggered, window, cx);
20376 }
20377
20378 cx.notify();
20379 }
20380
20381 fn start_git_blame_inline(
20382 &mut self,
20383 user_triggered: bool,
20384 window: &mut Window,
20385 cx: &mut Context<Self>,
20386 ) {
20387 self.start_git_blame(user_triggered, window, cx);
20388
20389 if ProjectSettings::get_global(cx)
20390 .git
20391 .inline_blame_delay()
20392 .is_some()
20393 {
20394 self.start_inline_blame_timer(window, cx);
20395 } else {
20396 self.show_git_blame_inline = true
20397 }
20398 }
20399
20400 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20401 self.blame.as_ref()
20402 }
20403
20404 pub fn show_git_blame_gutter(&self) -> bool {
20405 self.show_git_blame_gutter
20406 }
20407
20408 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20409 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20410 }
20411
20412 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20413 self.show_git_blame_inline
20414 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20415 && !self.newest_selection_head_on_empty_line(cx)
20416 && self.has_blame_entries(cx)
20417 }
20418
20419 fn has_blame_entries(&self, cx: &App) -> bool {
20420 self.blame()
20421 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20422 }
20423
20424 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20425 let cursor_anchor = self.selections.newest_anchor().head();
20426
20427 let snapshot = self.buffer.read(cx).snapshot(cx);
20428 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20429
20430 snapshot.line_len(buffer_row) == 0
20431 }
20432
20433 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20434 let buffer_and_selection = maybe!({
20435 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20436 let selection_range = selection.range();
20437
20438 let multi_buffer = self.buffer().read(cx);
20439 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20440 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20441
20442 let (buffer, range, _) = if selection.reversed {
20443 buffer_ranges.first()
20444 } else {
20445 buffer_ranges.last()
20446 }?;
20447
20448 let selection = text::ToPoint::to_point(&range.start, buffer).row
20449 ..text::ToPoint::to_point(&range.end, buffer).row;
20450 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20451 });
20452
20453 let Some((buffer, selection)) = buffer_and_selection else {
20454 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20455 };
20456
20457 let Some(project) = self.project() else {
20458 return Task::ready(Err(anyhow!("editor does not have project")));
20459 };
20460
20461 project.update(cx, |project, cx| {
20462 project.get_permalink_to_line(&buffer, selection, cx)
20463 })
20464 }
20465
20466 pub fn copy_permalink_to_line(
20467 &mut self,
20468 _: &CopyPermalinkToLine,
20469 window: &mut Window,
20470 cx: &mut Context<Self>,
20471 ) {
20472 let permalink_task = self.get_permalink_to_line(cx);
20473 let workspace = self.workspace();
20474
20475 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20476 Ok(permalink) => {
20477 cx.update(|_, cx| {
20478 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20479 })
20480 .ok();
20481 }
20482 Err(err) => {
20483 let message = format!("Failed to copy permalink: {err}");
20484
20485 anyhow::Result::<()>::Err(err).log_err();
20486
20487 if let Some(workspace) = workspace {
20488 workspace
20489 .update_in(cx, |workspace, _, cx| {
20490 struct CopyPermalinkToLine;
20491
20492 workspace.show_toast(
20493 Toast::new(
20494 NotificationId::unique::<CopyPermalinkToLine>(),
20495 message,
20496 ),
20497 cx,
20498 )
20499 })
20500 .ok();
20501 }
20502 }
20503 })
20504 .detach();
20505 }
20506
20507 pub fn copy_file_location(
20508 &mut self,
20509 _: &CopyFileLocation,
20510 _: &mut Window,
20511 cx: &mut Context<Self>,
20512 ) {
20513 let selection = self
20514 .selections
20515 .newest::<Point>(&self.display_snapshot(cx))
20516 .start
20517 .row
20518 + 1;
20519 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20520 let project = self.project()?.read(cx);
20521 let file = buffer.read(cx).file()?;
20522 let path = file.path().display(project.path_style(cx));
20523
20524 Some(format!("{path}:{selection}"))
20525 }) {
20526 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
20527 }
20528 }
20529
20530 pub fn open_permalink_to_line(
20531 &mut self,
20532 _: &OpenPermalinkToLine,
20533 window: &mut Window,
20534 cx: &mut Context<Self>,
20535 ) {
20536 let permalink_task = self.get_permalink_to_line(cx);
20537 let workspace = self.workspace();
20538
20539 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20540 Ok(permalink) => {
20541 cx.update(|_, cx| {
20542 cx.open_url(permalink.as_ref());
20543 })
20544 .ok();
20545 }
20546 Err(err) => {
20547 let message = format!("Failed to open permalink: {err}");
20548
20549 anyhow::Result::<()>::Err(err).log_err();
20550
20551 if let Some(workspace) = workspace {
20552 workspace
20553 .update(cx, |workspace, cx| {
20554 struct OpenPermalinkToLine;
20555
20556 workspace.show_toast(
20557 Toast::new(
20558 NotificationId::unique::<OpenPermalinkToLine>(),
20559 message,
20560 ),
20561 cx,
20562 )
20563 })
20564 .ok();
20565 }
20566 }
20567 })
20568 .detach();
20569 }
20570
20571 pub fn insert_uuid_v4(
20572 &mut self,
20573 _: &InsertUuidV4,
20574 window: &mut Window,
20575 cx: &mut Context<Self>,
20576 ) {
20577 self.insert_uuid(UuidVersion::V4, window, cx);
20578 }
20579
20580 pub fn insert_uuid_v7(
20581 &mut self,
20582 _: &InsertUuidV7,
20583 window: &mut Window,
20584 cx: &mut Context<Self>,
20585 ) {
20586 self.insert_uuid(UuidVersion::V7, window, cx);
20587 }
20588
20589 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20590 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20591 self.transact(window, cx, |this, window, cx| {
20592 let edits = this
20593 .selections
20594 .all::<Point>(&this.display_snapshot(cx))
20595 .into_iter()
20596 .map(|selection| {
20597 let uuid = match version {
20598 UuidVersion::V4 => uuid::Uuid::new_v4(),
20599 UuidVersion::V7 => uuid::Uuid::now_v7(),
20600 };
20601
20602 (selection.range(), uuid.to_string())
20603 });
20604 this.edit(edits, cx);
20605 this.refresh_edit_prediction(true, false, window, cx);
20606 });
20607 }
20608
20609 pub fn open_selections_in_multibuffer(
20610 &mut self,
20611 _: &OpenSelectionsInMultibuffer,
20612 window: &mut Window,
20613 cx: &mut Context<Self>,
20614 ) {
20615 let multibuffer = self.buffer.read(cx);
20616
20617 let Some(buffer) = multibuffer.as_singleton() else {
20618 return;
20619 };
20620
20621 let Some(workspace) = self.workspace() else {
20622 return;
20623 };
20624
20625 let title = multibuffer.title(cx).to_string();
20626
20627 let locations = self
20628 .selections
20629 .all_anchors(&self.display_snapshot(cx))
20630 .iter()
20631 .map(|selection| {
20632 (
20633 buffer.clone(),
20634 (selection.start.text_anchor..selection.end.text_anchor)
20635 .to_point(buffer.read(cx)),
20636 )
20637 })
20638 .into_group_map();
20639
20640 cx.spawn_in(window, async move |_, cx| {
20641 workspace.update_in(cx, |workspace, window, cx| {
20642 Self::open_locations_in_multibuffer(
20643 workspace,
20644 locations,
20645 format!("Selections for '{title}'"),
20646 false,
20647 MultibufferSelectionMode::All,
20648 window,
20649 cx,
20650 );
20651 })
20652 })
20653 .detach();
20654 }
20655
20656 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20657 /// last highlight added will be used.
20658 ///
20659 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20660 pub fn highlight_rows<T: 'static>(
20661 &mut self,
20662 range: Range<Anchor>,
20663 color: Hsla,
20664 options: RowHighlightOptions,
20665 cx: &mut Context<Self>,
20666 ) {
20667 let snapshot = self.buffer().read(cx).snapshot(cx);
20668 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20669 let ix = row_highlights.binary_search_by(|highlight| {
20670 Ordering::Equal
20671 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20672 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20673 });
20674
20675 if let Err(mut ix) = ix {
20676 let index = post_inc(&mut self.highlight_order);
20677
20678 // If this range intersects with the preceding highlight, then merge it with
20679 // the preceding highlight. Otherwise insert a new highlight.
20680 let mut merged = false;
20681 if ix > 0 {
20682 let prev_highlight = &mut row_highlights[ix - 1];
20683 if prev_highlight
20684 .range
20685 .end
20686 .cmp(&range.start, &snapshot)
20687 .is_ge()
20688 {
20689 ix -= 1;
20690 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20691 prev_highlight.range.end = range.end;
20692 }
20693 merged = true;
20694 prev_highlight.index = index;
20695 prev_highlight.color = color;
20696 prev_highlight.options = options;
20697 }
20698 }
20699
20700 if !merged {
20701 row_highlights.insert(
20702 ix,
20703 RowHighlight {
20704 range,
20705 index,
20706 color,
20707 options,
20708 type_id: TypeId::of::<T>(),
20709 },
20710 );
20711 }
20712
20713 // If any of the following highlights intersect with this one, merge them.
20714 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20715 let highlight = &row_highlights[ix];
20716 if next_highlight
20717 .range
20718 .start
20719 .cmp(&highlight.range.end, &snapshot)
20720 .is_le()
20721 {
20722 if next_highlight
20723 .range
20724 .end
20725 .cmp(&highlight.range.end, &snapshot)
20726 .is_gt()
20727 {
20728 row_highlights[ix].range.end = next_highlight.range.end;
20729 }
20730 row_highlights.remove(ix + 1);
20731 } else {
20732 break;
20733 }
20734 }
20735 }
20736 }
20737
20738 /// Remove any highlighted row ranges of the given type that intersect the
20739 /// given ranges.
20740 pub fn remove_highlighted_rows<T: 'static>(
20741 &mut self,
20742 ranges_to_remove: Vec<Range<Anchor>>,
20743 cx: &mut Context<Self>,
20744 ) {
20745 let snapshot = self.buffer().read(cx).snapshot(cx);
20746 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20747 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20748 row_highlights.retain(|highlight| {
20749 while let Some(range_to_remove) = ranges_to_remove.peek() {
20750 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20751 Ordering::Less | Ordering::Equal => {
20752 ranges_to_remove.next();
20753 }
20754 Ordering::Greater => {
20755 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20756 Ordering::Less | Ordering::Equal => {
20757 return false;
20758 }
20759 Ordering::Greater => break,
20760 }
20761 }
20762 }
20763 }
20764
20765 true
20766 })
20767 }
20768
20769 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20770 pub fn clear_row_highlights<T: 'static>(&mut self) {
20771 self.highlighted_rows.remove(&TypeId::of::<T>());
20772 }
20773
20774 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20775 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20776 self.highlighted_rows
20777 .get(&TypeId::of::<T>())
20778 .map_or(&[] as &[_], |vec| vec.as_slice())
20779 .iter()
20780 .map(|highlight| (highlight.range.clone(), highlight.color))
20781 }
20782
20783 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20784 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20785 /// Allows to ignore certain kinds of highlights.
20786 pub fn highlighted_display_rows(
20787 &self,
20788 window: &mut Window,
20789 cx: &mut App,
20790 ) -> BTreeMap<DisplayRow, LineHighlight> {
20791 let snapshot = self.snapshot(window, cx);
20792 let mut used_highlight_orders = HashMap::default();
20793 self.highlighted_rows
20794 .iter()
20795 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20796 .fold(
20797 BTreeMap::<DisplayRow, LineHighlight>::new(),
20798 |mut unique_rows, highlight| {
20799 let start = highlight.range.start.to_display_point(&snapshot);
20800 let end = highlight.range.end.to_display_point(&snapshot);
20801 let start_row = start.row().0;
20802 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
20803 {
20804 end.row().0.saturating_sub(1)
20805 } else {
20806 end.row().0
20807 };
20808 for row in start_row..=end_row {
20809 let used_index =
20810 used_highlight_orders.entry(row).or_insert(highlight.index);
20811 if highlight.index >= *used_index {
20812 *used_index = highlight.index;
20813 unique_rows.insert(
20814 DisplayRow(row),
20815 LineHighlight {
20816 include_gutter: highlight.options.include_gutter,
20817 border: None,
20818 background: highlight.color.into(),
20819 type_id: Some(highlight.type_id),
20820 },
20821 );
20822 }
20823 }
20824 unique_rows
20825 },
20826 )
20827 }
20828
20829 pub fn highlighted_display_row_for_autoscroll(
20830 &self,
20831 snapshot: &DisplaySnapshot,
20832 ) -> Option<DisplayRow> {
20833 self.highlighted_rows
20834 .values()
20835 .flat_map(|highlighted_rows| highlighted_rows.iter())
20836 .filter_map(|highlight| {
20837 if highlight.options.autoscroll {
20838 Some(highlight.range.start.to_display_point(snapshot).row())
20839 } else {
20840 None
20841 }
20842 })
20843 .min()
20844 }
20845
20846 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20847 self.highlight_background::<SearchWithinRange>(
20848 ranges,
20849 |colors| colors.colors().editor_document_highlight_read_background,
20850 cx,
20851 )
20852 }
20853
20854 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20855 self.breadcrumb_header = Some(new_header);
20856 }
20857
20858 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20859 self.clear_background_highlights::<SearchWithinRange>(cx);
20860 }
20861
20862 pub fn highlight_background<T: 'static>(
20863 &mut self,
20864 ranges: &[Range<Anchor>],
20865 color_fetcher: fn(&Theme) -> Hsla,
20866 cx: &mut Context<Self>,
20867 ) {
20868 self.background_highlights.insert(
20869 HighlightKey::Type(TypeId::of::<T>()),
20870 (color_fetcher, Arc::from(ranges)),
20871 );
20872 self.scrollbar_marker_state.dirty = true;
20873 cx.notify();
20874 }
20875
20876 pub fn highlight_background_key<T: 'static>(
20877 &mut self,
20878 key: usize,
20879 ranges: &[Range<Anchor>],
20880 color_fetcher: fn(&Theme) -> Hsla,
20881 cx: &mut Context<Self>,
20882 ) {
20883 self.background_highlights.insert(
20884 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20885 (color_fetcher, Arc::from(ranges)),
20886 );
20887 self.scrollbar_marker_state.dirty = true;
20888 cx.notify();
20889 }
20890
20891 pub fn clear_background_highlights<T: 'static>(
20892 &mut self,
20893 cx: &mut Context<Self>,
20894 ) -> Option<BackgroundHighlight> {
20895 let text_highlights = self
20896 .background_highlights
20897 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20898 if !text_highlights.1.is_empty() {
20899 self.scrollbar_marker_state.dirty = true;
20900 cx.notify();
20901 }
20902 Some(text_highlights)
20903 }
20904
20905 pub fn highlight_gutter<T: 'static>(
20906 &mut self,
20907 ranges: impl Into<Vec<Range<Anchor>>>,
20908 color_fetcher: fn(&App) -> Hsla,
20909 cx: &mut Context<Self>,
20910 ) {
20911 self.gutter_highlights
20912 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20913 cx.notify();
20914 }
20915
20916 pub fn clear_gutter_highlights<T: 'static>(
20917 &mut self,
20918 cx: &mut Context<Self>,
20919 ) -> Option<GutterHighlight> {
20920 cx.notify();
20921 self.gutter_highlights.remove(&TypeId::of::<T>())
20922 }
20923
20924 pub fn insert_gutter_highlight<T: 'static>(
20925 &mut self,
20926 range: Range<Anchor>,
20927 color_fetcher: fn(&App) -> Hsla,
20928 cx: &mut Context<Self>,
20929 ) {
20930 let snapshot = self.buffer().read(cx).snapshot(cx);
20931 let mut highlights = self
20932 .gutter_highlights
20933 .remove(&TypeId::of::<T>())
20934 .map(|(_, highlights)| highlights)
20935 .unwrap_or_default();
20936 let ix = highlights.binary_search_by(|highlight| {
20937 Ordering::Equal
20938 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20939 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20940 });
20941 if let Err(ix) = ix {
20942 highlights.insert(ix, range);
20943 }
20944 self.gutter_highlights
20945 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20946 }
20947
20948 pub fn remove_gutter_highlights<T: 'static>(
20949 &mut self,
20950 ranges_to_remove: Vec<Range<Anchor>>,
20951 cx: &mut Context<Self>,
20952 ) {
20953 let snapshot = self.buffer().read(cx).snapshot(cx);
20954 let Some((color_fetcher, mut gutter_highlights)) =
20955 self.gutter_highlights.remove(&TypeId::of::<T>())
20956 else {
20957 return;
20958 };
20959 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20960 gutter_highlights.retain(|highlight| {
20961 while let Some(range_to_remove) = ranges_to_remove.peek() {
20962 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20963 Ordering::Less | Ordering::Equal => {
20964 ranges_to_remove.next();
20965 }
20966 Ordering::Greater => {
20967 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20968 Ordering::Less | Ordering::Equal => {
20969 return false;
20970 }
20971 Ordering::Greater => break,
20972 }
20973 }
20974 }
20975 }
20976
20977 true
20978 });
20979 self.gutter_highlights
20980 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20981 }
20982
20983 #[cfg(feature = "test-support")]
20984 pub fn all_text_highlights(
20985 &self,
20986 window: &mut Window,
20987 cx: &mut Context<Self>,
20988 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20989 let snapshot = self.snapshot(window, cx);
20990 self.display_map.update(cx, |display_map, _| {
20991 display_map
20992 .all_text_highlights()
20993 .map(|highlight| {
20994 let (style, ranges) = highlight.as_ref();
20995 (
20996 *style,
20997 ranges
20998 .iter()
20999 .map(|range| range.clone().to_display_points(&snapshot))
21000 .collect(),
21001 )
21002 })
21003 .collect()
21004 })
21005 }
21006
21007 #[cfg(feature = "test-support")]
21008 pub fn all_text_background_highlights(
21009 &self,
21010 window: &mut Window,
21011 cx: &mut Context<Self>,
21012 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21013 let snapshot = self.snapshot(window, cx);
21014 let buffer = &snapshot.buffer_snapshot();
21015 let start = buffer.anchor_before(MultiBufferOffset(0));
21016 let end = buffer.anchor_after(buffer.len());
21017 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21018 }
21019
21020 #[cfg(any(test, feature = "test-support"))]
21021 pub fn sorted_background_highlights_in_range(
21022 &self,
21023 search_range: Range<Anchor>,
21024 display_snapshot: &DisplaySnapshot,
21025 theme: &Theme,
21026 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21027 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21028 res.sort_by(|a, b| {
21029 a.0.start
21030 .cmp(&b.0.start)
21031 .then_with(|| a.0.end.cmp(&b.0.end))
21032 .then_with(|| a.1.cmp(&b.1))
21033 });
21034 res
21035 }
21036
21037 #[cfg(feature = "test-support")]
21038 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21039 let snapshot = self.buffer().read(cx).snapshot(cx);
21040
21041 let highlights = self
21042 .background_highlights
21043 .get(&HighlightKey::Type(TypeId::of::<
21044 items::BufferSearchHighlights,
21045 >()));
21046
21047 if let Some((_color, ranges)) = highlights {
21048 ranges
21049 .iter()
21050 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21051 .collect_vec()
21052 } else {
21053 vec![]
21054 }
21055 }
21056
21057 fn document_highlights_for_position<'a>(
21058 &'a self,
21059 position: Anchor,
21060 buffer: &'a MultiBufferSnapshot,
21061 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21062 let read_highlights = self
21063 .background_highlights
21064 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21065 .map(|h| &h.1);
21066 let write_highlights = self
21067 .background_highlights
21068 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21069 .map(|h| &h.1);
21070 let left_position = position.bias_left(buffer);
21071 let right_position = position.bias_right(buffer);
21072 read_highlights
21073 .into_iter()
21074 .chain(write_highlights)
21075 .flat_map(move |ranges| {
21076 let start_ix = match ranges.binary_search_by(|probe| {
21077 let cmp = probe.end.cmp(&left_position, buffer);
21078 if cmp.is_ge() {
21079 Ordering::Greater
21080 } else {
21081 Ordering::Less
21082 }
21083 }) {
21084 Ok(i) | Err(i) => i,
21085 };
21086
21087 ranges[start_ix..]
21088 .iter()
21089 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21090 })
21091 }
21092
21093 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21094 self.background_highlights
21095 .get(&HighlightKey::Type(TypeId::of::<T>()))
21096 .is_some_and(|(_, highlights)| !highlights.is_empty())
21097 }
21098
21099 /// Returns all background highlights for a given range.
21100 ///
21101 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21102 pub fn background_highlights_in_range(
21103 &self,
21104 search_range: Range<Anchor>,
21105 display_snapshot: &DisplaySnapshot,
21106 theme: &Theme,
21107 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21108 let mut results = Vec::new();
21109 for (color_fetcher, ranges) in self.background_highlights.values() {
21110 let color = color_fetcher(theme);
21111 let start_ix = match ranges.binary_search_by(|probe| {
21112 let cmp = probe
21113 .end
21114 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21115 if cmp.is_gt() {
21116 Ordering::Greater
21117 } else {
21118 Ordering::Less
21119 }
21120 }) {
21121 Ok(i) | Err(i) => i,
21122 };
21123 for range in &ranges[start_ix..] {
21124 if range
21125 .start
21126 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21127 .is_ge()
21128 {
21129 break;
21130 }
21131
21132 let start = range.start.to_display_point(display_snapshot);
21133 let end = range.end.to_display_point(display_snapshot);
21134 results.push((start..end, color))
21135 }
21136 }
21137 results
21138 }
21139
21140 pub fn gutter_highlights_in_range(
21141 &self,
21142 search_range: Range<Anchor>,
21143 display_snapshot: &DisplaySnapshot,
21144 cx: &App,
21145 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21146 let mut results = Vec::new();
21147 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21148 let color = color_fetcher(cx);
21149 let start_ix = match ranges.binary_search_by(|probe| {
21150 let cmp = probe
21151 .end
21152 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21153 if cmp.is_gt() {
21154 Ordering::Greater
21155 } else {
21156 Ordering::Less
21157 }
21158 }) {
21159 Ok(i) | Err(i) => i,
21160 };
21161 for range in &ranges[start_ix..] {
21162 if range
21163 .start
21164 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21165 .is_ge()
21166 {
21167 break;
21168 }
21169
21170 let start = range.start.to_display_point(display_snapshot);
21171 let end = range.end.to_display_point(display_snapshot);
21172 results.push((start..end, color))
21173 }
21174 }
21175 results
21176 }
21177
21178 /// Get the text ranges corresponding to the redaction query
21179 pub fn redacted_ranges(
21180 &self,
21181 search_range: Range<Anchor>,
21182 display_snapshot: &DisplaySnapshot,
21183 cx: &App,
21184 ) -> Vec<Range<DisplayPoint>> {
21185 display_snapshot
21186 .buffer_snapshot()
21187 .redacted_ranges(search_range, |file| {
21188 if let Some(file) = file {
21189 file.is_private()
21190 && EditorSettings::get(
21191 Some(SettingsLocation {
21192 worktree_id: file.worktree_id(cx),
21193 path: file.path().as_ref(),
21194 }),
21195 cx,
21196 )
21197 .redact_private_values
21198 } else {
21199 false
21200 }
21201 })
21202 .map(|range| {
21203 range.start.to_display_point(display_snapshot)
21204 ..range.end.to_display_point(display_snapshot)
21205 })
21206 .collect()
21207 }
21208
21209 pub fn highlight_text_key<T: 'static>(
21210 &mut self,
21211 key: usize,
21212 ranges: Vec<Range<Anchor>>,
21213 style: HighlightStyle,
21214 merge: bool,
21215 cx: &mut Context<Self>,
21216 ) {
21217 self.display_map.update(cx, |map, cx| {
21218 map.highlight_text(
21219 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21220 ranges,
21221 style,
21222 merge,
21223 cx,
21224 );
21225 });
21226 cx.notify();
21227 }
21228
21229 pub fn highlight_text<T: 'static>(
21230 &mut self,
21231 ranges: Vec<Range<Anchor>>,
21232 style: HighlightStyle,
21233 cx: &mut Context<Self>,
21234 ) {
21235 self.display_map.update(cx, |map, cx| {
21236 map.highlight_text(
21237 HighlightKey::Type(TypeId::of::<T>()),
21238 ranges,
21239 style,
21240 false,
21241 cx,
21242 )
21243 });
21244 cx.notify();
21245 }
21246
21247 pub fn text_highlights<'a, T: 'static>(
21248 &'a self,
21249 cx: &'a App,
21250 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21251 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21252 }
21253
21254 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21255 let cleared = self
21256 .display_map
21257 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21258 if cleared {
21259 cx.notify();
21260 }
21261 }
21262
21263 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21264 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21265 && self.focus_handle.is_focused(window)
21266 }
21267
21268 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21269 self.show_cursor_when_unfocused = is_enabled;
21270 cx.notify();
21271 }
21272
21273 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21274 cx.notify();
21275 }
21276
21277 fn on_debug_session_event(
21278 &mut self,
21279 _session: Entity<Session>,
21280 event: &SessionEvent,
21281 cx: &mut Context<Self>,
21282 ) {
21283 if let SessionEvent::InvalidateInlineValue = event {
21284 self.refresh_inline_values(cx);
21285 }
21286 }
21287
21288 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21289 let Some(project) = self.project.clone() else {
21290 return;
21291 };
21292
21293 if !self.inline_value_cache.enabled {
21294 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21295 self.splice_inlays(&inlays, Vec::new(), cx);
21296 return;
21297 }
21298
21299 let current_execution_position = self
21300 .highlighted_rows
21301 .get(&TypeId::of::<ActiveDebugLine>())
21302 .and_then(|lines| lines.last().map(|line| line.range.end));
21303
21304 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21305 let inline_values = editor
21306 .update(cx, |editor, cx| {
21307 let Some(current_execution_position) = current_execution_position else {
21308 return Some(Task::ready(Ok(Vec::new())));
21309 };
21310
21311 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21312 let snapshot = buffer.snapshot(cx);
21313
21314 let excerpt = snapshot.excerpt_containing(
21315 current_execution_position..current_execution_position,
21316 )?;
21317
21318 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21319 })?;
21320
21321 let range =
21322 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21323
21324 project.inline_values(buffer, range, cx)
21325 })
21326 .ok()
21327 .flatten()?
21328 .await
21329 .context("refreshing debugger inlays")
21330 .log_err()?;
21331
21332 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21333
21334 for (buffer_id, inline_value) in inline_values
21335 .into_iter()
21336 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21337 {
21338 buffer_inline_values
21339 .entry(buffer_id)
21340 .or_default()
21341 .push(inline_value);
21342 }
21343
21344 editor
21345 .update(cx, |editor, cx| {
21346 let snapshot = editor.buffer.read(cx).snapshot(cx);
21347 let mut new_inlays = Vec::default();
21348
21349 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21350 let buffer_id = buffer_snapshot.remote_id();
21351 buffer_inline_values
21352 .get(&buffer_id)
21353 .into_iter()
21354 .flatten()
21355 .for_each(|hint| {
21356 let inlay = Inlay::debugger(
21357 post_inc(&mut editor.next_inlay_id),
21358 Anchor::in_buffer(excerpt_id, hint.position),
21359 hint.text(),
21360 );
21361 if !inlay.text().chars().contains(&'\n') {
21362 new_inlays.push(inlay);
21363 }
21364 });
21365 }
21366
21367 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21368 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21369
21370 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21371 })
21372 .ok()?;
21373 Some(())
21374 });
21375 }
21376
21377 fn on_buffer_event(
21378 &mut self,
21379 multibuffer: &Entity<MultiBuffer>,
21380 event: &multi_buffer::Event,
21381 window: &mut Window,
21382 cx: &mut Context<Self>,
21383 ) {
21384 match event {
21385 multi_buffer::Event::Edited { edited_buffer } => {
21386 self.scrollbar_marker_state.dirty = true;
21387 self.active_indent_guides_state.dirty = true;
21388 self.refresh_active_diagnostics(cx);
21389 self.refresh_code_actions(window, cx);
21390 self.refresh_single_line_folds(window, cx);
21391 self.refresh_matching_bracket_highlights(window, cx);
21392 if self.has_active_edit_prediction() {
21393 self.update_visible_edit_prediction(window, cx);
21394 }
21395
21396 if let Some(buffer) = edited_buffer {
21397 if buffer.read(cx).file().is_none() {
21398 cx.emit(EditorEvent::TitleChanged);
21399 }
21400
21401 if self.project.is_some() {
21402 let buffer_id = buffer.read(cx).remote_id();
21403 self.register_buffer(buffer_id, cx);
21404 self.update_lsp_data(Some(buffer_id), window, cx);
21405 self.refresh_inlay_hints(
21406 InlayHintRefreshReason::BufferEdited(buffer_id),
21407 cx,
21408 );
21409 }
21410 }
21411
21412 cx.emit(EditorEvent::BufferEdited);
21413 cx.emit(SearchEvent::MatchesInvalidated);
21414
21415 let Some(project) = &self.project else { return };
21416 let (telemetry, is_via_ssh) = {
21417 let project = project.read(cx);
21418 let telemetry = project.client().telemetry().clone();
21419 let is_via_ssh = project.is_via_remote_server();
21420 (telemetry, is_via_ssh)
21421 };
21422 telemetry.log_edit_event("editor", is_via_ssh);
21423 }
21424 multi_buffer::Event::ExcerptsAdded {
21425 buffer,
21426 predecessor,
21427 excerpts,
21428 } => {
21429 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21430 let buffer_id = buffer.read(cx).remote_id();
21431 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21432 && let Some(project) = &self.project
21433 {
21434 update_uncommitted_diff_for_buffer(
21435 cx.entity(),
21436 project,
21437 [buffer.clone()],
21438 self.buffer.clone(),
21439 cx,
21440 )
21441 .detach();
21442 }
21443 self.update_lsp_data(Some(buffer_id), window, cx);
21444 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21445 self.colorize_brackets(false, cx);
21446 cx.emit(EditorEvent::ExcerptsAdded {
21447 buffer: buffer.clone(),
21448 predecessor: *predecessor,
21449 excerpts: excerpts.clone(),
21450 });
21451 }
21452 multi_buffer::Event::ExcerptsRemoved {
21453 ids,
21454 removed_buffer_ids,
21455 } => {
21456 if let Some(inlay_hints) = &mut self.inlay_hints {
21457 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21458 }
21459 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21460 for buffer_id in removed_buffer_ids {
21461 self.registered_buffers.remove(buffer_id);
21462 }
21463 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21464 cx.emit(EditorEvent::ExcerptsRemoved {
21465 ids: ids.clone(),
21466 removed_buffer_ids: removed_buffer_ids.clone(),
21467 });
21468 }
21469 multi_buffer::Event::ExcerptsEdited {
21470 excerpt_ids,
21471 buffer_ids,
21472 } => {
21473 self.display_map.update(cx, |map, cx| {
21474 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21475 });
21476 cx.emit(EditorEvent::ExcerptsEdited {
21477 ids: excerpt_ids.clone(),
21478 });
21479 }
21480 multi_buffer::Event::ExcerptsExpanded { ids } => {
21481 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21482 self.refresh_document_highlights(cx);
21483 for id in ids {
21484 self.fetched_tree_sitter_chunks.remove(id);
21485 }
21486 self.colorize_brackets(false, cx);
21487 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21488 }
21489 multi_buffer::Event::Reparsed(buffer_id) => {
21490 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21491 self.refresh_selected_text_highlights(true, window, cx);
21492 self.colorize_brackets(true, cx);
21493 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21494
21495 cx.emit(EditorEvent::Reparsed(*buffer_id));
21496 }
21497 multi_buffer::Event::DiffHunksToggled => {
21498 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21499 }
21500 multi_buffer::Event::LanguageChanged(buffer_id) => {
21501 self.registered_buffers.remove(&buffer_id);
21502 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21503 cx.emit(EditorEvent::Reparsed(*buffer_id));
21504 cx.notify();
21505 }
21506 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21507 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21508 multi_buffer::Event::FileHandleChanged
21509 | multi_buffer::Event::Reloaded
21510 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21511 multi_buffer::Event::DiagnosticsUpdated => {
21512 self.update_diagnostics_state(window, cx);
21513 }
21514 _ => {}
21515 };
21516 }
21517
21518 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21519 if !self.diagnostics_enabled() {
21520 return;
21521 }
21522 self.refresh_active_diagnostics(cx);
21523 self.refresh_inline_diagnostics(true, window, cx);
21524 self.scrollbar_marker_state.dirty = true;
21525 cx.notify();
21526 }
21527
21528 pub fn start_temporary_diff_override(&mut self) {
21529 self.load_diff_task.take();
21530 self.temporary_diff_override = true;
21531 }
21532
21533 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21534 self.temporary_diff_override = false;
21535 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21536 self.buffer.update(cx, |buffer, cx| {
21537 buffer.set_all_diff_hunks_collapsed(cx);
21538 });
21539
21540 if let Some(project) = self.project.clone() {
21541 self.load_diff_task = Some(
21542 update_uncommitted_diff_for_buffer(
21543 cx.entity(),
21544 &project,
21545 self.buffer.read(cx).all_buffers(),
21546 self.buffer.clone(),
21547 cx,
21548 )
21549 .shared(),
21550 );
21551 }
21552 }
21553
21554 fn on_display_map_changed(
21555 &mut self,
21556 _: Entity<DisplayMap>,
21557 _: &mut Window,
21558 cx: &mut Context<Self>,
21559 ) {
21560 cx.notify();
21561 }
21562
21563 fn fetch_accent_overrides(&self, cx: &App) -> Vec<SharedString> {
21564 if !self.mode.is_full() {
21565 return Vec::new();
21566 }
21567
21568 let theme_settings = theme::ThemeSettings::get_global(cx);
21569
21570 theme_settings
21571 .theme_overrides
21572 .get(cx.theme().name.as_ref())
21573 .map(|theme_style| &theme_style.accents)
21574 .into_iter()
21575 .flatten()
21576 .chain(
21577 theme_settings
21578 .experimental_theme_overrides
21579 .as_ref()
21580 .map(|overrides| &overrides.accents)
21581 .into_iter()
21582 .flatten(),
21583 )
21584 .flat_map(|accent| accent.0.clone())
21585 .collect()
21586 }
21587
21588 fn fetch_applicable_language_settings(
21589 &self,
21590 cx: &App,
21591 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21592 if !self.mode.is_full() {
21593 return HashMap::default();
21594 }
21595
21596 self.buffer().read(cx).all_buffers().into_iter().fold(
21597 HashMap::default(),
21598 |mut acc, buffer| {
21599 let buffer = buffer.read(cx);
21600 let language = buffer.language().map(|language| language.name());
21601 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21602 let file = buffer.file();
21603 v.insert(language_settings(language, file, cx).into_owned());
21604 }
21605 acc
21606 },
21607 )
21608 }
21609
21610 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21611 let new_language_settings = self.fetch_applicable_language_settings(cx);
21612 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21613 self.applicable_language_settings = new_language_settings;
21614
21615 let new_accent_overrides = self.fetch_accent_overrides(cx);
21616 let accent_overrides_changed = new_accent_overrides != self.accent_overrides;
21617 self.accent_overrides = new_accent_overrides;
21618
21619 if self.diagnostics_enabled() {
21620 let new_severity = EditorSettings::get_global(cx)
21621 .diagnostics_max_severity
21622 .unwrap_or(DiagnosticSeverity::Hint);
21623 self.set_max_diagnostics_severity(new_severity, cx);
21624 }
21625 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21626 self.update_edit_prediction_settings(cx);
21627 self.refresh_edit_prediction(true, false, window, cx);
21628 self.refresh_inline_values(cx);
21629 self.refresh_inlay_hints(
21630 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21631 self.selections.newest_anchor().head(),
21632 &self.buffer.read(cx).snapshot(cx),
21633 cx,
21634 )),
21635 cx,
21636 );
21637
21638 let old_cursor_shape = self.cursor_shape;
21639 let old_show_breadcrumbs = self.show_breadcrumbs;
21640
21641 {
21642 let editor_settings = EditorSettings::get_global(cx);
21643 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21644 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21645 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21646 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21647 }
21648
21649 if old_cursor_shape != self.cursor_shape {
21650 cx.emit(EditorEvent::CursorShapeChanged);
21651 }
21652
21653 if old_show_breadcrumbs != self.show_breadcrumbs {
21654 cx.emit(EditorEvent::BreadcrumbsChanged);
21655 }
21656
21657 let project_settings = ProjectSettings::get_global(cx);
21658 self.buffer_serialization = self
21659 .should_serialize_buffer()
21660 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21661
21662 if self.mode.is_full() {
21663 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21664 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21665 if self.show_inline_diagnostics != show_inline_diagnostics {
21666 self.show_inline_diagnostics = show_inline_diagnostics;
21667 self.refresh_inline_diagnostics(false, window, cx);
21668 }
21669
21670 if self.git_blame_inline_enabled != inline_blame_enabled {
21671 self.toggle_git_blame_inline_internal(false, window, cx);
21672 }
21673
21674 let minimap_settings = EditorSettings::get_global(cx).minimap;
21675 if self.minimap_visibility != MinimapVisibility::Disabled {
21676 if self.minimap_visibility.settings_visibility()
21677 != minimap_settings.minimap_enabled()
21678 {
21679 self.set_minimap_visibility(
21680 MinimapVisibility::for_mode(self.mode(), cx),
21681 window,
21682 cx,
21683 );
21684 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21685 minimap_entity.update(cx, |minimap_editor, cx| {
21686 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21687 })
21688 }
21689 }
21690
21691 if language_settings_changed || accent_overrides_changed {
21692 self.colorize_brackets(true, cx);
21693 }
21694
21695 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21696 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21697 }) {
21698 if !inlay_splice.is_empty() {
21699 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21700 }
21701 self.refresh_colors_for_visible_range(None, window, cx);
21702 }
21703 }
21704
21705 cx.notify();
21706 }
21707
21708 pub fn set_searchable(&mut self, searchable: bool) {
21709 self.searchable = searchable;
21710 }
21711
21712 pub fn searchable(&self) -> bool {
21713 self.searchable
21714 }
21715
21716 pub fn open_excerpts_in_split(
21717 &mut self,
21718 _: &OpenExcerptsSplit,
21719 window: &mut Window,
21720 cx: &mut Context<Self>,
21721 ) {
21722 self.open_excerpts_common(None, true, window, cx)
21723 }
21724
21725 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21726 self.open_excerpts_common(None, false, window, cx)
21727 }
21728
21729 fn open_excerpts_common(
21730 &mut self,
21731 jump_data: Option<JumpData>,
21732 split: bool,
21733 window: &mut Window,
21734 cx: &mut Context<Self>,
21735 ) {
21736 let Some(workspace) = self.workspace() else {
21737 cx.propagate();
21738 return;
21739 };
21740
21741 if self.buffer.read(cx).is_singleton() {
21742 cx.propagate();
21743 return;
21744 }
21745
21746 let mut new_selections_by_buffer = HashMap::default();
21747 match &jump_data {
21748 Some(JumpData::MultiBufferPoint {
21749 excerpt_id,
21750 position,
21751 anchor,
21752 line_offset_from_top,
21753 }) => {
21754 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21755 if let Some(buffer) = multi_buffer_snapshot
21756 .buffer_id_for_excerpt(*excerpt_id)
21757 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21758 {
21759 let buffer_snapshot = buffer.read(cx).snapshot();
21760 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21761 language::ToPoint::to_point(anchor, &buffer_snapshot)
21762 } else {
21763 buffer_snapshot.clip_point(*position, Bias::Left)
21764 };
21765 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21766 new_selections_by_buffer.insert(
21767 buffer,
21768 (
21769 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
21770 Some(*line_offset_from_top),
21771 ),
21772 );
21773 }
21774 }
21775 Some(JumpData::MultiBufferRow {
21776 row,
21777 line_offset_from_top,
21778 }) => {
21779 let point = MultiBufferPoint::new(row.0, 0);
21780 if let Some((buffer, buffer_point, _)) =
21781 self.buffer.read(cx).point_to_buffer_point(point, cx)
21782 {
21783 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21784 new_selections_by_buffer
21785 .entry(buffer)
21786 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21787 .0
21788 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
21789 }
21790 }
21791 None => {
21792 let selections = self
21793 .selections
21794 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
21795 let multi_buffer = self.buffer.read(cx);
21796 for selection in selections {
21797 for (snapshot, range, _, anchor) in multi_buffer
21798 .snapshot(cx)
21799 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21800 {
21801 if let Some(anchor) = anchor {
21802 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21803 else {
21804 continue;
21805 };
21806 let offset = text::ToOffset::to_offset(
21807 &anchor.text_anchor,
21808 &buffer_handle.read(cx).snapshot(),
21809 );
21810 let range = BufferOffset(offset)..BufferOffset(offset);
21811 new_selections_by_buffer
21812 .entry(buffer_handle)
21813 .or_insert((Vec::new(), None))
21814 .0
21815 .push(range)
21816 } else {
21817 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21818 else {
21819 continue;
21820 };
21821 new_selections_by_buffer
21822 .entry(buffer_handle)
21823 .or_insert((Vec::new(), None))
21824 .0
21825 .push(range)
21826 }
21827 }
21828 }
21829 }
21830 }
21831
21832 new_selections_by_buffer
21833 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21834
21835 if new_selections_by_buffer.is_empty() {
21836 return;
21837 }
21838
21839 // We defer the pane interaction because we ourselves are a workspace item
21840 // and activating a new item causes the pane to call a method on us reentrantly,
21841 // which panics if we're on the stack.
21842 window.defer(cx, move |window, cx| {
21843 workspace.update(cx, |workspace, cx| {
21844 let pane = if split {
21845 workspace.adjacent_pane(window, cx)
21846 } else {
21847 workspace.active_pane().clone()
21848 };
21849
21850 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21851 let editor = buffer
21852 .read(cx)
21853 .file()
21854 .is_none()
21855 .then(|| {
21856 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21857 // so `workspace.open_project_item` will never find them, always opening a new editor.
21858 // Instead, we try to activate the existing editor in the pane first.
21859 let (editor, pane_item_index) =
21860 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21861 let editor = item.downcast::<Editor>()?;
21862 let singleton_buffer =
21863 editor.read(cx).buffer().read(cx).as_singleton()?;
21864 if singleton_buffer == buffer {
21865 Some((editor, i))
21866 } else {
21867 None
21868 }
21869 })?;
21870 pane.update(cx, |pane, cx| {
21871 pane.activate_item(pane_item_index, true, true, window, cx)
21872 });
21873 Some(editor)
21874 })
21875 .flatten()
21876 .unwrap_or_else(|| {
21877 workspace.open_project_item::<Self>(
21878 pane.clone(),
21879 buffer,
21880 true,
21881 true,
21882 window,
21883 cx,
21884 )
21885 });
21886
21887 editor.update(cx, |editor, cx| {
21888 let autoscroll = match scroll_offset {
21889 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21890 None => Autoscroll::newest(),
21891 };
21892 let nav_history = editor.nav_history.take();
21893 editor.change_selections(
21894 SelectionEffects::scroll(autoscroll),
21895 window,
21896 cx,
21897 |s| {
21898 s.select_ranges(ranges.into_iter().map(|range| {
21899 // we checked that the editor is a singleton editor so the offsets are valid
21900 MultiBufferOffset(range.start.0)..MultiBufferOffset(range.end.0)
21901 }));
21902 },
21903 );
21904 editor.nav_history = nav_history;
21905 });
21906 }
21907 })
21908 });
21909 }
21910
21911 // For now, don't allow opening excerpts in buffers that aren't backed by
21912 // regular project files.
21913 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21914 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21915 }
21916
21917 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
21918 let snapshot = self.buffer.read(cx).read(cx);
21919 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21920 Some(
21921 ranges
21922 .iter()
21923 .map(move |range| {
21924 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21925 })
21926 .collect(),
21927 )
21928 }
21929
21930 fn selection_replacement_ranges(
21931 &self,
21932 range: Range<MultiBufferOffsetUtf16>,
21933 cx: &mut App,
21934 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
21935 let selections = self
21936 .selections
21937 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
21938 let newest_selection = selections
21939 .iter()
21940 .max_by_key(|selection| selection.id)
21941 .unwrap();
21942 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
21943 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
21944 let snapshot = self.buffer.read(cx).read(cx);
21945 selections
21946 .into_iter()
21947 .map(|mut selection| {
21948 selection.start.0.0 =
21949 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
21950 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
21951 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21952 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21953 })
21954 .collect()
21955 }
21956
21957 fn report_editor_event(
21958 &self,
21959 reported_event: ReportEditorEvent,
21960 file_extension: Option<String>,
21961 cx: &App,
21962 ) {
21963 if cfg!(any(test, feature = "test-support")) {
21964 return;
21965 }
21966
21967 let Some(project) = &self.project else { return };
21968
21969 // If None, we are in a file without an extension
21970 let file = self
21971 .buffer
21972 .read(cx)
21973 .as_singleton()
21974 .and_then(|b| b.read(cx).file());
21975 let file_extension = file_extension.or(file
21976 .as_ref()
21977 .and_then(|file| Path::new(file.file_name(cx)).extension())
21978 .and_then(|e| e.to_str())
21979 .map(|a| a.to_string()));
21980
21981 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
21982 .map(|vim_mode| vim_mode.0)
21983 .unwrap_or(false);
21984
21985 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21986 let copilot_enabled = edit_predictions_provider
21987 == language::language_settings::EditPredictionProvider::Copilot;
21988 let copilot_enabled_for_language = self
21989 .buffer
21990 .read(cx)
21991 .language_settings(cx)
21992 .show_edit_predictions;
21993
21994 let project = project.read(cx);
21995 let event_type = reported_event.event_type();
21996
21997 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21998 telemetry::event!(
21999 event_type,
22000 type = if auto_saved {"autosave"} else {"manual"},
22001 file_extension,
22002 vim_mode,
22003 copilot_enabled,
22004 copilot_enabled_for_language,
22005 edit_predictions_provider,
22006 is_via_ssh = project.is_via_remote_server(),
22007 );
22008 } else {
22009 telemetry::event!(
22010 event_type,
22011 file_extension,
22012 vim_mode,
22013 copilot_enabled,
22014 copilot_enabled_for_language,
22015 edit_predictions_provider,
22016 is_via_ssh = project.is_via_remote_server(),
22017 );
22018 };
22019 }
22020
22021 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22022 /// with each line being an array of {text, highlight} objects.
22023 fn copy_highlight_json(
22024 &mut self,
22025 _: &CopyHighlightJson,
22026 window: &mut Window,
22027 cx: &mut Context<Self>,
22028 ) {
22029 #[derive(Serialize)]
22030 struct Chunk<'a> {
22031 text: String,
22032 highlight: Option<&'a str>,
22033 }
22034
22035 let snapshot = self.buffer.read(cx).snapshot(cx);
22036 let range = self
22037 .selected_text_range(false, window, cx)
22038 .and_then(|selection| {
22039 if selection.range.is_empty() {
22040 None
22041 } else {
22042 Some(
22043 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22044 selection.range.start,
22045 )))
22046 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22047 selection.range.end,
22048 ))),
22049 )
22050 }
22051 })
22052 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22053
22054 let chunks = snapshot.chunks(range, true);
22055 let mut lines = Vec::new();
22056 let mut line: VecDeque<Chunk> = VecDeque::new();
22057
22058 let Some(style) = self.style.as_ref() else {
22059 return;
22060 };
22061
22062 for chunk in chunks {
22063 let highlight = chunk
22064 .syntax_highlight_id
22065 .and_then(|id| id.name(&style.syntax));
22066 let mut chunk_lines = chunk.text.split('\n').peekable();
22067 while let Some(text) = chunk_lines.next() {
22068 let mut merged_with_last_token = false;
22069 if let Some(last_token) = line.back_mut()
22070 && last_token.highlight == highlight
22071 {
22072 last_token.text.push_str(text);
22073 merged_with_last_token = true;
22074 }
22075
22076 if !merged_with_last_token {
22077 line.push_back(Chunk {
22078 text: text.into(),
22079 highlight,
22080 });
22081 }
22082
22083 if chunk_lines.peek().is_some() {
22084 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22085 line.pop_front();
22086 }
22087 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22088 line.pop_back();
22089 }
22090
22091 lines.push(mem::take(&mut line));
22092 }
22093 }
22094 }
22095
22096 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22097 return;
22098 };
22099 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22100 }
22101
22102 pub fn open_context_menu(
22103 &mut self,
22104 _: &OpenContextMenu,
22105 window: &mut Window,
22106 cx: &mut Context<Self>,
22107 ) {
22108 self.request_autoscroll(Autoscroll::newest(), cx);
22109 let position = self
22110 .selections
22111 .newest_display(&self.display_snapshot(cx))
22112 .start;
22113 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22114 }
22115
22116 pub fn replay_insert_event(
22117 &mut self,
22118 text: &str,
22119 relative_utf16_range: Option<Range<isize>>,
22120 window: &mut Window,
22121 cx: &mut Context<Self>,
22122 ) {
22123 if !self.input_enabled {
22124 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22125 return;
22126 }
22127 if let Some(relative_utf16_range) = relative_utf16_range {
22128 let selections = self
22129 .selections
22130 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22131 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22132 let new_ranges = selections.into_iter().map(|range| {
22133 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22134 range
22135 .head()
22136 .0
22137 .0
22138 .saturating_add_signed(relative_utf16_range.start),
22139 ));
22140 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22141 range
22142 .head()
22143 .0
22144 .0
22145 .saturating_add_signed(relative_utf16_range.end),
22146 ));
22147 start..end
22148 });
22149 s.select_ranges(new_ranges);
22150 });
22151 }
22152
22153 self.handle_input(text, window, cx);
22154 }
22155
22156 pub fn is_focused(&self, window: &Window) -> bool {
22157 self.focus_handle.is_focused(window)
22158 }
22159
22160 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22161 cx.emit(EditorEvent::Focused);
22162
22163 if let Some(descendant) = self
22164 .last_focused_descendant
22165 .take()
22166 .and_then(|descendant| descendant.upgrade())
22167 {
22168 window.focus(&descendant);
22169 } else {
22170 if let Some(blame) = self.blame.as_ref() {
22171 blame.update(cx, GitBlame::focus)
22172 }
22173
22174 self.blink_manager.update(cx, BlinkManager::enable);
22175 self.show_cursor_names(window, cx);
22176 self.buffer.update(cx, |buffer, cx| {
22177 buffer.finalize_last_transaction(cx);
22178 if self.leader_id.is_none() {
22179 buffer.set_active_selections(
22180 &self.selections.disjoint_anchors_arc(),
22181 self.selections.line_mode(),
22182 self.cursor_shape,
22183 cx,
22184 );
22185 }
22186 });
22187
22188 if let Some(position_map) = self.last_position_map.clone() {
22189 EditorElement::mouse_moved(
22190 self,
22191 &MouseMoveEvent {
22192 position: window.mouse_position(),
22193 pressed_button: None,
22194 modifiers: window.modifiers(),
22195 },
22196 &position_map,
22197 window,
22198 cx,
22199 );
22200 }
22201 }
22202 }
22203
22204 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22205 cx.emit(EditorEvent::FocusedIn)
22206 }
22207
22208 fn handle_focus_out(
22209 &mut self,
22210 event: FocusOutEvent,
22211 _window: &mut Window,
22212 cx: &mut Context<Self>,
22213 ) {
22214 if event.blurred != self.focus_handle {
22215 self.last_focused_descendant = Some(event.blurred);
22216 }
22217 self.selection_drag_state = SelectionDragState::None;
22218 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22219 }
22220
22221 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22222 self.blink_manager.update(cx, BlinkManager::disable);
22223 self.buffer
22224 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22225
22226 if let Some(blame) = self.blame.as_ref() {
22227 blame.update(cx, GitBlame::blur)
22228 }
22229 if !self.hover_state.focused(window, cx) {
22230 hide_hover(self, cx);
22231 }
22232 if !self
22233 .context_menu
22234 .borrow()
22235 .as_ref()
22236 .is_some_and(|context_menu| context_menu.focused(window, cx))
22237 {
22238 self.hide_context_menu(window, cx);
22239 }
22240 self.take_active_edit_prediction(cx);
22241 cx.emit(EditorEvent::Blurred);
22242 cx.notify();
22243 }
22244
22245 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22246 let mut pending: String = window
22247 .pending_input_keystrokes()
22248 .into_iter()
22249 .flatten()
22250 .filter_map(|keystroke| keystroke.key_char.clone())
22251 .collect();
22252
22253 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22254 pending = "".to_string();
22255 }
22256
22257 let existing_pending = self
22258 .text_highlights::<PendingInput>(cx)
22259 .map(|(_, ranges)| ranges.to_vec());
22260 if existing_pending.is_none() && pending.is_empty() {
22261 return;
22262 }
22263 let transaction =
22264 self.transact(window, cx, |this, window, cx| {
22265 let selections = this
22266 .selections
22267 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22268 let edits = selections
22269 .iter()
22270 .map(|selection| (selection.end..selection.end, pending.clone()));
22271 this.edit(edits, cx);
22272 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22273 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22274 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22275 }));
22276 });
22277 if let Some(existing_ranges) = existing_pending {
22278 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22279 this.edit(edits, cx);
22280 }
22281 });
22282
22283 let snapshot = self.snapshot(window, cx);
22284 let ranges = self
22285 .selections
22286 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22287 .into_iter()
22288 .map(|selection| {
22289 snapshot.buffer_snapshot().anchor_after(selection.end)
22290 ..snapshot
22291 .buffer_snapshot()
22292 .anchor_before(selection.end + pending.len())
22293 })
22294 .collect();
22295
22296 if pending.is_empty() {
22297 self.clear_highlights::<PendingInput>(cx);
22298 } else {
22299 self.highlight_text::<PendingInput>(
22300 ranges,
22301 HighlightStyle {
22302 underline: Some(UnderlineStyle {
22303 thickness: px(1.),
22304 color: None,
22305 wavy: false,
22306 }),
22307 ..Default::default()
22308 },
22309 cx,
22310 );
22311 }
22312
22313 self.ime_transaction = self.ime_transaction.or(transaction);
22314 if let Some(transaction) = self.ime_transaction {
22315 self.buffer.update(cx, |buffer, cx| {
22316 buffer.group_until_transaction(transaction, cx);
22317 });
22318 }
22319
22320 if self.text_highlights::<PendingInput>(cx).is_none() {
22321 self.ime_transaction.take();
22322 }
22323 }
22324
22325 pub fn register_action_renderer(
22326 &mut self,
22327 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22328 ) -> Subscription {
22329 let id = self.next_editor_action_id.post_inc();
22330 self.editor_actions
22331 .borrow_mut()
22332 .insert(id, Box::new(listener));
22333
22334 let editor_actions = self.editor_actions.clone();
22335 Subscription::new(move || {
22336 editor_actions.borrow_mut().remove(&id);
22337 })
22338 }
22339
22340 pub fn register_action<A: Action>(
22341 &mut self,
22342 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22343 ) -> Subscription {
22344 let id = self.next_editor_action_id.post_inc();
22345 let listener = Arc::new(listener);
22346 self.editor_actions.borrow_mut().insert(
22347 id,
22348 Box::new(move |_, window, _| {
22349 let listener = listener.clone();
22350 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22351 let action = action.downcast_ref().unwrap();
22352 if phase == DispatchPhase::Bubble {
22353 listener(action, window, cx)
22354 }
22355 })
22356 }),
22357 );
22358
22359 let editor_actions = self.editor_actions.clone();
22360 Subscription::new(move || {
22361 editor_actions.borrow_mut().remove(&id);
22362 })
22363 }
22364
22365 pub fn file_header_size(&self) -> u32 {
22366 FILE_HEADER_HEIGHT
22367 }
22368
22369 pub fn restore(
22370 &mut self,
22371 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22372 window: &mut Window,
22373 cx: &mut Context<Self>,
22374 ) {
22375 let workspace = self.workspace();
22376 let project = self.project();
22377 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22378 let mut tasks = Vec::new();
22379 for (buffer_id, changes) in revert_changes {
22380 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22381 buffer.update(cx, |buffer, cx| {
22382 buffer.edit(
22383 changes
22384 .into_iter()
22385 .map(|(range, text)| (range, text.to_string())),
22386 None,
22387 cx,
22388 );
22389 });
22390
22391 if let Some(project) =
22392 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22393 {
22394 project.update(cx, |project, cx| {
22395 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22396 })
22397 }
22398 }
22399 }
22400 tasks
22401 });
22402 cx.spawn_in(window, async move |_, cx| {
22403 for (buffer, task) in save_tasks {
22404 let result = task.await;
22405 if result.is_err() {
22406 let Some(path) = buffer
22407 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22408 .ok()
22409 else {
22410 continue;
22411 };
22412 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22413 let Some(task) = cx
22414 .update_window_entity(workspace, |workspace, window, cx| {
22415 workspace
22416 .open_path_preview(path, None, false, false, false, window, cx)
22417 })
22418 .ok()
22419 else {
22420 continue;
22421 };
22422 task.await.log_err();
22423 }
22424 }
22425 }
22426 })
22427 .detach();
22428 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22429 selections.refresh()
22430 });
22431 }
22432
22433 pub fn to_pixel_point(
22434 &self,
22435 source: multi_buffer::Anchor,
22436 editor_snapshot: &EditorSnapshot,
22437 window: &mut Window,
22438 ) -> Option<gpui::Point<Pixels>> {
22439 let source_point = source.to_display_point(editor_snapshot);
22440 self.display_to_pixel_point(source_point, editor_snapshot, window)
22441 }
22442
22443 pub fn display_to_pixel_point(
22444 &self,
22445 source: DisplayPoint,
22446 editor_snapshot: &EditorSnapshot,
22447 window: &mut Window,
22448 ) -> Option<gpui::Point<Pixels>> {
22449 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22450 let text_layout_details = self.text_layout_details(window);
22451 let scroll_top = text_layout_details
22452 .scroll_anchor
22453 .scroll_position(editor_snapshot)
22454 .y;
22455
22456 if source.row().as_f64() < scroll_top.floor() {
22457 return None;
22458 }
22459 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22460 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22461 Some(gpui::Point::new(source_x, source_y))
22462 }
22463
22464 pub fn has_visible_completions_menu(&self) -> bool {
22465 !self.edit_prediction_preview_is_active()
22466 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22467 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22468 })
22469 }
22470
22471 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22472 if self.mode.is_minimap() {
22473 return;
22474 }
22475 self.addons
22476 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22477 }
22478
22479 pub fn unregister_addon<T: Addon>(&mut self) {
22480 self.addons.remove(&std::any::TypeId::of::<T>());
22481 }
22482
22483 pub fn addon<T: Addon>(&self) -> Option<&T> {
22484 let type_id = std::any::TypeId::of::<T>();
22485 self.addons
22486 .get(&type_id)
22487 .and_then(|item| item.to_any().downcast_ref::<T>())
22488 }
22489
22490 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22491 let type_id = std::any::TypeId::of::<T>();
22492 self.addons
22493 .get_mut(&type_id)
22494 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22495 }
22496
22497 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22498 let text_layout_details = self.text_layout_details(window);
22499 let style = &text_layout_details.editor_style;
22500 let font_id = window.text_system().resolve_font(&style.text.font());
22501 let font_size = style.text.font_size.to_pixels(window.rem_size());
22502 let line_height = style.text.line_height_in_pixels(window.rem_size());
22503 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22504 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22505
22506 CharacterDimensions {
22507 em_width,
22508 em_advance,
22509 line_height,
22510 }
22511 }
22512
22513 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22514 self.load_diff_task.clone()
22515 }
22516
22517 fn read_metadata_from_db(
22518 &mut self,
22519 item_id: u64,
22520 workspace_id: WorkspaceId,
22521 window: &mut Window,
22522 cx: &mut Context<Editor>,
22523 ) {
22524 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22525 && !self.mode.is_minimap()
22526 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22527 {
22528 let buffer_snapshot = OnceCell::new();
22529
22530 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22531 && !folds.is_empty()
22532 {
22533 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22534 self.fold_ranges(
22535 folds
22536 .into_iter()
22537 .map(|(start, end)| {
22538 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22539 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22540 })
22541 .collect(),
22542 false,
22543 window,
22544 cx,
22545 );
22546 }
22547
22548 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22549 && !selections.is_empty()
22550 {
22551 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22552 // skip adding the initial selection to selection history
22553 self.selection_history.mode = SelectionHistoryMode::Skipping;
22554 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22555 s.select_ranges(selections.into_iter().map(|(start, end)| {
22556 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22557 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22558 }));
22559 });
22560 self.selection_history.mode = SelectionHistoryMode::Normal;
22561 };
22562 }
22563
22564 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22565 }
22566
22567 fn update_lsp_data(
22568 &mut self,
22569 for_buffer: Option<BufferId>,
22570 window: &mut Window,
22571 cx: &mut Context<'_, Self>,
22572 ) {
22573 self.pull_diagnostics(for_buffer, window, cx);
22574 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22575 }
22576
22577 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22578 if self.ignore_lsp_data() {
22579 return;
22580 }
22581 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22582 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22583 }
22584 }
22585
22586 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22587 if self.ignore_lsp_data() {
22588 return;
22589 }
22590
22591 if !self.registered_buffers.contains_key(&buffer_id)
22592 && let Some(project) = self.project.as_ref()
22593 {
22594 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22595 project.update(cx, |project, cx| {
22596 self.registered_buffers.insert(
22597 buffer_id,
22598 project.register_buffer_with_language_servers(&buffer, cx),
22599 );
22600 });
22601 } else {
22602 self.registered_buffers.remove(&buffer_id);
22603 }
22604 }
22605 }
22606
22607 fn ignore_lsp_data(&self) -> bool {
22608 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22609 // skip any LSP updates for it.
22610 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22611 }
22612}
22613
22614fn edit_for_markdown_paste<'a>(
22615 buffer: &MultiBufferSnapshot,
22616 range: Range<MultiBufferOffset>,
22617 to_insert: &'a str,
22618 url: Option<url::Url>,
22619) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
22620 if url.is_none() {
22621 return (range, Cow::Borrowed(to_insert));
22622 };
22623
22624 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22625
22626 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22627 Cow::Borrowed(to_insert)
22628 } else {
22629 Cow::Owned(format!("[{old_text}]({to_insert})"))
22630 };
22631 (range, new_text)
22632}
22633
22634fn process_completion_for_edit(
22635 completion: &Completion,
22636 intent: CompletionIntent,
22637 buffer: &Entity<Buffer>,
22638 cursor_position: &text::Anchor,
22639 cx: &mut Context<Editor>,
22640) -> CompletionEdit {
22641 let buffer = buffer.read(cx);
22642 let buffer_snapshot = buffer.snapshot();
22643 let (snippet, new_text) = if completion.is_snippet() {
22644 let mut snippet_source = completion.new_text.clone();
22645 // Workaround for typescript language server issues so that methods don't expand within
22646 // strings and functions with type expressions. The previous point is used because the query
22647 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22648 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22649 let previous_point = if previous_point.column > 0 {
22650 cursor_position.to_previous_offset(&buffer_snapshot)
22651 } else {
22652 cursor_position.to_offset(&buffer_snapshot)
22653 };
22654 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22655 && scope.prefers_label_for_snippet_in_completion()
22656 && let Some(label) = completion.label()
22657 && matches!(
22658 completion.kind(),
22659 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22660 )
22661 {
22662 snippet_source = label;
22663 }
22664 match Snippet::parse(&snippet_source).log_err() {
22665 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22666 None => (None, completion.new_text.clone()),
22667 }
22668 } else {
22669 (None, completion.new_text.clone())
22670 };
22671
22672 let mut range_to_replace = {
22673 let replace_range = &completion.replace_range;
22674 if let CompletionSource::Lsp {
22675 insert_range: Some(insert_range),
22676 ..
22677 } = &completion.source
22678 {
22679 debug_assert_eq!(
22680 insert_range.start, replace_range.start,
22681 "insert_range and replace_range should start at the same position"
22682 );
22683 debug_assert!(
22684 insert_range
22685 .start
22686 .cmp(cursor_position, &buffer_snapshot)
22687 .is_le(),
22688 "insert_range should start before or at cursor position"
22689 );
22690 debug_assert!(
22691 replace_range
22692 .start
22693 .cmp(cursor_position, &buffer_snapshot)
22694 .is_le(),
22695 "replace_range should start before or at cursor position"
22696 );
22697
22698 let should_replace = match intent {
22699 CompletionIntent::CompleteWithInsert => false,
22700 CompletionIntent::CompleteWithReplace => true,
22701 CompletionIntent::Complete | CompletionIntent::Compose => {
22702 let insert_mode =
22703 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22704 .completions
22705 .lsp_insert_mode;
22706 match insert_mode {
22707 LspInsertMode::Insert => false,
22708 LspInsertMode::Replace => true,
22709 LspInsertMode::ReplaceSubsequence => {
22710 let mut text_to_replace = buffer.chars_for_range(
22711 buffer.anchor_before(replace_range.start)
22712 ..buffer.anchor_after(replace_range.end),
22713 );
22714 let mut current_needle = text_to_replace.next();
22715 for haystack_ch in completion.label.text.chars() {
22716 if let Some(needle_ch) = current_needle
22717 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22718 {
22719 current_needle = text_to_replace.next();
22720 }
22721 }
22722 current_needle.is_none()
22723 }
22724 LspInsertMode::ReplaceSuffix => {
22725 if replace_range
22726 .end
22727 .cmp(cursor_position, &buffer_snapshot)
22728 .is_gt()
22729 {
22730 let range_after_cursor = *cursor_position..replace_range.end;
22731 let text_after_cursor = buffer
22732 .text_for_range(
22733 buffer.anchor_before(range_after_cursor.start)
22734 ..buffer.anchor_after(range_after_cursor.end),
22735 )
22736 .collect::<String>()
22737 .to_ascii_lowercase();
22738 completion
22739 .label
22740 .text
22741 .to_ascii_lowercase()
22742 .ends_with(&text_after_cursor)
22743 } else {
22744 true
22745 }
22746 }
22747 }
22748 }
22749 };
22750
22751 if should_replace {
22752 replace_range.clone()
22753 } else {
22754 insert_range.clone()
22755 }
22756 } else {
22757 replace_range.clone()
22758 }
22759 };
22760
22761 if range_to_replace
22762 .end
22763 .cmp(cursor_position, &buffer_snapshot)
22764 .is_lt()
22765 {
22766 range_to_replace.end = *cursor_position;
22767 }
22768
22769 let replace_range = range_to_replace.to_offset(buffer);
22770 CompletionEdit {
22771 new_text,
22772 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
22773 snippet,
22774 }
22775}
22776
22777struct CompletionEdit {
22778 new_text: String,
22779 replace_range: Range<BufferOffset>,
22780 snippet: Option<Snippet>,
22781}
22782
22783fn insert_extra_newline_brackets(
22784 buffer: &MultiBufferSnapshot,
22785 range: Range<MultiBufferOffset>,
22786 language: &language::LanguageScope,
22787) -> bool {
22788 let leading_whitespace_len = buffer
22789 .reversed_chars_at(range.start)
22790 .take_while(|c| c.is_whitespace() && *c != '\n')
22791 .map(|c| c.len_utf8())
22792 .sum::<usize>();
22793 let trailing_whitespace_len = buffer
22794 .chars_at(range.end)
22795 .take_while(|c| c.is_whitespace() && *c != '\n')
22796 .map(|c| c.len_utf8())
22797 .sum::<usize>();
22798 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22799
22800 language.brackets().any(|(pair, enabled)| {
22801 let pair_start = pair.start.trim_end();
22802 let pair_end = pair.end.trim_start();
22803
22804 enabled
22805 && pair.newline
22806 && buffer.contains_str_at(range.end, pair_end)
22807 && buffer.contains_str_at(
22808 range.start.saturating_sub_usize(pair_start.len()),
22809 pair_start,
22810 )
22811 })
22812}
22813
22814fn insert_extra_newline_tree_sitter(
22815 buffer: &MultiBufferSnapshot,
22816 range: Range<MultiBufferOffset>,
22817) -> bool {
22818 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22819 [(buffer, range, _)] => (*buffer, range.clone()),
22820 _ => return false,
22821 };
22822 let pair = {
22823 let mut result: Option<BracketMatch<usize>> = None;
22824
22825 for pair in buffer
22826 .all_bracket_ranges(range.start.0..range.end.0)
22827 .filter(move |pair| {
22828 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
22829 })
22830 {
22831 let len = pair.close_range.end - pair.open_range.start;
22832
22833 if let Some(existing) = &result {
22834 let existing_len = existing.close_range.end - existing.open_range.start;
22835 if len > existing_len {
22836 continue;
22837 }
22838 }
22839
22840 result = Some(pair);
22841 }
22842
22843 result
22844 };
22845 let Some(pair) = pair else {
22846 return false;
22847 };
22848 pair.newline_only
22849 && buffer
22850 .chars_for_range(pair.open_range.end..range.start.0)
22851 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
22852 .all(|c| c.is_whitespace() && c != '\n')
22853}
22854
22855fn update_uncommitted_diff_for_buffer(
22856 editor: Entity<Editor>,
22857 project: &Entity<Project>,
22858 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22859 buffer: Entity<MultiBuffer>,
22860 cx: &mut App,
22861) -> Task<()> {
22862 let mut tasks = Vec::new();
22863 project.update(cx, |project, cx| {
22864 for buffer in buffers {
22865 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22866 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22867 }
22868 }
22869 });
22870 cx.spawn(async move |cx| {
22871 let diffs = future::join_all(tasks).await;
22872 if editor
22873 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22874 .unwrap_or(false)
22875 {
22876 return;
22877 }
22878
22879 buffer
22880 .update(cx, |buffer, cx| {
22881 for diff in diffs.into_iter().flatten() {
22882 buffer.add_diff(diff, cx);
22883 }
22884 })
22885 .ok();
22886 })
22887}
22888
22889fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22890 let tab_size = tab_size.get() as usize;
22891 let mut width = offset;
22892
22893 for ch in text.chars() {
22894 width += if ch == '\t' {
22895 tab_size - (width % tab_size)
22896 } else {
22897 1
22898 };
22899 }
22900
22901 width - offset
22902}
22903
22904#[cfg(test)]
22905mod tests {
22906 use super::*;
22907
22908 #[test]
22909 fn test_string_size_with_expanded_tabs() {
22910 let nz = |val| NonZeroU32::new(val).unwrap();
22911 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22912 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22913 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22914 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22915 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22916 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22917 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22918 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22919 }
22920}
22921
22922/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22923struct WordBreakingTokenizer<'a> {
22924 input: &'a str,
22925}
22926
22927impl<'a> WordBreakingTokenizer<'a> {
22928 fn new(input: &'a str) -> Self {
22929 Self { input }
22930 }
22931}
22932
22933fn is_char_ideographic(ch: char) -> bool {
22934 use unicode_script::Script::*;
22935 use unicode_script::UnicodeScript;
22936 matches!(ch.script(), Han | Tangut | Yi)
22937}
22938
22939fn is_grapheme_ideographic(text: &str) -> bool {
22940 text.chars().any(is_char_ideographic)
22941}
22942
22943fn is_grapheme_whitespace(text: &str) -> bool {
22944 text.chars().any(|x| x.is_whitespace())
22945}
22946
22947fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22948 text.chars()
22949 .next()
22950 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22951}
22952
22953#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22954enum WordBreakToken<'a> {
22955 Word { token: &'a str, grapheme_len: usize },
22956 InlineWhitespace { token: &'a str, grapheme_len: usize },
22957 Newline,
22958}
22959
22960impl<'a> Iterator for WordBreakingTokenizer<'a> {
22961 /// Yields a span, the count of graphemes in the token, and whether it was
22962 /// whitespace. Note that it also breaks at word boundaries.
22963 type Item = WordBreakToken<'a>;
22964
22965 fn next(&mut self) -> Option<Self::Item> {
22966 use unicode_segmentation::UnicodeSegmentation;
22967 if self.input.is_empty() {
22968 return None;
22969 }
22970
22971 let mut iter = self.input.graphemes(true).peekable();
22972 let mut offset = 0;
22973 let mut grapheme_len = 0;
22974 if let Some(first_grapheme) = iter.next() {
22975 let is_newline = first_grapheme == "\n";
22976 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22977 offset += first_grapheme.len();
22978 grapheme_len += 1;
22979 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22980 if let Some(grapheme) = iter.peek().copied()
22981 && should_stay_with_preceding_ideograph(grapheme)
22982 {
22983 offset += grapheme.len();
22984 grapheme_len += 1;
22985 }
22986 } else {
22987 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22988 let mut next_word_bound = words.peek().copied();
22989 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22990 next_word_bound = words.next();
22991 }
22992 while let Some(grapheme) = iter.peek().copied() {
22993 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22994 break;
22995 };
22996 if is_grapheme_whitespace(grapheme) != is_whitespace
22997 || (grapheme == "\n") != is_newline
22998 {
22999 break;
23000 };
23001 offset += grapheme.len();
23002 grapheme_len += 1;
23003 iter.next();
23004 }
23005 }
23006 let token = &self.input[..offset];
23007 self.input = &self.input[offset..];
23008 if token == "\n" {
23009 Some(WordBreakToken::Newline)
23010 } else if is_whitespace {
23011 Some(WordBreakToken::InlineWhitespace {
23012 token,
23013 grapheme_len,
23014 })
23015 } else {
23016 Some(WordBreakToken::Word {
23017 token,
23018 grapheme_len,
23019 })
23020 }
23021 } else {
23022 None
23023 }
23024 }
23025}
23026
23027#[test]
23028fn test_word_breaking_tokenizer() {
23029 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23030 ("", &[]),
23031 (" ", &[whitespace(" ", 2)]),
23032 ("Ʒ", &[word("Ʒ", 1)]),
23033 ("Ǽ", &[word("Ǽ", 1)]),
23034 ("⋑", &[word("⋑", 1)]),
23035 ("⋑⋑", &[word("⋑⋑", 2)]),
23036 (
23037 "原理,进而",
23038 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23039 ),
23040 (
23041 "hello world",
23042 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23043 ),
23044 (
23045 "hello, world",
23046 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23047 ),
23048 (
23049 " hello world",
23050 &[
23051 whitespace(" ", 2),
23052 word("hello", 5),
23053 whitespace(" ", 1),
23054 word("world", 5),
23055 ],
23056 ),
23057 (
23058 "这是什么 \n 钢笔",
23059 &[
23060 word("这", 1),
23061 word("是", 1),
23062 word("什", 1),
23063 word("么", 1),
23064 whitespace(" ", 1),
23065 newline(),
23066 whitespace(" ", 1),
23067 word("钢", 1),
23068 word("笔", 1),
23069 ],
23070 ),
23071 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23072 ];
23073
23074 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23075 WordBreakToken::Word {
23076 token,
23077 grapheme_len,
23078 }
23079 }
23080
23081 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23082 WordBreakToken::InlineWhitespace {
23083 token,
23084 grapheme_len,
23085 }
23086 }
23087
23088 fn newline() -> WordBreakToken<'static> {
23089 WordBreakToken::Newline
23090 }
23091
23092 for (input, result) in tests {
23093 assert_eq!(
23094 WordBreakingTokenizer::new(input)
23095 .collect::<Vec<_>>()
23096 .as_slice(),
23097 *result,
23098 );
23099 }
23100}
23101
23102fn wrap_with_prefix(
23103 first_line_prefix: String,
23104 subsequent_lines_prefix: String,
23105 unwrapped_text: String,
23106 wrap_column: usize,
23107 tab_size: NonZeroU32,
23108 preserve_existing_whitespace: bool,
23109) -> String {
23110 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23111 let subsequent_lines_prefix_len =
23112 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23113 let mut wrapped_text = String::new();
23114 let mut current_line = first_line_prefix;
23115 let mut is_first_line = true;
23116
23117 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23118 let mut current_line_len = first_line_prefix_len;
23119 let mut in_whitespace = false;
23120 for token in tokenizer {
23121 let have_preceding_whitespace = in_whitespace;
23122 match token {
23123 WordBreakToken::Word {
23124 token,
23125 grapheme_len,
23126 } => {
23127 in_whitespace = false;
23128 let current_prefix_len = if is_first_line {
23129 first_line_prefix_len
23130 } else {
23131 subsequent_lines_prefix_len
23132 };
23133 if current_line_len + grapheme_len > wrap_column
23134 && current_line_len != current_prefix_len
23135 {
23136 wrapped_text.push_str(current_line.trim_end());
23137 wrapped_text.push('\n');
23138 is_first_line = false;
23139 current_line = subsequent_lines_prefix.clone();
23140 current_line_len = subsequent_lines_prefix_len;
23141 }
23142 current_line.push_str(token);
23143 current_line_len += grapheme_len;
23144 }
23145 WordBreakToken::InlineWhitespace {
23146 mut token,
23147 mut grapheme_len,
23148 } => {
23149 in_whitespace = true;
23150 if have_preceding_whitespace && !preserve_existing_whitespace {
23151 continue;
23152 }
23153 if !preserve_existing_whitespace {
23154 // Keep a single whitespace grapheme as-is
23155 if let Some(first) =
23156 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23157 {
23158 token = first;
23159 } else {
23160 token = " ";
23161 }
23162 grapheme_len = 1;
23163 }
23164 let current_prefix_len = if is_first_line {
23165 first_line_prefix_len
23166 } else {
23167 subsequent_lines_prefix_len
23168 };
23169 if current_line_len + grapheme_len > wrap_column {
23170 wrapped_text.push_str(current_line.trim_end());
23171 wrapped_text.push('\n');
23172 is_first_line = false;
23173 current_line = subsequent_lines_prefix.clone();
23174 current_line_len = subsequent_lines_prefix_len;
23175 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23176 current_line.push_str(token);
23177 current_line_len += grapheme_len;
23178 }
23179 }
23180 WordBreakToken::Newline => {
23181 in_whitespace = true;
23182 let current_prefix_len = if is_first_line {
23183 first_line_prefix_len
23184 } else {
23185 subsequent_lines_prefix_len
23186 };
23187 if preserve_existing_whitespace {
23188 wrapped_text.push_str(current_line.trim_end());
23189 wrapped_text.push('\n');
23190 is_first_line = false;
23191 current_line = subsequent_lines_prefix.clone();
23192 current_line_len = subsequent_lines_prefix_len;
23193 } else if have_preceding_whitespace {
23194 continue;
23195 } else if current_line_len + 1 > wrap_column
23196 && current_line_len != current_prefix_len
23197 {
23198 wrapped_text.push_str(current_line.trim_end());
23199 wrapped_text.push('\n');
23200 is_first_line = false;
23201 current_line = subsequent_lines_prefix.clone();
23202 current_line_len = subsequent_lines_prefix_len;
23203 } else if current_line_len != current_prefix_len {
23204 current_line.push(' ');
23205 current_line_len += 1;
23206 }
23207 }
23208 }
23209 }
23210
23211 if !current_line.is_empty() {
23212 wrapped_text.push_str(¤t_line);
23213 }
23214 wrapped_text
23215}
23216
23217#[test]
23218fn test_wrap_with_prefix() {
23219 assert_eq!(
23220 wrap_with_prefix(
23221 "# ".to_string(),
23222 "# ".to_string(),
23223 "abcdefg".to_string(),
23224 4,
23225 NonZeroU32::new(4).unwrap(),
23226 false,
23227 ),
23228 "# abcdefg"
23229 );
23230 assert_eq!(
23231 wrap_with_prefix(
23232 "".to_string(),
23233 "".to_string(),
23234 "\thello world".to_string(),
23235 8,
23236 NonZeroU32::new(4).unwrap(),
23237 false,
23238 ),
23239 "hello\nworld"
23240 );
23241 assert_eq!(
23242 wrap_with_prefix(
23243 "// ".to_string(),
23244 "// ".to_string(),
23245 "xx \nyy zz aa bb cc".to_string(),
23246 12,
23247 NonZeroU32::new(4).unwrap(),
23248 false,
23249 ),
23250 "// xx yy zz\n// aa bb cc"
23251 );
23252 assert_eq!(
23253 wrap_with_prefix(
23254 String::new(),
23255 String::new(),
23256 "这是什么 \n 钢笔".to_string(),
23257 3,
23258 NonZeroU32::new(4).unwrap(),
23259 false,
23260 ),
23261 "这是什\n么 钢\n笔"
23262 );
23263 assert_eq!(
23264 wrap_with_prefix(
23265 String::new(),
23266 String::new(),
23267 format!("foo{}bar", '\u{2009}'), // thin space
23268 80,
23269 NonZeroU32::new(4).unwrap(),
23270 false,
23271 ),
23272 format!("foo{}bar", '\u{2009}')
23273 );
23274}
23275
23276pub trait CollaborationHub {
23277 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23278 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23279 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23280}
23281
23282impl CollaborationHub for Entity<Project> {
23283 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23284 self.read(cx).collaborators()
23285 }
23286
23287 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23288 self.read(cx).user_store().read(cx).participant_indices()
23289 }
23290
23291 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23292 let this = self.read(cx);
23293 let user_ids = this.collaborators().values().map(|c| c.user_id);
23294 this.user_store().read(cx).participant_names(user_ids, cx)
23295 }
23296}
23297
23298pub trait SemanticsProvider {
23299 fn hover(
23300 &self,
23301 buffer: &Entity<Buffer>,
23302 position: text::Anchor,
23303 cx: &mut App,
23304 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23305
23306 fn inline_values(
23307 &self,
23308 buffer_handle: Entity<Buffer>,
23309 range: Range<text::Anchor>,
23310 cx: &mut App,
23311 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23312
23313 fn applicable_inlay_chunks(
23314 &self,
23315 buffer: &Entity<Buffer>,
23316 ranges: &[Range<text::Anchor>],
23317 cx: &mut App,
23318 ) -> Vec<Range<BufferRow>>;
23319
23320 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23321
23322 fn inlay_hints(
23323 &self,
23324 invalidate: InvalidationStrategy,
23325 buffer: Entity<Buffer>,
23326 ranges: Vec<Range<text::Anchor>>,
23327 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23328 cx: &mut App,
23329 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23330
23331 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23332
23333 fn document_highlights(
23334 &self,
23335 buffer: &Entity<Buffer>,
23336 position: text::Anchor,
23337 cx: &mut App,
23338 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23339
23340 fn definitions(
23341 &self,
23342 buffer: &Entity<Buffer>,
23343 position: text::Anchor,
23344 kind: GotoDefinitionKind,
23345 cx: &mut App,
23346 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23347
23348 fn range_for_rename(
23349 &self,
23350 buffer: &Entity<Buffer>,
23351 position: text::Anchor,
23352 cx: &mut App,
23353 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23354
23355 fn perform_rename(
23356 &self,
23357 buffer: &Entity<Buffer>,
23358 position: text::Anchor,
23359 new_name: String,
23360 cx: &mut App,
23361 ) -> Option<Task<Result<ProjectTransaction>>>;
23362}
23363
23364pub trait CompletionProvider {
23365 fn completions(
23366 &self,
23367 excerpt_id: ExcerptId,
23368 buffer: &Entity<Buffer>,
23369 buffer_position: text::Anchor,
23370 trigger: CompletionContext,
23371 window: &mut Window,
23372 cx: &mut Context<Editor>,
23373 ) -> Task<Result<Vec<CompletionResponse>>>;
23374
23375 fn resolve_completions(
23376 &self,
23377 _buffer: Entity<Buffer>,
23378 _completion_indices: Vec<usize>,
23379 _completions: Rc<RefCell<Box<[Completion]>>>,
23380 _cx: &mut Context<Editor>,
23381 ) -> Task<Result<bool>> {
23382 Task::ready(Ok(false))
23383 }
23384
23385 fn apply_additional_edits_for_completion(
23386 &self,
23387 _buffer: Entity<Buffer>,
23388 _completions: Rc<RefCell<Box<[Completion]>>>,
23389 _completion_index: usize,
23390 _push_to_history: bool,
23391 _cx: &mut Context<Editor>,
23392 ) -> Task<Result<Option<language::Transaction>>> {
23393 Task::ready(Ok(None))
23394 }
23395
23396 fn is_completion_trigger(
23397 &self,
23398 buffer: &Entity<Buffer>,
23399 position: language::Anchor,
23400 text: &str,
23401 trigger_in_words: bool,
23402 menu_is_open: bool,
23403 cx: &mut Context<Editor>,
23404 ) -> bool;
23405
23406 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23407
23408 fn sort_completions(&self) -> bool {
23409 true
23410 }
23411
23412 fn filter_completions(&self) -> bool {
23413 true
23414 }
23415
23416 fn show_snippets(&self) -> bool {
23417 false
23418 }
23419}
23420
23421pub trait CodeActionProvider {
23422 fn id(&self) -> Arc<str>;
23423
23424 fn code_actions(
23425 &self,
23426 buffer: &Entity<Buffer>,
23427 range: Range<text::Anchor>,
23428 window: &mut Window,
23429 cx: &mut App,
23430 ) -> Task<Result<Vec<CodeAction>>>;
23431
23432 fn apply_code_action(
23433 &self,
23434 buffer_handle: Entity<Buffer>,
23435 action: CodeAction,
23436 excerpt_id: ExcerptId,
23437 push_to_history: bool,
23438 window: &mut Window,
23439 cx: &mut App,
23440 ) -> Task<Result<ProjectTransaction>>;
23441}
23442
23443impl CodeActionProvider for Entity<Project> {
23444 fn id(&self) -> Arc<str> {
23445 "project".into()
23446 }
23447
23448 fn code_actions(
23449 &self,
23450 buffer: &Entity<Buffer>,
23451 range: Range<text::Anchor>,
23452 _window: &mut Window,
23453 cx: &mut App,
23454 ) -> Task<Result<Vec<CodeAction>>> {
23455 self.update(cx, |project, cx| {
23456 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23457 let code_actions = project.code_actions(buffer, range, None, cx);
23458 cx.background_spawn(async move {
23459 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23460 Ok(code_lens_actions
23461 .context("code lens fetch")?
23462 .into_iter()
23463 .flatten()
23464 .chain(
23465 code_actions
23466 .context("code action fetch")?
23467 .into_iter()
23468 .flatten(),
23469 )
23470 .collect())
23471 })
23472 })
23473 }
23474
23475 fn apply_code_action(
23476 &self,
23477 buffer_handle: Entity<Buffer>,
23478 action: CodeAction,
23479 _excerpt_id: ExcerptId,
23480 push_to_history: bool,
23481 _window: &mut Window,
23482 cx: &mut App,
23483 ) -> Task<Result<ProjectTransaction>> {
23484 self.update(cx, |project, cx| {
23485 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23486 })
23487 }
23488}
23489
23490fn snippet_completions(
23491 project: &Project,
23492 buffer: &Entity<Buffer>,
23493 buffer_anchor: text::Anchor,
23494 classifier: CharClassifier,
23495 cx: &mut App,
23496) -> Task<Result<CompletionResponse>> {
23497 let languages = buffer.read(cx).languages_at(buffer_anchor);
23498 let snippet_store = project.snippets().read(cx);
23499
23500 let scopes: Vec<_> = languages
23501 .iter()
23502 .filter_map(|language| {
23503 let language_name = language.lsp_id();
23504 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23505
23506 if snippets.is_empty() {
23507 None
23508 } else {
23509 Some((language.default_scope(), snippets))
23510 }
23511 })
23512 .collect();
23513
23514 if scopes.is_empty() {
23515 return Task::ready(Ok(CompletionResponse {
23516 completions: vec![],
23517 display_options: CompletionDisplayOptions::default(),
23518 is_incomplete: false,
23519 }));
23520 }
23521
23522 let snapshot = buffer.read(cx).text_snapshot();
23523 let executor = cx.background_executor().clone();
23524
23525 cx.background_spawn(async move {
23526 let is_word_char = |c| classifier.is_word(c);
23527
23528 let mut is_incomplete = false;
23529 let mut completions: Vec<Completion> = Vec::new();
23530
23531 const MAX_PREFIX_LEN: usize = 128;
23532 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23533 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23534 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23535
23536 let max_buffer_window: String = snapshot
23537 .text_for_range(window_start..buffer_offset)
23538 .collect();
23539
23540 if max_buffer_window.is_empty() {
23541 return Ok(CompletionResponse {
23542 completions: vec![],
23543 display_options: CompletionDisplayOptions::default(),
23544 is_incomplete: true,
23545 });
23546 }
23547
23548 for (_scope, snippets) in scopes.into_iter() {
23549 // Sort snippets by word count to match longer snippet prefixes first.
23550 let mut sorted_snippet_candidates = snippets
23551 .iter()
23552 .enumerate()
23553 .flat_map(|(snippet_ix, snippet)| {
23554 snippet
23555 .prefix
23556 .iter()
23557 .enumerate()
23558 .map(move |(prefix_ix, prefix)| {
23559 let word_count =
23560 snippet_candidate_suffixes(prefix, is_word_char).count();
23561 ((snippet_ix, prefix_ix), prefix, word_count)
23562 })
23563 })
23564 .collect_vec();
23565 sorted_snippet_candidates
23566 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23567
23568 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23569
23570 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23571 .take(
23572 sorted_snippet_candidates
23573 .first()
23574 .map(|(_, _, word_count)| *word_count)
23575 .unwrap_or_default(),
23576 )
23577 .collect_vec();
23578
23579 const MAX_RESULTS: usize = 100;
23580 // Each match also remembers how many characters from the buffer it consumed
23581 let mut matches: Vec<(StringMatch, usize)> = vec![];
23582
23583 let mut snippet_list_cutoff_index = 0;
23584 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23585 let word_count = buffer_index + 1;
23586 // Increase `snippet_list_cutoff_index` until we have all of the
23587 // snippets with sufficiently many words.
23588 while sorted_snippet_candidates
23589 .get(snippet_list_cutoff_index)
23590 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23591 *snippet_word_count >= word_count
23592 })
23593 {
23594 snippet_list_cutoff_index += 1;
23595 }
23596
23597 // Take only the candidates with at least `word_count` many words
23598 let snippet_candidates_at_word_len =
23599 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23600
23601 let candidates = snippet_candidates_at_word_len
23602 .iter()
23603 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23604 .enumerate() // index in `sorted_snippet_candidates`
23605 // First char must match
23606 .filter(|(_ix, prefix)| {
23607 itertools::equal(
23608 prefix
23609 .chars()
23610 .next()
23611 .into_iter()
23612 .flat_map(|c| c.to_lowercase()),
23613 buffer_window
23614 .chars()
23615 .next()
23616 .into_iter()
23617 .flat_map(|c| c.to_lowercase()),
23618 )
23619 })
23620 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23621 .collect::<Vec<StringMatchCandidate>>();
23622
23623 matches.extend(
23624 fuzzy::match_strings(
23625 &candidates,
23626 &buffer_window,
23627 buffer_window.chars().any(|c| c.is_uppercase()),
23628 true,
23629 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23630 &Default::default(),
23631 executor.clone(),
23632 )
23633 .await
23634 .into_iter()
23635 .map(|string_match| (string_match, buffer_window.len())),
23636 );
23637
23638 if matches.len() >= MAX_RESULTS {
23639 break;
23640 }
23641 }
23642
23643 let to_lsp = |point: &text::Anchor| {
23644 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23645 point_to_lsp(end)
23646 };
23647 let lsp_end = to_lsp(&buffer_anchor);
23648
23649 if matches.len() >= MAX_RESULTS {
23650 is_incomplete = true;
23651 }
23652
23653 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23654 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23655 sorted_snippet_candidates[string_match.candidate_id];
23656 let snippet = &snippets[snippet_index];
23657 let start = buffer_offset - buffer_window_len;
23658 let start = snapshot.anchor_before(start);
23659 let range = start..buffer_anchor;
23660 let lsp_start = to_lsp(&start);
23661 let lsp_range = lsp::Range {
23662 start: lsp_start,
23663 end: lsp_end,
23664 };
23665 Completion {
23666 replace_range: range,
23667 new_text: snippet.body.clone(),
23668 source: CompletionSource::Lsp {
23669 insert_range: None,
23670 server_id: LanguageServerId(usize::MAX),
23671 resolved: true,
23672 lsp_completion: Box::new(lsp::CompletionItem {
23673 label: snippet.prefix.first().unwrap().clone(),
23674 kind: Some(CompletionItemKind::SNIPPET),
23675 label_details: snippet.description.as_ref().map(|description| {
23676 lsp::CompletionItemLabelDetails {
23677 detail: Some(description.clone()),
23678 description: None,
23679 }
23680 }),
23681 insert_text_format: Some(InsertTextFormat::SNIPPET),
23682 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23683 lsp::InsertReplaceEdit {
23684 new_text: snippet.body.clone(),
23685 insert: lsp_range,
23686 replace: lsp_range,
23687 },
23688 )),
23689 filter_text: Some(snippet.body.clone()),
23690 sort_text: Some(char::MAX.to_string()),
23691 ..lsp::CompletionItem::default()
23692 }),
23693 lsp_defaults: None,
23694 },
23695 label: CodeLabel {
23696 text: matching_prefix.clone(),
23697 runs: Vec::new(),
23698 filter_range: 0..matching_prefix.len(),
23699 },
23700 icon_path: None,
23701 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23702 single_line: snippet.name.clone().into(),
23703 plain_text: snippet
23704 .description
23705 .clone()
23706 .map(|description| description.into()),
23707 }),
23708 insert_text_mode: None,
23709 confirm: None,
23710 match_start: Some(start),
23711 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23712 }
23713 }));
23714 }
23715
23716 Ok(CompletionResponse {
23717 completions,
23718 display_options: CompletionDisplayOptions::default(),
23719 is_incomplete,
23720 })
23721 })
23722}
23723
23724impl CompletionProvider for Entity<Project> {
23725 fn completions(
23726 &self,
23727 _excerpt_id: ExcerptId,
23728 buffer: &Entity<Buffer>,
23729 buffer_position: text::Anchor,
23730 options: CompletionContext,
23731 _window: &mut Window,
23732 cx: &mut Context<Editor>,
23733 ) -> Task<Result<Vec<CompletionResponse>>> {
23734 self.update(cx, |project, cx| {
23735 let task = project.completions(buffer, buffer_position, options, cx);
23736 cx.background_spawn(task)
23737 })
23738 }
23739
23740 fn resolve_completions(
23741 &self,
23742 buffer: Entity<Buffer>,
23743 completion_indices: Vec<usize>,
23744 completions: Rc<RefCell<Box<[Completion]>>>,
23745 cx: &mut Context<Editor>,
23746 ) -> Task<Result<bool>> {
23747 self.update(cx, |project, cx| {
23748 project.lsp_store().update(cx, |lsp_store, cx| {
23749 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23750 })
23751 })
23752 }
23753
23754 fn apply_additional_edits_for_completion(
23755 &self,
23756 buffer: Entity<Buffer>,
23757 completions: Rc<RefCell<Box<[Completion]>>>,
23758 completion_index: usize,
23759 push_to_history: bool,
23760 cx: &mut Context<Editor>,
23761 ) -> Task<Result<Option<language::Transaction>>> {
23762 self.update(cx, |project, cx| {
23763 project.lsp_store().update(cx, |lsp_store, cx| {
23764 lsp_store.apply_additional_edits_for_completion(
23765 buffer,
23766 completions,
23767 completion_index,
23768 push_to_history,
23769 cx,
23770 )
23771 })
23772 })
23773 }
23774
23775 fn is_completion_trigger(
23776 &self,
23777 buffer: &Entity<Buffer>,
23778 position: language::Anchor,
23779 text: &str,
23780 trigger_in_words: bool,
23781 menu_is_open: bool,
23782 cx: &mut Context<Editor>,
23783 ) -> bool {
23784 let mut chars = text.chars();
23785 let char = if let Some(char) = chars.next() {
23786 char
23787 } else {
23788 return false;
23789 };
23790 if chars.next().is_some() {
23791 return false;
23792 }
23793
23794 let buffer = buffer.read(cx);
23795 let snapshot = buffer.snapshot();
23796 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23797 return false;
23798 }
23799 let classifier = snapshot
23800 .char_classifier_at(position)
23801 .scope_context(Some(CharScopeContext::Completion));
23802 if trigger_in_words && classifier.is_word(char) {
23803 return true;
23804 }
23805
23806 buffer.completion_triggers().contains(text)
23807 }
23808
23809 fn show_snippets(&self) -> bool {
23810 true
23811 }
23812}
23813
23814impl SemanticsProvider for Entity<Project> {
23815 fn hover(
23816 &self,
23817 buffer: &Entity<Buffer>,
23818 position: text::Anchor,
23819 cx: &mut App,
23820 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23821 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23822 }
23823
23824 fn document_highlights(
23825 &self,
23826 buffer: &Entity<Buffer>,
23827 position: text::Anchor,
23828 cx: &mut App,
23829 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23830 Some(self.update(cx, |project, cx| {
23831 project.document_highlights(buffer, position, cx)
23832 }))
23833 }
23834
23835 fn definitions(
23836 &self,
23837 buffer: &Entity<Buffer>,
23838 position: text::Anchor,
23839 kind: GotoDefinitionKind,
23840 cx: &mut App,
23841 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23842 Some(self.update(cx, |project, cx| match kind {
23843 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23844 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23845 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23846 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23847 }))
23848 }
23849
23850 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23851 self.update(cx, |project, cx| {
23852 if project
23853 .active_debug_session(cx)
23854 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23855 {
23856 return true;
23857 }
23858
23859 buffer.update(cx, |buffer, cx| {
23860 project.any_language_server_supports_inlay_hints(buffer, cx)
23861 })
23862 })
23863 }
23864
23865 fn inline_values(
23866 &self,
23867 buffer_handle: Entity<Buffer>,
23868 range: Range<text::Anchor>,
23869 cx: &mut App,
23870 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23871 self.update(cx, |project, cx| {
23872 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23873
23874 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23875 })
23876 }
23877
23878 fn applicable_inlay_chunks(
23879 &self,
23880 buffer: &Entity<Buffer>,
23881 ranges: &[Range<text::Anchor>],
23882 cx: &mut App,
23883 ) -> Vec<Range<BufferRow>> {
23884 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23885 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23886 })
23887 }
23888
23889 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23890 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23891 lsp_store.invalidate_inlay_hints(for_buffers)
23892 });
23893 }
23894
23895 fn inlay_hints(
23896 &self,
23897 invalidate: InvalidationStrategy,
23898 buffer: Entity<Buffer>,
23899 ranges: Vec<Range<text::Anchor>>,
23900 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23901 cx: &mut App,
23902 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23903 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23904 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23905 }))
23906 }
23907
23908 fn range_for_rename(
23909 &self,
23910 buffer: &Entity<Buffer>,
23911 position: text::Anchor,
23912 cx: &mut App,
23913 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23914 Some(self.update(cx, |project, cx| {
23915 let buffer = buffer.clone();
23916 let task = project.prepare_rename(buffer.clone(), position, cx);
23917 cx.spawn(async move |_, cx| {
23918 Ok(match task.await? {
23919 PrepareRenameResponse::Success(range) => Some(range),
23920 PrepareRenameResponse::InvalidPosition => None,
23921 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23922 // Fallback on using TreeSitter info to determine identifier range
23923 buffer.read_with(cx, |buffer, _| {
23924 let snapshot = buffer.snapshot();
23925 let (range, kind) = snapshot.surrounding_word(position, None);
23926 if kind != Some(CharKind::Word) {
23927 return None;
23928 }
23929 Some(
23930 snapshot.anchor_before(range.start)
23931 ..snapshot.anchor_after(range.end),
23932 )
23933 })?
23934 }
23935 })
23936 })
23937 }))
23938 }
23939
23940 fn perform_rename(
23941 &self,
23942 buffer: &Entity<Buffer>,
23943 position: text::Anchor,
23944 new_name: String,
23945 cx: &mut App,
23946 ) -> Option<Task<Result<ProjectTransaction>>> {
23947 Some(self.update(cx, |project, cx| {
23948 project.perform_rename(buffer.clone(), position, new_name, cx)
23949 }))
23950 }
23951}
23952
23953fn consume_contiguous_rows(
23954 contiguous_row_selections: &mut Vec<Selection<Point>>,
23955 selection: &Selection<Point>,
23956 display_map: &DisplaySnapshot,
23957 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23958) -> (MultiBufferRow, MultiBufferRow) {
23959 contiguous_row_selections.push(selection.clone());
23960 let start_row = starting_row(selection, display_map);
23961 let mut end_row = ending_row(selection, display_map);
23962
23963 while let Some(next_selection) = selections.peek() {
23964 if next_selection.start.row <= end_row.0 {
23965 end_row = ending_row(next_selection, display_map);
23966 contiguous_row_selections.push(selections.next().unwrap().clone());
23967 } else {
23968 break;
23969 }
23970 }
23971 (start_row, end_row)
23972}
23973
23974fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23975 if selection.start.column > 0 {
23976 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23977 } else {
23978 MultiBufferRow(selection.start.row)
23979 }
23980}
23981
23982fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23983 if next_selection.end.column > 0 || next_selection.is_empty() {
23984 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23985 } else {
23986 MultiBufferRow(next_selection.end.row)
23987 }
23988}
23989
23990impl EditorSnapshot {
23991 pub fn remote_selections_in_range<'a>(
23992 &'a self,
23993 range: &'a Range<Anchor>,
23994 collaboration_hub: &dyn CollaborationHub,
23995 cx: &'a App,
23996 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23997 let participant_names = collaboration_hub.user_names(cx);
23998 let participant_indices = collaboration_hub.user_participant_indices(cx);
23999 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24000 let collaborators_by_replica_id = collaborators_by_peer_id
24001 .values()
24002 .map(|collaborator| (collaborator.replica_id, collaborator))
24003 .collect::<HashMap<_, _>>();
24004 self.buffer_snapshot()
24005 .selections_in_range(range, false)
24006 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24007 if replica_id == ReplicaId::AGENT {
24008 Some(RemoteSelection {
24009 replica_id,
24010 selection,
24011 cursor_shape,
24012 line_mode,
24013 collaborator_id: CollaboratorId::Agent,
24014 user_name: Some("Agent".into()),
24015 color: cx.theme().players().agent(),
24016 })
24017 } else {
24018 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24019 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24020 let user_name = participant_names.get(&collaborator.user_id).cloned();
24021 Some(RemoteSelection {
24022 replica_id,
24023 selection,
24024 cursor_shape,
24025 line_mode,
24026 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24027 user_name,
24028 color: if let Some(index) = participant_index {
24029 cx.theme().players().color_for_participant(index.0)
24030 } else {
24031 cx.theme().players().absent()
24032 },
24033 })
24034 }
24035 })
24036 }
24037
24038 pub fn hunks_for_ranges(
24039 &self,
24040 ranges: impl IntoIterator<Item = Range<Point>>,
24041 ) -> Vec<MultiBufferDiffHunk> {
24042 let mut hunks = Vec::new();
24043 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24044 HashMap::default();
24045 for query_range in ranges {
24046 let query_rows =
24047 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24048 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24049 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24050 ) {
24051 // Include deleted hunks that are adjacent to the query range, because
24052 // otherwise they would be missed.
24053 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24054 if hunk.status().is_deleted() {
24055 intersects_range |= hunk.row_range.start == query_rows.end;
24056 intersects_range |= hunk.row_range.end == query_rows.start;
24057 }
24058 if intersects_range {
24059 if !processed_buffer_rows
24060 .entry(hunk.buffer_id)
24061 .or_default()
24062 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24063 {
24064 continue;
24065 }
24066 hunks.push(hunk);
24067 }
24068 }
24069 }
24070
24071 hunks
24072 }
24073
24074 fn display_diff_hunks_for_rows<'a>(
24075 &'a self,
24076 display_rows: Range<DisplayRow>,
24077 folded_buffers: &'a HashSet<BufferId>,
24078 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24079 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24080 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24081
24082 self.buffer_snapshot()
24083 .diff_hunks_in_range(buffer_start..buffer_end)
24084 .filter_map(|hunk| {
24085 if folded_buffers.contains(&hunk.buffer_id) {
24086 return None;
24087 }
24088
24089 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24090 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24091
24092 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24093 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24094
24095 let display_hunk = if hunk_display_start.column() != 0 {
24096 DisplayDiffHunk::Folded {
24097 display_row: hunk_display_start.row(),
24098 }
24099 } else {
24100 let mut end_row = hunk_display_end.row();
24101 if hunk_display_end.column() > 0 {
24102 end_row.0 += 1;
24103 }
24104 let is_created_file = hunk.is_created_file();
24105 DisplayDiffHunk::Unfolded {
24106 status: hunk.status(),
24107 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24108 ..hunk.diff_base_byte_range.end.0,
24109 display_row_range: hunk_display_start.row()..end_row,
24110 multi_buffer_range: Anchor::range_in_buffer(
24111 hunk.excerpt_id,
24112 hunk.buffer_range,
24113 ),
24114 is_created_file,
24115 }
24116 };
24117
24118 Some(display_hunk)
24119 })
24120 }
24121
24122 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24123 self.display_snapshot
24124 .buffer_snapshot()
24125 .language_at(position)
24126 }
24127
24128 pub fn is_focused(&self) -> bool {
24129 self.is_focused
24130 }
24131
24132 pub fn placeholder_text(&self) -> Option<String> {
24133 self.placeholder_display_snapshot
24134 .as_ref()
24135 .map(|display_map| display_map.text())
24136 }
24137
24138 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24139 self.scroll_anchor.scroll_position(&self.display_snapshot)
24140 }
24141
24142 fn gutter_dimensions(
24143 &self,
24144 font_id: FontId,
24145 font_size: Pixels,
24146 max_line_number_width: Pixels,
24147 cx: &App,
24148 ) -> Option<GutterDimensions> {
24149 if !self.show_gutter {
24150 return None;
24151 }
24152
24153 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
24154 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
24155
24156 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24157 matches!(
24158 ProjectSettings::get_global(cx).git.git_gutter,
24159 GitGutterSetting::TrackedFiles
24160 )
24161 });
24162 let gutter_settings = EditorSettings::get_global(cx).gutter;
24163 let show_line_numbers = self
24164 .show_line_numbers
24165 .unwrap_or(gutter_settings.line_numbers);
24166 let line_gutter_width = if show_line_numbers {
24167 // Avoid flicker-like gutter resizes when the line number gains another digit by
24168 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24169 let min_width_for_number_on_gutter =
24170 ch_advance * gutter_settings.min_line_number_digits as f32;
24171 max_line_number_width.max(min_width_for_number_on_gutter)
24172 } else {
24173 0.0.into()
24174 };
24175
24176 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24177 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24178
24179 let git_blame_entries_width =
24180 self.git_blame_gutter_max_author_length
24181 .map(|max_author_length| {
24182 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24183 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24184
24185 /// The number of characters to dedicate to gaps and margins.
24186 const SPACING_WIDTH: usize = 4;
24187
24188 let max_char_count = max_author_length.min(renderer.max_author_length())
24189 + ::git::SHORT_SHA_LENGTH
24190 + MAX_RELATIVE_TIMESTAMP.len()
24191 + SPACING_WIDTH;
24192
24193 ch_advance * max_char_count
24194 });
24195
24196 let is_singleton = self.buffer_snapshot().is_singleton();
24197
24198 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24199 left_padding += if !is_singleton {
24200 ch_width * 4.0
24201 } else if show_runnables || show_breakpoints {
24202 ch_width * 3.0
24203 } else if show_git_gutter && show_line_numbers {
24204 ch_width * 2.0
24205 } else if show_git_gutter || show_line_numbers {
24206 ch_width
24207 } else {
24208 px(0.)
24209 };
24210
24211 let shows_folds = is_singleton && gutter_settings.folds;
24212
24213 let right_padding = if shows_folds && show_line_numbers {
24214 ch_width * 4.0
24215 } else if shows_folds || (!is_singleton && show_line_numbers) {
24216 ch_width * 3.0
24217 } else if show_line_numbers {
24218 ch_width
24219 } else {
24220 px(0.)
24221 };
24222
24223 Some(GutterDimensions {
24224 left_padding,
24225 right_padding,
24226 width: line_gutter_width + left_padding + right_padding,
24227 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24228 git_blame_entries_width,
24229 })
24230 }
24231
24232 pub fn render_crease_toggle(
24233 &self,
24234 buffer_row: MultiBufferRow,
24235 row_contains_cursor: bool,
24236 editor: Entity<Editor>,
24237 window: &mut Window,
24238 cx: &mut App,
24239 ) -> Option<AnyElement> {
24240 let folded = self.is_line_folded(buffer_row);
24241 let mut is_foldable = false;
24242
24243 if let Some(crease) = self
24244 .crease_snapshot
24245 .query_row(buffer_row, self.buffer_snapshot())
24246 {
24247 is_foldable = true;
24248 match crease {
24249 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24250 if let Some(render_toggle) = render_toggle {
24251 let toggle_callback =
24252 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24253 if folded {
24254 editor.update(cx, |editor, cx| {
24255 editor.fold_at(buffer_row, window, cx)
24256 });
24257 } else {
24258 editor.update(cx, |editor, cx| {
24259 editor.unfold_at(buffer_row, window, cx)
24260 });
24261 }
24262 });
24263 return Some((render_toggle)(
24264 buffer_row,
24265 folded,
24266 toggle_callback,
24267 window,
24268 cx,
24269 ));
24270 }
24271 }
24272 }
24273 }
24274
24275 is_foldable |= self.starts_indent(buffer_row);
24276
24277 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24278 Some(
24279 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24280 .toggle_state(folded)
24281 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24282 if folded {
24283 this.unfold_at(buffer_row, window, cx);
24284 } else {
24285 this.fold_at(buffer_row, window, cx);
24286 }
24287 }))
24288 .into_any_element(),
24289 )
24290 } else {
24291 None
24292 }
24293 }
24294
24295 pub fn render_crease_trailer(
24296 &self,
24297 buffer_row: MultiBufferRow,
24298 window: &mut Window,
24299 cx: &mut App,
24300 ) -> Option<AnyElement> {
24301 let folded = self.is_line_folded(buffer_row);
24302 if let Crease::Inline { render_trailer, .. } = self
24303 .crease_snapshot
24304 .query_row(buffer_row, self.buffer_snapshot())?
24305 {
24306 let render_trailer = render_trailer.as_ref()?;
24307 Some(render_trailer(buffer_row, folded, window, cx))
24308 } else {
24309 None
24310 }
24311 }
24312}
24313
24314impl Deref for EditorSnapshot {
24315 type Target = DisplaySnapshot;
24316
24317 fn deref(&self) -> &Self::Target {
24318 &self.display_snapshot
24319 }
24320}
24321
24322#[derive(Clone, Debug, PartialEq, Eq)]
24323pub enum EditorEvent {
24324 InputIgnored {
24325 text: Arc<str>,
24326 },
24327 InputHandled {
24328 utf16_range_to_replace: Option<Range<isize>>,
24329 text: Arc<str>,
24330 },
24331 ExcerptsAdded {
24332 buffer: Entity<Buffer>,
24333 predecessor: ExcerptId,
24334 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24335 },
24336 ExcerptsRemoved {
24337 ids: Vec<ExcerptId>,
24338 removed_buffer_ids: Vec<BufferId>,
24339 },
24340 BufferFoldToggled {
24341 ids: Vec<ExcerptId>,
24342 folded: bool,
24343 },
24344 ExcerptsEdited {
24345 ids: Vec<ExcerptId>,
24346 },
24347 ExcerptsExpanded {
24348 ids: Vec<ExcerptId>,
24349 },
24350 BufferEdited,
24351 Edited {
24352 transaction_id: clock::Lamport,
24353 },
24354 Reparsed(BufferId),
24355 Focused,
24356 FocusedIn,
24357 Blurred,
24358 DirtyChanged,
24359 Saved,
24360 TitleChanged,
24361 SelectionsChanged {
24362 local: bool,
24363 },
24364 ScrollPositionChanged {
24365 local: bool,
24366 autoscroll: bool,
24367 },
24368 TransactionUndone {
24369 transaction_id: clock::Lamport,
24370 },
24371 TransactionBegun {
24372 transaction_id: clock::Lamport,
24373 },
24374 CursorShapeChanged,
24375 BreadcrumbsChanged,
24376 PushedToNavHistory {
24377 anchor: Anchor,
24378 is_deactivate: bool,
24379 },
24380}
24381
24382impl EventEmitter<EditorEvent> for Editor {}
24383
24384impl Focusable for Editor {
24385 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24386 self.focus_handle.clone()
24387 }
24388}
24389
24390impl Render for Editor {
24391 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24392 let settings = ThemeSettings::get_global(cx);
24393
24394 let mut text_style = match self.mode {
24395 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24396 color: cx.theme().colors().editor_foreground,
24397 font_family: settings.ui_font.family.clone(),
24398 font_features: settings.ui_font.features.clone(),
24399 font_fallbacks: settings.ui_font.fallbacks.clone(),
24400 font_size: rems(0.875).into(),
24401 font_weight: settings.ui_font.weight,
24402 line_height: relative(settings.buffer_line_height.value()),
24403 ..Default::default()
24404 },
24405 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24406 color: cx.theme().colors().editor_foreground,
24407 font_family: settings.buffer_font.family.clone(),
24408 font_features: settings.buffer_font.features.clone(),
24409 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24410 font_size: settings.buffer_font_size(cx).into(),
24411 font_weight: settings.buffer_font.weight,
24412 line_height: relative(settings.buffer_line_height.value()),
24413 ..Default::default()
24414 },
24415 };
24416 if let Some(text_style_refinement) = &self.text_style_refinement {
24417 text_style.refine(text_style_refinement)
24418 }
24419
24420 let background = match self.mode {
24421 EditorMode::SingleLine => cx.theme().system().transparent,
24422 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24423 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24424 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24425 };
24426
24427 EditorElement::new(
24428 &cx.entity(),
24429 EditorStyle {
24430 background,
24431 border: cx.theme().colors().border,
24432 local_player: cx.theme().players().local(),
24433 text: text_style,
24434 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24435 syntax: cx.theme().syntax().clone(),
24436 status: cx.theme().status().clone(),
24437 inlay_hints_style: make_inlay_hints_style(cx),
24438 edit_prediction_styles: make_suggestion_styles(cx),
24439 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24440 show_underlines: self.diagnostics_enabled(),
24441 },
24442 )
24443 }
24444}
24445
24446impl EntityInputHandler for Editor {
24447 fn text_for_range(
24448 &mut self,
24449 range_utf16: Range<usize>,
24450 adjusted_range: &mut Option<Range<usize>>,
24451 _: &mut Window,
24452 cx: &mut Context<Self>,
24453 ) -> Option<String> {
24454 let snapshot = self.buffer.read(cx).read(cx);
24455 let start = snapshot.clip_offset_utf16(
24456 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
24457 Bias::Left,
24458 );
24459 let end = snapshot.clip_offset_utf16(
24460 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
24461 Bias::Right,
24462 );
24463 if (start.0.0..end.0.0) != range_utf16 {
24464 adjusted_range.replace(start.0.0..end.0.0);
24465 }
24466 Some(snapshot.text_for_range(start..end).collect())
24467 }
24468
24469 fn selected_text_range(
24470 &mut self,
24471 ignore_disabled_input: bool,
24472 _: &mut Window,
24473 cx: &mut Context<Self>,
24474 ) -> Option<UTF16Selection> {
24475 // Prevent the IME menu from appearing when holding down an alphabetic key
24476 // while input is disabled.
24477 if !ignore_disabled_input && !self.input_enabled {
24478 return None;
24479 }
24480
24481 let selection = self
24482 .selections
24483 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24484 let range = selection.range();
24485
24486 Some(UTF16Selection {
24487 range: range.start.0.0..range.end.0.0,
24488 reversed: selection.reversed,
24489 })
24490 }
24491
24492 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24493 let snapshot = self.buffer.read(cx).read(cx);
24494 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24495 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
24496 }
24497
24498 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24499 self.clear_highlights::<InputComposition>(cx);
24500 self.ime_transaction.take();
24501 }
24502
24503 fn replace_text_in_range(
24504 &mut self,
24505 range_utf16: Option<Range<usize>>,
24506 text: &str,
24507 window: &mut Window,
24508 cx: &mut Context<Self>,
24509 ) {
24510 if !self.input_enabled {
24511 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24512 return;
24513 }
24514
24515 self.transact(window, cx, |this, window, cx| {
24516 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24517 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24518 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24519 Some(this.selection_replacement_ranges(range_utf16, cx))
24520 } else {
24521 this.marked_text_ranges(cx)
24522 };
24523
24524 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24525 let newest_selection_id = this.selections.newest_anchor().id;
24526 this.selections
24527 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24528 .iter()
24529 .zip(ranges_to_replace.iter())
24530 .find_map(|(selection, range)| {
24531 if selection.id == newest_selection_id {
24532 Some(
24533 (range.start.0.0 as isize - selection.head().0.0 as isize)
24534 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24535 )
24536 } else {
24537 None
24538 }
24539 })
24540 });
24541
24542 cx.emit(EditorEvent::InputHandled {
24543 utf16_range_to_replace: range_to_replace,
24544 text: text.into(),
24545 });
24546
24547 if let Some(new_selected_ranges) = new_selected_ranges {
24548 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24549 selections.select_ranges(new_selected_ranges)
24550 });
24551 this.backspace(&Default::default(), window, cx);
24552 }
24553
24554 this.handle_input(text, window, cx);
24555 });
24556
24557 if let Some(transaction) = self.ime_transaction {
24558 self.buffer.update(cx, |buffer, cx| {
24559 buffer.group_until_transaction(transaction, cx);
24560 });
24561 }
24562
24563 self.unmark_text(window, cx);
24564 }
24565
24566 fn replace_and_mark_text_in_range(
24567 &mut self,
24568 range_utf16: Option<Range<usize>>,
24569 text: &str,
24570 new_selected_range_utf16: Option<Range<usize>>,
24571 window: &mut Window,
24572 cx: &mut Context<Self>,
24573 ) {
24574 if !self.input_enabled {
24575 return;
24576 }
24577
24578 let transaction = self.transact(window, cx, |this, window, cx| {
24579 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24580 let snapshot = this.buffer.read(cx).read(cx);
24581 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24582 for marked_range in &mut marked_ranges {
24583 marked_range.end = marked_range.start + relative_range_utf16.end;
24584 marked_range.start += relative_range_utf16.start;
24585 marked_range.start =
24586 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24587 marked_range.end =
24588 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24589 }
24590 }
24591 Some(marked_ranges)
24592 } else if let Some(range_utf16) = range_utf16 {
24593 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24594 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24595 Some(this.selection_replacement_ranges(range_utf16, cx))
24596 } else {
24597 None
24598 };
24599
24600 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24601 let newest_selection_id = this.selections.newest_anchor().id;
24602 this.selections
24603 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24604 .iter()
24605 .zip(ranges_to_replace.iter())
24606 .find_map(|(selection, range)| {
24607 if selection.id == newest_selection_id {
24608 Some(
24609 (range.start.0.0 as isize - selection.head().0.0 as isize)
24610 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24611 )
24612 } else {
24613 None
24614 }
24615 })
24616 });
24617
24618 cx.emit(EditorEvent::InputHandled {
24619 utf16_range_to_replace: range_to_replace,
24620 text: text.into(),
24621 });
24622
24623 if let Some(ranges) = ranges_to_replace {
24624 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24625 s.select_ranges(ranges)
24626 });
24627 }
24628
24629 let marked_ranges = {
24630 let snapshot = this.buffer.read(cx).read(cx);
24631 this.selections
24632 .disjoint_anchors_arc()
24633 .iter()
24634 .map(|selection| {
24635 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24636 })
24637 .collect::<Vec<_>>()
24638 };
24639
24640 if text.is_empty() {
24641 this.unmark_text(window, cx);
24642 } else {
24643 this.highlight_text::<InputComposition>(
24644 marked_ranges.clone(),
24645 HighlightStyle {
24646 underline: Some(UnderlineStyle {
24647 thickness: px(1.),
24648 color: None,
24649 wavy: false,
24650 }),
24651 ..Default::default()
24652 },
24653 cx,
24654 );
24655 }
24656
24657 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24658 let use_autoclose = this.use_autoclose;
24659 let use_auto_surround = this.use_auto_surround;
24660 this.set_use_autoclose(false);
24661 this.set_use_auto_surround(false);
24662 this.handle_input(text, window, cx);
24663 this.set_use_autoclose(use_autoclose);
24664 this.set_use_auto_surround(use_auto_surround);
24665
24666 if let Some(new_selected_range) = new_selected_range_utf16 {
24667 let snapshot = this.buffer.read(cx).read(cx);
24668 let new_selected_ranges = marked_ranges
24669 .into_iter()
24670 .map(|marked_range| {
24671 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24672 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
24673 insertion_start.0 + new_selected_range.start,
24674 ));
24675 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
24676 insertion_start.0 + new_selected_range.end,
24677 ));
24678 snapshot.clip_offset_utf16(new_start, Bias::Left)
24679 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24680 })
24681 .collect::<Vec<_>>();
24682
24683 drop(snapshot);
24684 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24685 selections.select_ranges(new_selected_ranges)
24686 });
24687 }
24688 });
24689
24690 self.ime_transaction = self.ime_transaction.or(transaction);
24691 if let Some(transaction) = self.ime_transaction {
24692 self.buffer.update(cx, |buffer, cx| {
24693 buffer.group_until_transaction(transaction, cx);
24694 });
24695 }
24696
24697 if self.text_highlights::<InputComposition>(cx).is_none() {
24698 self.ime_transaction.take();
24699 }
24700 }
24701
24702 fn bounds_for_range(
24703 &mut self,
24704 range_utf16: Range<usize>,
24705 element_bounds: gpui::Bounds<Pixels>,
24706 window: &mut Window,
24707 cx: &mut Context<Self>,
24708 ) -> Option<gpui::Bounds<Pixels>> {
24709 let text_layout_details = self.text_layout_details(window);
24710 let CharacterDimensions {
24711 em_width,
24712 em_advance,
24713 line_height,
24714 } = self.character_dimensions(window);
24715
24716 let snapshot = self.snapshot(window, cx);
24717 let scroll_position = snapshot.scroll_position();
24718 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24719
24720 let start =
24721 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
24722 let x = Pixels::from(
24723 ScrollOffset::from(
24724 snapshot.x_for_display_point(start, &text_layout_details)
24725 + self.gutter_dimensions.full_width(),
24726 ) - scroll_left,
24727 );
24728 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24729
24730 Some(Bounds {
24731 origin: element_bounds.origin + point(x, y),
24732 size: size(em_width, line_height),
24733 })
24734 }
24735
24736 fn character_index_for_point(
24737 &mut self,
24738 point: gpui::Point<Pixels>,
24739 _window: &mut Window,
24740 _cx: &mut Context<Self>,
24741 ) -> Option<usize> {
24742 let position_map = self.last_position_map.as_ref()?;
24743 if !position_map.text_hitbox.contains(&point) {
24744 return None;
24745 }
24746 let display_point = position_map.point_for_position(point).previous_valid;
24747 let anchor = position_map
24748 .snapshot
24749 .display_point_to_anchor(display_point, Bias::Left);
24750 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24751 Some(utf16_offset.0.0)
24752 }
24753
24754 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24755 self.input_enabled
24756 }
24757}
24758
24759trait SelectionExt {
24760 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24761 fn spanned_rows(
24762 &self,
24763 include_end_if_at_line_start: bool,
24764 map: &DisplaySnapshot,
24765 ) -> Range<MultiBufferRow>;
24766}
24767
24768impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24769 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24770 let start = self
24771 .start
24772 .to_point(map.buffer_snapshot())
24773 .to_display_point(map);
24774 let end = self
24775 .end
24776 .to_point(map.buffer_snapshot())
24777 .to_display_point(map);
24778 if self.reversed {
24779 end..start
24780 } else {
24781 start..end
24782 }
24783 }
24784
24785 fn spanned_rows(
24786 &self,
24787 include_end_if_at_line_start: bool,
24788 map: &DisplaySnapshot,
24789 ) -> Range<MultiBufferRow> {
24790 let start = self.start.to_point(map.buffer_snapshot());
24791 let mut end = self.end.to_point(map.buffer_snapshot());
24792 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24793 end.row -= 1;
24794 }
24795
24796 let buffer_start = map.prev_line_boundary(start).0;
24797 let buffer_end = map.next_line_boundary(end).0;
24798 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24799 }
24800}
24801
24802impl<T: InvalidationRegion> InvalidationStack<T> {
24803 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24804 where
24805 S: Clone + ToOffset,
24806 {
24807 while let Some(region) = self.last() {
24808 let all_selections_inside_invalidation_ranges =
24809 if selections.len() == region.ranges().len() {
24810 selections
24811 .iter()
24812 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24813 .all(|(selection, invalidation_range)| {
24814 let head = selection.head().to_offset(buffer);
24815 invalidation_range.start <= head && invalidation_range.end >= head
24816 })
24817 } else {
24818 false
24819 };
24820
24821 if all_selections_inside_invalidation_ranges {
24822 break;
24823 } else {
24824 self.pop();
24825 }
24826 }
24827 }
24828}
24829
24830impl<T> Default for InvalidationStack<T> {
24831 fn default() -> Self {
24832 Self(Default::default())
24833 }
24834}
24835
24836impl<T> Deref for InvalidationStack<T> {
24837 type Target = Vec<T>;
24838
24839 fn deref(&self) -> &Self::Target {
24840 &self.0
24841 }
24842}
24843
24844impl<T> DerefMut for InvalidationStack<T> {
24845 fn deref_mut(&mut self) -> &mut Self::Target {
24846 &mut self.0
24847 }
24848}
24849
24850impl InvalidationRegion for SnippetState {
24851 fn ranges(&self) -> &[Range<Anchor>] {
24852 &self.ranges[self.active_index]
24853 }
24854}
24855
24856fn edit_prediction_edit_text(
24857 current_snapshot: &BufferSnapshot,
24858 edits: &[(Range<Anchor>, impl AsRef<str>)],
24859 edit_preview: &EditPreview,
24860 include_deletions: bool,
24861 cx: &App,
24862) -> HighlightedText {
24863 let edits = edits
24864 .iter()
24865 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24866 .collect::<Vec<_>>();
24867
24868 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24869}
24870
24871fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24872 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24873 // Just show the raw edit text with basic styling
24874 let mut text = String::new();
24875 let mut highlights = Vec::new();
24876
24877 let insertion_highlight_style = HighlightStyle {
24878 color: Some(cx.theme().colors().text),
24879 ..Default::default()
24880 };
24881
24882 for (_, edit_text) in edits {
24883 let start_offset = text.len();
24884 text.push_str(edit_text);
24885 let end_offset = text.len();
24886
24887 if start_offset < end_offset {
24888 highlights.push((start_offset..end_offset, insertion_highlight_style));
24889 }
24890 }
24891
24892 HighlightedText {
24893 text: text.into(),
24894 highlights,
24895 }
24896}
24897
24898pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24899 match severity {
24900 lsp::DiagnosticSeverity::ERROR => colors.error,
24901 lsp::DiagnosticSeverity::WARNING => colors.warning,
24902 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24903 lsp::DiagnosticSeverity::HINT => colors.info,
24904 _ => colors.ignored,
24905 }
24906}
24907
24908pub fn styled_runs_for_code_label<'a>(
24909 label: &'a CodeLabel,
24910 syntax_theme: &'a theme::SyntaxTheme,
24911) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24912 let fade_out = HighlightStyle {
24913 fade_out: Some(0.35),
24914 ..Default::default()
24915 };
24916
24917 let mut prev_end = label.filter_range.end;
24918 label
24919 .runs
24920 .iter()
24921 .enumerate()
24922 .flat_map(move |(ix, (range, highlight_id))| {
24923 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24924 style
24925 } else {
24926 return Default::default();
24927 };
24928 let muted_style = style.highlight(fade_out);
24929
24930 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24931 if range.start >= label.filter_range.end {
24932 if range.start > prev_end {
24933 runs.push((prev_end..range.start, fade_out));
24934 }
24935 runs.push((range.clone(), muted_style));
24936 } else if range.end <= label.filter_range.end {
24937 runs.push((range.clone(), style));
24938 } else {
24939 runs.push((range.start..label.filter_range.end, style));
24940 runs.push((label.filter_range.end..range.end, muted_style));
24941 }
24942 prev_end = cmp::max(prev_end, range.end);
24943
24944 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24945 runs.push((prev_end..label.text.len(), fade_out));
24946 }
24947
24948 runs
24949 })
24950}
24951
24952pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24953 let mut prev_index = 0;
24954 let mut prev_codepoint: Option<char> = None;
24955 text.char_indices()
24956 .chain([(text.len(), '\0')])
24957 .filter_map(move |(index, codepoint)| {
24958 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24959 let is_boundary = index == text.len()
24960 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24961 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24962 if is_boundary {
24963 let chunk = &text[prev_index..index];
24964 prev_index = index;
24965 Some(chunk)
24966 } else {
24967 None
24968 }
24969 })
24970}
24971
24972/// Given a string of text immediately before the cursor, iterates over possible
24973/// strings a snippet could match to. More precisely: returns an iterator over
24974/// suffixes of `text` created by splitting at word boundaries (before & after
24975/// every non-word character).
24976///
24977/// Shorter suffixes are returned first.
24978pub(crate) fn snippet_candidate_suffixes(
24979 text: &str,
24980 is_word_char: impl Fn(char) -> bool,
24981) -> impl std::iter::Iterator<Item = &str> {
24982 let mut prev_index = text.len();
24983 let mut prev_codepoint = None;
24984 text.char_indices()
24985 .rev()
24986 .chain([(0, '\0')])
24987 .filter_map(move |(index, codepoint)| {
24988 let prev_index = std::mem::replace(&mut prev_index, index);
24989 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24990 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
24991 None
24992 } else {
24993 let chunk = &text[prev_index..]; // go to end of string
24994 Some(chunk)
24995 }
24996 })
24997}
24998
24999pub trait RangeToAnchorExt: Sized {
25000 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25001
25002 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25003 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25004 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25005 }
25006}
25007
25008impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25009 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25010 let start_offset = self.start.to_offset(snapshot);
25011 let end_offset = self.end.to_offset(snapshot);
25012 if start_offset == end_offset {
25013 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25014 } else {
25015 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25016 }
25017 }
25018}
25019
25020pub trait RowExt {
25021 fn as_f64(&self) -> f64;
25022
25023 fn next_row(&self) -> Self;
25024
25025 fn previous_row(&self) -> Self;
25026
25027 fn minus(&self, other: Self) -> u32;
25028}
25029
25030impl RowExt for DisplayRow {
25031 fn as_f64(&self) -> f64 {
25032 self.0 as _
25033 }
25034
25035 fn next_row(&self) -> Self {
25036 Self(self.0 + 1)
25037 }
25038
25039 fn previous_row(&self) -> Self {
25040 Self(self.0.saturating_sub(1))
25041 }
25042
25043 fn minus(&self, other: Self) -> u32 {
25044 self.0 - other.0
25045 }
25046}
25047
25048impl RowExt for MultiBufferRow {
25049 fn as_f64(&self) -> f64 {
25050 self.0 as _
25051 }
25052
25053 fn next_row(&self) -> Self {
25054 Self(self.0 + 1)
25055 }
25056
25057 fn previous_row(&self) -> Self {
25058 Self(self.0.saturating_sub(1))
25059 }
25060
25061 fn minus(&self, other: Self) -> u32 {
25062 self.0 - other.0
25063 }
25064}
25065
25066trait RowRangeExt {
25067 type Row;
25068
25069 fn len(&self) -> usize;
25070
25071 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25072}
25073
25074impl RowRangeExt for Range<MultiBufferRow> {
25075 type Row = MultiBufferRow;
25076
25077 fn len(&self) -> usize {
25078 (self.end.0 - self.start.0) as usize
25079 }
25080
25081 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25082 (self.start.0..self.end.0).map(MultiBufferRow)
25083 }
25084}
25085
25086impl RowRangeExt for Range<DisplayRow> {
25087 type Row = DisplayRow;
25088
25089 fn len(&self) -> usize {
25090 (self.end.0 - self.start.0) as usize
25091 }
25092
25093 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25094 (self.start.0..self.end.0).map(DisplayRow)
25095 }
25096}
25097
25098/// If select range has more than one line, we
25099/// just point the cursor to range.start.
25100fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25101 if range.start.row == range.end.row {
25102 range
25103 } else {
25104 range.start..range.start
25105 }
25106}
25107pub struct KillRing(ClipboardItem);
25108impl Global for KillRing {}
25109
25110const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25111
25112enum BreakpointPromptEditAction {
25113 Log,
25114 Condition,
25115 HitCondition,
25116}
25117
25118struct BreakpointPromptEditor {
25119 pub(crate) prompt: Entity<Editor>,
25120 editor: WeakEntity<Editor>,
25121 breakpoint_anchor: Anchor,
25122 breakpoint: Breakpoint,
25123 edit_action: BreakpointPromptEditAction,
25124 block_ids: HashSet<CustomBlockId>,
25125 editor_margins: Arc<Mutex<EditorMargins>>,
25126 _subscriptions: Vec<Subscription>,
25127}
25128
25129impl BreakpointPromptEditor {
25130 const MAX_LINES: u8 = 4;
25131
25132 fn new(
25133 editor: WeakEntity<Editor>,
25134 breakpoint_anchor: Anchor,
25135 breakpoint: Breakpoint,
25136 edit_action: BreakpointPromptEditAction,
25137 window: &mut Window,
25138 cx: &mut Context<Self>,
25139 ) -> Self {
25140 let base_text = match edit_action {
25141 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25142 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25143 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25144 }
25145 .map(|msg| msg.to_string())
25146 .unwrap_or_default();
25147
25148 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25149 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25150
25151 let prompt = cx.new(|cx| {
25152 let mut prompt = Editor::new(
25153 EditorMode::AutoHeight {
25154 min_lines: 1,
25155 max_lines: Some(Self::MAX_LINES as usize),
25156 },
25157 buffer,
25158 None,
25159 window,
25160 cx,
25161 );
25162 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25163 prompt.set_show_cursor_when_unfocused(false, cx);
25164 prompt.set_placeholder_text(
25165 match edit_action {
25166 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25167 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25168 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25169 },
25170 window,
25171 cx,
25172 );
25173
25174 prompt
25175 });
25176
25177 Self {
25178 prompt,
25179 editor,
25180 breakpoint_anchor,
25181 breakpoint,
25182 edit_action,
25183 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25184 block_ids: Default::default(),
25185 _subscriptions: vec![],
25186 }
25187 }
25188
25189 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25190 self.block_ids.extend(block_ids)
25191 }
25192
25193 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25194 if let Some(editor) = self.editor.upgrade() {
25195 let message = self
25196 .prompt
25197 .read(cx)
25198 .buffer
25199 .read(cx)
25200 .as_singleton()
25201 .expect("A multi buffer in breakpoint prompt isn't possible")
25202 .read(cx)
25203 .as_rope()
25204 .to_string();
25205
25206 editor.update(cx, |editor, cx| {
25207 editor.edit_breakpoint_at_anchor(
25208 self.breakpoint_anchor,
25209 self.breakpoint.clone(),
25210 match self.edit_action {
25211 BreakpointPromptEditAction::Log => {
25212 BreakpointEditAction::EditLogMessage(message.into())
25213 }
25214 BreakpointPromptEditAction::Condition => {
25215 BreakpointEditAction::EditCondition(message.into())
25216 }
25217 BreakpointPromptEditAction::HitCondition => {
25218 BreakpointEditAction::EditHitCondition(message.into())
25219 }
25220 },
25221 cx,
25222 );
25223
25224 editor.remove_blocks(self.block_ids.clone(), None, cx);
25225 cx.focus_self(window);
25226 });
25227 }
25228 }
25229
25230 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25231 self.editor
25232 .update(cx, |editor, cx| {
25233 editor.remove_blocks(self.block_ids.clone(), None, cx);
25234 window.focus(&editor.focus_handle);
25235 })
25236 .log_err();
25237 }
25238
25239 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25240 let settings = ThemeSettings::get_global(cx);
25241 let text_style = TextStyle {
25242 color: if self.prompt.read(cx).read_only(cx) {
25243 cx.theme().colors().text_disabled
25244 } else {
25245 cx.theme().colors().text
25246 },
25247 font_family: settings.buffer_font.family.clone(),
25248 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25249 font_size: settings.buffer_font_size(cx).into(),
25250 font_weight: settings.buffer_font.weight,
25251 line_height: relative(settings.buffer_line_height.value()),
25252 ..Default::default()
25253 };
25254 EditorElement::new(
25255 &self.prompt,
25256 EditorStyle {
25257 background: cx.theme().colors().editor_background,
25258 local_player: cx.theme().players().local(),
25259 text: text_style,
25260 ..Default::default()
25261 },
25262 )
25263 }
25264}
25265
25266impl Render for BreakpointPromptEditor {
25267 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25268 let editor_margins = *self.editor_margins.lock();
25269 let gutter_dimensions = editor_margins.gutter;
25270 h_flex()
25271 .key_context("Editor")
25272 .bg(cx.theme().colors().editor_background)
25273 .border_y_1()
25274 .border_color(cx.theme().status().info_border)
25275 .size_full()
25276 .py(window.line_height() / 2.5)
25277 .on_action(cx.listener(Self::confirm))
25278 .on_action(cx.listener(Self::cancel))
25279 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25280 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25281 }
25282}
25283
25284impl Focusable for BreakpointPromptEditor {
25285 fn focus_handle(&self, cx: &App) -> FocusHandle {
25286 self.prompt.focus_handle(cx)
25287 }
25288}
25289
25290fn all_edits_insertions_or_deletions(
25291 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25292 snapshot: &MultiBufferSnapshot,
25293) -> bool {
25294 let mut all_insertions = true;
25295 let mut all_deletions = true;
25296
25297 for (range, new_text) in edits.iter() {
25298 let range_is_empty = range.to_offset(snapshot).is_empty();
25299 let text_is_empty = new_text.is_empty();
25300
25301 if range_is_empty != text_is_empty {
25302 if range_is_empty {
25303 all_deletions = false;
25304 } else {
25305 all_insertions = false;
25306 }
25307 } else {
25308 return false;
25309 }
25310
25311 if !all_insertions && !all_deletions {
25312 return false;
25313 }
25314 }
25315 all_insertions || all_deletions
25316}
25317
25318struct MissingEditPredictionKeybindingTooltip;
25319
25320impl Render for MissingEditPredictionKeybindingTooltip {
25321 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25322 ui::tooltip_container(cx, |container, cx| {
25323 container
25324 .flex_shrink_0()
25325 .max_w_80()
25326 .min_h(rems_from_px(124.))
25327 .justify_between()
25328 .child(
25329 v_flex()
25330 .flex_1()
25331 .text_ui_sm(cx)
25332 .child(Label::new("Conflict with Accept Keybinding"))
25333 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25334 )
25335 .child(
25336 h_flex()
25337 .pb_1()
25338 .gap_1()
25339 .items_end()
25340 .w_full()
25341 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25342 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25343 }))
25344 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25345 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25346 })),
25347 )
25348 })
25349 }
25350}
25351
25352#[derive(Debug, Clone, Copy, PartialEq)]
25353pub struct LineHighlight {
25354 pub background: Background,
25355 pub border: Option<gpui::Hsla>,
25356 pub include_gutter: bool,
25357 pub type_id: Option<TypeId>,
25358}
25359
25360struct LineManipulationResult {
25361 pub new_text: String,
25362 pub line_count_before: usize,
25363 pub line_count_after: usize,
25364}
25365
25366fn render_diff_hunk_controls(
25367 row: u32,
25368 status: &DiffHunkStatus,
25369 hunk_range: Range<Anchor>,
25370 is_created_file: bool,
25371 line_height: Pixels,
25372 editor: &Entity<Editor>,
25373 _window: &mut Window,
25374 cx: &mut App,
25375) -> AnyElement {
25376 h_flex()
25377 .h(line_height)
25378 .mr_1()
25379 .gap_1()
25380 .px_0p5()
25381 .pb_1()
25382 .border_x_1()
25383 .border_b_1()
25384 .border_color(cx.theme().colors().border_variant)
25385 .rounded_b_lg()
25386 .bg(cx.theme().colors().editor_background)
25387 .gap_1()
25388 .block_mouse_except_scroll()
25389 .shadow_md()
25390 .child(if status.has_secondary_hunk() {
25391 Button::new(("stage", row as u64), "Stage")
25392 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25393 .tooltip({
25394 let focus_handle = editor.focus_handle(cx);
25395 move |_window, cx| {
25396 Tooltip::for_action_in(
25397 "Stage Hunk",
25398 &::git::ToggleStaged,
25399 &focus_handle,
25400 cx,
25401 )
25402 }
25403 })
25404 .on_click({
25405 let editor = editor.clone();
25406 move |_event, _window, cx| {
25407 editor.update(cx, |editor, cx| {
25408 editor.stage_or_unstage_diff_hunks(
25409 true,
25410 vec![hunk_range.start..hunk_range.start],
25411 cx,
25412 );
25413 });
25414 }
25415 })
25416 } else {
25417 Button::new(("unstage", row as u64), "Unstage")
25418 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25419 .tooltip({
25420 let focus_handle = editor.focus_handle(cx);
25421 move |_window, cx| {
25422 Tooltip::for_action_in(
25423 "Unstage Hunk",
25424 &::git::ToggleStaged,
25425 &focus_handle,
25426 cx,
25427 )
25428 }
25429 })
25430 .on_click({
25431 let editor = editor.clone();
25432 move |_event, _window, cx| {
25433 editor.update(cx, |editor, cx| {
25434 editor.stage_or_unstage_diff_hunks(
25435 false,
25436 vec![hunk_range.start..hunk_range.start],
25437 cx,
25438 );
25439 });
25440 }
25441 })
25442 })
25443 .child(
25444 Button::new(("restore", row as u64), "Restore")
25445 .tooltip({
25446 let focus_handle = editor.focus_handle(cx);
25447 move |_window, cx| {
25448 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25449 }
25450 })
25451 .on_click({
25452 let editor = editor.clone();
25453 move |_event, window, cx| {
25454 editor.update(cx, |editor, cx| {
25455 let snapshot = editor.snapshot(window, cx);
25456 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25457 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25458 });
25459 }
25460 })
25461 .disabled(is_created_file),
25462 )
25463 .when(
25464 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25465 |el| {
25466 el.child(
25467 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25468 .shape(IconButtonShape::Square)
25469 .icon_size(IconSize::Small)
25470 // .disabled(!has_multiple_hunks)
25471 .tooltip({
25472 let focus_handle = editor.focus_handle(cx);
25473 move |_window, cx| {
25474 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25475 }
25476 })
25477 .on_click({
25478 let editor = editor.clone();
25479 move |_event, window, cx| {
25480 editor.update(cx, |editor, cx| {
25481 let snapshot = editor.snapshot(window, cx);
25482 let position =
25483 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25484 editor.go_to_hunk_before_or_after_position(
25485 &snapshot,
25486 position,
25487 Direction::Next,
25488 window,
25489 cx,
25490 );
25491 editor.expand_selected_diff_hunks(cx);
25492 });
25493 }
25494 }),
25495 )
25496 .child(
25497 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25498 .shape(IconButtonShape::Square)
25499 .icon_size(IconSize::Small)
25500 // .disabled(!has_multiple_hunks)
25501 .tooltip({
25502 let focus_handle = editor.focus_handle(cx);
25503 move |_window, cx| {
25504 Tooltip::for_action_in(
25505 "Previous Hunk",
25506 &GoToPreviousHunk,
25507 &focus_handle,
25508 cx,
25509 )
25510 }
25511 })
25512 .on_click({
25513 let editor = editor.clone();
25514 move |_event, window, cx| {
25515 editor.update(cx, |editor, cx| {
25516 let snapshot = editor.snapshot(window, cx);
25517 let point =
25518 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25519 editor.go_to_hunk_before_or_after_position(
25520 &snapshot,
25521 point,
25522 Direction::Prev,
25523 window,
25524 cx,
25525 );
25526 editor.expand_selected_diff_hunks(cx);
25527 });
25528 }
25529 }),
25530 )
25531 },
25532 )
25533 .into_any_element()
25534}
25535
25536pub fn multibuffer_context_lines(cx: &App) -> u32 {
25537 EditorSettings::try_get(cx)
25538 .map(|settings| settings.excerpt_context_lines)
25539 .unwrap_or(2)
25540 .min(32)
25541}