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 = cx.theme().colors();
8424
8425 let color = if is_phantom {
8426 if collides_with_existing {
8427 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8428 } else {
8429 Color::Hint
8430 }
8431 } else if is_rejected {
8432 Color::Disabled
8433 } else {
8434 Color::Debugger
8435 };
8436
8437 (color, icon)
8438 };
8439
8440 let breakpoint = Arc::from(breakpoint.clone());
8441
8442 let alt_as_text = gpui::Keystroke {
8443 modifiers: Modifiers::secondary_key(),
8444 ..Default::default()
8445 };
8446 let primary_action_text = if breakpoint.is_disabled() {
8447 "Enable breakpoint"
8448 } else if is_phantom && !collides_with_existing {
8449 "Set breakpoint"
8450 } else {
8451 "Unset breakpoint"
8452 };
8453 let focus_handle = self.focus_handle.clone();
8454
8455 let meta = if is_rejected {
8456 SharedString::from("No executable code is associated with this line.")
8457 } else if collides_with_existing && !breakpoint.is_disabled() {
8458 SharedString::from(format!(
8459 "{alt_as_text}-click to disable,\nright-click for more options."
8460 ))
8461 } else {
8462 SharedString::from("Right-click for more options.")
8463 };
8464 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8465 .icon_size(IconSize::XSmall)
8466 .size(ui::ButtonSize::None)
8467 .when(is_rejected, |this| {
8468 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8469 })
8470 .icon_color(color)
8471 .style(ButtonStyle::Transparent)
8472 .on_click(cx.listener({
8473 move |editor, event: &ClickEvent, window, cx| {
8474 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8475 BreakpointEditAction::InvertState
8476 } else {
8477 BreakpointEditAction::Toggle
8478 };
8479
8480 window.focus(&editor.focus_handle(cx));
8481 editor.edit_breakpoint_at_anchor(
8482 position,
8483 breakpoint.as_ref().clone(),
8484 edit_action,
8485 cx,
8486 );
8487 }
8488 }))
8489 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8490 editor.set_breakpoint_context_menu(
8491 row,
8492 Some(position),
8493 event.position(),
8494 window,
8495 cx,
8496 );
8497 }))
8498 .tooltip(move |_window, cx| {
8499 Tooltip::with_meta_in(
8500 primary_action_text,
8501 Some(&ToggleBreakpoint),
8502 meta.clone(),
8503 &focus_handle,
8504 cx,
8505 )
8506 })
8507 }
8508
8509 fn build_tasks_context(
8510 project: &Entity<Project>,
8511 buffer: &Entity<Buffer>,
8512 buffer_row: u32,
8513 tasks: &Arc<RunnableTasks>,
8514 cx: &mut Context<Self>,
8515 ) -> Task<Option<task::TaskContext>> {
8516 let position = Point::new(buffer_row, tasks.column);
8517 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8518 let location = Location {
8519 buffer: buffer.clone(),
8520 range: range_start..range_start,
8521 };
8522 // Fill in the environmental variables from the tree-sitter captures
8523 let mut captured_task_variables = TaskVariables::default();
8524 for (capture_name, value) in tasks.extra_variables.clone() {
8525 captured_task_variables.insert(
8526 task::VariableName::Custom(capture_name.into()),
8527 value.clone(),
8528 );
8529 }
8530 project.update(cx, |project, cx| {
8531 project.task_store().update(cx, |task_store, cx| {
8532 task_store.task_context_for_location(captured_task_variables, location, cx)
8533 })
8534 })
8535 }
8536
8537 pub fn spawn_nearest_task(
8538 &mut self,
8539 action: &SpawnNearestTask,
8540 window: &mut Window,
8541 cx: &mut Context<Self>,
8542 ) {
8543 let Some((workspace, _)) = self.workspace.clone() else {
8544 return;
8545 };
8546 let Some(project) = self.project.clone() else {
8547 return;
8548 };
8549
8550 // Try to find a closest, enclosing node using tree-sitter that has a task
8551 let Some((buffer, buffer_row, tasks)) = self
8552 .find_enclosing_node_task(cx)
8553 // Or find the task that's closest in row-distance.
8554 .or_else(|| self.find_closest_task(cx))
8555 else {
8556 return;
8557 };
8558
8559 let reveal_strategy = action.reveal;
8560 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8561 cx.spawn_in(window, async move |_, cx| {
8562 let context = task_context.await?;
8563 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8564
8565 let resolved = &mut resolved_task.resolved;
8566 resolved.reveal = reveal_strategy;
8567
8568 workspace
8569 .update_in(cx, |workspace, window, cx| {
8570 workspace.schedule_resolved_task(
8571 task_source_kind,
8572 resolved_task,
8573 false,
8574 window,
8575 cx,
8576 );
8577 })
8578 .ok()
8579 })
8580 .detach();
8581 }
8582
8583 fn find_closest_task(
8584 &mut self,
8585 cx: &mut Context<Self>,
8586 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8587 let cursor_row = self
8588 .selections
8589 .newest_adjusted(&self.display_snapshot(cx))
8590 .head()
8591 .row;
8592
8593 let ((buffer_id, row), tasks) = self
8594 .tasks
8595 .iter()
8596 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8597
8598 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8599 let tasks = Arc::new(tasks.to_owned());
8600 Some((buffer, *row, tasks))
8601 }
8602
8603 fn find_enclosing_node_task(
8604 &mut self,
8605 cx: &mut Context<Self>,
8606 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8607 let snapshot = self.buffer.read(cx).snapshot(cx);
8608 let offset = self
8609 .selections
8610 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8611 .head();
8612 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8613 let offset = excerpt.map_offset_to_buffer(offset);
8614 let buffer_id = excerpt.buffer().remote_id();
8615
8616 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8617 let mut cursor = layer.node().walk();
8618
8619 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8620 if cursor.node().end_byte() == offset.0 {
8621 cursor.goto_next_sibling();
8622 }
8623 }
8624
8625 // Ascend to the smallest ancestor that contains the range and has a task.
8626 loop {
8627 let node = cursor.node();
8628 let node_range = node.byte_range();
8629 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8630
8631 // Check if this node contains our offset
8632 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8633 // If it contains offset, check for task
8634 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8635 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8636 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8637 }
8638 }
8639
8640 if !cursor.goto_parent() {
8641 break;
8642 }
8643 }
8644 None
8645 }
8646
8647 fn render_run_indicator(
8648 &self,
8649 _style: &EditorStyle,
8650 is_active: bool,
8651 row: DisplayRow,
8652 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8653 cx: &mut Context<Self>,
8654 ) -> IconButton {
8655 let color = Color::Muted;
8656 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8657
8658 IconButton::new(
8659 ("run_indicator", row.0 as usize),
8660 ui::IconName::PlayOutlined,
8661 )
8662 .shape(ui::IconButtonShape::Square)
8663 .icon_size(IconSize::XSmall)
8664 .icon_color(color)
8665 .toggle_state(is_active)
8666 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8667 let quick_launch = match e {
8668 ClickEvent::Keyboard(_) => true,
8669 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8670 };
8671
8672 window.focus(&editor.focus_handle(cx));
8673 editor.toggle_code_actions(
8674 &ToggleCodeActions {
8675 deployed_from: Some(CodeActionSource::RunMenu(row)),
8676 quick_launch,
8677 },
8678 window,
8679 cx,
8680 );
8681 }))
8682 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8683 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8684 }))
8685 }
8686
8687 pub fn context_menu_visible(&self) -> bool {
8688 !self.edit_prediction_preview_is_active()
8689 && self
8690 .context_menu
8691 .borrow()
8692 .as_ref()
8693 .is_some_and(|menu| menu.visible())
8694 }
8695
8696 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8697 self.context_menu
8698 .borrow()
8699 .as_ref()
8700 .map(|menu| menu.origin())
8701 }
8702
8703 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8704 self.context_menu_options = Some(options);
8705 }
8706
8707 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8708 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8709
8710 fn render_edit_prediction_popover(
8711 &mut self,
8712 text_bounds: &Bounds<Pixels>,
8713 content_origin: gpui::Point<Pixels>,
8714 right_margin: Pixels,
8715 editor_snapshot: &EditorSnapshot,
8716 visible_row_range: Range<DisplayRow>,
8717 scroll_top: ScrollOffset,
8718 scroll_bottom: ScrollOffset,
8719 line_layouts: &[LineWithInvisibles],
8720 line_height: Pixels,
8721 scroll_position: gpui::Point<ScrollOffset>,
8722 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8723 newest_selection_head: Option<DisplayPoint>,
8724 editor_width: Pixels,
8725 style: &EditorStyle,
8726 window: &mut Window,
8727 cx: &mut App,
8728 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8729 if self.mode().is_minimap() {
8730 return None;
8731 }
8732 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8733
8734 if self.edit_prediction_visible_in_cursor_popover(true) {
8735 return None;
8736 }
8737
8738 match &active_edit_prediction.completion {
8739 EditPrediction::MoveWithin { target, .. } => {
8740 let target_display_point = target.to_display_point(editor_snapshot);
8741
8742 if self.edit_prediction_requires_modifier() {
8743 if !self.edit_prediction_preview_is_active() {
8744 return None;
8745 }
8746
8747 self.render_edit_prediction_modifier_jump_popover(
8748 text_bounds,
8749 content_origin,
8750 visible_row_range,
8751 line_layouts,
8752 line_height,
8753 scroll_pixel_position,
8754 newest_selection_head,
8755 target_display_point,
8756 window,
8757 cx,
8758 )
8759 } else {
8760 self.render_edit_prediction_eager_jump_popover(
8761 text_bounds,
8762 content_origin,
8763 editor_snapshot,
8764 visible_row_range,
8765 scroll_top,
8766 scroll_bottom,
8767 line_height,
8768 scroll_pixel_position,
8769 target_display_point,
8770 editor_width,
8771 window,
8772 cx,
8773 )
8774 }
8775 }
8776 EditPrediction::Edit {
8777 display_mode: EditDisplayMode::Inline,
8778 ..
8779 } => None,
8780 EditPrediction::Edit {
8781 display_mode: EditDisplayMode::TabAccept,
8782 edits,
8783 ..
8784 } => {
8785 let range = &edits.first()?.0;
8786 let target_display_point = range.end.to_display_point(editor_snapshot);
8787
8788 self.render_edit_prediction_end_of_line_popover(
8789 "Accept",
8790 editor_snapshot,
8791 visible_row_range,
8792 target_display_point,
8793 line_height,
8794 scroll_pixel_position,
8795 content_origin,
8796 editor_width,
8797 window,
8798 cx,
8799 )
8800 }
8801 EditPrediction::Edit {
8802 edits,
8803 edit_preview,
8804 display_mode: EditDisplayMode::DiffPopover,
8805 snapshot,
8806 } => self.render_edit_prediction_diff_popover(
8807 text_bounds,
8808 content_origin,
8809 right_margin,
8810 editor_snapshot,
8811 visible_row_range,
8812 line_layouts,
8813 line_height,
8814 scroll_position,
8815 scroll_pixel_position,
8816 newest_selection_head,
8817 editor_width,
8818 style,
8819 edits,
8820 edit_preview,
8821 snapshot,
8822 window,
8823 cx,
8824 ),
8825 EditPrediction::MoveOutside { snapshot, .. } => {
8826 let mut element = self
8827 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8828 .into_any();
8829
8830 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8831 let origin_x = text_bounds.size.width - size.width - px(30.);
8832 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8833 element.prepaint_at(origin, window, cx);
8834
8835 Some((element, origin))
8836 }
8837 }
8838 }
8839
8840 fn render_edit_prediction_modifier_jump_popover(
8841 &mut self,
8842 text_bounds: &Bounds<Pixels>,
8843 content_origin: gpui::Point<Pixels>,
8844 visible_row_range: Range<DisplayRow>,
8845 line_layouts: &[LineWithInvisibles],
8846 line_height: Pixels,
8847 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8848 newest_selection_head: Option<DisplayPoint>,
8849 target_display_point: DisplayPoint,
8850 window: &mut Window,
8851 cx: &mut App,
8852 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8853 let scrolled_content_origin =
8854 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8855
8856 const SCROLL_PADDING_Y: Pixels = px(12.);
8857
8858 if target_display_point.row() < visible_row_range.start {
8859 return self.render_edit_prediction_scroll_popover(
8860 |_| SCROLL_PADDING_Y,
8861 IconName::ArrowUp,
8862 visible_row_range,
8863 line_layouts,
8864 newest_selection_head,
8865 scrolled_content_origin,
8866 window,
8867 cx,
8868 );
8869 } else if target_display_point.row() >= visible_row_range.end {
8870 return self.render_edit_prediction_scroll_popover(
8871 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8872 IconName::ArrowDown,
8873 visible_row_range,
8874 line_layouts,
8875 newest_selection_head,
8876 scrolled_content_origin,
8877 window,
8878 cx,
8879 );
8880 }
8881
8882 const POLE_WIDTH: Pixels = px(2.);
8883
8884 let line_layout =
8885 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8886 let target_column = target_display_point.column() as usize;
8887
8888 let target_x = line_layout.x_for_index(target_column);
8889 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8890 - scroll_pixel_position.y;
8891
8892 let flag_on_right = target_x < text_bounds.size.width / 2.;
8893
8894 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8895 border_color.l += 0.001;
8896
8897 let mut element = v_flex()
8898 .items_end()
8899 .when(flag_on_right, |el| el.items_start())
8900 .child(if flag_on_right {
8901 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8902 .rounded_bl(px(0.))
8903 .rounded_tl(px(0.))
8904 .border_l_2()
8905 .border_color(border_color)
8906 } else {
8907 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8908 .rounded_br(px(0.))
8909 .rounded_tr(px(0.))
8910 .border_r_2()
8911 .border_color(border_color)
8912 })
8913 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8914 .into_any();
8915
8916 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8917
8918 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8919 - point(
8920 if flag_on_right {
8921 POLE_WIDTH
8922 } else {
8923 size.width - POLE_WIDTH
8924 },
8925 size.height - line_height,
8926 );
8927
8928 origin.x = origin.x.max(content_origin.x);
8929
8930 element.prepaint_at(origin, window, cx);
8931
8932 Some((element, origin))
8933 }
8934
8935 fn render_edit_prediction_scroll_popover(
8936 &mut self,
8937 to_y: impl Fn(Size<Pixels>) -> Pixels,
8938 scroll_icon: IconName,
8939 visible_row_range: Range<DisplayRow>,
8940 line_layouts: &[LineWithInvisibles],
8941 newest_selection_head: Option<DisplayPoint>,
8942 scrolled_content_origin: gpui::Point<Pixels>,
8943 window: &mut Window,
8944 cx: &mut App,
8945 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8946 let mut element = self
8947 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8948 .into_any();
8949
8950 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8951
8952 let cursor = newest_selection_head?;
8953 let cursor_row_layout =
8954 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8955 let cursor_column = cursor.column() as usize;
8956
8957 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8958
8959 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8960
8961 element.prepaint_at(origin, window, cx);
8962 Some((element, origin))
8963 }
8964
8965 fn render_edit_prediction_eager_jump_popover(
8966 &mut self,
8967 text_bounds: &Bounds<Pixels>,
8968 content_origin: gpui::Point<Pixels>,
8969 editor_snapshot: &EditorSnapshot,
8970 visible_row_range: Range<DisplayRow>,
8971 scroll_top: ScrollOffset,
8972 scroll_bottom: ScrollOffset,
8973 line_height: Pixels,
8974 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8975 target_display_point: DisplayPoint,
8976 editor_width: Pixels,
8977 window: &mut Window,
8978 cx: &mut App,
8979 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8980 if target_display_point.row().as_f64() < scroll_top {
8981 let mut element = self
8982 .render_edit_prediction_line_popover(
8983 "Jump to Edit",
8984 Some(IconName::ArrowUp),
8985 window,
8986 cx,
8987 )
8988 .into_any();
8989
8990 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8991 let offset = point(
8992 (text_bounds.size.width - size.width) / 2.,
8993 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8994 );
8995
8996 let origin = text_bounds.origin + offset;
8997 element.prepaint_at(origin, window, cx);
8998 Some((element, origin))
8999 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9000 let mut element = self
9001 .render_edit_prediction_line_popover(
9002 "Jump to Edit",
9003 Some(IconName::ArrowDown),
9004 window,
9005 cx,
9006 )
9007 .into_any();
9008
9009 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9010 let offset = point(
9011 (text_bounds.size.width - size.width) / 2.,
9012 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9013 );
9014
9015 let origin = text_bounds.origin + offset;
9016 element.prepaint_at(origin, window, cx);
9017 Some((element, origin))
9018 } else {
9019 self.render_edit_prediction_end_of_line_popover(
9020 "Jump to Edit",
9021 editor_snapshot,
9022 visible_row_range,
9023 target_display_point,
9024 line_height,
9025 scroll_pixel_position,
9026 content_origin,
9027 editor_width,
9028 window,
9029 cx,
9030 )
9031 }
9032 }
9033
9034 fn render_edit_prediction_end_of_line_popover(
9035 self: &mut Editor,
9036 label: &'static str,
9037 editor_snapshot: &EditorSnapshot,
9038 visible_row_range: Range<DisplayRow>,
9039 target_display_point: DisplayPoint,
9040 line_height: Pixels,
9041 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9042 content_origin: gpui::Point<Pixels>,
9043 editor_width: Pixels,
9044 window: &mut Window,
9045 cx: &mut App,
9046 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9047 let target_line_end = DisplayPoint::new(
9048 target_display_point.row(),
9049 editor_snapshot.line_len(target_display_point.row()),
9050 );
9051
9052 let mut element = self
9053 .render_edit_prediction_line_popover(label, None, window, cx)
9054 .into_any();
9055
9056 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9057
9058 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9059
9060 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9061 let mut origin = start_point
9062 + line_origin
9063 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9064 origin.x = origin.x.max(content_origin.x);
9065
9066 let max_x = content_origin.x + editor_width - size.width;
9067
9068 if origin.x > max_x {
9069 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9070
9071 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9072 origin.y += offset;
9073 IconName::ArrowUp
9074 } else {
9075 origin.y -= offset;
9076 IconName::ArrowDown
9077 };
9078
9079 element = self
9080 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9081 .into_any();
9082
9083 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9084
9085 origin.x = content_origin.x + editor_width - size.width - px(2.);
9086 }
9087
9088 element.prepaint_at(origin, window, cx);
9089 Some((element, origin))
9090 }
9091
9092 fn render_edit_prediction_diff_popover(
9093 self: &Editor,
9094 text_bounds: &Bounds<Pixels>,
9095 content_origin: gpui::Point<Pixels>,
9096 right_margin: Pixels,
9097 editor_snapshot: &EditorSnapshot,
9098 visible_row_range: Range<DisplayRow>,
9099 line_layouts: &[LineWithInvisibles],
9100 line_height: Pixels,
9101 scroll_position: gpui::Point<ScrollOffset>,
9102 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9103 newest_selection_head: Option<DisplayPoint>,
9104 editor_width: Pixels,
9105 style: &EditorStyle,
9106 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9107 edit_preview: &Option<language::EditPreview>,
9108 snapshot: &language::BufferSnapshot,
9109 window: &mut Window,
9110 cx: &mut App,
9111 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9112 let edit_start = edits
9113 .first()
9114 .unwrap()
9115 .0
9116 .start
9117 .to_display_point(editor_snapshot);
9118 let edit_end = edits
9119 .last()
9120 .unwrap()
9121 .0
9122 .end
9123 .to_display_point(editor_snapshot);
9124
9125 let is_visible = visible_row_range.contains(&edit_start.row())
9126 || visible_row_range.contains(&edit_end.row());
9127 if !is_visible {
9128 return None;
9129 }
9130
9131 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9132 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9133 } else {
9134 // Fallback for providers without edit_preview
9135 crate::edit_prediction_fallback_text(edits, cx)
9136 };
9137
9138 let styled_text = highlighted_edits.to_styled_text(&style.text);
9139 let line_count = highlighted_edits.text.lines().count();
9140
9141 const BORDER_WIDTH: Pixels = px(1.);
9142
9143 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9144 let has_keybind = keybind.is_some();
9145
9146 let mut element = h_flex()
9147 .items_start()
9148 .child(
9149 h_flex()
9150 .bg(cx.theme().colors().editor_background)
9151 .border(BORDER_WIDTH)
9152 .shadow_xs()
9153 .border_color(cx.theme().colors().border)
9154 .rounded_l_lg()
9155 .when(line_count > 1, |el| el.rounded_br_lg())
9156 .pr_1()
9157 .child(styled_text),
9158 )
9159 .child(
9160 h_flex()
9161 .h(line_height + BORDER_WIDTH * 2.)
9162 .px_1p5()
9163 .gap_1()
9164 // Workaround: For some reason, there's a gap if we don't do this
9165 .ml(-BORDER_WIDTH)
9166 .shadow(vec![gpui::BoxShadow {
9167 color: gpui::black().opacity(0.05),
9168 offset: point(px(1.), px(1.)),
9169 blur_radius: px(2.),
9170 spread_radius: px(0.),
9171 }])
9172 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9173 .border(BORDER_WIDTH)
9174 .border_color(cx.theme().colors().border)
9175 .rounded_r_lg()
9176 .id("edit_prediction_diff_popover_keybind")
9177 .when(!has_keybind, |el| {
9178 let status_colors = cx.theme().status();
9179
9180 el.bg(status_colors.error_background)
9181 .border_color(status_colors.error.opacity(0.6))
9182 .child(Icon::new(IconName::Info).color(Color::Error))
9183 .cursor_default()
9184 .hoverable_tooltip(move |_window, cx| {
9185 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9186 })
9187 })
9188 .children(keybind),
9189 )
9190 .into_any();
9191
9192 let longest_row =
9193 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9194 let longest_line_width = if visible_row_range.contains(&longest_row) {
9195 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9196 } else {
9197 layout_line(
9198 longest_row,
9199 editor_snapshot,
9200 style,
9201 editor_width,
9202 |_| false,
9203 window,
9204 cx,
9205 )
9206 .width
9207 };
9208
9209 let viewport_bounds =
9210 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9211 right: -right_margin,
9212 ..Default::default()
9213 });
9214
9215 let x_after_longest = Pixels::from(
9216 ScrollPixelOffset::from(
9217 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9218 ) - scroll_pixel_position.x,
9219 );
9220
9221 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9222
9223 // Fully visible if it can be displayed within the window (allow overlapping other
9224 // panes). However, this is only allowed if the popover starts within text_bounds.
9225 let can_position_to_the_right = x_after_longest < text_bounds.right()
9226 && x_after_longest + element_bounds.width < viewport_bounds.right();
9227
9228 let mut origin = if can_position_to_the_right {
9229 point(
9230 x_after_longest,
9231 text_bounds.origin.y
9232 + Pixels::from(
9233 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9234 - scroll_pixel_position.y,
9235 ),
9236 )
9237 } else {
9238 let cursor_row = newest_selection_head.map(|head| head.row());
9239 let above_edit = edit_start
9240 .row()
9241 .0
9242 .checked_sub(line_count as u32)
9243 .map(DisplayRow);
9244 let below_edit = Some(edit_end.row() + 1);
9245 let above_cursor =
9246 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9247 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9248
9249 // Place the edit popover adjacent to the edit if there is a location
9250 // available that is onscreen and does not obscure the cursor. Otherwise,
9251 // place it adjacent to the cursor.
9252 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9253 .into_iter()
9254 .flatten()
9255 .find(|&start_row| {
9256 let end_row = start_row + line_count as u32;
9257 visible_row_range.contains(&start_row)
9258 && visible_row_range.contains(&end_row)
9259 && cursor_row
9260 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9261 })?;
9262
9263 content_origin
9264 + point(
9265 Pixels::from(-scroll_pixel_position.x),
9266 Pixels::from(
9267 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9268 ),
9269 )
9270 };
9271
9272 origin.x -= BORDER_WIDTH;
9273
9274 window.defer_draw(element, origin, 1);
9275
9276 // Do not return an element, since it will already be drawn due to defer_draw.
9277 None
9278 }
9279
9280 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9281 px(30.)
9282 }
9283
9284 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9285 if self.read_only(cx) {
9286 cx.theme().players().read_only()
9287 } else {
9288 self.style.as_ref().unwrap().local_player
9289 }
9290 }
9291
9292 fn render_edit_prediction_accept_keybind(
9293 &self,
9294 window: &mut Window,
9295 cx: &mut App,
9296 ) -> Option<AnyElement> {
9297 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9298 let accept_keystroke = accept_binding.keystroke()?;
9299
9300 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9301
9302 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9303 Color::Accent
9304 } else {
9305 Color::Muted
9306 };
9307
9308 h_flex()
9309 .px_0p5()
9310 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9311 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9312 .text_size(TextSize::XSmall.rems(cx))
9313 .child(h_flex().children(ui::render_modifiers(
9314 accept_keystroke.modifiers(),
9315 PlatformStyle::platform(),
9316 Some(modifiers_color),
9317 Some(IconSize::XSmall.rems().into()),
9318 true,
9319 )))
9320 .when(is_platform_style_mac, |parent| {
9321 parent.child(accept_keystroke.key().to_string())
9322 })
9323 .when(!is_platform_style_mac, |parent| {
9324 parent.child(
9325 Key::new(
9326 util::capitalize(accept_keystroke.key()),
9327 Some(Color::Default),
9328 )
9329 .size(Some(IconSize::XSmall.rems().into())),
9330 )
9331 })
9332 .into_any()
9333 .into()
9334 }
9335
9336 fn render_edit_prediction_line_popover(
9337 &self,
9338 label: impl Into<SharedString>,
9339 icon: Option<IconName>,
9340 window: &mut Window,
9341 cx: &mut App,
9342 ) -> Stateful<Div> {
9343 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9344
9345 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9346 let has_keybind = keybind.is_some();
9347
9348 h_flex()
9349 .id("ep-line-popover")
9350 .py_0p5()
9351 .pl_1()
9352 .pr(padding_right)
9353 .gap_1()
9354 .rounded_md()
9355 .border_1()
9356 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9357 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9358 .shadow_xs()
9359 .when(!has_keybind, |el| {
9360 let status_colors = cx.theme().status();
9361
9362 el.bg(status_colors.error_background)
9363 .border_color(status_colors.error.opacity(0.6))
9364 .pl_2()
9365 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9366 .cursor_default()
9367 .hoverable_tooltip(move |_window, cx| {
9368 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9369 })
9370 })
9371 .children(keybind)
9372 .child(
9373 Label::new(label)
9374 .size(LabelSize::Small)
9375 .when(!has_keybind, |el| {
9376 el.color(cx.theme().status().error.into()).strikethrough()
9377 }),
9378 )
9379 .when(!has_keybind, |el| {
9380 el.child(
9381 h_flex().ml_1().child(
9382 Icon::new(IconName::Info)
9383 .size(IconSize::Small)
9384 .color(cx.theme().status().error.into()),
9385 ),
9386 )
9387 })
9388 .when_some(icon, |element, icon| {
9389 element.child(
9390 div()
9391 .mt(px(1.5))
9392 .child(Icon::new(icon).size(IconSize::Small)),
9393 )
9394 })
9395 }
9396
9397 fn render_edit_prediction_jump_outside_popover(
9398 &self,
9399 snapshot: &BufferSnapshot,
9400 window: &mut Window,
9401 cx: &mut App,
9402 ) -> Stateful<Div> {
9403 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9404 let has_keybind = keybind.is_some();
9405
9406 let file_name = snapshot
9407 .file()
9408 .map(|file| SharedString::new(file.file_name(cx)))
9409 .unwrap_or(SharedString::new_static("untitled"));
9410
9411 h_flex()
9412 .id("ep-jump-outside-popover")
9413 .py_1()
9414 .px_2()
9415 .gap_1()
9416 .rounded_md()
9417 .border_1()
9418 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9419 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9420 .shadow_xs()
9421 .when(!has_keybind, |el| {
9422 let status_colors = cx.theme().status();
9423
9424 el.bg(status_colors.error_background)
9425 .border_color(status_colors.error.opacity(0.6))
9426 .pl_2()
9427 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9428 .cursor_default()
9429 .hoverable_tooltip(move |_window, cx| {
9430 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9431 })
9432 })
9433 .children(keybind)
9434 .child(
9435 Label::new(file_name)
9436 .size(LabelSize::Small)
9437 .buffer_font(cx)
9438 .when(!has_keybind, |el| {
9439 el.color(cx.theme().status().error.into()).strikethrough()
9440 }),
9441 )
9442 .when(!has_keybind, |el| {
9443 el.child(
9444 h_flex().ml_1().child(
9445 Icon::new(IconName::Info)
9446 .size(IconSize::Small)
9447 .color(cx.theme().status().error.into()),
9448 ),
9449 )
9450 })
9451 .child(
9452 div()
9453 .mt(px(1.5))
9454 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9455 )
9456 }
9457
9458 fn edit_prediction_line_popover_bg_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.1))
9462 }
9463
9464 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9465 let accent_color = cx.theme().colors().text_accent;
9466 let editor_bg_color = cx.theme().colors().editor_background;
9467 editor_bg_color.blend(accent_color.opacity(0.6))
9468 }
9469 fn get_prediction_provider_icon_name(
9470 provider: &Option<RegisteredEditPredictionProvider>,
9471 ) -> IconName {
9472 match provider {
9473 Some(provider) => match provider.provider.name() {
9474 "copilot" => IconName::Copilot,
9475 "supermaven" => IconName::Supermaven,
9476 _ => IconName::ZedPredict,
9477 },
9478 None => IconName::ZedPredict,
9479 }
9480 }
9481
9482 fn render_edit_prediction_cursor_popover(
9483 &self,
9484 min_width: Pixels,
9485 max_width: Pixels,
9486 cursor_point: Point,
9487 style: &EditorStyle,
9488 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9489 _window: &Window,
9490 cx: &mut Context<Editor>,
9491 ) -> Option<AnyElement> {
9492 let provider = self.edit_prediction_provider.as_ref()?;
9493 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9494
9495 let is_refreshing = provider.provider.is_refreshing(cx);
9496
9497 fn pending_completion_container(icon: IconName) -> Div {
9498 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9499 }
9500
9501 let completion = match &self.active_edit_prediction {
9502 Some(prediction) => {
9503 if !self.has_visible_completions_menu() {
9504 const RADIUS: Pixels = px(6.);
9505 const BORDER_WIDTH: Pixels = px(1.);
9506
9507 return Some(
9508 h_flex()
9509 .elevation_2(cx)
9510 .border(BORDER_WIDTH)
9511 .border_color(cx.theme().colors().border)
9512 .when(accept_keystroke.is_none(), |el| {
9513 el.border_color(cx.theme().status().error)
9514 })
9515 .rounded(RADIUS)
9516 .rounded_tl(px(0.))
9517 .overflow_hidden()
9518 .child(div().px_1p5().child(match &prediction.completion {
9519 EditPrediction::MoveWithin { target, snapshot } => {
9520 use text::ToPoint as _;
9521 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9522 {
9523 Icon::new(IconName::ZedPredictDown)
9524 } else {
9525 Icon::new(IconName::ZedPredictUp)
9526 }
9527 }
9528 EditPrediction::MoveOutside { .. } => {
9529 // TODO [zeta2] custom icon for external jump?
9530 Icon::new(provider_icon)
9531 }
9532 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9533 }))
9534 .child(
9535 h_flex()
9536 .gap_1()
9537 .py_1()
9538 .px_2()
9539 .rounded_r(RADIUS - BORDER_WIDTH)
9540 .border_l_1()
9541 .border_color(cx.theme().colors().border)
9542 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9543 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9544 el.child(
9545 Label::new("Hold")
9546 .size(LabelSize::Small)
9547 .when(accept_keystroke.is_none(), |el| {
9548 el.strikethrough()
9549 })
9550 .line_height_style(LineHeightStyle::UiLabel),
9551 )
9552 })
9553 .id("edit_prediction_cursor_popover_keybind")
9554 .when(accept_keystroke.is_none(), |el| {
9555 let status_colors = cx.theme().status();
9556
9557 el.bg(status_colors.error_background)
9558 .border_color(status_colors.error.opacity(0.6))
9559 .child(Icon::new(IconName::Info).color(Color::Error))
9560 .cursor_default()
9561 .hoverable_tooltip(move |_window, cx| {
9562 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9563 .into()
9564 })
9565 })
9566 .when_some(
9567 accept_keystroke.as_ref(),
9568 |el, accept_keystroke| {
9569 el.child(h_flex().children(ui::render_modifiers(
9570 accept_keystroke.modifiers(),
9571 PlatformStyle::platform(),
9572 Some(Color::Default),
9573 Some(IconSize::XSmall.rems().into()),
9574 false,
9575 )))
9576 },
9577 ),
9578 )
9579 .into_any(),
9580 );
9581 }
9582
9583 self.render_edit_prediction_cursor_popover_preview(
9584 prediction,
9585 cursor_point,
9586 style,
9587 cx,
9588 )?
9589 }
9590
9591 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9592 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9593 stale_completion,
9594 cursor_point,
9595 style,
9596 cx,
9597 )?,
9598
9599 None => pending_completion_container(provider_icon)
9600 .child(Label::new("...").size(LabelSize::Small)),
9601 },
9602
9603 None => pending_completion_container(provider_icon)
9604 .child(Label::new("...").size(LabelSize::Small)),
9605 };
9606
9607 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9608 completion
9609 .with_animation(
9610 "loading-completion",
9611 Animation::new(Duration::from_secs(2))
9612 .repeat()
9613 .with_easing(pulsating_between(0.4, 0.8)),
9614 |label, delta| label.opacity(delta),
9615 )
9616 .into_any_element()
9617 } else {
9618 completion.into_any_element()
9619 };
9620
9621 let has_completion = self.active_edit_prediction.is_some();
9622
9623 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9624 Some(
9625 h_flex()
9626 .min_w(min_width)
9627 .max_w(max_width)
9628 .flex_1()
9629 .elevation_2(cx)
9630 .border_color(cx.theme().colors().border)
9631 .child(
9632 div()
9633 .flex_1()
9634 .py_1()
9635 .px_2()
9636 .overflow_hidden()
9637 .child(completion),
9638 )
9639 .when_some(accept_keystroke, |el, accept_keystroke| {
9640 if !accept_keystroke.modifiers().modified() {
9641 return el;
9642 }
9643
9644 el.child(
9645 h_flex()
9646 .h_full()
9647 .border_l_1()
9648 .rounded_r_lg()
9649 .border_color(cx.theme().colors().border)
9650 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9651 .gap_1()
9652 .py_1()
9653 .px_2()
9654 .child(
9655 h_flex()
9656 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9657 .when(is_platform_style_mac, |parent| parent.gap_1())
9658 .child(h_flex().children(ui::render_modifiers(
9659 accept_keystroke.modifiers(),
9660 PlatformStyle::platform(),
9661 Some(if !has_completion {
9662 Color::Muted
9663 } else {
9664 Color::Default
9665 }),
9666 None,
9667 false,
9668 ))),
9669 )
9670 .child(Label::new("Preview").into_any_element())
9671 .opacity(if has_completion { 1.0 } else { 0.4 }),
9672 )
9673 })
9674 .into_any(),
9675 )
9676 }
9677
9678 fn render_edit_prediction_cursor_popover_preview(
9679 &self,
9680 completion: &EditPredictionState,
9681 cursor_point: Point,
9682 style: &EditorStyle,
9683 cx: &mut Context<Editor>,
9684 ) -> Option<Div> {
9685 use text::ToPoint as _;
9686
9687 fn render_relative_row_jump(
9688 prefix: impl Into<String>,
9689 current_row: u32,
9690 target_row: u32,
9691 ) -> Div {
9692 let (row_diff, arrow) = if target_row < current_row {
9693 (current_row - target_row, IconName::ArrowUp)
9694 } else {
9695 (target_row - current_row, IconName::ArrowDown)
9696 };
9697
9698 h_flex()
9699 .child(
9700 Label::new(format!("{}{}", prefix.into(), row_diff))
9701 .color(Color::Muted)
9702 .size(LabelSize::Small),
9703 )
9704 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9705 }
9706
9707 let supports_jump = self
9708 .edit_prediction_provider
9709 .as_ref()
9710 .map(|provider| provider.provider.supports_jump_to_edit())
9711 .unwrap_or(true);
9712
9713 match &completion.completion {
9714 EditPrediction::MoveWithin {
9715 target, snapshot, ..
9716 } => {
9717 if !supports_jump {
9718 return None;
9719 }
9720
9721 Some(
9722 h_flex()
9723 .px_2()
9724 .gap_2()
9725 .flex_1()
9726 .child(
9727 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9728 Icon::new(IconName::ZedPredictDown)
9729 } else {
9730 Icon::new(IconName::ZedPredictUp)
9731 },
9732 )
9733 .child(Label::new("Jump to Edit")),
9734 )
9735 }
9736 EditPrediction::MoveOutside { snapshot, .. } => {
9737 let file_name = snapshot
9738 .file()
9739 .map(|file| file.file_name(cx))
9740 .unwrap_or("untitled");
9741 Some(
9742 h_flex()
9743 .px_2()
9744 .gap_2()
9745 .flex_1()
9746 .child(Icon::new(IconName::ZedPredict))
9747 .child(Label::new(format!("Jump to {file_name}"))),
9748 )
9749 }
9750 EditPrediction::Edit {
9751 edits,
9752 edit_preview,
9753 snapshot,
9754 display_mode: _,
9755 } => {
9756 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9757
9758 let (highlighted_edits, has_more_lines) =
9759 if let Some(edit_preview) = edit_preview.as_ref() {
9760 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9761 .first_line_preview()
9762 } else {
9763 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9764 };
9765
9766 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9767 .with_default_highlights(&style.text, highlighted_edits.highlights);
9768
9769 let preview = h_flex()
9770 .gap_1()
9771 .min_w_16()
9772 .child(styled_text)
9773 .when(has_more_lines, |parent| parent.child("…"));
9774
9775 let left = if supports_jump && first_edit_row != cursor_point.row {
9776 render_relative_row_jump("", cursor_point.row, first_edit_row)
9777 .into_any_element()
9778 } else {
9779 let icon_name =
9780 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9781 Icon::new(icon_name).into_any_element()
9782 };
9783
9784 Some(
9785 h_flex()
9786 .h_full()
9787 .flex_1()
9788 .gap_2()
9789 .pr_1()
9790 .overflow_x_hidden()
9791 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9792 .child(left)
9793 .child(preview),
9794 )
9795 }
9796 }
9797 }
9798
9799 pub fn render_context_menu(
9800 &self,
9801 style: &EditorStyle,
9802 max_height_in_lines: u32,
9803 window: &mut Window,
9804 cx: &mut Context<Editor>,
9805 ) -> Option<AnyElement> {
9806 let menu = self.context_menu.borrow();
9807 let menu = menu.as_ref()?;
9808 if !menu.visible() {
9809 return None;
9810 };
9811 Some(menu.render(style, max_height_in_lines, window, cx))
9812 }
9813
9814 fn render_context_menu_aside(
9815 &mut self,
9816 max_size: Size<Pixels>,
9817 window: &mut Window,
9818 cx: &mut Context<Editor>,
9819 ) -> Option<AnyElement> {
9820 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9821 if menu.visible() {
9822 menu.render_aside(max_size, window, cx)
9823 } else {
9824 None
9825 }
9826 })
9827 }
9828
9829 fn hide_context_menu(
9830 &mut self,
9831 window: &mut Window,
9832 cx: &mut Context<Self>,
9833 ) -> Option<CodeContextMenu> {
9834 cx.notify();
9835 self.completion_tasks.clear();
9836 let context_menu = self.context_menu.borrow_mut().take();
9837 self.stale_edit_prediction_in_menu.take();
9838 self.update_visible_edit_prediction(window, cx);
9839 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9840 && let Some(completion_provider) = &self.completion_provider
9841 {
9842 completion_provider.selection_changed(None, window, cx);
9843 }
9844 context_menu
9845 }
9846
9847 fn show_snippet_choices(
9848 &mut self,
9849 choices: &Vec<String>,
9850 selection: Range<Anchor>,
9851 cx: &mut Context<Self>,
9852 ) {
9853 let Some((_, buffer, _)) = self
9854 .buffer()
9855 .read(cx)
9856 .excerpt_containing(selection.start, cx)
9857 else {
9858 return;
9859 };
9860 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9861 else {
9862 return;
9863 };
9864 if buffer != end_buffer {
9865 log::error!("expected anchor range to have matching buffer IDs");
9866 return;
9867 }
9868
9869 let id = post_inc(&mut self.next_completion_id);
9870 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9871 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9872 CompletionsMenu::new_snippet_choices(
9873 id,
9874 true,
9875 choices,
9876 selection,
9877 buffer,
9878 snippet_sort_order,
9879 ),
9880 ));
9881 }
9882
9883 pub fn insert_snippet(
9884 &mut self,
9885 insertion_ranges: &[Range<MultiBufferOffset>],
9886 snippet: Snippet,
9887 window: &mut Window,
9888 cx: &mut Context<Self>,
9889 ) -> Result<()> {
9890 struct Tabstop<T> {
9891 is_end_tabstop: bool,
9892 ranges: Vec<Range<T>>,
9893 choices: Option<Vec<String>>,
9894 }
9895
9896 let tabstops = self.buffer.update(cx, |buffer, cx| {
9897 let snippet_text: Arc<str> = snippet.text.clone().into();
9898 let edits = insertion_ranges
9899 .iter()
9900 .cloned()
9901 .map(|range| (range, snippet_text.clone()));
9902 let autoindent_mode = AutoindentMode::Block {
9903 original_indent_columns: Vec::new(),
9904 };
9905 buffer.edit(edits, Some(autoindent_mode), cx);
9906
9907 let snapshot = &*buffer.read(cx);
9908 let snippet = &snippet;
9909 snippet
9910 .tabstops
9911 .iter()
9912 .map(|tabstop| {
9913 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9914 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9915 });
9916 let mut tabstop_ranges = tabstop
9917 .ranges
9918 .iter()
9919 .flat_map(|tabstop_range| {
9920 let mut delta = 0_isize;
9921 insertion_ranges.iter().map(move |insertion_range| {
9922 let insertion_start = insertion_range.start + delta;
9923 delta += snippet.text.len() as isize
9924 - (insertion_range.end - insertion_range.start) as isize;
9925
9926 let start =
9927 (insertion_start + tabstop_range.start).min(snapshot.len());
9928 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
9929 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9930 })
9931 })
9932 .collect::<Vec<_>>();
9933 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9934
9935 Tabstop {
9936 is_end_tabstop,
9937 ranges: tabstop_ranges,
9938 choices: tabstop.choices.clone(),
9939 }
9940 })
9941 .collect::<Vec<_>>()
9942 });
9943 if let Some(tabstop) = tabstops.first() {
9944 self.change_selections(Default::default(), window, cx, |s| {
9945 // Reverse order so that the first range is the newest created selection.
9946 // Completions will use it and autoscroll will prioritize it.
9947 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9948 });
9949
9950 if let Some(choices) = &tabstop.choices
9951 && let Some(selection) = tabstop.ranges.first()
9952 {
9953 self.show_snippet_choices(choices, selection.clone(), cx)
9954 }
9955
9956 // If we're already at the last tabstop and it's at the end of the snippet,
9957 // we're done, we don't need to keep the state around.
9958 if !tabstop.is_end_tabstop {
9959 let choices = tabstops
9960 .iter()
9961 .map(|tabstop| tabstop.choices.clone())
9962 .collect();
9963
9964 let ranges = tabstops
9965 .into_iter()
9966 .map(|tabstop| tabstop.ranges)
9967 .collect::<Vec<_>>();
9968
9969 self.snippet_stack.push(SnippetState {
9970 active_index: 0,
9971 ranges,
9972 choices,
9973 });
9974 }
9975
9976 // Check whether the just-entered snippet ends with an auto-closable bracket.
9977 if self.autoclose_regions.is_empty() {
9978 let snapshot = self.buffer.read(cx).snapshot(cx);
9979 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9980 let selection_head = selection.head();
9981 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9982 continue;
9983 };
9984
9985 let mut bracket_pair = None;
9986 let max_lookup_length = scope
9987 .brackets()
9988 .map(|(pair, _)| {
9989 pair.start
9990 .as_str()
9991 .chars()
9992 .count()
9993 .max(pair.end.as_str().chars().count())
9994 })
9995 .max();
9996 if let Some(max_lookup_length) = max_lookup_length {
9997 let next_text = snapshot
9998 .chars_at(selection_head)
9999 .take(max_lookup_length)
10000 .collect::<String>();
10001 let prev_text = snapshot
10002 .reversed_chars_at(selection_head)
10003 .take(max_lookup_length)
10004 .collect::<String>();
10005
10006 for (pair, enabled) in scope.brackets() {
10007 if enabled
10008 && pair.close
10009 && prev_text.starts_with(pair.start.as_str())
10010 && next_text.starts_with(pair.end.as_str())
10011 {
10012 bracket_pair = Some(pair.clone());
10013 break;
10014 }
10015 }
10016 }
10017
10018 if let Some(pair) = bracket_pair {
10019 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10020 let autoclose_enabled =
10021 self.use_autoclose && snapshot_settings.use_autoclose;
10022 if autoclose_enabled {
10023 let start = snapshot.anchor_after(selection_head);
10024 let end = snapshot.anchor_after(selection_head);
10025 self.autoclose_regions.push(AutocloseRegion {
10026 selection_id: selection.id,
10027 range: start..end,
10028 pair,
10029 });
10030 }
10031 }
10032 }
10033 }
10034 }
10035 Ok(())
10036 }
10037
10038 pub fn move_to_next_snippet_tabstop(
10039 &mut self,
10040 window: &mut Window,
10041 cx: &mut Context<Self>,
10042 ) -> bool {
10043 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10044 }
10045
10046 pub fn move_to_prev_snippet_tabstop(
10047 &mut self,
10048 window: &mut Window,
10049 cx: &mut Context<Self>,
10050 ) -> bool {
10051 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10052 }
10053
10054 pub fn move_to_snippet_tabstop(
10055 &mut self,
10056 bias: Bias,
10057 window: &mut Window,
10058 cx: &mut Context<Self>,
10059 ) -> bool {
10060 if let Some(mut snippet) = self.snippet_stack.pop() {
10061 match bias {
10062 Bias::Left => {
10063 if snippet.active_index > 0 {
10064 snippet.active_index -= 1;
10065 } else {
10066 self.snippet_stack.push(snippet);
10067 return false;
10068 }
10069 }
10070 Bias::Right => {
10071 if snippet.active_index + 1 < snippet.ranges.len() {
10072 snippet.active_index += 1;
10073 } else {
10074 self.snippet_stack.push(snippet);
10075 return false;
10076 }
10077 }
10078 }
10079 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10080 self.change_selections(Default::default(), window, cx, |s| {
10081 // Reverse order so that the first range is the newest created selection.
10082 // Completions will use it and autoscroll will prioritize it.
10083 s.select_ranges(current_ranges.iter().rev().cloned())
10084 });
10085
10086 if let Some(choices) = &snippet.choices[snippet.active_index]
10087 && let Some(selection) = current_ranges.first()
10088 {
10089 self.show_snippet_choices(choices, selection.clone(), cx);
10090 }
10091
10092 // If snippet state is not at the last tabstop, push it back on the stack
10093 if snippet.active_index + 1 < snippet.ranges.len() {
10094 self.snippet_stack.push(snippet);
10095 }
10096 return true;
10097 }
10098 }
10099
10100 false
10101 }
10102
10103 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10104 self.transact(window, cx, |this, window, cx| {
10105 this.select_all(&SelectAll, window, cx);
10106 this.insert("", window, cx);
10107 });
10108 }
10109
10110 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10111 if self.read_only(cx) {
10112 return;
10113 }
10114 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10115 self.transact(window, cx, |this, window, cx| {
10116 this.select_autoclose_pair(window, cx);
10117
10118 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10119
10120 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10121 if !this.linked_edit_ranges.is_empty() {
10122 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10123 let snapshot = this.buffer.read(cx).snapshot(cx);
10124
10125 for selection in selections.iter() {
10126 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10127 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10128 if selection_start.buffer_id != selection_end.buffer_id {
10129 continue;
10130 }
10131 if let Some(ranges) =
10132 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10133 {
10134 for (buffer, entries) in ranges {
10135 linked_ranges.entry(buffer).or_default().extend(entries);
10136 }
10137 }
10138 }
10139 }
10140
10141 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10142 for selection in &mut selections {
10143 if selection.is_empty() {
10144 let old_head = selection.head();
10145 let mut new_head =
10146 movement::left(&display_map, old_head.to_display_point(&display_map))
10147 .to_point(&display_map);
10148 if let Some((buffer, line_buffer_range)) = display_map
10149 .buffer_snapshot()
10150 .buffer_line_for_row(MultiBufferRow(old_head.row))
10151 {
10152 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10153 let indent_len = match indent_size.kind {
10154 IndentKind::Space => {
10155 buffer.settings_at(line_buffer_range.start, cx).tab_size
10156 }
10157 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10158 };
10159 if old_head.column <= indent_size.len && old_head.column > 0 {
10160 let indent_len = indent_len.get();
10161 new_head = cmp::min(
10162 new_head,
10163 MultiBufferPoint::new(
10164 old_head.row,
10165 ((old_head.column - 1) / indent_len) * indent_len,
10166 ),
10167 );
10168 }
10169 }
10170
10171 selection.set_head(new_head, SelectionGoal::None);
10172 }
10173 }
10174
10175 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10176 this.insert("", window, cx);
10177 let empty_str: Arc<str> = Arc::from("");
10178 for (buffer, edits) in linked_ranges {
10179 let snapshot = buffer.read(cx).snapshot();
10180 use text::ToPoint as TP;
10181
10182 let edits = edits
10183 .into_iter()
10184 .map(|range| {
10185 let end_point = TP::to_point(&range.end, &snapshot);
10186 let mut start_point = TP::to_point(&range.start, &snapshot);
10187
10188 if end_point == start_point {
10189 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10190 .saturating_sub(1);
10191 start_point =
10192 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10193 };
10194
10195 (start_point..end_point, empty_str.clone())
10196 })
10197 .sorted_by_key(|(range, _)| range.start)
10198 .collect::<Vec<_>>();
10199 buffer.update(cx, |this, cx| {
10200 this.edit(edits, None, cx);
10201 })
10202 }
10203 this.refresh_edit_prediction(true, false, window, cx);
10204 refresh_linked_ranges(this, window, cx);
10205 });
10206 }
10207
10208 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10209 if self.read_only(cx) {
10210 return;
10211 }
10212 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10213 self.transact(window, cx, |this, window, cx| {
10214 this.change_selections(Default::default(), window, cx, |s| {
10215 s.move_with(|map, selection| {
10216 if selection.is_empty() {
10217 let cursor = movement::right(map, selection.head());
10218 selection.end = cursor;
10219 selection.reversed = true;
10220 selection.goal = SelectionGoal::None;
10221 }
10222 })
10223 });
10224 this.insert("", window, cx);
10225 this.refresh_edit_prediction(true, false, window, cx);
10226 });
10227 }
10228
10229 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10230 if self.mode.is_single_line() {
10231 cx.propagate();
10232 return;
10233 }
10234
10235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10236 if self.move_to_prev_snippet_tabstop(window, cx) {
10237 return;
10238 }
10239 self.outdent(&Outdent, window, cx);
10240 }
10241
10242 pub fn next_snippet_tabstop(
10243 &mut self,
10244 _: &NextSnippetTabstop,
10245 window: &mut Window,
10246 cx: &mut Context<Self>,
10247 ) {
10248 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10249 cx.propagate();
10250 return;
10251 }
10252
10253 if self.move_to_next_snippet_tabstop(window, cx) {
10254 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10255 return;
10256 }
10257 cx.propagate();
10258 }
10259
10260 pub fn previous_snippet_tabstop(
10261 &mut self,
10262 _: &PreviousSnippetTabstop,
10263 window: &mut Window,
10264 cx: &mut Context<Self>,
10265 ) {
10266 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10267 cx.propagate();
10268 return;
10269 }
10270
10271 if self.move_to_prev_snippet_tabstop(window, cx) {
10272 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10273 return;
10274 }
10275 cx.propagate();
10276 }
10277
10278 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10279 if self.mode.is_single_line() {
10280 cx.propagate();
10281 return;
10282 }
10283
10284 if self.move_to_next_snippet_tabstop(window, cx) {
10285 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10286 return;
10287 }
10288 if self.read_only(cx) {
10289 return;
10290 }
10291 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10292 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10293 let buffer = self.buffer.read(cx);
10294 let snapshot = buffer.snapshot(cx);
10295 let rows_iter = selections.iter().map(|s| s.head().row);
10296 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10297
10298 let has_some_cursor_in_whitespace = selections
10299 .iter()
10300 .filter(|selection| selection.is_empty())
10301 .any(|selection| {
10302 let cursor = selection.head();
10303 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10304 cursor.column < current_indent.len
10305 });
10306
10307 let mut edits = Vec::new();
10308 let mut prev_edited_row = 0;
10309 let mut row_delta = 0;
10310 for selection in &mut selections {
10311 if selection.start.row != prev_edited_row {
10312 row_delta = 0;
10313 }
10314 prev_edited_row = selection.end.row;
10315
10316 // If the selection is non-empty, then increase the indentation of the selected lines.
10317 if !selection.is_empty() {
10318 row_delta =
10319 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10320 continue;
10321 }
10322
10323 let cursor = selection.head();
10324 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10325 if let Some(suggested_indent) =
10326 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10327 {
10328 // Don't do anything if already at suggested indent
10329 // and there is any other cursor which is not
10330 if has_some_cursor_in_whitespace
10331 && cursor.column == current_indent.len
10332 && current_indent.len == suggested_indent.len
10333 {
10334 continue;
10335 }
10336
10337 // Adjust line and move cursor to suggested indent
10338 // if cursor is not at suggested indent
10339 if cursor.column < suggested_indent.len
10340 && cursor.column <= current_indent.len
10341 && current_indent.len <= suggested_indent.len
10342 {
10343 selection.start = Point::new(cursor.row, suggested_indent.len);
10344 selection.end = selection.start;
10345 if row_delta == 0 {
10346 edits.extend(Buffer::edit_for_indent_size_adjustment(
10347 cursor.row,
10348 current_indent,
10349 suggested_indent,
10350 ));
10351 row_delta = suggested_indent.len - current_indent.len;
10352 }
10353 continue;
10354 }
10355
10356 // If current indent is more than suggested indent
10357 // only move cursor to current indent and skip indent
10358 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10359 selection.start = Point::new(cursor.row, current_indent.len);
10360 selection.end = selection.start;
10361 continue;
10362 }
10363 }
10364
10365 // Otherwise, insert a hard or soft tab.
10366 let settings = buffer.language_settings_at(cursor, cx);
10367 let tab_size = if settings.hard_tabs {
10368 IndentSize::tab()
10369 } else {
10370 let tab_size = settings.tab_size.get();
10371 let indent_remainder = snapshot
10372 .text_for_range(Point::new(cursor.row, 0)..cursor)
10373 .flat_map(str::chars)
10374 .fold(row_delta % tab_size, |counter: u32, c| {
10375 if c == '\t' {
10376 0
10377 } else {
10378 (counter + 1) % tab_size
10379 }
10380 });
10381
10382 let chars_to_next_tab_stop = tab_size - indent_remainder;
10383 IndentSize::spaces(chars_to_next_tab_stop)
10384 };
10385 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10386 selection.end = selection.start;
10387 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10388 row_delta += tab_size.len;
10389 }
10390
10391 self.transact(window, cx, |this, window, cx| {
10392 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10393 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10394 this.refresh_edit_prediction(true, false, window, cx);
10395 });
10396 }
10397
10398 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10399 if self.read_only(cx) {
10400 return;
10401 }
10402 if self.mode.is_single_line() {
10403 cx.propagate();
10404 return;
10405 }
10406
10407 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10408 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10409 let mut prev_edited_row = 0;
10410 let mut row_delta = 0;
10411 let mut edits = Vec::new();
10412 let buffer = self.buffer.read(cx);
10413 let snapshot = buffer.snapshot(cx);
10414 for selection in &mut selections {
10415 if selection.start.row != prev_edited_row {
10416 row_delta = 0;
10417 }
10418 prev_edited_row = selection.end.row;
10419
10420 row_delta =
10421 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10422 }
10423
10424 self.transact(window, cx, |this, window, cx| {
10425 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10426 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10427 });
10428 }
10429
10430 fn indent_selection(
10431 buffer: &MultiBuffer,
10432 snapshot: &MultiBufferSnapshot,
10433 selection: &mut Selection<Point>,
10434 edits: &mut Vec<(Range<Point>, String)>,
10435 delta_for_start_row: u32,
10436 cx: &App,
10437 ) -> u32 {
10438 let settings = buffer.language_settings_at(selection.start, cx);
10439 let tab_size = settings.tab_size.get();
10440 let indent_kind = if settings.hard_tabs {
10441 IndentKind::Tab
10442 } else {
10443 IndentKind::Space
10444 };
10445 let mut start_row = selection.start.row;
10446 let mut end_row = selection.end.row + 1;
10447
10448 // If a selection ends at the beginning of a line, don't indent
10449 // that last line.
10450 if selection.end.column == 0 && selection.end.row > selection.start.row {
10451 end_row -= 1;
10452 }
10453
10454 // Avoid re-indenting a row that has already been indented by a
10455 // previous selection, but still update this selection's column
10456 // to reflect that indentation.
10457 if delta_for_start_row > 0 {
10458 start_row += 1;
10459 selection.start.column += delta_for_start_row;
10460 if selection.end.row == selection.start.row {
10461 selection.end.column += delta_for_start_row;
10462 }
10463 }
10464
10465 let mut delta_for_end_row = 0;
10466 let has_multiple_rows = start_row + 1 != end_row;
10467 for row in start_row..end_row {
10468 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10469 let indent_delta = match (current_indent.kind, indent_kind) {
10470 (IndentKind::Space, IndentKind::Space) => {
10471 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10472 IndentSize::spaces(columns_to_next_tab_stop)
10473 }
10474 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10475 (_, IndentKind::Tab) => IndentSize::tab(),
10476 };
10477
10478 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10479 0
10480 } else {
10481 selection.start.column
10482 };
10483 let row_start = Point::new(row, start);
10484 edits.push((
10485 row_start..row_start,
10486 indent_delta.chars().collect::<String>(),
10487 ));
10488
10489 // Update this selection's endpoints to reflect the indentation.
10490 if row == selection.start.row {
10491 selection.start.column += indent_delta.len;
10492 }
10493 if row == selection.end.row {
10494 selection.end.column += indent_delta.len;
10495 delta_for_end_row = indent_delta.len;
10496 }
10497 }
10498
10499 if selection.start.row == selection.end.row {
10500 delta_for_start_row + delta_for_end_row
10501 } else {
10502 delta_for_end_row
10503 }
10504 }
10505
10506 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10507 if self.read_only(cx) {
10508 return;
10509 }
10510 if self.mode.is_single_line() {
10511 cx.propagate();
10512 return;
10513 }
10514
10515 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10516 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10517 let selections = self.selections.all::<Point>(&display_map);
10518 let mut deletion_ranges = Vec::new();
10519 let mut last_outdent = None;
10520 {
10521 let buffer = self.buffer.read(cx);
10522 let snapshot = buffer.snapshot(cx);
10523 for selection in &selections {
10524 let settings = buffer.language_settings_at(selection.start, cx);
10525 let tab_size = settings.tab_size.get();
10526 let mut rows = selection.spanned_rows(false, &display_map);
10527
10528 // Avoid re-outdenting a row that has already been outdented by a
10529 // previous selection.
10530 if let Some(last_row) = last_outdent
10531 && last_row == rows.start
10532 {
10533 rows.start = rows.start.next_row();
10534 }
10535 let has_multiple_rows = rows.len() > 1;
10536 for row in rows.iter_rows() {
10537 let indent_size = snapshot.indent_size_for_line(row);
10538 if indent_size.len > 0 {
10539 let deletion_len = match indent_size.kind {
10540 IndentKind::Space => {
10541 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10542 if columns_to_prev_tab_stop == 0 {
10543 tab_size
10544 } else {
10545 columns_to_prev_tab_stop
10546 }
10547 }
10548 IndentKind::Tab => 1,
10549 };
10550 let start = if has_multiple_rows
10551 || deletion_len > selection.start.column
10552 || indent_size.len < selection.start.column
10553 {
10554 0
10555 } else {
10556 selection.start.column - deletion_len
10557 };
10558 deletion_ranges.push(
10559 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10560 );
10561 last_outdent = Some(row);
10562 }
10563 }
10564 }
10565 }
10566
10567 self.transact(window, cx, |this, window, cx| {
10568 this.buffer.update(cx, |buffer, cx| {
10569 let empty_str: Arc<str> = Arc::default();
10570 buffer.edit(
10571 deletion_ranges
10572 .into_iter()
10573 .map(|range| (range, empty_str.clone())),
10574 None,
10575 cx,
10576 );
10577 });
10578 let selections = this
10579 .selections
10580 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10581 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10582 });
10583 }
10584
10585 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10586 if self.read_only(cx) {
10587 return;
10588 }
10589 if self.mode.is_single_line() {
10590 cx.propagate();
10591 return;
10592 }
10593
10594 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10595 let selections = self
10596 .selections
10597 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10598 .into_iter()
10599 .map(|s| s.range());
10600
10601 self.transact(window, cx, |this, window, cx| {
10602 this.buffer.update(cx, |buffer, cx| {
10603 buffer.autoindent_ranges(selections, cx);
10604 });
10605 let selections = this
10606 .selections
10607 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10608 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10609 });
10610 }
10611
10612 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10613 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10614 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10615 let selections = self.selections.all::<Point>(&display_map);
10616
10617 let mut new_cursors = Vec::new();
10618 let mut edit_ranges = Vec::new();
10619 let mut selections = selections.iter().peekable();
10620 while let Some(selection) = selections.next() {
10621 let mut rows = selection.spanned_rows(false, &display_map);
10622
10623 // Accumulate contiguous regions of rows that we want to delete.
10624 while let Some(next_selection) = selections.peek() {
10625 let next_rows = next_selection.spanned_rows(false, &display_map);
10626 if next_rows.start <= rows.end {
10627 rows.end = next_rows.end;
10628 selections.next().unwrap();
10629 } else {
10630 break;
10631 }
10632 }
10633
10634 let buffer = display_map.buffer_snapshot();
10635 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10636 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10637 // If there's a line after the range, delete the \n from the end of the row range
10638 (
10639 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10640 rows.end,
10641 )
10642 } else {
10643 // If there isn't a line after the range, delete the \n from the line before the
10644 // start of the row range
10645 edit_start = edit_start.saturating_sub_usize(1);
10646 (buffer.len(), rows.start.previous_row())
10647 };
10648
10649 let text_layout_details = self.text_layout_details(window);
10650 let x = display_map.x_for_display_point(
10651 selection.head().to_display_point(&display_map),
10652 &text_layout_details,
10653 );
10654 let row = Point::new(target_row.0, 0)
10655 .to_display_point(&display_map)
10656 .row();
10657 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10658
10659 new_cursors.push((
10660 selection.id,
10661 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10662 SelectionGoal::None,
10663 ));
10664 edit_ranges.push(edit_start..edit_end);
10665 }
10666
10667 self.transact(window, cx, |this, window, cx| {
10668 let buffer = this.buffer.update(cx, |buffer, cx| {
10669 let empty_str: Arc<str> = Arc::default();
10670 buffer.edit(
10671 edit_ranges
10672 .into_iter()
10673 .map(|range| (range, empty_str.clone())),
10674 None,
10675 cx,
10676 );
10677 buffer.snapshot(cx)
10678 });
10679 let new_selections = new_cursors
10680 .into_iter()
10681 .map(|(id, cursor, goal)| {
10682 let cursor = cursor.to_point(&buffer);
10683 Selection {
10684 id,
10685 start: cursor,
10686 end: cursor,
10687 reversed: false,
10688 goal,
10689 }
10690 })
10691 .collect();
10692
10693 this.change_selections(Default::default(), window, cx, |s| {
10694 s.select(new_selections);
10695 });
10696 });
10697 }
10698
10699 pub fn join_lines_impl(
10700 &mut self,
10701 insert_whitespace: bool,
10702 window: &mut Window,
10703 cx: &mut Context<Self>,
10704 ) {
10705 if self.read_only(cx) {
10706 return;
10707 }
10708 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10709 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10710 let start = MultiBufferRow(selection.start.row);
10711 // Treat single line selections as if they include the next line. Otherwise this action
10712 // would do nothing for single line selections individual cursors.
10713 let end = if selection.start.row == selection.end.row {
10714 MultiBufferRow(selection.start.row + 1)
10715 } else {
10716 MultiBufferRow(selection.end.row)
10717 };
10718
10719 if let Some(last_row_range) = row_ranges.last_mut()
10720 && start <= last_row_range.end
10721 {
10722 last_row_range.end = end;
10723 continue;
10724 }
10725 row_ranges.push(start..end);
10726 }
10727
10728 let snapshot = self.buffer.read(cx).snapshot(cx);
10729 let mut cursor_positions = Vec::new();
10730 for row_range in &row_ranges {
10731 let anchor = snapshot.anchor_before(Point::new(
10732 row_range.end.previous_row().0,
10733 snapshot.line_len(row_range.end.previous_row()),
10734 ));
10735 cursor_positions.push(anchor..anchor);
10736 }
10737
10738 self.transact(window, cx, |this, window, cx| {
10739 for row_range in row_ranges.into_iter().rev() {
10740 for row in row_range.iter_rows().rev() {
10741 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10742 let next_line_row = row.next_row();
10743 let indent = snapshot.indent_size_for_line(next_line_row);
10744 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10745
10746 let replace =
10747 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10748 " "
10749 } else {
10750 ""
10751 };
10752
10753 this.buffer.update(cx, |buffer, cx| {
10754 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10755 });
10756 }
10757 }
10758
10759 this.change_selections(Default::default(), window, cx, |s| {
10760 s.select_anchor_ranges(cursor_positions)
10761 });
10762 });
10763 }
10764
10765 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10766 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10767 self.join_lines_impl(true, window, cx);
10768 }
10769
10770 pub fn sort_lines_case_sensitive(
10771 &mut self,
10772 _: &SortLinesCaseSensitive,
10773 window: &mut Window,
10774 cx: &mut Context<Self>,
10775 ) {
10776 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10777 }
10778
10779 pub fn sort_lines_by_length(
10780 &mut self,
10781 _: &SortLinesByLength,
10782 window: &mut Window,
10783 cx: &mut Context<Self>,
10784 ) {
10785 self.manipulate_immutable_lines(window, cx, |lines| {
10786 lines.sort_by_key(|&line| line.chars().count())
10787 })
10788 }
10789
10790 pub fn sort_lines_case_insensitive(
10791 &mut self,
10792 _: &SortLinesCaseInsensitive,
10793 window: &mut Window,
10794 cx: &mut Context<Self>,
10795 ) {
10796 self.manipulate_immutable_lines(window, cx, |lines| {
10797 lines.sort_by_key(|line| line.to_lowercase())
10798 })
10799 }
10800
10801 pub fn unique_lines_case_insensitive(
10802 &mut self,
10803 _: &UniqueLinesCaseInsensitive,
10804 window: &mut Window,
10805 cx: &mut Context<Self>,
10806 ) {
10807 self.manipulate_immutable_lines(window, cx, |lines| {
10808 let mut seen = HashSet::default();
10809 lines.retain(|line| seen.insert(line.to_lowercase()));
10810 })
10811 }
10812
10813 pub fn unique_lines_case_sensitive(
10814 &mut self,
10815 _: &UniqueLinesCaseSensitive,
10816 window: &mut Window,
10817 cx: &mut Context<Self>,
10818 ) {
10819 self.manipulate_immutable_lines(window, cx, |lines| {
10820 let mut seen = HashSet::default();
10821 lines.retain(|line| seen.insert(*line));
10822 })
10823 }
10824
10825 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10826 let snapshot = self.buffer.read(cx).snapshot(cx);
10827 for selection in self.selections.disjoint_anchors_arc().iter() {
10828 if snapshot
10829 .language_at(selection.start)
10830 .and_then(|lang| lang.config().wrap_characters.as_ref())
10831 .is_some()
10832 {
10833 return true;
10834 }
10835 }
10836 false
10837 }
10838
10839 fn wrap_selections_in_tag(
10840 &mut self,
10841 _: &WrapSelectionsInTag,
10842 window: &mut Window,
10843 cx: &mut Context<Self>,
10844 ) {
10845 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10846
10847 let snapshot = self.buffer.read(cx).snapshot(cx);
10848
10849 let mut edits = Vec::new();
10850 let mut boundaries = Vec::new();
10851
10852 for selection in self
10853 .selections
10854 .all_adjusted(&self.display_snapshot(cx))
10855 .iter()
10856 {
10857 let Some(wrap_config) = snapshot
10858 .language_at(selection.start)
10859 .and_then(|lang| lang.config().wrap_characters.clone())
10860 else {
10861 continue;
10862 };
10863
10864 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10865 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10866
10867 let start_before = snapshot.anchor_before(selection.start);
10868 let end_after = snapshot.anchor_after(selection.end);
10869
10870 edits.push((start_before..start_before, open_tag));
10871 edits.push((end_after..end_after, close_tag));
10872
10873 boundaries.push((
10874 start_before,
10875 end_after,
10876 wrap_config.start_prefix.len(),
10877 wrap_config.end_suffix.len(),
10878 ));
10879 }
10880
10881 if edits.is_empty() {
10882 return;
10883 }
10884
10885 self.transact(window, cx, |this, window, cx| {
10886 let buffer = this.buffer.update(cx, |buffer, cx| {
10887 buffer.edit(edits, None, cx);
10888 buffer.snapshot(cx)
10889 });
10890
10891 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10892 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10893 boundaries.into_iter()
10894 {
10895 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10896 let close_offset = end_after
10897 .to_offset(&buffer)
10898 .saturating_sub_usize(end_suffix_len);
10899 new_selections.push(open_offset..open_offset);
10900 new_selections.push(close_offset..close_offset);
10901 }
10902
10903 this.change_selections(Default::default(), window, cx, |s| {
10904 s.select_ranges(new_selections);
10905 });
10906
10907 this.request_autoscroll(Autoscroll::fit(), cx);
10908 });
10909 }
10910
10911 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10912 let Some(project) = self.project.clone() else {
10913 return;
10914 };
10915 self.reload(project, window, cx)
10916 .detach_and_notify_err(window, cx);
10917 }
10918
10919 pub fn restore_file(
10920 &mut self,
10921 _: &::git::RestoreFile,
10922 window: &mut Window,
10923 cx: &mut Context<Self>,
10924 ) {
10925 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10926 let mut buffer_ids = HashSet::default();
10927 let snapshot = self.buffer().read(cx).snapshot(cx);
10928 for selection in self
10929 .selections
10930 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10931 {
10932 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10933 }
10934
10935 let buffer = self.buffer().read(cx);
10936 let ranges = buffer_ids
10937 .into_iter()
10938 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10939 .collect::<Vec<_>>();
10940
10941 self.restore_hunks_in_ranges(ranges, window, cx);
10942 }
10943
10944 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10945 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10946 let selections = self
10947 .selections
10948 .all(&self.display_snapshot(cx))
10949 .into_iter()
10950 .map(|s| s.range())
10951 .collect();
10952 self.restore_hunks_in_ranges(selections, window, cx);
10953 }
10954
10955 pub fn restore_hunks_in_ranges(
10956 &mut self,
10957 ranges: Vec<Range<Point>>,
10958 window: &mut Window,
10959 cx: &mut Context<Editor>,
10960 ) {
10961 let mut revert_changes = HashMap::default();
10962 let chunk_by = self
10963 .snapshot(window, cx)
10964 .hunks_for_ranges(ranges)
10965 .into_iter()
10966 .chunk_by(|hunk| hunk.buffer_id);
10967 for (buffer_id, hunks) in &chunk_by {
10968 let hunks = hunks.collect::<Vec<_>>();
10969 for hunk in &hunks {
10970 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10971 }
10972 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10973 }
10974 drop(chunk_by);
10975 if !revert_changes.is_empty() {
10976 self.transact(window, cx, |editor, window, cx| {
10977 editor.restore(revert_changes, window, cx);
10978 });
10979 }
10980 }
10981
10982 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10983 if let Some(status) = self
10984 .addons
10985 .iter()
10986 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10987 {
10988 return Some(status);
10989 }
10990 self.project
10991 .as_ref()?
10992 .read(cx)
10993 .status_for_buffer_id(buffer_id, cx)
10994 }
10995
10996 pub fn open_active_item_in_terminal(
10997 &mut self,
10998 _: &OpenInTerminal,
10999 window: &mut Window,
11000 cx: &mut Context<Self>,
11001 ) {
11002 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11003 let project_path = buffer.read(cx).project_path(cx)?;
11004 let project = self.project()?.read(cx);
11005 let entry = project.entry_for_path(&project_path, cx)?;
11006 let parent = match &entry.canonical_path {
11007 Some(canonical_path) => canonical_path.to_path_buf(),
11008 None => project.absolute_path(&project_path, cx)?,
11009 }
11010 .parent()?
11011 .to_path_buf();
11012 Some(parent)
11013 }) {
11014 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11015 }
11016 }
11017
11018 fn set_breakpoint_context_menu(
11019 &mut self,
11020 display_row: DisplayRow,
11021 position: Option<Anchor>,
11022 clicked_point: gpui::Point<Pixels>,
11023 window: &mut Window,
11024 cx: &mut Context<Self>,
11025 ) {
11026 let source = self
11027 .buffer
11028 .read(cx)
11029 .snapshot(cx)
11030 .anchor_before(Point::new(display_row.0, 0u32));
11031
11032 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11033
11034 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11035 self,
11036 source,
11037 clicked_point,
11038 context_menu,
11039 window,
11040 cx,
11041 );
11042 }
11043
11044 fn add_edit_breakpoint_block(
11045 &mut self,
11046 anchor: Anchor,
11047 breakpoint: &Breakpoint,
11048 edit_action: BreakpointPromptEditAction,
11049 window: &mut Window,
11050 cx: &mut Context<Self>,
11051 ) {
11052 let weak_editor = cx.weak_entity();
11053 let bp_prompt = cx.new(|cx| {
11054 BreakpointPromptEditor::new(
11055 weak_editor,
11056 anchor,
11057 breakpoint.clone(),
11058 edit_action,
11059 window,
11060 cx,
11061 )
11062 });
11063
11064 let height = bp_prompt.update(cx, |this, cx| {
11065 this.prompt
11066 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11067 });
11068 let cloned_prompt = bp_prompt.clone();
11069 let blocks = vec![BlockProperties {
11070 style: BlockStyle::Sticky,
11071 placement: BlockPlacement::Above(anchor),
11072 height: Some(height),
11073 render: Arc::new(move |cx| {
11074 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11075 cloned_prompt.clone().into_any_element()
11076 }),
11077 priority: 0,
11078 }];
11079
11080 let focus_handle = bp_prompt.focus_handle(cx);
11081 window.focus(&focus_handle);
11082
11083 let block_ids = self.insert_blocks(blocks, None, cx);
11084 bp_prompt.update(cx, |prompt, _| {
11085 prompt.add_block_ids(block_ids);
11086 });
11087 }
11088
11089 pub(crate) fn breakpoint_at_row(
11090 &self,
11091 row: u32,
11092 window: &mut Window,
11093 cx: &mut Context<Self>,
11094 ) -> Option<(Anchor, Breakpoint)> {
11095 let snapshot = self.snapshot(window, cx);
11096 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11097
11098 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11099 }
11100
11101 pub(crate) fn breakpoint_at_anchor(
11102 &self,
11103 breakpoint_position: Anchor,
11104 snapshot: &EditorSnapshot,
11105 cx: &mut Context<Self>,
11106 ) -> Option<(Anchor, Breakpoint)> {
11107 let buffer = self
11108 .buffer
11109 .read(cx)
11110 .buffer_for_anchor(breakpoint_position, cx)?;
11111
11112 let enclosing_excerpt = breakpoint_position.excerpt_id;
11113 let buffer_snapshot = buffer.read(cx).snapshot();
11114
11115 let row = buffer_snapshot
11116 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11117 .row;
11118
11119 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11120 let anchor_end = snapshot
11121 .buffer_snapshot()
11122 .anchor_after(Point::new(row, line_len));
11123
11124 self.breakpoint_store
11125 .as_ref()?
11126 .read_with(cx, |breakpoint_store, cx| {
11127 breakpoint_store
11128 .breakpoints(
11129 &buffer,
11130 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11131 &buffer_snapshot,
11132 cx,
11133 )
11134 .next()
11135 .and_then(|(bp, _)| {
11136 let breakpoint_row = buffer_snapshot
11137 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11138 .row;
11139
11140 if breakpoint_row == row {
11141 snapshot
11142 .buffer_snapshot()
11143 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11144 .map(|position| (position, bp.bp.clone()))
11145 } else {
11146 None
11147 }
11148 })
11149 })
11150 }
11151
11152 pub fn edit_log_breakpoint(
11153 &mut self,
11154 _: &EditLogBreakpoint,
11155 window: &mut Window,
11156 cx: &mut Context<Self>,
11157 ) {
11158 if self.breakpoint_store.is_none() {
11159 return;
11160 }
11161
11162 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11163 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11164 message: None,
11165 state: BreakpointState::Enabled,
11166 condition: None,
11167 hit_condition: None,
11168 });
11169
11170 self.add_edit_breakpoint_block(
11171 anchor,
11172 &breakpoint,
11173 BreakpointPromptEditAction::Log,
11174 window,
11175 cx,
11176 );
11177 }
11178 }
11179
11180 fn breakpoints_at_cursors(
11181 &self,
11182 window: &mut Window,
11183 cx: &mut Context<Self>,
11184 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11185 let snapshot = self.snapshot(window, cx);
11186 let cursors = self
11187 .selections
11188 .disjoint_anchors_arc()
11189 .iter()
11190 .map(|selection| {
11191 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11192
11193 let breakpoint_position = self
11194 .breakpoint_at_row(cursor_position.row, window, cx)
11195 .map(|bp| bp.0)
11196 .unwrap_or_else(|| {
11197 snapshot
11198 .display_snapshot
11199 .buffer_snapshot()
11200 .anchor_after(Point::new(cursor_position.row, 0))
11201 });
11202
11203 let breakpoint = self
11204 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11205 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11206
11207 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11208 })
11209 // 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.
11210 .collect::<HashMap<Anchor, _>>();
11211
11212 cursors.into_iter().collect()
11213 }
11214
11215 pub fn enable_breakpoint(
11216 &mut self,
11217 _: &crate::actions::EnableBreakpoint,
11218 window: &mut Window,
11219 cx: &mut Context<Self>,
11220 ) {
11221 if self.breakpoint_store.is_none() {
11222 return;
11223 }
11224
11225 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11226 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11227 continue;
11228 };
11229 self.edit_breakpoint_at_anchor(
11230 anchor,
11231 breakpoint,
11232 BreakpointEditAction::InvertState,
11233 cx,
11234 );
11235 }
11236 }
11237
11238 pub fn disable_breakpoint(
11239 &mut self,
11240 _: &crate::actions::DisableBreakpoint,
11241 window: &mut Window,
11242 cx: &mut Context<Self>,
11243 ) {
11244 if self.breakpoint_store.is_none() {
11245 return;
11246 }
11247
11248 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11249 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11250 continue;
11251 };
11252 self.edit_breakpoint_at_anchor(
11253 anchor,
11254 breakpoint,
11255 BreakpointEditAction::InvertState,
11256 cx,
11257 );
11258 }
11259 }
11260
11261 pub fn toggle_breakpoint(
11262 &mut self,
11263 _: &crate::actions::ToggleBreakpoint,
11264 window: &mut Window,
11265 cx: &mut Context<Self>,
11266 ) {
11267 if self.breakpoint_store.is_none() {
11268 return;
11269 }
11270
11271 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11272 if let Some(breakpoint) = breakpoint {
11273 self.edit_breakpoint_at_anchor(
11274 anchor,
11275 breakpoint,
11276 BreakpointEditAction::Toggle,
11277 cx,
11278 );
11279 } else {
11280 self.edit_breakpoint_at_anchor(
11281 anchor,
11282 Breakpoint::new_standard(),
11283 BreakpointEditAction::Toggle,
11284 cx,
11285 );
11286 }
11287 }
11288 }
11289
11290 pub fn edit_breakpoint_at_anchor(
11291 &mut self,
11292 breakpoint_position: Anchor,
11293 breakpoint: Breakpoint,
11294 edit_action: BreakpointEditAction,
11295 cx: &mut Context<Self>,
11296 ) {
11297 let Some(breakpoint_store) = &self.breakpoint_store else {
11298 return;
11299 };
11300
11301 let Some(buffer) = self
11302 .buffer
11303 .read(cx)
11304 .buffer_for_anchor(breakpoint_position, cx)
11305 else {
11306 return;
11307 };
11308
11309 breakpoint_store.update(cx, |breakpoint_store, cx| {
11310 breakpoint_store.toggle_breakpoint(
11311 buffer,
11312 BreakpointWithPosition {
11313 position: breakpoint_position.text_anchor,
11314 bp: breakpoint,
11315 },
11316 edit_action,
11317 cx,
11318 );
11319 });
11320
11321 cx.notify();
11322 }
11323
11324 #[cfg(any(test, feature = "test-support"))]
11325 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11326 self.breakpoint_store.clone()
11327 }
11328
11329 pub fn prepare_restore_change(
11330 &self,
11331 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11332 hunk: &MultiBufferDiffHunk,
11333 cx: &mut App,
11334 ) -> Option<()> {
11335 if hunk.is_created_file() {
11336 return None;
11337 }
11338 let buffer = self.buffer.read(cx);
11339 let diff = buffer.diff_for(hunk.buffer_id)?;
11340 let buffer = buffer.buffer(hunk.buffer_id)?;
11341 let buffer = buffer.read(cx);
11342 let original_text = diff
11343 .read(cx)
11344 .base_text()
11345 .as_rope()
11346 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11347 let buffer_snapshot = buffer.snapshot();
11348 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11349 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11350 probe
11351 .0
11352 .start
11353 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11354 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11355 }) {
11356 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11357 Some(())
11358 } else {
11359 None
11360 }
11361 }
11362
11363 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11364 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11365 }
11366
11367 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11368 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11369 }
11370
11371 fn manipulate_lines<M>(
11372 &mut self,
11373 window: &mut Window,
11374 cx: &mut Context<Self>,
11375 mut manipulate: M,
11376 ) where
11377 M: FnMut(&str) -> LineManipulationResult,
11378 {
11379 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11380
11381 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11382 let buffer = self.buffer.read(cx).snapshot(cx);
11383
11384 let mut edits = Vec::new();
11385
11386 let selections = self.selections.all::<Point>(&display_map);
11387 let mut selections = selections.iter().peekable();
11388 let mut contiguous_row_selections = Vec::new();
11389 let mut new_selections = Vec::new();
11390 let mut added_lines = 0;
11391 let mut removed_lines = 0;
11392
11393 while let Some(selection) = selections.next() {
11394 let (start_row, end_row) = consume_contiguous_rows(
11395 &mut contiguous_row_selections,
11396 selection,
11397 &display_map,
11398 &mut selections,
11399 );
11400
11401 let start_point = Point::new(start_row.0, 0);
11402 let end_point = Point::new(
11403 end_row.previous_row().0,
11404 buffer.line_len(end_row.previous_row()),
11405 );
11406 let text = buffer
11407 .text_for_range(start_point..end_point)
11408 .collect::<String>();
11409
11410 let LineManipulationResult {
11411 new_text,
11412 line_count_before,
11413 line_count_after,
11414 } = manipulate(&text);
11415
11416 edits.push((start_point..end_point, new_text));
11417
11418 // Selections must change based on added and removed line count
11419 let start_row =
11420 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11421 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11422 new_selections.push(Selection {
11423 id: selection.id,
11424 start: start_row,
11425 end: end_row,
11426 goal: SelectionGoal::None,
11427 reversed: selection.reversed,
11428 });
11429
11430 if line_count_after > line_count_before {
11431 added_lines += line_count_after - line_count_before;
11432 } else if line_count_before > line_count_after {
11433 removed_lines += line_count_before - line_count_after;
11434 }
11435 }
11436
11437 self.transact(window, cx, |this, window, cx| {
11438 let buffer = this.buffer.update(cx, |buffer, cx| {
11439 buffer.edit(edits, None, cx);
11440 buffer.snapshot(cx)
11441 });
11442
11443 // Recalculate offsets on newly edited buffer
11444 let new_selections = new_selections
11445 .iter()
11446 .map(|s| {
11447 let start_point = Point::new(s.start.0, 0);
11448 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11449 Selection {
11450 id: s.id,
11451 start: buffer.point_to_offset(start_point),
11452 end: buffer.point_to_offset(end_point),
11453 goal: s.goal,
11454 reversed: s.reversed,
11455 }
11456 })
11457 .collect();
11458
11459 this.change_selections(Default::default(), window, cx, |s| {
11460 s.select(new_selections);
11461 });
11462
11463 this.request_autoscroll(Autoscroll::fit(), cx);
11464 });
11465 }
11466
11467 fn manipulate_immutable_lines<Fn>(
11468 &mut self,
11469 window: &mut Window,
11470 cx: &mut Context<Self>,
11471 mut callback: Fn,
11472 ) where
11473 Fn: FnMut(&mut Vec<&str>),
11474 {
11475 self.manipulate_lines(window, cx, |text| {
11476 let mut lines: Vec<&str> = text.split('\n').collect();
11477 let line_count_before = lines.len();
11478
11479 callback(&mut lines);
11480
11481 LineManipulationResult {
11482 new_text: lines.join("\n"),
11483 line_count_before,
11484 line_count_after: lines.len(),
11485 }
11486 });
11487 }
11488
11489 fn manipulate_mutable_lines<Fn>(
11490 &mut self,
11491 window: &mut Window,
11492 cx: &mut Context<Self>,
11493 mut callback: Fn,
11494 ) where
11495 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11496 {
11497 self.manipulate_lines(window, cx, |text| {
11498 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11499 let line_count_before = lines.len();
11500
11501 callback(&mut lines);
11502
11503 LineManipulationResult {
11504 new_text: lines.join("\n"),
11505 line_count_before,
11506 line_count_after: lines.len(),
11507 }
11508 });
11509 }
11510
11511 pub fn convert_indentation_to_spaces(
11512 &mut self,
11513 _: &ConvertIndentationToSpaces,
11514 window: &mut Window,
11515 cx: &mut Context<Self>,
11516 ) {
11517 let settings = self.buffer.read(cx).language_settings(cx);
11518 let tab_size = settings.tab_size.get() as usize;
11519
11520 self.manipulate_mutable_lines(window, cx, |lines| {
11521 // Allocates a reasonably sized scratch buffer once for the whole loop
11522 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11523 // Avoids recomputing spaces that could be inserted many times
11524 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11525 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11526 .collect();
11527
11528 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11529 let mut chars = line.as_ref().chars();
11530 let mut col = 0;
11531 let mut changed = false;
11532
11533 for ch in chars.by_ref() {
11534 match ch {
11535 ' ' => {
11536 reindented_line.push(' ');
11537 col += 1;
11538 }
11539 '\t' => {
11540 // \t are converted to spaces depending on the current column
11541 let spaces_len = tab_size - (col % tab_size);
11542 reindented_line.extend(&space_cache[spaces_len - 1]);
11543 col += spaces_len;
11544 changed = true;
11545 }
11546 _ => {
11547 // If we dont append before break, the character is consumed
11548 reindented_line.push(ch);
11549 break;
11550 }
11551 }
11552 }
11553
11554 if !changed {
11555 reindented_line.clear();
11556 continue;
11557 }
11558 // Append the rest of the line and replace old reference with new one
11559 reindented_line.extend(chars);
11560 *line = Cow::Owned(reindented_line.clone());
11561 reindented_line.clear();
11562 }
11563 });
11564 }
11565
11566 pub fn convert_indentation_to_tabs(
11567 &mut self,
11568 _: &ConvertIndentationToTabs,
11569 window: &mut Window,
11570 cx: &mut Context<Self>,
11571 ) {
11572 let settings = self.buffer.read(cx).language_settings(cx);
11573 let tab_size = settings.tab_size.get() as usize;
11574
11575 self.manipulate_mutable_lines(window, cx, |lines| {
11576 // Allocates a reasonably sized buffer once for the whole loop
11577 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11578 // Avoids recomputing spaces that could be inserted many times
11579 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11580 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11581 .collect();
11582
11583 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11584 let mut chars = line.chars();
11585 let mut spaces_count = 0;
11586 let mut first_non_indent_char = None;
11587 let mut changed = false;
11588
11589 for ch in chars.by_ref() {
11590 match ch {
11591 ' ' => {
11592 // Keep track of spaces. Append \t when we reach tab_size
11593 spaces_count += 1;
11594 changed = true;
11595 if spaces_count == tab_size {
11596 reindented_line.push('\t');
11597 spaces_count = 0;
11598 }
11599 }
11600 '\t' => {
11601 reindented_line.push('\t');
11602 spaces_count = 0;
11603 }
11604 _ => {
11605 // Dont append it yet, we might have remaining spaces
11606 first_non_indent_char = Some(ch);
11607 break;
11608 }
11609 }
11610 }
11611
11612 if !changed {
11613 reindented_line.clear();
11614 continue;
11615 }
11616 // Remaining spaces that didn't make a full tab stop
11617 if spaces_count > 0 {
11618 reindented_line.extend(&space_cache[spaces_count - 1]);
11619 }
11620 // If we consume an extra character that was not indentation, add it back
11621 if let Some(extra_char) = first_non_indent_char {
11622 reindented_line.push(extra_char);
11623 }
11624 // Append the rest of the line and replace old reference with new one
11625 reindented_line.extend(chars);
11626 *line = Cow::Owned(reindented_line.clone());
11627 reindented_line.clear();
11628 }
11629 });
11630 }
11631
11632 pub fn convert_to_upper_case(
11633 &mut self,
11634 _: &ConvertToUpperCase,
11635 window: &mut Window,
11636 cx: &mut Context<Self>,
11637 ) {
11638 self.manipulate_text(window, cx, |text| text.to_uppercase())
11639 }
11640
11641 pub fn convert_to_lower_case(
11642 &mut self,
11643 _: &ConvertToLowerCase,
11644 window: &mut Window,
11645 cx: &mut Context<Self>,
11646 ) {
11647 self.manipulate_text(window, cx, |text| text.to_lowercase())
11648 }
11649
11650 pub fn convert_to_title_case(
11651 &mut self,
11652 _: &ConvertToTitleCase,
11653 window: &mut Window,
11654 cx: &mut Context<Self>,
11655 ) {
11656 self.manipulate_text(window, cx, |text| {
11657 text.split('\n')
11658 .map(|line| line.to_case(Case::Title))
11659 .join("\n")
11660 })
11661 }
11662
11663 pub fn convert_to_snake_case(
11664 &mut self,
11665 _: &ConvertToSnakeCase,
11666 window: &mut Window,
11667 cx: &mut Context<Self>,
11668 ) {
11669 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11670 }
11671
11672 pub fn convert_to_kebab_case(
11673 &mut self,
11674 _: &ConvertToKebabCase,
11675 window: &mut Window,
11676 cx: &mut Context<Self>,
11677 ) {
11678 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11679 }
11680
11681 pub fn convert_to_upper_camel_case(
11682 &mut self,
11683 _: &ConvertToUpperCamelCase,
11684 window: &mut Window,
11685 cx: &mut Context<Self>,
11686 ) {
11687 self.manipulate_text(window, cx, |text| {
11688 text.split('\n')
11689 .map(|line| line.to_case(Case::UpperCamel))
11690 .join("\n")
11691 })
11692 }
11693
11694 pub fn convert_to_lower_camel_case(
11695 &mut self,
11696 _: &ConvertToLowerCamelCase,
11697 window: &mut Window,
11698 cx: &mut Context<Self>,
11699 ) {
11700 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11701 }
11702
11703 pub fn convert_to_opposite_case(
11704 &mut self,
11705 _: &ConvertToOppositeCase,
11706 window: &mut Window,
11707 cx: &mut Context<Self>,
11708 ) {
11709 self.manipulate_text(window, cx, |text| {
11710 text.chars()
11711 .fold(String::with_capacity(text.len()), |mut t, c| {
11712 if c.is_uppercase() {
11713 t.extend(c.to_lowercase());
11714 } else {
11715 t.extend(c.to_uppercase());
11716 }
11717 t
11718 })
11719 })
11720 }
11721
11722 pub fn convert_to_sentence_case(
11723 &mut self,
11724 _: &ConvertToSentenceCase,
11725 window: &mut Window,
11726 cx: &mut Context<Self>,
11727 ) {
11728 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11729 }
11730
11731 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11732 self.manipulate_text(window, cx, |text| {
11733 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11734 if has_upper_case_characters {
11735 text.to_lowercase()
11736 } else {
11737 text.to_uppercase()
11738 }
11739 })
11740 }
11741
11742 pub fn convert_to_rot13(
11743 &mut self,
11744 _: &ConvertToRot13,
11745 window: &mut Window,
11746 cx: &mut Context<Self>,
11747 ) {
11748 self.manipulate_text(window, cx, |text| {
11749 text.chars()
11750 .map(|c| match c {
11751 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11752 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11753 _ => c,
11754 })
11755 .collect()
11756 })
11757 }
11758
11759 pub fn convert_to_rot47(
11760 &mut self,
11761 _: &ConvertToRot47,
11762 window: &mut Window,
11763 cx: &mut Context<Self>,
11764 ) {
11765 self.manipulate_text(window, cx, |text| {
11766 text.chars()
11767 .map(|c| {
11768 let code_point = c as u32;
11769 if code_point >= 33 && code_point <= 126 {
11770 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11771 }
11772 c
11773 })
11774 .collect()
11775 })
11776 }
11777
11778 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11779 where
11780 Fn: FnMut(&str) -> String,
11781 {
11782 let buffer = self.buffer.read(cx).snapshot(cx);
11783
11784 let mut new_selections = Vec::new();
11785 let mut edits = Vec::new();
11786 let mut selection_adjustment = 0isize;
11787
11788 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11789 let selection_is_empty = selection.is_empty();
11790
11791 let (start, end) = if selection_is_empty {
11792 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11793 (word_range.start, word_range.end)
11794 } else {
11795 (
11796 buffer.point_to_offset(selection.start),
11797 buffer.point_to_offset(selection.end),
11798 )
11799 };
11800
11801 let text = buffer.text_for_range(start..end).collect::<String>();
11802 let old_length = text.len() as isize;
11803 let text = callback(&text);
11804
11805 new_selections.push(Selection {
11806 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11807 end: MultiBufferOffset(
11808 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11809 ),
11810 goal: SelectionGoal::None,
11811 id: selection.id,
11812 reversed: selection.reversed,
11813 });
11814
11815 selection_adjustment += old_length - text.len() as isize;
11816
11817 edits.push((start..end, text));
11818 }
11819
11820 self.transact(window, cx, |this, window, cx| {
11821 this.buffer.update(cx, |buffer, cx| {
11822 buffer.edit(edits, None, cx);
11823 });
11824
11825 this.change_selections(Default::default(), window, cx, |s| {
11826 s.select(new_selections);
11827 });
11828
11829 this.request_autoscroll(Autoscroll::fit(), cx);
11830 });
11831 }
11832
11833 pub fn move_selection_on_drop(
11834 &mut self,
11835 selection: &Selection<Anchor>,
11836 target: DisplayPoint,
11837 is_cut: bool,
11838 window: &mut Window,
11839 cx: &mut Context<Self>,
11840 ) {
11841 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11842 let buffer = display_map.buffer_snapshot();
11843 let mut edits = Vec::new();
11844 let insert_point = display_map
11845 .clip_point(target, Bias::Left)
11846 .to_point(&display_map);
11847 let text = buffer
11848 .text_for_range(selection.start..selection.end)
11849 .collect::<String>();
11850 if is_cut {
11851 edits.push(((selection.start..selection.end), String::new()));
11852 }
11853 let insert_anchor = buffer.anchor_before(insert_point);
11854 edits.push(((insert_anchor..insert_anchor), text));
11855 let last_edit_start = insert_anchor.bias_left(buffer);
11856 let last_edit_end = insert_anchor.bias_right(buffer);
11857 self.transact(window, cx, |this, window, cx| {
11858 this.buffer.update(cx, |buffer, cx| {
11859 buffer.edit(edits, None, cx);
11860 });
11861 this.change_selections(Default::default(), window, cx, |s| {
11862 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11863 });
11864 });
11865 }
11866
11867 pub fn clear_selection_drag_state(&mut self) {
11868 self.selection_drag_state = SelectionDragState::None;
11869 }
11870
11871 pub fn duplicate(
11872 &mut self,
11873 upwards: bool,
11874 whole_lines: bool,
11875 window: &mut Window,
11876 cx: &mut Context<Self>,
11877 ) {
11878 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11879
11880 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11881 let buffer = display_map.buffer_snapshot();
11882 let selections = self.selections.all::<Point>(&display_map);
11883
11884 let mut edits = Vec::new();
11885 let mut selections_iter = selections.iter().peekable();
11886 while let Some(selection) = selections_iter.next() {
11887 let mut rows = selection.spanned_rows(false, &display_map);
11888 // duplicate line-wise
11889 if whole_lines || selection.start == selection.end {
11890 // Avoid duplicating the same lines twice.
11891 while let Some(next_selection) = selections_iter.peek() {
11892 let next_rows = next_selection.spanned_rows(false, &display_map);
11893 if next_rows.start < rows.end {
11894 rows.end = next_rows.end;
11895 selections_iter.next().unwrap();
11896 } else {
11897 break;
11898 }
11899 }
11900
11901 // Copy the text from the selected row region and splice it either at the start
11902 // or end of the region.
11903 let start = Point::new(rows.start.0, 0);
11904 let end = Point::new(
11905 rows.end.previous_row().0,
11906 buffer.line_len(rows.end.previous_row()),
11907 );
11908
11909 let mut text = buffer.text_for_range(start..end).collect::<String>();
11910
11911 let insert_location = if upwards {
11912 // When duplicating upward, we need to insert before the current line.
11913 // If we're on the last line and it doesn't end with a newline,
11914 // we need to add a newline before the duplicated content.
11915 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11916 && buffer.max_point().column > 0
11917 && !text.ends_with('\n');
11918
11919 if needs_leading_newline {
11920 text.insert(0, '\n');
11921 end
11922 } else {
11923 text.push('\n');
11924 Point::new(rows.start.0, 0)
11925 }
11926 } else {
11927 text.push('\n');
11928 start
11929 };
11930 edits.push((insert_location..insert_location, text));
11931 } else {
11932 // duplicate character-wise
11933 let start = selection.start;
11934 let end = selection.end;
11935 let text = buffer.text_for_range(start..end).collect::<String>();
11936 edits.push((selection.end..selection.end, text));
11937 }
11938 }
11939
11940 self.transact(window, cx, |this, window, cx| {
11941 this.buffer.update(cx, |buffer, cx| {
11942 buffer.edit(edits, None, cx);
11943 });
11944
11945 // When duplicating upward with whole lines, move the cursor to the duplicated line
11946 if upwards && whole_lines {
11947 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11948
11949 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11950 let mut new_ranges = Vec::new();
11951 let selections = s.all::<Point>(&display_map);
11952 let mut selections_iter = selections.iter().peekable();
11953
11954 while let Some(first_selection) = selections_iter.next() {
11955 // Group contiguous selections together to find the total row span
11956 let mut group_selections = vec![first_selection];
11957 let mut rows = first_selection.spanned_rows(false, &display_map);
11958
11959 while let Some(next_selection) = selections_iter.peek() {
11960 let next_rows = next_selection.spanned_rows(false, &display_map);
11961 if next_rows.start < rows.end {
11962 rows.end = next_rows.end;
11963 group_selections.push(selections_iter.next().unwrap());
11964 } else {
11965 break;
11966 }
11967 }
11968
11969 let row_count = rows.end.0 - rows.start.0;
11970
11971 // Move all selections in this group up by the total number of duplicated rows
11972 for selection in group_selections {
11973 let new_start = Point::new(
11974 selection.start.row.saturating_sub(row_count),
11975 selection.start.column,
11976 );
11977
11978 let new_end = Point::new(
11979 selection.end.row.saturating_sub(row_count),
11980 selection.end.column,
11981 );
11982
11983 new_ranges.push(new_start..new_end);
11984 }
11985 }
11986
11987 s.select_ranges(new_ranges);
11988 });
11989 }
11990
11991 this.request_autoscroll(Autoscroll::fit(), cx);
11992 });
11993 }
11994
11995 pub fn duplicate_line_up(
11996 &mut self,
11997 _: &DuplicateLineUp,
11998 window: &mut Window,
11999 cx: &mut Context<Self>,
12000 ) {
12001 self.duplicate(true, true, window, cx);
12002 }
12003
12004 pub fn duplicate_line_down(
12005 &mut self,
12006 _: &DuplicateLineDown,
12007 window: &mut Window,
12008 cx: &mut Context<Self>,
12009 ) {
12010 self.duplicate(false, true, window, cx);
12011 }
12012
12013 pub fn duplicate_selection(
12014 &mut self,
12015 _: &DuplicateSelection,
12016 window: &mut Window,
12017 cx: &mut Context<Self>,
12018 ) {
12019 self.duplicate(false, false, window, cx);
12020 }
12021
12022 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12023 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12024 if self.mode.is_single_line() {
12025 cx.propagate();
12026 return;
12027 }
12028
12029 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12030 let buffer = self.buffer.read(cx).snapshot(cx);
12031
12032 let mut edits = Vec::new();
12033 let mut unfold_ranges = Vec::new();
12034 let mut refold_creases = Vec::new();
12035
12036 let selections = self.selections.all::<Point>(&display_map);
12037 let mut selections = selections.iter().peekable();
12038 let mut contiguous_row_selections = Vec::new();
12039 let mut new_selections = Vec::new();
12040
12041 while let Some(selection) = selections.next() {
12042 // Find all the selections that span a contiguous row range
12043 let (start_row, end_row) = consume_contiguous_rows(
12044 &mut contiguous_row_selections,
12045 selection,
12046 &display_map,
12047 &mut selections,
12048 );
12049
12050 // Move the text spanned by the row range to be before the line preceding the row range
12051 if start_row.0 > 0 {
12052 let range_to_move = Point::new(
12053 start_row.previous_row().0,
12054 buffer.line_len(start_row.previous_row()),
12055 )
12056 ..Point::new(
12057 end_row.previous_row().0,
12058 buffer.line_len(end_row.previous_row()),
12059 );
12060 let insertion_point = display_map
12061 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12062 .0;
12063
12064 // Don't move lines across excerpts
12065 if buffer
12066 .excerpt_containing(insertion_point..range_to_move.end)
12067 .is_some()
12068 {
12069 let text = buffer
12070 .text_for_range(range_to_move.clone())
12071 .flat_map(|s| s.chars())
12072 .skip(1)
12073 .chain(['\n'])
12074 .collect::<String>();
12075
12076 edits.push((
12077 buffer.anchor_after(range_to_move.start)
12078 ..buffer.anchor_before(range_to_move.end),
12079 String::new(),
12080 ));
12081 let insertion_anchor = buffer.anchor_after(insertion_point);
12082 edits.push((insertion_anchor..insertion_anchor, text));
12083
12084 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12085
12086 // Move selections up
12087 new_selections.extend(contiguous_row_selections.drain(..).map(
12088 |mut selection| {
12089 selection.start.row -= row_delta;
12090 selection.end.row -= row_delta;
12091 selection
12092 },
12093 ));
12094
12095 // Move folds up
12096 unfold_ranges.push(range_to_move.clone());
12097 for fold in display_map.folds_in_range(
12098 buffer.anchor_before(range_to_move.start)
12099 ..buffer.anchor_after(range_to_move.end),
12100 ) {
12101 let mut start = fold.range.start.to_point(&buffer);
12102 let mut end = fold.range.end.to_point(&buffer);
12103 start.row -= row_delta;
12104 end.row -= row_delta;
12105 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12106 }
12107 }
12108 }
12109
12110 // If we didn't move line(s), preserve the existing selections
12111 new_selections.append(&mut contiguous_row_selections);
12112 }
12113
12114 self.transact(window, cx, |this, window, cx| {
12115 this.unfold_ranges(&unfold_ranges, true, true, cx);
12116 this.buffer.update(cx, |buffer, cx| {
12117 for (range, text) in edits {
12118 buffer.edit([(range, text)], None, cx);
12119 }
12120 });
12121 this.fold_creases(refold_creases, true, window, cx);
12122 this.change_selections(Default::default(), window, cx, |s| {
12123 s.select(new_selections);
12124 })
12125 });
12126 }
12127
12128 pub fn move_line_down(
12129 &mut self,
12130 _: &MoveLineDown,
12131 window: &mut Window,
12132 cx: &mut Context<Self>,
12133 ) {
12134 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12135 if self.mode.is_single_line() {
12136 cx.propagate();
12137 return;
12138 }
12139
12140 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12141 let buffer = self.buffer.read(cx).snapshot(cx);
12142
12143 let mut edits = Vec::new();
12144 let mut unfold_ranges = Vec::new();
12145 let mut refold_creases = Vec::new();
12146
12147 let selections = self.selections.all::<Point>(&display_map);
12148 let mut selections = selections.iter().peekable();
12149 let mut contiguous_row_selections = Vec::new();
12150 let mut new_selections = Vec::new();
12151
12152 while let Some(selection) = selections.next() {
12153 // Find all the selections that span a contiguous row range
12154 let (start_row, end_row) = consume_contiguous_rows(
12155 &mut contiguous_row_selections,
12156 selection,
12157 &display_map,
12158 &mut selections,
12159 );
12160
12161 // Move the text spanned by the row range to be after the last line of the row range
12162 if end_row.0 <= buffer.max_point().row {
12163 let range_to_move =
12164 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12165 let insertion_point = display_map
12166 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12167 .0;
12168
12169 // Don't move lines across excerpt boundaries
12170 if buffer
12171 .excerpt_containing(range_to_move.start..insertion_point)
12172 .is_some()
12173 {
12174 let mut text = String::from("\n");
12175 text.extend(buffer.text_for_range(range_to_move.clone()));
12176 text.pop(); // Drop trailing newline
12177 edits.push((
12178 buffer.anchor_after(range_to_move.start)
12179 ..buffer.anchor_before(range_to_move.end),
12180 String::new(),
12181 ));
12182 let insertion_anchor = buffer.anchor_after(insertion_point);
12183 edits.push((insertion_anchor..insertion_anchor, text));
12184
12185 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12186
12187 // Move selections down
12188 new_selections.extend(contiguous_row_selections.drain(..).map(
12189 |mut selection| {
12190 selection.start.row += row_delta;
12191 selection.end.row += row_delta;
12192 selection
12193 },
12194 ));
12195
12196 // Move folds down
12197 unfold_ranges.push(range_to_move.clone());
12198 for fold in display_map.folds_in_range(
12199 buffer.anchor_before(range_to_move.start)
12200 ..buffer.anchor_after(range_to_move.end),
12201 ) {
12202 let mut start = fold.range.start.to_point(&buffer);
12203 let mut end = fold.range.end.to_point(&buffer);
12204 start.row += row_delta;
12205 end.row += row_delta;
12206 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12207 }
12208 }
12209 }
12210
12211 // If we didn't move line(s), preserve the existing selections
12212 new_selections.append(&mut contiguous_row_selections);
12213 }
12214
12215 self.transact(window, cx, |this, window, cx| {
12216 this.unfold_ranges(&unfold_ranges, true, true, cx);
12217 this.buffer.update(cx, |buffer, cx| {
12218 for (range, text) in edits {
12219 buffer.edit([(range, text)], None, cx);
12220 }
12221 });
12222 this.fold_creases(refold_creases, true, window, cx);
12223 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12224 });
12225 }
12226
12227 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12228 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12229 let text_layout_details = &self.text_layout_details(window);
12230 self.transact(window, cx, |this, window, cx| {
12231 let edits = this.change_selections(Default::default(), window, cx, |s| {
12232 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12233 s.move_with(|display_map, selection| {
12234 if !selection.is_empty() {
12235 return;
12236 }
12237
12238 let mut head = selection.head();
12239 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12240 if head.column() == display_map.line_len(head.row()) {
12241 transpose_offset = display_map
12242 .buffer_snapshot()
12243 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12244 }
12245
12246 if transpose_offset == MultiBufferOffset(0) {
12247 return;
12248 }
12249
12250 *head.column_mut() += 1;
12251 head = display_map.clip_point(head, Bias::Right);
12252 let goal = SelectionGoal::HorizontalPosition(
12253 display_map
12254 .x_for_display_point(head, text_layout_details)
12255 .into(),
12256 );
12257 selection.collapse_to(head, goal);
12258
12259 let transpose_start = display_map
12260 .buffer_snapshot()
12261 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12262 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12263 let transpose_end = display_map
12264 .buffer_snapshot()
12265 .clip_offset(transpose_offset + 1usize, Bias::Right);
12266 if let Some(ch) = display_map
12267 .buffer_snapshot()
12268 .chars_at(transpose_start)
12269 .next()
12270 {
12271 edits.push((transpose_start..transpose_offset, String::new()));
12272 edits.push((transpose_end..transpose_end, ch.to_string()));
12273 }
12274 }
12275 });
12276 edits
12277 });
12278 this.buffer
12279 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12280 let selections = this
12281 .selections
12282 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12283 this.change_selections(Default::default(), window, cx, |s| {
12284 s.select(selections);
12285 });
12286 });
12287 }
12288
12289 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12291 if self.mode.is_single_line() {
12292 cx.propagate();
12293 return;
12294 }
12295
12296 self.rewrap_impl(RewrapOptions::default(), cx)
12297 }
12298
12299 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12300 let buffer = self.buffer.read(cx).snapshot(cx);
12301 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12302
12303 #[derive(Clone, Debug, PartialEq)]
12304 enum CommentFormat {
12305 /// single line comment, with prefix for line
12306 Line(String),
12307 /// single line within a block comment, with prefix for line
12308 BlockLine(String),
12309 /// a single line of a block comment that includes the initial delimiter
12310 BlockCommentWithStart(BlockCommentConfig),
12311 /// a single line of a block comment that includes the ending delimiter
12312 BlockCommentWithEnd(BlockCommentConfig),
12313 }
12314
12315 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12316 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12317 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12318 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12319 .peekable();
12320
12321 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12322 row
12323 } else {
12324 return Vec::new();
12325 };
12326
12327 let language_settings = buffer.language_settings_at(selection.head(), cx);
12328 let language_scope = buffer.language_scope_at(selection.head());
12329
12330 let indent_and_prefix_for_row =
12331 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12332 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12333 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12334 &language_scope
12335 {
12336 let indent_end = Point::new(row, indent.len);
12337 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12338 let line_text_after_indent = buffer
12339 .text_for_range(indent_end..line_end)
12340 .collect::<String>();
12341
12342 let is_within_comment_override = buffer
12343 .language_scope_at(indent_end)
12344 .is_some_and(|scope| scope.override_name() == Some("comment"));
12345 let comment_delimiters = if is_within_comment_override {
12346 // we are within a comment syntax node, but we don't
12347 // yet know what kind of comment: block, doc or line
12348 match (
12349 language_scope.documentation_comment(),
12350 language_scope.block_comment(),
12351 ) {
12352 (Some(config), _) | (_, Some(config))
12353 if buffer.contains_str_at(indent_end, &config.start) =>
12354 {
12355 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12356 }
12357 (Some(config), _) | (_, Some(config))
12358 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12359 {
12360 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12361 }
12362 (Some(config), _) | (_, Some(config))
12363 if buffer.contains_str_at(indent_end, &config.prefix) =>
12364 {
12365 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12366 }
12367 (_, _) => language_scope
12368 .line_comment_prefixes()
12369 .iter()
12370 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12371 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12372 }
12373 } else {
12374 // we not in an overridden comment node, but we may
12375 // be within a non-overridden line comment node
12376 language_scope
12377 .line_comment_prefixes()
12378 .iter()
12379 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12380 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12381 };
12382
12383 let rewrap_prefix = language_scope
12384 .rewrap_prefixes()
12385 .iter()
12386 .find_map(|prefix_regex| {
12387 prefix_regex.find(&line_text_after_indent).map(|mat| {
12388 if mat.start() == 0 {
12389 Some(mat.as_str().to_string())
12390 } else {
12391 None
12392 }
12393 })
12394 })
12395 .flatten();
12396 (comment_delimiters, rewrap_prefix)
12397 } else {
12398 (None, None)
12399 };
12400 (indent, comment_prefix, rewrap_prefix)
12401 };
12402
12403 let mut ranges = Vec::new();
12404 let from_empty_selection = selection.is_empty();
12405
12406 let mut current_range_start = first_row;
12407 let mut prev_row = first_row;
12408 let (
12409 mut current_range_indent,
12410 mut current_range_comment_delimiters,
12411 mut current_range_rewrap_prefix,
12412 ) = indent_and_prefix_for_row(first_row);
12413
12414 for row in non_blank_rows_iter.skip(1) {
12415 let has_paragraph_break = row > prev_row + 1;
12416
12417 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12418 indent_and_prefix_for_row(row);
12419
12420 let has_indent_change = row_indent != current_range_indent;
12421 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12422
12423 let has_boundary_change = has_comment_change
12424 || row_rewrap_prefix.is_some()
12425 || (has_indent_change && current_range_comment_delimiters.is_some());
12426
12427 if has_paragraph_break || has_boundary_change {
12428 ranges.push((
12429 language_settings.clone(),
12430 Point::new(current_range_start, 0)
12431 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12432 current_range_indent,
12433 current_range_comment_delimiters.clone(),
12434 current_range_rewrap_prefix.clone(),
12435 from_empty_selection,
12436 ));
12437 current_range_start = row;
12438 current_range_indent = row_indent;
12439 current_range_comment_delimiters = row_comment_delimiters;
12440 current_range_rewrap_prefix = row_rewrap_prefix;
12441 }
12442 prev_row = row;
12443 }
12444
12445 ranges.push((
12446 language_settings.clone(),
12447 Point::new(current_range_start, 0)
12448 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12449 current_range_indent,
12450 current_range_comment_delimiters,
12451 current_range_rewrap_prefix,
12452 from_empty_selection,
12453 ));
12454
12455 ranges
12456 });
12457
12458 let mut edits = Vec::new();
12459 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12460
12461 for (
12462 language_settings,
12463 wrap_range,
12464 mut indent_size,
12465 comment_prefix,
12466 rewrap_prefix,
12467 from_empty_selection,
12468 ) in wrap_ranges
12469 {
12470 let mut start_row = wrap_range.start.row;
12471 let mut end_row = wrap_range.end.row;
12472
12473 // Skip selections that overlap with a range that has already been rewrapped.
12474 let selection_range = start_row..end_row;
12475 if rewrapped_row_ranges
12476 .iter()
12477 .any(|range| range.overlaps(&selection_range))
12478 {
12479 continue;
12480 }
12481
12482 let tab_size = language_settings.tab_size;
12483
12484 let (line_prefix, inside_comment) = match &comment_prefix {
12485 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12486 (Some(prefix.as_str()), true)
12487 }
12488 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12489 (Some(prefix.as_ref()), true)
12490 }
12491 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12492 start: _,
12493 end: _,
12494 prefix,
12495 tab_size,
12496 })) => {
12497 indent_size.len += tab_size;
12498 (Some(prefix.as_ref()), true)
12499 }
12500 None => (None, false),
12501 };
12502 let indent_prefix = indent_size.chars().collect::<String>();
12503 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12504
12505 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12506 RewrapBehavior::InComments => inside_comment,
12507 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12508 RewrapBehavior::Anywhere => true,
12509 };
12510
12511 let should_rewrap = options.override_language_settings
12512 || allow_rewrap_based_on_language
12513 || self.hard_wrap.is_some();
12514 if !should_rewrap {
12515 continue;
12516 }
12517
12518 if from_empty_selection {
12519 'expand_upwards: while start_row > 0 {
12520 let prev_row = start_row - 1;
12521 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12522 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12523 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12524 {
12525 start_row = prev_row;
12526 } else {
12527 break 'expand_upwards;
12528 }
12529 }
12530
12531 'expand_downwards: while end_row < buffer.max_point().row {
12532 let next_row = end_row + 1;
12533 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12534 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12535 && !buffer.is_line_blank(MultiBufferRow(next_row))
12536 {
12537 end_row = next_row;
12538 } else {
12539 break 'expand_downwards;
12540 }
12541 }
12542 }
12543
12544 let start = Point::new(start_row, 0);
12545 let start_offset = ToOffset::to_offset(&start, &buffer);
12546 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12547 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12548 let mut first_line_delimiter = None;
12549 let mut last_line_delimiter = None;
12550 let Some(lines_without_prefixes) = selection_text
12551 .lines()
12552 .enumerate()
12553 .map(|(ix, line)| {
12554 let line_trimmed = line.trim_start();
12555 if rewrap_prefix.is_some() && ix > 0 {
12556 Ok(line_trimmed)
12557 } else if let Some(
12558 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12559 start,
12560 prefix,
12561 end,
12562 tab_size,
12563 })
12564 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12565 start,
12566 prefix,
12567 end,
12568 tab_size,
12569 }),
12570 ) = &comment_prefix
12571 {
12572 let line_trimmed = line_trimmed
12573 .strip_prefix(start.as_ref())
12574 .map(|s| {
12575 let mut indent_size = indent_size;
12576 indent_size.len -= tab_size;
12577 let indent_prefix: String = indent_size.chars().collect();
12578 first_line_delimiter = Some((indent_prefix, start));
12579 s.trim_start()
12580 })
12581 .unwrap_or(line_trimmed);
12582 let line_trimmed = line_trimmed
12583 .strip_suffix(end.as_ref())
12584 .map(|s| {
12585 last_line_delimiter = Some(end);
12586 s.trim_end()
12587 })
12588 .unwrap_or(line_trimmed);
12589 let line_trimmed = line_trimmed
12590 .strip_prefix(prefix.as_ref())
12591 .unwrap_or(line_trimmed);
12592 Ok(line_trimmed)
12593 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12594 line_trimmed.strip_prefix(prefix).with_context(|| {
12595 format!("line did not start with prefix {prefix:?}: {line:?}")
12596 })
12597 } else {
12598 line_trimmed
12599 .strip_prefix(&line_prefix.trim_start())
12600 .with_context(|| {
12601 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12602 })
12603 }
12604 })
12605 .collect::<Result<Vec<_>, _>>()
12606 .log_err()
12607 else {
12608 continue;
12609 };
12610
12611 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12612 buffer
12613 .language_settings_at(Point::new(start_row, 0), cx)
12614 .preferred_line_length as usize
12615 });
12616
12617 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12618 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12619 } else {
12620 line_prefix.clone()
12621 };
12622
12623 let wrapped_text = {
12624 let mut wrapped_text = wrap_with_prefix(
12625 line_prefix,
12626 subsequent_lines_prefix,
12627 lines_without_prefixes.join("\n"),
12628 wrap_column,
12629 tab_size,
12630 options.preserve_existing_whitespace,
12631 );
12632
12633 if let Some((indent, delimiter)) = first_line_delimiter {
12634 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12635 }
12636 if let Some(last_line) = last_line_delimiter {
12637 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12638 }
12639
12640 wrapped_text
12641 };
12642
12643 // TODO: should always use char-based diff while still supporting cursor behavior that
12644 // matches vim.
12645 let mut diff_options = DiffOptions::default();
12646 if options.override_language_settings {
12647 diff_options.max_word_diff_len = 0;
12648 diff_options.max_word_diff_line_count = 0;
12649 } else {
12650 diff_options.max_word_diff_len = usize::MAX;
12651 diff_options.max_word_diff_line_count = usize::MAX;
12652 }
12653
12654 for (old_range, new_text) in
12655 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12656 {
12657 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12658 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12659 edits.push((edit_start..edit_end, new_text));
12660 }
12661
12662 rewrapped_row_ranges.push(start_row..=end_row);
12663 }
12664
12665 self.buffer
12666 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12667 }
12668
12669 pub fn cut_common(
12670 &mut self,
12671 cut_no_selection_line: bool,
12672 window: &mut Window,
12673 cx: &mut Context<Self>,
12674 ) -> ClipboardItem {
12675 let mut text = String::new();
12676 let buffer = self.buffer.read(cx).snapshot(cx);
12677 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12678 let mut clipboard_selections = Vec::with_capacity(selections.len());
12679 {
12680 let max_point = buffer.max_point();
12681 let mut is_first = true;
12682 let mut prev_selection_was_entire_line = false;
12683 for selection in &mut selections {
12684 let is_entire_line =
12685 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12686 if is_entire_line {
12687 selection.start = Point::new(selection.start.row, 0);
12688 if !selection.is_empty() && selection.end.column == 0 {
12689 selection.end = cmp::min(max_point, selection.end);
12690 } else {
12691 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12692 }
12693 selection.goal = SelectionGoal::None;
12694 }
12695 if is_first {
12696 is_first = false;
12697 } else if !prev_selection_was_entire_line {
12698 text += "\n";
12699 }
12700 prev_selection_was_entire_line = is_entire_line;
12701 let mut len = 0;
12702 for chunk in buffer.text_for_range(selection.start..selection.end) {
12703 text.push_str(chunk);
12704 len += chunk.len();
12705 }
12706 clipboard_selections.push(ClipboardSelection {
12707 len,
12708 is_entire_line,
12709 first_line_indent: buffer
12710 .indent_size_for_line(MultiBufferRow(selection.start.row))
12711 .len,
12712 });
12713 }
12714 }
12715
12716 self.transact(window, cx, |this, window, cx| {
12717 this.change_selections(Default::default(), window, cx, |s| {
12718 s.select(selections);
12719 });
12720 this.insert("", window, cx);
12721 });
12722 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12723 }
12724
12725 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12726 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12727 let item = self.cut_common(true, window, cx);
12728 cx.write_to_clipboard(item);
12729 }
12730
12731 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12732 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12733 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12734 s.move_with(|snapshot, sel| {
12735 if sel.is_empty() {
12736 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12737 }
12738 if sel.is_empty() {
12739 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12740 }
12741 });
12742 });
12743 let item = self.cut_common(false, window, cx);
12744 cx.set_global(KillRing(item))
12745 }
12746
12747 pub fn kill_ring_yank(
12748 &mut self,
12749 _: &KillRingYank,
12750 window: &mut Window,
12751 cx: &mut Context<Self>,
12752 ) {
12753 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12754 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12755 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12756 (kill_ring.text().to_string(), kill_ring.metadata_json())
12757 } else {
12758 return;
12759 }
12760 } else {
12761 return;
12762 };
12763 self.do_paste(&text, metadata, false, window, cx);
12764 }
12765
12766 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12767 self.do_copy(true, cx);
12768 }
12769
12770 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12771 self.do_copy(false, cx);
12772 }
12773
12774 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12775 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12776 let buffer = self.buffer.read(cx).read(cx);
12777 let mut text = String::new();
12778
12779 let mut clipboard_selections = Vec::with_capacity(selections.len());
12780 {
12781 let max_point = buffer.max_point();
12782 let mut is_first = true;
12783 let mut prev_selection_was_entire_line = false;
12784 for selection in &selections {
12785 let mut start = selection.start;
12786 let mut end = selection.end;
12787 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12788 let mut add_trailing_newline = false;
12789 if is_entire_line {
12790 start = Point::new(start.row, 0);
12791 let next_line_start = Point::new(end.row + 1, 0);
12792 if next_line_start <= max_point {
12793 end = next_line_start;
12794 } else {
12795 // We're on the last line without a trailing newline.
12796 // Copy to the end of the line and add a newline afterwards.
12797 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12798 add_trailing_newline = true;
12799 }
12800 }
12801
12802 let mut trimmed_selections = Vec::new();
12803 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12804 let row = MultiBufferRow(start.row);
12805 let first_indent = buffer.indent_size_for_line(row);
12806 if first_indent.len == 0 || start.column > first_indent.len {
12807 trimmed_selections.push(start..end);
12808 } else {
12809 trimmed_selections.push(
12810 Point::new(row.0, first_indent.len)
12811 ..Point::new(row.0, buffer.line_len(row)),
12812 );
12813 for row in start.row + 1..=end.row {
12814 let mut line_len = buffer.line_len(MultiBufferRow(row));
12815 if row == end.row {
12816 line_len = end.column;
12817 }
12818 if line_len == 0 {
12819 trimmed_selections
12820 .push(Point::new(row, 0)..Point::new(row, line_len));
12821 continue;
12822 }
12823 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12824 if row_indent_size.len >= first_indent.len {
12825 trimmed_selections.push(
12826 Point::new(row, first_indent.len)..Point::new(row, line_len),
12827 );
12828 } else {
12829 trimmed_selections.clear();
12830 trimmed_selections.push(start..end);
12831 break;
12832 }
12833 }
12834 }
12835 } else {
12836 trimmed_selections.push(start..end);
12837 }
12838
12839 for trimmed_range in trimmed_selections {
12840 if is_first {
12841 is_first = false;
12842 } else if !prev_selection_was_entire_line {
12843 text += "\n";
12844 }
12845 prev_selection_was_entire_line = is_entire_line;
12846 let mut len = 0;
12847 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12848 text.push_str(chunk);
12849 len += chunk.len();
12850 }
12851 if add_trailing_newline {
12852 text.push('\n');
12853 len += 1;
12854 }
12855 clipboard_selections.push(ClipboardSelection {
12856 len,
12857 is_entire_line,
12858 first_line_indent: buffer
12859 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12860 .len,
12861 });
12862 }
12863 }
12864 }
12865
12866 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12867 text,
12868 clipboard_selections,
12869 ));
12870 }
12871
12872 pub fn do_paste(
12873 &mut self,
12874 text: &String,
12875 clipboard_selections: Option<Vec<ClipboardSelection>>,
12876 handle_entire_lines: bool,
12877 window: &mut Window,
12878 cx: &mut Context<Self>,
12879 ) {
12880 if self.read_only(cx) {
12881 return;
12882 }
12883
12884 let clipboard_text = Cow::Borrowed(text.as_str());
12885
12886 self.transact(window, cx, |this, window, cx| {
12887 let had_active_edit_prediction = this.has_active_edit_prediction();
12888 let display_map = this.display_snapshot(cx);
12889 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
12890 let cursor_offset = this
12891 .selections
12892 .last::<MultiBufferOffset>(&display_map)
12893 .head();
12894
12895 if let Some(mut clipboard_selections) = clipboard_selections {
12896 let all_selections_were_entire_line =
12897 clipboard_selections.iter().all(|s| s.is_entire_line);
12898 let first_selection_indent_column =
12899 clipboard_selections.first().map(|s| s.first_line_indent);
12900 if clipboard_selections.len() != old_selections.len() {
12901 clipboard_selections.drain(..);
12902 }
12903 let mut auto_indent_on_paste = true;
12904
12905 this.buffer.update(cx, |buffer, cx| {
12906 let snapshot = buffer.read(cx);
12907 auto_indent_on_paste = snapshot
12908 .language_settings_at(cursor_offset, cx)
12909 .auto_indent_on_paste;
12910
12911 let mut start_offset = 0;
12912 let mut edits = Vec::new();
12913 let mut original_indent_columns = Vec::new();
12914 for (ix, selection) in old_selections.iter().enumerate() {
12915 let to_insert;
12916 let entire_line;
12917 let original_indent_column;
12918 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12919 let end_offset = start_offset + clipboard_selection.len;
12920 to_insert = &clipboard_text[start_offset..end_offset];
12921 entire_line = clipboard_selection.is_entire_line;
12922 start_offset = if entire_line {
12923 end_offset
12924 } else {
12925 end_offset + 1
12926 };
12927 original_indent_column = Some(clipboard_selection.first_line_indent);
12928 } else {
12929 to_insert = &*clipboard_text;
12930 entire_line = all_selections_were_entire_line;
12931 original_indent_column = first_selection_indent_column
12932 }
12933
12934 let (range, to_insert) =
12935 if selection.is_empty() && handle_entire_lines && entire_line {
12936 // If the corresponding selection was empty when this slice of the
12937 // clipboard text was written, then the entire line containing the
12938 // selection was copied. If this selection is also currently empty,
12939 // then paste the line before the current line of the buffer.
12940 let column = selection.start.to_point(&snapshot).column as usize;
12941 let line_start = selection.start - column;
12942 (line_start..line_start, Cow::Borrowed(to_insert))
12943 } else {
12944 let language = snapshot.language_at(selection.head());
12945 let range = selection.range();
12946 if let Some(language) = language
12947 && language.name() == "Markdown".into()
12948 {
12949 edit_for_markdown_paste(
12950 &snapshot,
12951 range,
12952 to_insert,
12953 url::Url::parse(to_insert).ok(),
12954 )
12955 } else {
12956 (range, Cow::Borrowed(to_insert))
12957 }
12958 };
12959
12960 edits.push((range, to_insert));
12961 original_indent_columns.push(original_indent_column);
12962 }
12963 drop(snapshot);
12964
12965 buffer.edit(
12966 edits,
12967 if auto_indent_on_paste {
12968 Some(AutoindentMode::Block {
12969 original_indent_columns,
12970 })
12971 } else {
12972 None
12973 },
12974 cx,
12975 );
12976 });
12977
12978 let selections = this
12979 .selections
12980 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12981 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12982 } else {
12983 let url = url::Url::parse(&clipboard_text).ok();
12984
12985 let auto_indent_mode = if !clipboard_text.is_empty() {
12986 Some(AutoindentMode::Block {
12987 original_indent_columns: Vec::new(),
12988 })
12989 } else {
12990 None
12991 };
12992
12993 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12994 let snapshot = buffer.snapshot(cx);
12995
12996 let anchors = old_selections
12997 .iter()
12998 .map(|s| {
12999 let anchor = snapshot.anchor_after(s.head());
13000 s.map(|_| anchor)
13001 })
13002 .collect::<Vec<_>>();
13003
13004 let mut edits = Vec::new();
13005
13006 for selection in old_selections.iter() {
13007 let language = snapshot.language_at(selection.head());
13008 let range = selection.range();
13009
13010 let (edit_range, edit_text) = if let Some(language) = language
13011 && language.name() == "Markdown".into()
13012 {
13013 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13014 } else {
13015 (range, clipboard_text.clone())
13016 };
13017
13018 edits.push((edit_range, edit_text));
13019 }
13020
13021 drop(snapshot);
13022 buffer.edit(edits, auto_indent_mode, cx);
13023
13024 anchors
13025 });
13026
13027 this.change_selections(Default::default(), window, cx, |s| {
13028 s.select_anchors(selection_anchors);
13029 });
13030 }
13031
13032 // 🤔 | .. | show_in_menu |
13033 // | .. | true true
13034 // | had_edit_prediction | false true
13035
13036 let trigger_in_words =
13037 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13038
13039 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13040 });
13041 }
13042
13043 pub fn diff_clipboard_with_selection(
13044 &mut self,
13045 _: &DiffClipboardWithSelection,
13046 window: &mut Window,
13047 cx: &mut Context<Self>,
13048 ) {
13049 let selections = self
13050 .selections
13051 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13052
13053 if selections.is_empty() {
13054 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13055 return;
13056 };
13057
13058 let clipboard_text = match cx.read_from_clipboard() {
13059 Some(item) => match item.entries().first() {
13060 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13061 _ => None,
13062 },
13063 None => None,
13064 };
13065
13066 let Some(clipboard_text) = clipboard_text else {
13067 log::warn!("Clipboard doesn't contain text.");
13068 return;
13069 };
13070
13071 window.dispatch_action(
13072 Box::new(DiffClipboardWithSelectionData {
13073 clipboard_text,
13074 editor: cx.entity(),
13075 }),
13076 cx,
13077 );
13078 }
13079
13080 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13081 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13082 if let Some(item) = cx.read_from_clipboard() {
13083 let entries = item.entries();
13084
13085 match entries.first() {
13086 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13087 // of all the pasted entries.
13088 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13089 .do_paste(
13090 clipboard_string.text(),
13091 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13092 true,
13093 window,
13094 cx,
13095 ),
13096 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13097 }
13098 }
13099 }
13100
13101 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13102 if self.read_only(cx) {
13103 return;
13104 }
13105
13106 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13107
13108 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13109 if let Some((selections, _)) =
13110 self.selection_history.transaction(transaction_id).cloned()
13111 {
13112 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13113 s.select_anchors(selections.to_vec());
13114 });
13115 } else {
13116 log::error!(
13117 "No entry in selection_history found for undo. \
13118 This may correspond to a bug where undo does not update the selection. \
13119 If this is occurring, please add details to \
13120 https://github.com/zed-industries/zed/issues/22692"
13121 );
13122 }
13123 self.request_autoscroll(Autoscroll::fit(), cx);
13124 self.unmark_text(window, cx);
13125 self.refresh_edit_prediction(true, false, window, cx);
13126 cx.emit(EditorEvent::Edited { transaction_id });
13127 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13128 }
13129 }
13130
13131 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13132 if self.read_only(cx) {
13133 return;
13134 }
13135
13136 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13137
13138 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13139 if let Some((_, Some(selections))) =
13140 self.selection_history.transaction(transaction_id).cloned()
13141 {
13142 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13143 s.select_anchors(selections.to_vec());
13144 });
13145 } else {
13146 log::error!(
13147 "No entry in selection_history found for redo. \
13148 This may correspond to a bug where undo does not update the selection. \
13149 If this is occurring, please add details to \
13150 https://github.com/zed-industries/zed/issues/22692"
13151 );
13152 }
13153 self.request_autoscroll(Autoscroll::fit(), cx);
13154 self.unmark_text(window, cx);
13155 self.refresh_edit_prediction(true, false, window, cx);
13156 cx.emit(EditorEvent::Edited { transaction_id });
13157 }
13158 }
13159
13160 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13161 self.buffer
13162 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13163 }
13164
13165 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13166 self.buffer
13167 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13168 }
13169
13170 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13171 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13172 self.change_selections(Default::default(), window, cx, |s| {
13173 s.move_with(|map, selection| {
13174 let cursor = if selection.is_empty() {
13175 movement::left(map, selection.start)
13176 } else {
13177 selection.start
13178 };
13179 selection.collapse_to(cursor, SelectionGoal::None);
13180 });
13181 })
13182 }
13183
13184 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13185 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13186 self.change_selections(Default::default(), window, cx, |s| {
13187 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13188 })
13189 }
13190
13191 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13192 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13193 self.change_selections(Default::default(), window, cx, |s| {
13194 s.move_with(|map, selection| {
13195 let cursor = if selection.is_empty() {
13196 movement::right(map, selection.end)
13197 } else {
13198 selection.end
13199 };
13200 selection.collapse_to(cursor, SelectionGoal::None)
13201 });
13202 })
13203 }
13204
13205 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13206 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13207 self.change_selections(Default::default(), window, cx, |s| {
13208 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13209 });
13210 }
13211
13212 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13213 if self.take_rename(true, window, cx).is_some() {
13214 return;
13215 }
13216
13217 if self.mode.is_single_line() {
13218 cx.propagate();
13219 return;
13220 }
13221
13222 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13223
13224 let text_layout_details = &self.text_layout_details(window);
13225 let selection_count = self.selections.count();
13226 let first_selection = self.selections.first_anchor();
13227
13228 self.change_selections(Default::default(), window, cx, |s| {
13229 s.move_with(|map, selection| {
13230 if !selection.is_empty() {
13231 selection.goal = SelectionGoal::None;
13232 }
13233 let (cursor, goal) = movement::up(
13234 map,
13235 selection.start,
13236 selection.goal,
13237 false,
13238 text_layout_details,
13239 );
13240 selection.collapse_to(cursor, goal);
13241 });
13242 });
13243
13244 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13245 {
13246 cx.propagate();
13247 }
13248 }
13249
13250 pub fn move_up_by_lines(
13251 &mut self,
13252 action: &MoveUpByLines,
13253 window: &mut Window,
13254 cx: &mut Context<Self>,
13255 ) {
13256 if self.take_rename(true, window, cx).is_some() {
13257 return;
13258 }
13259
13260 if self.mode.is_single_line() {
13261 cx.propagate();
13262 return;
13263 }
13264
13265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13266
13267 let text_layout_details = &self.text_layout_details(window);
13268
13269 self.change_selections(Default::default(), window, cx, |s| {
13270 s.move_with(|map, selection| {
13271 if !selection.is_empty() {
13272 selection.goal = SelectionGoal::None;
13273 }
13274 let (cursor, goal) = movement::up_by_rows(
13275 map,
13276 selection.start,
13277 action.lines,
13278 selection.goal,
13279 false,
13280 text_layout_details,
13281 );
13282 selection.collapse_to(cursor, goal);
13283 });
13284 })
13285 }
13286
13287 pub fn move_down_by_lines(
13288 &mut self,
13289 action: &MoveDownByLines,
13290 window: &mut Window,
13291 cx: &mut Context<Self>,
13292 ) {
13293 if self.take_rename(true, window, cx).is_some() {
13294 return;
13295 }
13296
13297 if self.mode.is_single_line() {
13298 cx.propagate();
13299 return;
13300 }
13301
13302 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13303
13304 let text_layout_details = &self.text_layout_details(window);
13305
13306 self.change_selections(Default::default(), window, cx, |s| {
13307 s.move_with(|map, selection| {
13308 if !selection.is_empty() {
13309 selection.goal = SelectionGoal::None;
13310 }
13311 let (cursor, goal) = movement::down_by_rows(
13312 map,
13313 selection.start,
13314 action.lines,
13315 selection.goal,
13316 false,
13317 text_layout_details,
13318 );
13319 selection.collapse_to(cursor, goal);
13320 });
13321 })
13322 }
13323
13324 pub fn select_down_by_lines(
13325 &mut self,
13326 action: &SelectDownByLines,
13327 window: &mut Window,
13328 cx: &mut Context<Self>,
13329 ) {
13330 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13331 let text_layout_details = &self.text_layout_details(window);
13332 self.change_selections(Default::default(), window, cx, |s| {
13333 s.move_heads_with(|map, head, goal| {
13334 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13335 })
13336 })
13337 }
13338
13339 pub fn select_up_by_lines(
13340 &mut self,
13341 action: &SelectUpByLines,
13342 window: &mut Window,
13343 cx: &mut Context<Self>,
13344 ) {
13345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13346 let text_layout_details = &self.text_layout_details(window);
13347 self.change_selections(Default::default(), window, cx, |s| {
13348 s.move_heads_with(|map, head, goal| {
13349 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13350 })
13351 })
13352 }
13353
13354 pub fn select_page_up(
13355 &mut self,
13356 _: &SelectPageUp,
13357 window: &mut Window,
13358 cx: &mut Context<Self>,
13359 ) {
13360 let Some(row_count) = self.visible_row_count() else {
13361 return;
13362 };
13363
13364 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13365
13366 let text_layout_details = &self.text_layout_details(window);
13367
13368 self.change_selections(Default::default(), window, cx, |s| {
13369 s.move_heads_with(|map, head, goal| {
13370 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13371 })
13372 })
13373 }
13374
13375 pub fn move_page_up(
13376 &mut self,
13377 action: &MovePageUp,
13378 window: &mut Window,
13379 cx: &mut Context<Self>,
13380 ) {
13381 if self.take_rename(true, window, cx).is_some() {
13382 return;
13383 }
13384
13385 if self
13386 .context_menu
13387 .borrow_mut()
13388 .as_mut()
13389 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13390 .unwrap_or(false)
13391 {
13392 return;
13393 }
13394
13395 if matches!(self.mode, EditorMode::SingleLine) {
13396 cx.propagate();
13397 return;
13398 }
13399
13400 let Some(row_count) = self.visible_row_count() else {
13401 return;
13402 };
13403
13404 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13405
13406 let effects = if action.center_cursor {
13407 SelectionEffects::scroll(Autoscroll::center())
13408 } else {
13409 SelectionEffects::default()
13410 };
13411
13412 let text_layout_details = &self.text_layout_details(window);
13413
13414 self.change_selections(effects, window, cx, |s| {
13415 s.move_with(|map, selection| {
13416 if !selection.is_empty() {
13417 selection.goal = SelectionGoal::None;
13418 }
13419 let (cursor, goal) = movement::up_by_rows(
13420 map,
13421 selection.end,
13422 row_count,
13423 selection.goal,
13424 false,
13425 text_layout_details,
13426 );
13427 selection.collapse_to(cursor, goal);
13428 });
13429 });
13430 }
13431
13432 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13433 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13434 let text_layout_details = &self.text_layout_details(window);
13435 self.change_selections(Default::default(), window, cx, |s| {
13436 s.move_heads_with(|map, head, goal| {
13437 movement::up(map, head, goal, false, text_layout_details)
13438 })
13439 })
13440 }
13441
13442 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13443 self.take_rename(true, window, cx);
13444
13445 if self.mode.is_single_line() {
13446 cx.propagate();
13447 return;
13448 }
13449
13450 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13451
13452 let text_layout_details = &self.text_layout_details(window);
13453 let selection_count = self.selections.count();
13454 let first_selection = self.selections.first_anchor();
13455
13456 self.change_selections(Default::default(), window, cx, |s| {
13457 s.move_with(|map, selection| {
13458 if !selection.is_empty() {
13459 selection.goal = SelectionGoal::None;
13460 }
13461 let (cursor, goal) = movement::down(
13462 map,
13463 selection.end,
13464 selection.goal,
13465 false,
13466 text_layout_details,
13467 );
13468 selection.collapse_to(cursor, goal);
13469 });
13470 });
13471
13472 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13473 {
13474 cx.propagate();
13475 }
13476 }
13477
13478 pub fn select_page_down(
13479 &mut self,
13480 _: &SelectPageDown,
13481 window: &mut Window,
13482 cx: &mut Context<Self>,
13483 ) {
13484 let Some(row_count) = self.visible_row_count() else {
13485 return;
13486 };
13487
13488 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13489
13490 let text_layout_details = &self.text_layout_details(window);
13491
13492 self.change_selections(Default::default(), window, cx, |s| {
13493 s.move_heads_with(|map, head, goal| {
13494 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13495 })
13496 })
13497 }
13498
13499 pub fn move_page_down(
13500 &mut self,
13501 action: &MovePageDown,
13502 window: &mut Window,
13503 cx: &mut Context<Self>,
13504 ) {
13505 if self.take_rename(true, window, cx).is_some() {
13506 return;
13507 }
13508
13509 if self
13510 .context_menu
13511 .borrow_mut()
13512 .as_mut()
13513 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13514 .unwrap_or(false)
13515 {
13516 return;
13517 }
13518
13519 if matches!(self.mode, EditorMode::SingleLine) {
13520 cx.propagate();
13521 return;
13522 }
13523
13524 let Some(row_count) = self.visible_row_count() else {
13525 return;
13526 };
13527
13528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13529
13530 let effects = if action.center_cursor {
13531 SelectionEffects::scroll(Autoscroll::center())
13532 } else {
13533 SelectionEffects::default()
13534 };
13535
13536 let text_layout_details = &self.text_layout_details(window);
13537 self.change_selections(effects, window, cx, |s| {
13538 s.move_with(|map, selection| {
13539 if !selection.is_empty() {
13540 selection.goal = SelectionGoal::None;
13541 }
13542 let (cursor, goal) = movement::down_by_rows(
13543 map,
13544 selection.end,
13545 row_count,
13546 selection.goal,
13547 false,
13548 text_layout_details,
13549 );
13550 selection.collapse_to(cursor, goal);
13551 });
13552 });
13553 }
13554
13555 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13556 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13557 let text_layout_details = &self.text_layout_details(window);
13558 self.change_selections(Default::default(), window, cx, |s| {
13559 s.move_heads_with(|map, head, goal| {
13560 movement::down(map, head, goal, false, text_layout_details)
13561 })
13562 });
13563 }
13564
13565 pub fn context_menu_first(
13566 &mut self,
13567 _: &ContextMenuFirst,
13568 window: &mut Window,
13569 cx: &mut Context<Self>,
13570 ) {
13571 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13572 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13573 }
13574 }
13575
13576 pub fn context_menu_prev(
13577 &mut self,
13578 _: &ContextMenuPrevious,
13579 window: &mut Window,
13580 cx: &mut Context<Self>,
13581 ) {
13582 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13583 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13584 }
13585 }
13586
13587 pub fn context_menu_next(
13588 &mut self,
13589 _: &ContextMenuNext,
13590 window: &mut Window,
13591 cx: &mut Context<Self>,
13592 ) {
13593 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13594 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13595 }
13596 }
13597
13598 pub fn context_menu_last(
13599 &mut self,
13600 _: &ContextMenuLast,
13601 window: &mut Window,
13602 cx: &mut Context<Self>,
13603 ) {
13604 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13605 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13606 }
13607 }
13608
13609 pub fn signature_help_prev(
13610 &mut self,
13611 _: &SignatureHelpPrevious,
13612 _: &mut Window,
13613 cx: &mut Context<Self>,
13614 ) {
13615 if let Some(popover) = self.signature_help_state.popover_mut() {
13616 if popover.current_signature == 0 {
13617 popover.current_signature = popover.signatures.len() - 1;
13618 } else {
13619 popover.current_signature -= 1;
13620 }
13621 cx.notify();
13622 }
13623 }
13624
13625 pub fn signature_help_next(
13626 &mut self,
13627 _: &SignatureHelpNext,
13628 _: &mut Window,
13629 cx: &mut Context<Self>,
13630 ) {
13631 if let Some(popover) = self.signature_help_state.popover_mut() {
13632 if popover.current_signature + 1 == popover.signatures.len() {
13633 popover.current_signature = 0;
13634 } else {
13635 popover.current_signature += 1;
13636 }
13637 cx.notify();
13638 }
13639 }
13640
13641 pub fn move_to_previous_word_start(
13642 &mut self,
13643 _: &MoveToPreviousWordStart,
13644 window: &mut Window,
13645 cx: &mut Context<Self>,
13646 ) {
13647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13648 self.change_selections(Default::default(), window, cx, |s| {
13649 s.move_cursors_with(|map, head, _| {
13650 (
13651 movement::previous_word_start(map, head),
13652 SelectionGoal::None,
13653 )
13654 });
13655 })
13656 }
13657
13658 pub fn move_to_previous_subword_start(
13659 &mut self,
13660 _: &MoveToPreviousSubwordStart,
13661 window: &mut Window,
13662 cx: &mut Context<Self>,
13663 ) {
13664 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13665 self.change_selections(Default::default(), window, cx, |s| {
13666 s.move_cursors_with(|map, head, _| {
13667 (
13668 movement::previous_subword_start(map, head),
13669 SelectionGoal::None,
13670 )
13671 });
13672 })
13673 }
13674
13675 pub fn select_to_previous_word_start(
13676 &mut self,
13677 _: &SelectToPreviousWordStart,
13678 window: &mut Window,
13679 cx: &mut Context<Self>,
13680 ) {
13681 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13682 self.change_selections(Default::default(), window, cx, |s| {
13683 s.move_heads_with(|map, head, _| {
13684 (
13685 movement::previous_word_start(map, head),
13686 SelectionGoal::None,
13687 )
13688 });
13689 })
13690 }
13691
13692 pub fn select_to_previous_subword_start(
13693 &mut self,
13694 _: &SelectToPreviousSubwordStart,
13695 window: &mut Window,
13696 cx: &mut Context<Self>,
13697 ) {
13698 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13699 self.change_selections(Default::default(), window, cx, |s| {
13700 s.move_heads_with(|map, head, _| {
13701 (
13702 movement::previous_subword_start(map, head),
13703 SelectionGoal::None,
13704 )
13705 });
13706 })
13707 }
13708
13709 pub fn delete_to_previous_word_start(
13710 &mut self,
13711 action: &DeleteToPreviousWordStart,
13712 window: &mut Window,
13713 cx: &mut Context<Self>,
13714 ) {
13715 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13716 self.transact(window, cx, |this, window, cx| {
13717 this.select_autoclose_pair(window, cx);
13718 this.change_selections(Default::default(), window, cx, |s| {
13719 s.move_with(|map, selection| {
13720 if selection.is_empty() {
13721 let mut cursor = if action.ignore_newlines {
13722 movement::previous_word_start(map, selection.head())
13723 } else {
13724 movement::previous_word_start_or_newline(map, selection.head())
13725 };
13726 cursor = movement::adjust_greedy_deletion(
13727 map,
13728 selection.head(),
13729 cursor,
13730 action.ignore_brackets,
13731 );
13732 selection.set_head(cursor, SelectionGoal::None);
13733 }
13734 });
13735 });
13736 this.insert("", window, cx);
13737 });
13738 }
13739
13740 pub fn delete_to_previous_subword_start(
13741 &mut self,
13742 _: &DeleteToPreviousSubwordStart,
13743 window: &mut Window,
13744 cx: &mut Context<Self>,
13745 ) {
13746 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13747 self.transact(window, cx, |this, window, cx| {
13748 this.select_autoclose_pair(window, cx);
13749 this.change_selections(Default::default(), window, cx, |s| {
13750 s.move_with(|map, selection| {
13751 if selection.is_empty() {
13752 let mut cursor = movement::previous_subword_start(map, selection.head());
13753 cursor =
13754 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13755 selection.set_head(cursor, SelectionGoal::None);
13756 }
13757 });
13758 });
13759 this.insert("", window, cx);
13760 });
13761 }
13762
13763 pub fn move_to_next_word_end(
13764 &mut self,
13765 _: &MoveToNextWordEnd,
13766 window: &mut Window,
13767 cx: &mut Context<Self>,
13768 ) {
13769 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13770 self.change_selections(Default::default(), window, cx, |s| {
13771 s.move_cursors_with(|map, head, _| {
13772 (movement::next_word_end(map, head), SelectionGoal::None)
13773 });
13774 })
13775 }
13776
13777 pub fn move_to_next_subword_end(
13778 &mut self,
13779 _: &MoveToNextSubwordEnd,
13780 window: &mut Window,
13781 cx: &mut Context<Self>,
13782 ) {
13783 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13784 self.change_selections(Default::default(), window, cx, |s| {
13785 s.move_cursors_with(|map, head, _| {
13786 (movement::next_subword_end(map, head), SelectionGoal::None)
13787 });
13788 })
13789 }
13790
13791 pub fn select_to_next_word_end(
13792 &mut self,
13793 _: &SelectToNextWordEnd,
13794 window: &mut Window,
13795 cx: &mut Context<Self>,
13796 ) {
13797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13798 self.change_selections(Default::default(), window, cx, |s| {
13799 s.move_heads_with(|map, head, _| {
13800 (movement::next_word_end(map, head), SelectionGoal::None)
13801 });
13802 })
13803 }
13804
13805 pub fn select_to_next_subword_end(
13806 &mut self,
13807 _: &SelectToNextSubwordEnd,
13808 window: &mut Window,
13809 cx: &mut Context<Self>,
13810 ) {
13811 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13812 self.change_selections(Default::default(), window, cx, |s| {
13813 s.move_heads_with(|map, head, _| {
13814 (movement::next_subword_end(map, head), SelectionGoal::None)
13815 });
13816 })
13817 }
13818
13819 pub fn delete_to_next_word_end(
13820 &mut self,
13821 action: &DeleteToNextWordEnd,
13822 window: &mut Window,
13823 cx: &mut Context<Self>,
13824 ) {
13825 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13826 self.transact(window, cx, |this, window, cx| {
13827 this.change_selections(Default::default(), window, cx, |s| {
13828 s.move_with(|map, selection| {
13829 if selection.is_empty() {
13830 let mut cursor = if action.ignore_newlines {
13831 movement::next_word_end(map, selection.head())
13832 } else {
13833 movement::next_word_end_or_newline(map, selection.head())
13834 };
13835 cursor = movement::adjust_greedy_deletion(
13836 map,
13837 selection.head(),
13838 cursor,
13839 action.ignore_brackets,
13840 );
13841 selection.set_head(cursor, SelectionGoal::None);
13842 }
13843 });
13844 });
13845 this.insert("", window, cx);
13846 });
13847 }
13848
13849 pub fn delete_to_next_subword_end(
13850 &mut self,
13851 _: &DeleteToNextSubwordEnd,
13852 window: &mut Window,
13853 cx: &mut Context<Self>,
13854 ) {
13855 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13856 self.transact(window, cx, |this, window, cx| {
13857 this.change_selections(Default::default(), window, cx, |s| {
13858 s.move_with(|map, selection| {
13859 if selection.is_empty() {
13860 let mut cursor = movement::next_subword_end(map, selection.head());
13861 cursor =
13862 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13863 selection.set_head(cursor, SelectionGoal::None);
13864 }
13865 });
13866 });
13867 this.insert("", window, cx);
13868 });
13869 }
13870
13871 pub fn move_to_beginning_of_line(
13872 &mut self,
13873 action: &MoveToBeginningOfLine,
13874 window: &mut Window,
13875 cx: &mut Context<Self>,
13876 ) {
13877 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13878 self.change_selections(Default::default(), window, cx, |s| {
13879 s.move_cursors_with(|map, head, _| {
13880 (
13881 movement::indented_line_beginning(
13882 map,
13883 head,
13884 action.stop_at_soft_wraps,
13885 action.stop_at_indent,
13886 ),
13887 SelectionGoal::None,
13888 )
13889 });
13890 })
13891 }
13892
13893 pub fn select_to_beginning_of_line(
13894 &mut self,
13895 action: &SelectToBeginningOfLine,
13896 window: &mut Window,
13897 cx: &mut Context<Self>,
13898 ) {
13899 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13900 self.change_selections(Default::default(), window, cx, |s| {
13901 s.move_heads_with(|map, head, _| {
13902 (
13903 movement::indented_line_beginning(
13904 map,
13905 head,
13906 action.stop_at_soft_wraps,
13907 action.stop_at_indent,
13908 ),
13909 SelectionGoal::None,
13910 )
13911 });
13912 });
13913 }
13914
13915 pub fn delete_to_beginning_of_line(
13916 &mut self,
13917 action: &DeleteToBeginningOfLine,
13918 window: &mut Window,
13919 cx: &mut Context<Self>,
13920 ) {
13921 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13922 self.transact(window, cx, |this, window, cx| {
13923 this.change_selections(Default::default(), window, cx, |s| {
13924 s.move_with(|_, selection| {
13925 selection.reversed = true;
13926 });
13927 });
13928
13929 this.select_to_beginning_of_line(
13930 &SelectToBeginningOfLine {
13931 stop_at_soft_wraps: false,
13932 stop_at_indent: action.stop_at_indent,
13933 },
13934 window,
13935 cx,
13936 );
13937 this.backspace(&Backspace, window, cx);
13938 });
13939 }
13940
13941 pub fn move_to_end_of_line(
13942 &mut self,
13943 action: &MoveToEndOfLine,
13944 window: &mut Window,
13945 cx: &mut Context<Self>,
13946 ) {
13947 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13948 self.change_selections(Default::default(), window, cx, |s| {
13949 s.move_cursors_with(|map, head, _| {
13950 (
13951 movement::line_end(map, head, action.stop_at_soft_wraps),
13952 SelectionGoal::None,
13953 )
13954 });
13955 })
13956 }
13957
13958 pub fn select_to_end_of_line(
13959 &mut self,
13960 action: &SelectToEndOfLine,
13961 window: &mut Window,
13962 cx: &mut Context<Self>,
13963 ) {
13964 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13965 self.change_selections(Default::default(), window, cx, |s| {
13966 s.move_heads_with(|map, head, _| {
13967 (
13968 movement::line_end(map, head, action.stop_at_soft_wraps),
13969 SelectionGoal::None,
13970 )
13971 });
13972 })
13973 }
13974
13975 pub fn delete_to_end_of_line(
13976 &mut self,
13977 _: &DeleteToEndOfLine,
13978 window: &mut Window,
13979 cx: &mut Context<Self>,
13980 ) {
13981 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13982 self.transact(window, cx, |this, window, cx| {
13983 this.select_to_end_of_line(
13984 &SelectToEndOfLine {
13985 stop_at_soft_wraps: false,
13986 },
13987 window,
13988 cx,
13989 );
13990 this.delete(&Delete, window, cx);
13991 });
13992 }
13993
13994 pub fn cut_to_end_of_line(
13995 &mut self,
13996 action: &CutToEndOfLine,
13997 window: &mut Window,
13998 cx: &mut Context<Self>,
13999 ) {
14000 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14001 self.transact(window, cx, |this, window, cx| {
14002 this.select_to_end_of_line(
14003 &SelectToEndOfLine {
14004 stop_at_soft_wraps: false,
14005 },
14006 window,
14007 cx,
14008 );
14009 if !action.stop_at_newlines {
14010 this.change_selections(Default::default(), window, cx, |s| {
14011 s.move_with(|_, sel| {
14012 if sel.is_empty() {
14013 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14014 }
14015 });
14016 });
14017 }
14018 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14019 let item = this.cut_common(false, window, cx);
14020 cx.write_to_clipboard(item);
14021 });
14022 }
14023
14024 pub fn move_to_start_of_paragraph(
14025 &mut self,
14026 _: &MoveToStartOfParagraph,
14027 window: &mut Window,
14028 cx: &mut Context<Self>,
14029 ) {
14030 if matches!(self.mode, EditorMode::SingleLine) {
14031 cx.propagate();
14032 return;
14033 }
14034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14035 self.change_selections(Default::default(), window, cx, |s| {
14036 s.move_with(|map, selection| {
14037 selection.collapse_to(
14038 movement::start_of_paragraph(map, selection.head(), 1),
14039 SelectionGoal::None,
14040 )
14041 });
14042 })
14043 }
14044
14045 pub fn move_to_end_of_paragraph(
14046 &mut self,
14047 _: &MoveToEndOfParagraph,
14048 window: &mut Window,
14049 cx: &mut Context<Self>,
14050 ) {
14051 if matches!(self.mode, EditorMode::SingleLine) {
14052 cx.propagate();
14053 return;
14054 }
14055 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14056 self.change_selections(Default::default(), window, cx, |s| {
14057 s.move_with(|map, selection| {
14058 selection.collapse_to(
14059 movement::end_of_paragraph(map, selection.head(), 1),
14060 SelectionGoal::None,
14061 )
14062 });
14063 })
14064 }
14065
14066 pub fn select_to_start_of_paragraph(
14067 &mut self,
14068 _: &SelectToStartOfParagraph,
14069 window: &mut Window,
14070 cx: &mut Context<Self>,
14071 ) {
14072 if matches!(self.mode, EditorMode::SingleLine) {
14073 cx.propagate();
14074 return;
14075 }
14076 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14077 self.change_selections(Default::default(), window, cx, |s| {
14078 s.move_heads_with(|map, head, _| {
14079 (
14080 movement::start_of_paragraph(map, head, 1),
14081 SelectionGoal::None,
14082 )
14083 });
14084 })
14085 }
14086
14087 pub fn select_to_end_of_paragraph(
14088 &mut self,
14089 _: &SelectToEndOfParagraph,
14090 window: &mut Window,
14091 cx: &mut Context<Self>,
14092 ) {
14093 if matches!(self.mode, EditorMode::SingleLine) {
14094 cx.propagate();
14095 return;
14096 }
14097 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14098 self.change_selections(Default::default(), window, cx, |s| {
14099 s.move_heads_with(|map, head, _| {
14100 (
14101 movement::end_of_paragraph(map, head, 1),
14102 SelectionGoal::None,
14103 )
14104 });
14105 })
14106 }
14107
14108 pub fn move_to_start_of_excerpt(
14109 &mut self,
14110 _: &MoveToStartOfExcerpt,
14111 window: &mut Window,
14112 cx: &mut Context<Self>,
14113 ) {
14114 if matches!(self.mode, EditorMode::SingleLine) {
14115 cx.propagate();
14116 return;
14117 }
14118 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14119 self.change_selections(Default::default(), window, cx, |s| {
14120 s.move_with(|map, selection| {
14121 selection.collapse_to(
14122 movement::start_of_excerpt(
14123 map,
14124 selection.head(),
14125 workspace::searchable::Direction::Prev,
14126 ),
14127 SelectionGoal::None,
14128 )
14129 });
14130 })
14131 }
14132
14133 pub fn move_to_start_of_next_excerpt(
14134 &mut self,
14135 _: &MoveToStartOfNextExcerpt,
14136 window: &mut Window,
14137 cx: &mut Context<Self>,
14138 ) {
14139 if matches!(self.mode, EditorMode::SingleLine) {
14140 cx.propagate();
14141 return;
14142 }
14143
14144 self.change_selections(Default::default(), window, cx, |s| {
14145 s.move_with(|map, selection| {
14146 selection.collapse_to(
14147 movement::start_of_excerpt(
14148 map,
14149 selection.head(),
14150 workspace::searchable::Direction::Next,
14151 ),
14152 SelectionGoal::None,
14153 )
14154 });
14155 })
14156 }
14157
14158 pub fn move_to_end_of_excerpt(
14159 &mut self,
14160 _: &MoveToEndOfExcerpt,
14161 window: &mut Window,
14162 cx: &mut Context<Self>,
14163 ) {
14164 if matches!(self.mode, EditorMode::SingleLine) {
14165 cx.propagate();
14166 return;
14167 }
14168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14169 self.change_selections(Default::default(), window, cx, |s| {
14170 s.move_with(|map, selection| {
14171 selection.collapse_to(
14172 movement::end_of_excerpt(
14173 map,
14174 selection.head(),
14175 workspace::searchable::Direction::Next,
14176 ),
14177 SelectionGoal::None,
14178 )
14179 });
14180 })
14181 }
14182
14183 pub fn move_to_end_of_previous_excerpt(
14184 &mut self,
14185 _: &MoveToEndOfPreviousExcerpt,
14186 window: &mut Window,
14187 cx: &mut Context<Self>,
14188 ) {
14189 if matches!(self.mode, EditorMode::SingleLine) {
14190 cx.propagate();
14191 return;
14192 }
14193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14194 self.change_selections(Default::default(), window, cx, |s| {
14195 s.move_with(|map, selection| {
14196 selection.collapse_to(
14197 movement::end_of_excerpt(
14198 map,
14199 selection.head(),
14200 workspace::searchable::Direction::Prev,
14201 ),
14202 SelectionGoal::None,
14203 )
14204 });
14205 })
14206 }
14207
14208 pub fn select_to_start_of_excerpt(
14209 &mut self,
14210 _: &SelectToStartOfExcerpt,
14211 window: &mut Window,
14212 cx: &mut Context<Self>,
14213 ) {
14214 if matches!(self.mode, EditorMode::SingleLine) {
14215 cx.propagate();
14216 return;
14217 }
14218 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14219 self.change_selections(Default::default(), window, cx, |s| {
14220 s.move_heads_with(|map, head, _| {
14221 (
14222 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14223 SelectionGoal::None,
14224 )
14225 });
14226 })
14227 }
14228
14229 pub fn select_to_start_of_next_excerpt(
14230 &mut self,
14231 _: &SelectToStartOfNextExcerpt,
14232 window: &mut Window,
14233 cx: &mut Context<Self>,
14234 ) {
14235 if matches!(self.mode, EditorMode::SingleLine) {
14236 cx.propagate();
14237 return;
14238 }
14239 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14240 self.change_selections(Default::default(), window, cx, |s| {
14241 s.move_heads_with(|map, head, _| {
14242 (
14243 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14244 SelectionGoal::None,
14245 )
14246 });
14247 })
14248 }
14249
14250 pub fn select_to_end_of_excerpt(
14251 &mut self,
14252 _: &SelectToEndOfExcerpt,
14253 window: &mut Window,
14254 cx: &mut Context<Self>,
14255 ) {
14256 if matches!(self.mode, EditorMode::SingleLine) {
14257 cx.propagate();
14258 return;
14259 }
14260 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14261 self.change_selections(Default::default(), window, cx, |s| {
14262 s.move_heads_with(|map, head, _| {
14263 (
14264 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14265 SelectionGoal::None,
14266 )
14267 });
14268 })
14269 }
14270
14271 pub fn select_to_end_of_previous_excerpt(
14272 &mut self,
14273 _: &SelectToEndOfPreviousExcerpt,
14274 window: &mut Window,
14275 cx: &mut Context<Self>,
14276 ) {
14277 if matches!(self.mode, EditorMode::SingleLine) {
14278 cx.propagate();
14279 return;
14280 }
14281 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14282 self.change_selections(Default::default(), window, cx, |s| {
14283 s.move_heads_with(|map, head, _| {
14284 (
14285 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14286 SelectionGoal::None,
14287 )
14288 });
14289 })
14290 }
14291
14292 pub fn move_to_beginning(
14293 &mut self,
14294 _: &MoveToBeginning,
14295 window: &mut Window,
14296 cx: &mut Context<Self>,
14297 ) {
14298 if matches!(self.mode, EditorMode::SingleLine) {
14299 cx.propagate();
14300 return;
14301 }
14302 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14303 self.change_selections(Default::default(), window, cx, |s| {
14304 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14305 });
14306 }
14307
14308 pub fn select_to_beginning(
14309 &mut self,
14310 _: &SelectToBeginning,
14311 window: &mut Window,
14312 cx: &mut Context<Self>,
14313 ) {
14314 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14315 selection.set_head(Point::zero(), SelectionGoal::None);
14316 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14317 self.change_selections(Default::default(), window, cx, |s| {
14318 s.select(vec![selection]);
14319 });
14320 }
14321
14322 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14323 if matches!(self.mode, EditorMode::SingleLine) {
14324 cx.propagate();
14325 return;
14326 }
14327 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14328 let cursor = self.buffer.read(cx).read(cx).len();
14329 self.change_selections(Default::default(), window, cx, |s| {
14330 s.select_ranges(vec![cursor..cursor])
14331 });
14332 }
14333
14334 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14335 self.nav_history = nav_history;
14336 }
14337
14338 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14339 self.nav_history.as_ref()
14340 }
14341
14342 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14343 self.push_to_nav_history(
14344 self.selections.newest_anchor().head(),
14345 None,
14346 false,
14347 true,
14348 cx,
14349 );
14350 }
14351
14352 fn push_to_nav_history(
14353 &mut self,
14354 cursor_anchor: Anchor,
14355 new_position: Option<Point>,
14356 is_deactivate: bool,
14357 always: bool,
14358 cx: &mut Context<Self>,
14359 ) {
14360 if let Some(nav_history) = self.nav_history.as_mut() {
14361 let buffer = self.buffer.read(cx).read(cx);
14362 let cursor_position = cursor_anchor.to_point(&buffer);
14363 let scroll_state = self.scroll_manager.anchor();
14364 let scroll_top_row = scroll_state.top_row(&buffer);
14365 drop(buffer);
14366
14367 if let Some(new_position) = new_position {
14368 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14369 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14370 return;
14371 }
14372 }
14373
14374 nav_history.push(
14375 Some(NavigationData {
14376 cursor_anchor,
14377 cursor_position,
14378 scroll_anchor: scroll_state,
14379 scroll_top_row,
14380 }),
14381 cx,
14382 );
14383 cx.emit(EditorEvent::PushedToNavHistory {
14384 anchor: cursor_anchor,
14385 is_deactivate,
14386 })
14387 }
14388 }
14389
14390 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14391 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14392 let buffer = self.buffer.read(cx).snapshot(cx);
14393 let mut selection = self
14394 .selections
14395 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14396 selection.set_head(buffer.len(), SelectionGoal::None);
14397 self.change_selections(Default::default(), window, cx, |s| {
14398 s.select(vec![selection]);
14399 });
14400 }
14401
14402 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14404 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14405 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14406 });
14407 }
14408
14409 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14410 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14411 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14412 let mut selections = self.selections.all::<Point>(&display_map);
14413 let max_point = display_map.buffer_snapshot().max_point();
14414 for selection in &mut selections {
14415 let rows = selection.spanned_rows(true, &display_map);
14416 selection.start = Point::new(rows.start.0, 0);
14417 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14418 selection.reversed = false;
14419 }
14420 self.change_selections(Default::default(), window, cx, |s| {
14421 s.select(selections);
14422 });
14423 }
14424
14425 pub fn split_selection_into_lines(
14426 &mut self,
14427 action: &SplitSelectionIntoLines,
14428 window: &mut Window,
14429 cx: &mut Context<Self>,
14430 ) {
14431 let selections = self
14432 .selections
14433 .all::<Point>(&self.display_snapshot(cx))
14434 .into_iter()
14435 .map(|selection| selection.start..selection.end)
14436 .collect::<Vec<_>>();
14437 self.unfold_ranges(&selections, true, true, cx);
14438
14439 let mut new_selection_ranges = Vec::new();
14440 {
14441 let buffer = self.buffer.read(cx).read(cx);
14442 for selection in selections {
14443 for row in selection.start.row..selection.end.row {
14444 let line_start = Point::new(row, 0);
14445 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14446
14447 if action.keep_selections {
14448 // Keep the selection range for each line
14449 let selection_start = if row == selection.start.row {
14450 selection.start
14451 } else {
14452 line_start
14453 };
14454 new_selection_ranges.push(selection_start..line_end);
14455 } else {
14456 // Collapse to cursor at end of line
14457 new_selection_ranges.push(line_end..line_end);
14458 }
14459 }
14460
14461 let is_multiline_selection = selection.start.row != selection.end.row;
14462 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14463 // so this action feels more ergonomic when paired with other selection operations
14464 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14465 if !should_skip_last {
14466 if action.keep_selections {
14467 if is_multiline_selection {
14468 let line_start = Point::new(selection.end.row, 0);
14469 new_selection_ranges.push(line_start..selection.end);
14470 } else {
14471 new_selection_ranges.push(selection.start..selection.end);
14472 }
14473 } else {
14474 new_selection_ranges.push(selection.end..selection.end);
14475 }
14476 }
14477 }
14478 }
14479 self.change_selections(Default::default(), window, cx, |s| {
14480 s.select_ranges(new_selection_ranges);
14481 });
14482 }
14483
14484 pub fn add_selection_above(
14485 &mut self,
14486 action: &AddSelectionAbove,
14487 window: &mut Window,
14488 cx: &mut Context<Self>,
14489 ) {
14490 self.add_selection(true, action.skip_soft_wrap, window, cx);
14491 }
14492
14493 pub fn add_selection_below(
14494 &mut self,
14495 action: &AddSelectionBelow,
14496 window: &mut Window,
14497 cx: &mut Context<Self>,
14498 ) {
14499 self.add_selection(false, action.skip_soft_wrap, window, cx);
14500 }
14501
14502 fn add_selection(
14503 &mut self,
14504 above: bool,
14505 skip_soft_wrap: bool,
14506 window: &mut Window,
14507 cx: &mut Context<Self>,
14508 ) {
14509 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14510
14511 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14512 let all_selections = self.selections.all::<Point>(&display_map);
14513 let text_layout_details = self.text_layout_details(window);
14514
14515 let (mut columnar_selections, new_selections_to_columnarize) = {
14516 if let Some(state) = self.add_selections_state.as_ref() {
14517 let columnar_selection_ids: HashSet<_> = state
14518 .groups
14519 .iter()
14520 .flat_map(|group| group.stack.iter())
14521 .copied()
14522 .collect();
14523
14524 all_selections
14525 .into_iter()
14526 .partition(|s| columnar_selection_ids.contains(&s.id))
14527 } else {
14528 (Vec::new(), all_selections)
14529 }
14530 };
14531
14532 let mut state = self
14533 .add_selections_state
14534 .take()
14535 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14536
14537 for selection in new_selections_to_columnarize {
14538 let range = selection.display_range(&display_map).sorted();
14539 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14540 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14541 let positions = start_x.min(end_x)..start_x.max(end_x);
14542 let mut stack = Vec::new();
14543 for row in range.start.row().0..=range.end.row().0 {
14544 if let Some(selection) = self.selections.build_columnar_selection(
14545 &display_map,
14546 DisplayRow(row),
14547 &positions,
14548 selection.reversed,
14549 &text_layout_details,
14550 ) {
14551 stack.push(selection.id);
14552 columnar_selections.push(selection);
14553 }
14554 }
14555 if !stack.is_empty() {
14556 if above {
14557 stack.reverse();
14558 }
14559 state.groups.push(AddSelectionsGroup { above, stack });
14560 }
14561 }
14562
14563 let mut final_selections = Vec::new();
14564 let end_row = if above {
14565 DisplayRow(0)
14566 } else {
14567 display_map.max_point().row()
14568 };
14569
14570 let mut last_added_item_per_group = HashMap::default();
14571 for group in state.groups.iter_mut() {
14572 if let Some(last_id) = group.stack.last() {
14573 last_added_item_per_group.insert(*last_id, group);
14574 }
14575 }
14576
14577 for selection in columnar_selections {
14578 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14579 if above == group.above {
14580 let range = selection.display_range(&display_map).sorted();
14581 debug_assert_eq!(range.start.row(), range.end.row());
14582 let mut row = range.start.row();
14583 let positions =
14584 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14585 Pixels::from(start)..Pixels::from(end)
14586 } else {
14587 let start_x =
14588 display_map.x_for_display_point(range.start, &text_layout_details);
14589 let end_x =
14590 display_map.x_for_display_point(range.end, &text_layout_details);
14591 start_x.min(end_x)..start_x.max(end_x)
14592 };
14593
14594 let mut maybe_new_selection = None;
14595 let direction = if above { -1 } else { 1 };
14596
14597 while row != end_row {
14598 if skip_soft_wrap {
14599 row = display_map
14600 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14601 .row();
14602 } else if above {
14603 row.0 -= 1;
14604 } else {
14605 row.0 += 1;
14606 }
14607
14608 if let Some(new_selection) = self.selections.build_columnar_selection(
14609 &display_map,
14610 row,
14611 &positions,
14612 selection.reversed,
14613 &text_layout_details,
14614 ) {
14615 maybe_new_selection = Some(new_selection);
14616 break;
14617 }
14618 }
14619
14620 if let Some(new_selection) = maybe_new_selection {
14621 group.stack.push(new_selection.id);
14622 if above {
14623 final_selections.push(new_selection);
14624 final_selections.push(selection);
14625 } else {
14626 final_selections.push(selection);
14627 final_selections.push(new_selection);
14628 }
14629 } else {
14630 final_selections.push(selection);
14631 }
14632 } else {
14633 group.stack.pop();
14634 }
14635 } else {
14636 final_selections.push(selection);
14637 }
14638 }
14639
14640 self.change_selections(Default::default(), window, cx, |s| {
14641 s.select(final_selections);
14642 });
14643
14644 let final_selection_ids: HashSet<_> = self
14645 .selections
14646 .all::<Point>(&display_map)
14647 .iter()
14648 .map(|s| s.id)
14649 .collect();
14650 state.groups.retain_mut(|group| {
14651 // selections might get merged above so we remove invalid items from stacks
14652 group.stack.retain(|id| final_selection_ids.contains(id));
14653
14654 // single selection in stack can be treated as initial state
14655 group.stack.len() > 1
14656 });
14657
14658 if !state.groups.is_empty() {
14659 self.add_selections_state = Some(state);
14660 }
14661 }
14662
14663 fn select_match_ranges(
14664 &mut self,
14665 range: Range<MultiBufferOffset>,
14666 reversed: bool,
14667 replace_newest: bool,
14668 auto_scroll: Option<Autoscroll>,
14669 window: &mut Window,
14670 cx: &mut Context<Editor>,
14671 ) {
14672 self.unfold_ranges(
14673 std::slice::from_ref(&range),
14674 false,
14675 auto_scroll.is_some(),
14676 cx,
14677 );
14678 let effects = if let Some(scroll) = auto_scroll {
14679 SelectionEffects::scroll(scroll)
14680 } else {
14681 SelectionEffects::no_scroll()
14682 };
14683 self.change_selections(effects, window, cx, |s| {
14684 if replace_newest {
14685 s.delete(s.newest_anchor().id);
14686 }
14687 if reversed {
14688 s.insert_range(range.end..range.start);
14689 } else {
14690 s.insert_range(range);
14691 }
14692 });
14693 }
14694
14695 pub fn select_next_match_internal(
14696 &mut self,
14697 display_map: &DisplaySnapshot,
14698 replace_newest: bool,
14699 autoscroll: Option<Autoscroll>,
14700 window: &mut Window,
14701 cx: &mut Context<Self>,
14702 ) -> Result<()> {
14703 let buffer = display_map.buffer_snapshot();
14704 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14705 if let Some(mut select_next_state) = self.select_next_state.take() {
14706 let query = &select_next_state.query;
14707 if !select_next_state.done {
14708 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14709 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14710 let mut next_selected_range = None;
14711
14712 let bytes_after_last_selection =
14713 buffer.bytes_in_range(last_selection.end..buffer.len());
14714 let bytes_before_first_selection =
14715 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14716 let query_matches = query
14717 .stream_find_iter(bytes_after_last_selection)
14718 .map(|result| (last_selection.end, result))
14719 .chain(
14720 query
14721 .stream_find_iter(bytes_before_first_selection)
14722 .map(|result| (MultiBufferOffset(0), result)),
14723 );
14724
14725 for (start_offset, query_match) in query_matches {
14726 let query_match = query_match.unwrap(); // can only fail due to I/O
14727 let offset_range =
14728 start_offset + query_match.start()..start_offset + query_match.end();
14729
14730 if !select_next_state.wordwise
14731 || (!buffer.is_inside_word(offset_range.start, None)
14732 && !buffer.is_inside_word(offset_range.end, None))
14733 {
14734 let idx = selections
14735 .partition_point(|selection| selection.end <= offset_range.start);
14736 let overlaps = selections
14737 .get(idx)
14738 .map_or(false, |selection| selection.start < offset_range.end);
14739
14740 if !overlaps {
14741 next_selected_range = Some(offset_range);
14742 break;
14743 }
14744 }
14745 }
14746
14747 if let Some(next_selected_range) = next_selected_range {
14748 self.select_match_ranges(
14749 next_selected_range,
14750 last_selection.reversed,
14751 replace_newest,
14752 autoscroll,
14753 window,
14754 cx,
14755 );
14756 } else {
14757 select_next_state.done = true;
14758 }
14759 }
14760
14761 self.select_next_state = Some(select_next_state);
14762 } else {
14763 let mut only_carets = true;
14764 let mut same_text_selected = true;
14765 let mut selected_text = None;
14766
14767 let mut selections_iter = selections.iter().peekable();
14768 while let Some(selection) = selections_iter.next() {
14769 if selection.start != selection.end {
14770 only_carets = false;
14771 }
14772
14773 if same_text_selected {
14774 if selected_text.is_none() {
14775 selected_text =
14776 Some(buffer.text_for_range(selection.range()).collect::<String>());
14777 }
14778
14779 if let Some(next_selection) = selections_iter.peek() {
14780 if next_selection.len() == selection.len() {
14781 let next_selected_text = buffer
14782 .text_for_range(next_selection.range())
14783 .collect::<String>();
14784 if Some(next_selected_text) != selected_text {
14785 same_text_selected = false;
14786 selected_text = None;
14787 }
14788 } else {
14789 same_text_selected = false;
14790 selected_text = None;
14791 }
14792 }
14793 }
14794 }
14795
14796 if only_carets {
14797 for selection in &mut selections {
14798 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14799 selection.start = word_range.start;
14800 selection.end = word_range.end;
14801 selection.goal = SelectionGoal::None;
14802 selection.reversed = false;
14803 self.select_match_ranges(
14804 selection.start..selection.end,
14805 selection.reversed,
14806 replace_newest,
14807 autoscroll,
14808 window,
14809 cx,
14810 );
14811 }
14812
14813 if selections.len() == 1 {
14814 let selection = selections
14815 .last()
14816 .expect("ensured that there's only one selection");
14817 let query = buffer
14818 .text_for_range(selection.start..selection.end)
14819 .collect::<String>();
14820 let is_empty = query.is_empty();
14821 let select_state = SelectNextState {
14822 query: self.build_query(&[query], cx)?,
14823 wordwise: true,
14824 done: is_empty,
14825 };
14826 self.select_next_state = Some(select_state);
14827 } else {
14828 self.select_next_state = None;
14829 }
14830 } else if let Some(selected_text) = selected_text {
14831 self.select_next_state = Some(SelectNextState {
14832 query: self.build_query(&[selected_text], cx)?,
14833 wordwise: false,
14834 done: false,
14835 });
14836 self.select_next_match_internal(
14837 display_map,
14838 replace_newest,
14839 autoscroll,
14840 window,
14841 cx,
14842 )?;
14843 }
14844 }
14845 Ok(())
14846 }
14847
14848 pub fn select_all_matches(
14849 &mut self,
14850 _action: &SelectAllMatches,
14851 window: &mut Window,
14852 cx: &mut Context<Self>,
14853 ) -> Result<()> {
14854 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14855
14856 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14857
14858 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14859 let Some(select_next_state) = self.select_next_state.as_mut() else {
14860 return Ok(());
14861 };
14862 if select_next_state.done {
14863 return Ok(());
14864 }
14865
14866 let mut new_selections = Vec::new();
14867
14868 let reversed = self
14869 .selections
14870 .oldest::<MultiBufferOffset>(&display_map)
14871 .reversed;
14872 let buffer = display_map.buffer_snapshot();
14873 let query_matches = select_next_state
14874 .query
14875 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
14876
14877 for query_match in query_matches.into_iter() {
14878 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14879 let offset_range = if reversed {
14880 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
14881 } else {
14882 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
14883 };
14884
14885 if !select_next_state.wordwise
14886 || (!buffer.is_inside_word(offset_range.start, None)
14887 && !buffer.is_inside_word(offset_range.end, None))
14888 {
14889 new_selections.push(offset_range.start..offset_range.end);
14890 }
14891 }
14892
14893 select_next_state.done = true;
14894
14895 if new_selections.is_empty() {
14896 log::error!("bug: new_selections is empty in select_all_matches");
14897 return Ok(());
14898 }
14899
14900 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14901 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14902 selections.select_ranges(new_selections)
14903 });
14904
14905 Ok(())
14906 }
14907
14908 pub fn select_next(
14909 &mut self,
14910 action: &SelectNext,
14911 window: &mut Window,
14912 cx: &mut Context<Self>,
14913 ) -> Result<()> {
14914 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14915 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14916 self.select_next_match_internal(
14917 &display_map,
14918 action.replace_newest,
14919 Some(Autoscroll::newest()),
14920 window,
14921 cx,
14922 )?;
14923 Ok(())
14924 }
14925
14926 pub fn select_previous(
14927 &mut self,
14928 action: &SelectPrevious,
14929 window: &mut Window,
14930 cx: &mut Context<Self>,
14931 ) -> Result<()> {
14932 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14933 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14934 let buffer = display_map.buffer_snapshot();
14935 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14936 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14937 let query = &select_prev_state.query;
14938 if !select_prev_state.done {
14939 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14940 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14941 let mut next_selected_range = None;
14942 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14943 let bytes_before_last_selection =
14944 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
14945 let bytes_after_first_selection =
14946 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14947 let query_matches = query
14948 .stream_find_iter(bytes_before_last_selection)
14949 .map(|result| (last_selection.start, result))
14950 .chain(
14951 query
14952 .stream_find_iter(bytes_after_first_selection)
14953 .map(|result| (buffer.len(), result)),
14954 );
14955 for (end_offset, query_match) in query_matches {
14956 let query_match = query_match.unwrap(); // can only fail due to I/O
14957 let offset_range =
14958 end_offset - query_match.end()..end_offset - query_match.start();
14959
14960 if !select_prev_state.wordwise
14961 || (!buffer.is_inside_word(offset_range.start, None)
14962 && !buffer.is_inside_word(offset_range.end, None))
14963 {
14964 next_selected_range = Some(offset_range);
14965 break;
14966 }
14967 }
14968
14969 if let Some(next_selected_range) = next_selected_range {
14970 self.select_match_ranges(
14971 next_selected_range,
14972 last_selection.reversed,
14973 action.replace_newest,
14974 Some(Autoscroll::newest()),
14975 window,
14976 cx,
14977 );
14978 } else {
14979 select_prev_state.done = true;
14980 }
14981 }
14982
14983 self.select_prev_state = Some(select_prev_state);
14984 } else {
14985 let mut only_carets = true;
14986 let mut same_text_selected = true;
14987 let mut selected_text = None;
14988
14989 let mut selections_iter = selections.iter().peekable();
14990 while let Some(selection) = selections_iter.next() {
14991 if selection.start != selection.end {
14992 only_carets = false;
14993 }
14994
14995 if same_text_selected {
14996 if selected_text.is_none() {
14997 selected_text =
14998 Some(buffer.text_for_range(selection.range()).collect::<String>());
14999 }
15000
15001 if let Some(next_selection) = selections_iter.peek() {
15002 if next_selection.len() == selection.len() {
15003 let next_selected_text = buffer
15004 .text_for_range(next_selection.range())
15005 .collect::<String>();
15006 if Some(next_selected_text) != selected_text {
15007 same_text_selected = false;
15008 selected_text = None;
15009 }
15010 } else {
15011 same_text_selected = false;
15012 selected_text = None;
15013 }
15014 }
15015 }
15016 }
15017
15018 if only_carets {
15019 for selection in &mut selections {
15020 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15021 selection.start = word_range.start;
15022 selection.end = word_range.end;
15023 selection.goal = SelectionGoal::None;
15024 selection.reversed = false;
15025 self.select_match_ranges(
15026 selection.start..selection.end,
15027 selection.reversed,
15028 action.replace_newest,
15029 Some(Autoscroll::newest()),
15030 window,
15031 cx,
15032 );
15033 }
15034 if selections.len() == 1 {
15035 let selection = selections
15036 .last()
15037 .expect("ensured that there's only one selection");
15038 let query = buffer
15039 .text_for_range(selection.start..selection.end)
15040 .collect::<String>();
15041 let is_empty = query.is_empty();
15042 let select_state = SelectNextState {
15043 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15044 wordwise: true,
15045 done: is_empty,
15046 };
15047 self.select_prev_state = Some(select_state);
15048 } else {
15049 self.select_prev_state = None;
15050 }
15051 } else if let Some(selected_text) = selected_text {
15052 self.select_prev_state = Some(SelectNextState {
15053 query: self
15054 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15055 wordwise: false,
15056 done: false,
15057 });
15058 self.select_previous(action, window, cx)?;
15059 }
15060 }
15061 Ok(())
15062 }
15063
15064 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15065 /// setting the case sensitivity based on the global
15066 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15067 /// editor's settings.
15068 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15069 where
15070 I: IntoIterator<Item = P>,
15071 P: AsRef<[u8]>,
15072 {
15073 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15074 || EditorSettings::get_global(cx).search.case_sensitive,
15075 |value| value,
15076 );
15077
15078 let mut builder = AhoCorasickBuilder::new();
15079 builder.ascii_case_insensitive(!case_sensitive);
15080 builder.build(patterns)
15081 }
15082
15083 pub fn find_next_match(
15084 &mut self,
15085 _: &FindNextMatch,
15086 window: &mut Window,
15087 cx: &mut Context<Self>,
15088 ) -> Result<()> {
15089 let selections = self.selections.disjoint_anchors_arc();
15090 match selections.first() {
15091 Some(first) if selections.len() >= 2 => {
15092 self.change_selections(Default::default(), window, cx, |s| {
15093 s.select_ranges([first.range()]);
15094 });
15095 }
15096 _ => self.select_next(
15097 &SelectNext {
15098 replace_newest: true,
15099 },
15100 window,
15101 cx,
15102 )?,
15103 }
15104 Ok(())
15105 }
15106
15107 pub fn find_previous_match(
15108 &mut self,
15109 _: &FindPreviousMatch,
15110 window: &mut Window,
15111 cx: &mut Context<Self>,
15112 ) -> Result<()> {
15113 let selections = self.selections.disjoint_anchors_arc();
15114 match selections.last() {
15115 Some(last) if selections.len() >= 2 => {
15116 self.change_selections(Default::default(), window, cx, |s| {
15117 s.select_ranges([last.range()]);
15118 });
15119 }
15120 _ => self.select_previous(
15121 &SelectPrevious {
15122 replace_newest: true,
15123 },
15124 window,
15125 cx,
15126 )?,
15127 }
15128 Ok(())
15129 }
15130
15131 pub fn toggle_comments(
15132 &mut self,
15133 action: &ToggleComments,
15134 window: &mut Window,
15135 cx: &mut Context<Self>,
15136 ) {
15137 if self.read_only(cx) {
15138 return;
15139 }
15140 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15141 let text_layout_details = &self.text_layout_details(window);
15142 self.transact(window, cx, |this, window, cx| {
15143 let mut selections = this
15144 .selections
15145 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15146 let mut edits = Vec::new();
15147 let mut selection_edit_ranges = Vec::new();
15148 let mut last_toggled_row = None;
15149 let snapshot = this.buffer.read(cx).read(cx);
15150 let empty_str: Arc<str> = Arc::default();
15151 let mut suffixes_inserted = Vec::new();
15152 let ignore_indent = action.ignore_indent;
15153
15154 fn comment_prefix_range(
15155 snapshot: &MultiBufferSnapshot,
15156 row: MultiBufferRow,
15157 comment_prefix: &str,
15158 comment_prefix_whitespace: &str,
15159 ignore_indent: bool,
15160 ) -> Range<Point> {
15161 let indent_size = if ignore_indent {
15162 0
15163 } else {
15164 snapshot.indent_size_for_line(row).len
15165 };
15166
15167 let start = Point::new(row.0, indent_size);
15168
15169 let mut line_bytes = snapshot
15170 .bytes_in_range(start..snapshot.max_point())
15171 .flatten()
15172 .copied();
15173
15174 // If this line currently begins with the line comment prefix, then record
15175 // the range containing the prefix.
15176 if line_bytes
15177 .by_ref()
15178 .take(comment_prefix.len())
15179 .eq(comment_prefix.bytes())
15180 {
15181 // Include any whitespace that matches the comment prefix.
15182 let matching_whitespace_len = line_bytes
15183 .zip(comment_prefix_whitespace.bytes())
15184 .take_while(|(a, b)| a == b)
15185 .count() as u32;
15186 let end = Point::new(
15187 start.row,
15188 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15189 );
15190 start..end
15191 } else {
15192 start..start
15193 }
15194 }
15195
15196 fn comment_suffix_range(
15197 snapshot: &MultiBufferSnapshot,
15198 row: MultiBufferRow,
15199 comment_suffix: &str,
15200 comment_suffix_has_leading_space: bool,
15201 ) -> Range<Point> {
15202 let end = Point::new(row.0, snapshot.line_len(row));
15203 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15204
15205 let mut line_end_bytes = snapshot
15206 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15207 .flatten()
15208 .copied();
15209
15210 let leading_space_len = if suffix_start_column > 0
15211 && line_end_bytes.next() == Some(b' ')
15212 && comment_suffix_has_leading_space
15213 {
15214 1
15215 } else {
15216 0
15217 };
15218
15219 // If this line currently begins with the line comment prefix, then record
15220 // the range containing the prefix.
15221 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15222 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15223 start..end
15224 } else {
15225 end..end
15226 }
15227 }
15228
15229 // TODO: Handle selections that cross excerpts
15230 for selection in &mut selections {
15231 let start_column = snapshot
15232 .indent_size_for_line(MultiBufferRow(selection.start.row))
15233 .len;
15234 let language = if let Some(language) =
15235 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15236 {
15237 language
15238 } else {
15239 continue;
15240 };
15241
15242 selection_edit_ranges.clear();
15243
15244 // If multiple selections contain a given row, avoid processing that
15245 // row more than once.
15246 let mut start_row = MultiBufferRow(selection.start.row);
15247 if last_toggled_row == Some(start_row) {
15248 start_row = start_row.next_row();
15249 }
15250 let end_row =
15251 if selection.end.row > selection.start.row && selection.end.column == 0 {
15252 MultiBufferRow(selection.end.row - 1)
15253 } else {
15254 MultiBufferRow(selection.end.row)
15255 };
15256 last_toggled_row = Some(end_row);
15257
15258 if start_row > end_row {
15259 continue;
15260 }
15261
15262 // If the language has line comments, toggle those.
15263 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15264
15265 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15266 if ignore_indent {
15267 full_comment_prefixes = full_comment_prefixes
15268 .into_iter()
15269 .map(|s| Arc::from(s.trim_end()))
15270 .collect();
15271 }
15272
15273 if !full_comment_prefixes.is_empty() {
15274 let first_prefix = full_comment_prefixes
15275 .first()
15276 .expect("prefixes is non-empty");
15277 let prefix_trimmed_lengths = full_comment_prefixes
15278 .iter()
15279 .map(|p| p.trim_end_matches(' ').len())
15280 .collect::<SmallVec<[usize; 4]>>();
15281
15282 let mut all_selection_lines_are_comments = true;
15283
15284 for row in start_row.0..=end_row.0 {
15285 let row = MultiBufferRow(row);
15286 if start_row < end_row && snapshot.is_line_blank(row) {
15287 continue;
15288 }
15289
15290 let prefix_range = full_comment_prefixes
15291 .iter()
15292 .zip(prefix_trimmed_lengths.iter().copied())
15293 .map(|(prefix, trimmed_prefix_len)| {
15294 comment_prefix_range(
15295 snapshot.deref(),
15296 row,
15297 &prefix[..trimmed_prefix_len],
15298 &prefix[trimmed_prefix_len..],
15299 ignore_indent,
15300 )
15301 })
15302 .max_by_key(|range| range.end.column - range.start.column)
15303 .expect("prefixes is non-empty");
15304
15305 if prefix_range.is_empty() {
15306 all_selection_lines_are_comments = false;
15307 }
15308
15309 selection_edit_ranges.push(prefix_range);
15310 }
15311
15312 if all_selection_lines_are_comments {
15313 edits.extend(
15314 selection_edit_ranges
15315 .iter()
15316 .cloned()
15317 .map(|range| (range, empty_str.clone())),
15318 );
15319 } else {
15320 let min_column = selection_edit_ranges
15321 .iter()
15322 .map(|range| range.start.column)
15323 .min()
15324 .unwrap_or(0);
15325 edits.extend(selection_edit_ranges.iter().map(|range| {
15326 let position = Point::new(range.start.row, min_column);
15327 (position..position, first_prefix.clone())
15328 }));
15329 }
15330 } else if let Some(BlockCommentConfig {
15331 start: full_comment_prefix,
15332 end: comment_suffix,
15333 ..
15334 }) = language.block_comment()
15335 {
15336 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15337 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15338 let prefix_range = comment_prefix_range(
15339 snapshot.deref(),
15340 start_row,
15341 comment_prefix,
15342 comment_prefix_whitespace,
15343 ignore_indent,
15344 );
15345 let suffix_range = comment_suffix_range(
15346 snapshot.deref(),
15347 end_row,
15348 comment_suffix.trim_start_matches(' '),
15349 comment_suffix.starts_with(' '),
15350 );
15351
15352 if prefix_range.is_empty() || suffix_range.is_empty() {
15353 edits.push((
15354 prefix_range.start..prefix_range.start,
15355 full_comment_prefix.clone(),
15356 ));
15357 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15358 suffixes_inserted.push((end_row, comment_suffix.len()));
15359 } else {
15360 edits.push((prefix_range, empty_str.clone()));
15361 edits.push((suffix_range, empty_str.clone()));
15362 }
15363 } else {
15364 continue;
15365 }
15366 }
15367
15368 drop(snapshot);
15369 this.buffer.update(cx, |buffer, cx| {
15370 buffer.edit(edits, None, cx);
15371 });
15372
15373 // Adjust selections so that they end before any comment suffixes that
15374 // were inserted.
15375 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15376 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15377 let snapshot = this.buffer.read(cx).read(cx);
15378 for selection in &mut selections {
15379 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15380 match row.cmp(&MultiBufferRow(selection.end.row)) {
15381 Ordering::Less => {
15382 suffixes_inserted.next();
15383 continue;
15384 }
15385 Ordering::Greater => break,
15386 Ordering::Equal => {
15387 if selection.end.column == snapshot.line_len(row) {
15388 if selection.is_empty() {
15389 selection.start.column -= suffix_len as u32;
15390 }
15391 selection.end.column -= suffix_len as u32;
15392 }
15393 break;
15394 }
15395 }
15396 }
15397 }
15398
15399 drop(snapshot);
15400 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15401
15402 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15403 let selections_on_single_row = selections.windows(2).all(|selections| {
15404 selections[0].start.row == selections[1].start.row
15405 && selections[0].end.row == selections[1].end.row
15406 && selections[0].start.row == selections[0].end.row
15407 });
15408 let selections_selecting = selections
15409 .iter()
15410 .any(|selection| selection.start != selection.end);
15411 let advance_downwards = action.advance_downwards
15412 && selections_on_single_row
15413 && !selections_selecting
15414 && !matches!(this.mode, EditorMode::SingleLine);
15415
15416 if advance_downwards {
15417 let snapshot = this.buffer.read(cx).snapshot(cx);
15418
15419 this.change_selections(Default::default(), window, cx, |s| {
15420 s.move_cursors_with(|display_snapshot, display_point, _| {
15421 let mut point = display_point.to_point(display_snapshot);
15422 point.row += 1;
15423 point = snapshot.clip_point(point, Bias::Left);
15424 let display_point = point.to_display_point(display_snapshot);
15425 let goal = SelectionGoal::HorizontalPosition(
15426 display_snapshot
15427 .x_for_display_point(display_point, text_layout_details)
15428 .into(),
15429 );
15430 (display_point, goal)
15431 })
15432 });
15433 }
15434 });
15435 }
15436
15437 pub fn select_enclosing_symbol(
15438 &mut self,
15439 _: &SelectEnclosingSymbol,
15440 window: &mut Window,
15441 cx: &mut Context<Self>,
15442 ) {
15443 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15444
15445 let buffer = self.buffer.read(cx).snapshot(cx);
15446 let old_selections = self
15447 .selections
15448 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15449 .into_boxed_slice();
15450
15451 fn update_selection(
15452 selection: &Selection<MultiBufferOffset>,
15453 buffer_snap: &MultiBufferSnapshot,
15454 ) -> Option<Selection<MultiBufferOffset>> {
15455 let cursor = selection.head();
15456 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15457 for symbol in symbols.iter().rev() {
15458 let start = symbol.range.start.to_offset(buffer_snap);
15459 let end = symbol.range.end.to_offset(buffer_snap);
15460 let new_range = start..end;
15461 if start < selection.start || end > selection.end {
15462 return Some(Selection {
15463 id: selection.id,
15464 start: new_range.start,
15465 end: new_range.end,
15466 goal: SelectionGoal::None,
15467 reversed: selection.reversed,
15468 });
15469 }
15470 }
15471 None
15472 }
15473
15474 let mut selected_larger_symbol = false;
15475 let new_selections = old_selections
15476 .iter()
15477 .map(|selection| match update_selection(selection, &buffer) {
15478 Some(new_selection) => {
15479 if new_selection.range() != selection.range() {
15480 selected_larger_symbol = true;
15481 }
15482 new_selection
15483 }
15484 None => selection.clone(),
15485 })
15486 .collect::<Vec<_>>();
15487
15488 if selected_larger_symbol {
15489 self.change_selections(Default::default(), window, cx, |s| {
15490 s.select(new_selections);
15491 });
15492 }
15493 }
15494
15495 pub fn select_larger_syntax_node(
15496 &mut self,
15497 _: &SelectLargerSyntaxNode,
15498 window: &mut Window,
15499 cx: &mut Context<Self>,
15500 ) {
15501 let Some(visible_row_count) = self.visible_row_count() else {
15502 return;
15503 };
15504 let old_selections: Box<[_]> = self
15505 .selections
15506 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15507 .into();
15508 if old_selections.is_empty() {
15509 return;
15510 }
15511
15512 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15513
15514 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15515 let buffer = self.buffer.read(cx).snapshot(cx);
15516
15517 let mut selected_larger_node = false;
15518 let mut new_selections = old_selections
15519 .iter()
15520 .map(|selection| {
15521 let old_range = selection.start..selection.end;
15522
15523 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15524 // manually select word at selection
15525 if ["string_content", "inline"].contains(&node.kind()) {
15526 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15527 // ignore if word is already selected
15528 if !word_range.is_empty() && old_range != word_range {
15529 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15530 // only select word if start and end point belongs to same word
15531 if word_range == last_word_range {
15532 selected_larger_node = true;
15533 return Selection {
15534 id: selection.id,
15535 start: word_range.start,
15536 end: word_range.end,
15537 goal: SelectionGoal::None,
15538 reversed: selection.reversed,
15539 };
15540 }
15541 }
15542 }
15543 }
15544
15545 let mut new_range = old_range.clone();
15546 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15547 new_range = range;
15548 if !node.is_named() {
15549 continue;
15550 }
15551 if !display_map.intersects_fold(new_range.start)
15552 && !display_map.intersects_fold(new_range.end)
15553 {
15554 break;
15555 }
15556 }
15557
15558 selected_larger_node |= new_range != old_range;
15559 Selection {
15560 id: selection.id,
15561 start: new_range.start,
15562 end: new_range.end,
15563 goal: SelectionGoal::None,
15564 reversed: selection.reversed,
15565 }
15566 })
15567 .collect::<Vec<_>>();
15568
15569 if !selected_larger_node {
15570 return; // don't put this call in the history
15571 }
15572
15573 // scroll based on transformation done to the last selection created by the user
15574 let (last_old, last_new) = old_selections
15575 .last()
15576 .zip(new_selections.last().cloned())
15577 .expect("old_selections isn't empty");
15578
15579 // revert selection
15580 let is_selection_reversed = {
15581 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15582 new_selections.last_mut().expect("checked above").reversed =
15583 should_newest_selection_be_reversed;
15584 should_newest_selection_be_reversed
15585 };
15586
15587 if selected_larger_node {
15588 self.select_syntax_node_history.disable_clearing = true;
15589 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15590 s.select(new_selections.clone());
15591 });
15592 self.select_syntax_node_history.disable_clearing = false;
15593 }
15594
15595 let start_row = last_new.start.to_display_point(&display_map).row().0;
15596 let end_row = last_new.end.to_display_point(&display_map).row().0;
15597 let selection_height = end_row - start_row + 1;
15598 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15599
15600 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15601 let scroll_behavior = if fits_on_the_screen {
15602 self.request_autoscroll(Autoscroll::fit(), cx);
15603 SelectSyntaxNodeScrollBehavior::FitSelection
15604 } else if is_selection_reversed {
15605 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15606 SelectSyntaxNodeScrollBehavior::CursorTop
15607 } else {
15608 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15609 SelectSyntaxNodeScrollBehavior::CursorBottom
15610 };
15611
15612 self.select_syntax_node_history.push((
15613 old_selections,
15614 scroll_behavior,
15615 is_selection_reversed,
15616 ));
15617 }
15618
15619 pub fn select_smaller_syntax_node(
15620 &mut self,
15621 _: &SelectSmallerSyntaxNode,
15622 window: &mut Window,
15623 cx: &mut Context<Self>,
15624 ) {
15625 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15626
15627 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15628 self.select_syntax_node_history.pop()
15629 {
15630 if let Some(selection) = selections.last_mut() {
15631 selection.reversed = is_selection_reversed;
15632 }
15633
15634 self.select_syntax_node_history.disable_clearing = true;
15635 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15636 s.select(selections.to_vec());
15637 });
15638 self.select_syntax_node_history.disable_clearing = false;
15639
15640 match scroll_behavior {
15641 SelectSyntaxNodeScrollBehavior::CursorTop => {
15642 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15643 }
15644 SelectSyntaxNodeScrollBehavior::FitSelection => {
15645 self.request_autoscroll(Autoscroll::fit(), cx);
15646 }
15647 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15648 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15649 }
15650 }
15651 }
15652 }
15653
15654 pub fn unwrap_syntax_node(
15655 &mut self,
15656 _: &UnwrapSyntaxNode,
15657 window: &mut Window,
15658 cx: &mut Context<Self>,
15659 ) {
15660 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15661
15662 let buffer = self.buffer.read(cx).snapshot(cx);
15663 let selections = self
15664 .selections
15665 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15666 .into_iter()
15667 // subtracting the offset requires sorting
15668 .sorted_by_key(|i| i.start);
15669
15670 let full_edits = selections
15671 .into_iter()
15672 .filter_map(|selection| {
15673 let child = if selection.is_empty()
15674 && let Some((_, ancestor_range)) =
15675 buffer.syntax_ancestor(selection.start..selection.end)
15676 {
15677 ancestor_range
15678 } else {
15679 selection.range()
15680 };
15681
15682 let mut parent = child.clone();
15683 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15684 parent = ancestor_range;
15685 if parent.start < child.start || parent.end > child.end {
15686 break;
15687 }
15688 }
15689
15690 if parent == child {
15691 return None;
15692 }
15693 let text = buffer.text_for_range(child).collect::<String>();
15694 Some((selection.id, parent, text))
15695 })
15696 .collect::<Vec<_>>();
15697 if full_edits.is_empty() {
15698 return;
15699 }
15700
15701 self.transact(window, cx, |this, window, cx| {
15702 this.buffer.update(cx, |buffer, cx| {
15703 buffer.edit(
15704 full_edits
15705 .iter()
15706 .map(|(_, p, t)| (p.clone(), t.clone()))
15707 .collect::<Vec<_>>(),
15708 None,
15709 cx,
15710 );
15711 });
15712 this.change_selections(Default::default(), window, cx, |s| {
15713 let mut offset = 0;
15714 let mut selections = vec![];
15715 for (id, parent, text) in full_edits {
15716 let start = parent.start - offset;
15717 offset += (parent.end - parent.start) - text.len();
15718 selections.push(Selection {
15719 id,
15720 start,
15721 end: start + text.len(),
15722 reversed: false,
15723 goal: Default::default(),
15724 });
15725 }
15726 s.select(selections);
15727 });
15728 });
15729 }
15730
15731 pub fn select_next_syntax_node(
15732 &mut self,
15733 _: &SelectNextSyntaxNode,
15734 window: &mut Window,
15735 cx: &mut Context<Self>,
15736 ) {
15737 let old_selections: Box<[_]> = self
15738 .selections
15739 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15740 .into();
15741 if old_selections.is_empty() {
15742 return;
15743 }
15744
15745 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15746
15747 let buffer = self.buffer.read(cx).snapshot(cx);
15748 let mut selected_sibling = false;
15749
15750 let new_selections = old_selections
15751 .iter()
15752 .map(|selection| {
15753 let old_range = selection.start..selection.end;
15754
15755 let old_range =
15756 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15757 let excerpt = buffer.excerpt_containing(old_range.clone());
15758
15759 if let Some(mut excerpt) = excerpt
15760 && let Some(node) = excerpt
15761 .buffer()
15762 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15763 {
15764 let new_range = excerpt.map_range_from_buffer(
15765 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15766 );
15767 selected_sibling = true;
15768 Selection {
15769 id: selection.id,
15770 start: new_range.start,
15771 end: new_range.end,
15772 goal: SelectionGoal::None,
15773 reversed: selection.reversed,
15774 }
15775 } else {
15776 selection.clone()
15777 }
15778 })
15779 .collect::<Vec<_>>();
15780
15781 if selected_sibling {
15782 self.change_selections(
15783 SelectionEffects::scroll(Autoscroll::fit()),
15784 window,
15785 cx,
15786 |s| {
15787 s.select(new_selections);
15788 },
15789 );
15790 }
15791 }
15792
15793 pub fn select_prev_syntax_node(
15794 &mut self,
15795 _: &SelectPreviousSyntaxNode,
15796 window: &mut Window,
15797 cx: &mut Context<Self>,
15798 ) {
15799 let old_selections: Box<[_]> = self
15800 .selections
15801 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15802 .into();
15803 if old_selections.is_empty() {
15804 return;
15805 }
15806
15807 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15808
15809 let buffer = self.buffer.read(cx).snapshot(cx);
15810 let mut selected_sibling = false;
15811
15812 let new_selections = old_selections
15813 .iter()
15814 .map(|selection| {
15815 let old_range = selection.start..selection.end;
15816 let old_range =
15817 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15818 let excerpt = buffer.excerpt_containing(old_range.clone());
15819
15820 if let Some(mut excerpt) = excerpt
15821 && let Some(node) = excerpt
15822 .buffer()
15823 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
15824 {
15825 let new_range = excerpt.map_range_from_buffer(
15826 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15827 );
15828 selected_sibling = true;
15829 Selection {
15830 id: selection.id,
15831 start: new_range.start,
15832 end: new_range.end,
15833 goal: SelectionGoal::None,
15834 reversed: selection.reversed,
15835 }
15836 } else {
15837 selection.clone()
15838 }
15839 })
15840 .collect::<Vec<_>>();
15841
15842 if selected_sibling {
15843 self.change_selections(
15844 SelectionEffects::scroll(Autoscroll::fit()),
15845 window,
15846 cx,
15847 |s| {
15848 s.select(new_selections);
15849 },
15850 );
15851 }
15852 }
15853
15854 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15855 if !EditorSettings::get_global(cx).gutter.runnables {
15856 self.clear_tasks();
15857 return Task::ready(());
15858 }
15859 let project = self.project().map(Entity::downgrade);
15860 let task_sources = self.lsp_task_sources(cx);
15861 let multi_buffer = self.buffer.downgrade();
15862 cx.spawn_in(window, async move |editor, cx| {
15863 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15864 let Some(project) = project.and_then(|p| p.upgrade()) else {
15865 return;
15866 };
15867 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15868 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15869 }) else {
15870 return;
15871 };
15872
15873 let hide_runnables = project
15874 .update(cx, |project, _| project.is_via_collab())
15875 .unwrap_or(true);
15876 if hide_runnables {
15877 return;
15878 }
15879 let new_rows =
15880 cx.background_spawn({
15881 let snapshot = display_snapshot.clone();
15882 async move {
15883 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15884 }
15885 })
15886 .await;
15887 let Ok(lsp_tasks) =
15888 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15889 else {
15890 return;
15891 };
15892 let lsp_tasks = lsp_tasks.await;
15893
15894 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15895 lsp_tasks
15896 .into_iter()
15897 .flat_map(|(kind, tasks)| {
15898 tasks.into_iter().filter_map(move |(location, task)| {
15899 Some((kind.clone(), location?, task))
15900 })
15901 })
15902 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15903 let buffer = location.target.buffer;
15904 let buffer_snapshot = buffer.read(cx).snapshot();
15905 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15906 |(excerpt_id, snapshot, _)| {
15907 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15908 display_snapshot
15909 .buffer_snapshot()
15910 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15911 } else {
15912 None
15913 }
15914 },
15915 );
15916 if let Some(offset) = offset {
15917 let task_buffer_range =
15918 location.target.range.to_point(&buffer_snapshot);
15919 let context_buffer_range =
15920 task_buffer_range.to_offset(&buffer_snapshot);
15921 let context_range = BufferOffset(context_buffer_range.start)
15922 ..BufferOffset(context_buffer_range.end);
15923
15924 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15925 .or_insert_with(|| RunnableTasks {
15926 templates: Vec::new(),
15927 offset,
15928 column: task_buffer_range.start.column,
15929 extra_variables: HashMap::default(),
15930 context_range,
15931 })
15932 .templates
15933 .push((kind, task.original_task().clone()));
15934 }
15935
15936 acc
15937 })
15938 }) else {
15939 return;
15940 };
15941
15942 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15943 buffer.language_settings(cx).tasks.prefer_lsp
15944 }) else {
15945 return;
15946 };
15947
15948 let rows = Self::runnable_rows(
15949 project,
15950 display_snapshot,
15951 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15952 new_rows,
15953 cx.clone(),
15954 )
15955 .await;
15956 editor
15957 .update(cx, |editor, _| {
15958 editor.clear_tasks();
15959 for (key, mut value) in rows {
15960 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15961 value.templates.extend(lsp_tasks.templates);
15962 }
15963
15964 editor.insert_tasks(key, value);
15965 }
15966 for (key, value) in lsp_tasks_by_rows {
15967 editor.insert_tasks(key, value);
15968 }
15969 })
15970 .ok();
15971 })
15972 }
15973 fn fetch_runnable_ranges(
15974 snapshot: &DisplaySnapshot,
15975 range: Range<Anchor>,
15976 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
15977 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15978 }
15979
15980 fn runnable_rows(
15981 project: Entity<Project>,
15982 snapshot: DisplaySnapshot,
15983 prefer_lsp: bool,
15984 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
15985 cx: AsyncWindowContext,
15986 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15987 cx.spawn(async move |cx| {
15988 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15989 for (run_range, mut runnable) in runnable_ranges {
15990 let Some(tasks) = cx
15991 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15992 .ok()
15993 else {
15994 continue;
15995 };
15996 let mut tasks = tasks.await;
15997
15998 if prefer_lsp {
15999 tasks.retain(|(task_kind, _)| {
16000 !matches!(task_kind, TaskSourceKind::Language { .. })
16001 });
16002 }
16003 if tasks.is_empty() {
16004 continue;
16005 }
16006
16007 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16008 let Some(row) = snapshot
16009 .buffer_snapshot()
16010 .buffer_line_for_row(MultiBufferRow(point.row))
16011 .map(|(_, range)| range.start.row)
16012 else {
16013 continue;
16014 };
16015
16016 let context_range =
16017 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16018 runnable_rows.push((
16019 (runnable.buffer_id, row),
16020 RunnableTasks {
16021 templates: tasks,
16022 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16023 context_range,
16024 column: point.column,
16025 extra_variables: runnable.extra_captures,
16026 },
16027 ));
16028 }
16029 runnable_rows
16030 })
16031 }
16032
16033 fn templates_with_tags(
16034 project: &Entity<Project>,
16035 runnable: &mut Runnable,
16036 cx: &mut App,
16037 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16038 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16039 let (worktree_id, file) = project
16040 .buffer_for_id(runnable.buffer, cx)
16041 .and_then(|buffer| buffer.read(cx).file())
16042 .map(|file| (file.worktree_id(cx), file.clone()))
16043 .unzip();
16044
16045 (
16046 project.task_store().read(cx).task_inventory().cloned(),
16047 worktree_id,
16048 file,
16049 )
16050 });
16051
16052 let tags = mem::take(&mut runnable.tags);
16053 let language = runnable.language.clone();
16054 cx.spawn(async move |cx| {
16055 let mut templates_with_tags = Vec::new();
16056 if let Some(inventory) = inventory {
16057 for RunnableTag(tag) in tags {
16058 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16059 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16060 }) else {
16061 return templates_with_tags;
16062 };
16063 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16064 move |(_, template)| {
16065 template.tags.iter().any(|source_tag| source_tag == &tag)
16066 },
16067 ));
16068 }
16069 }
16070 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16071
16072 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16073 // Strongest source wins; if we have worktree tag binding, prefer that to
16074 // global and language bindings;
16075 // if we have a global binding, prefer that to language binding.
16076 let first_mismatch = templates_with_tags
16077 .iter()
16078 .position(|(tag_source, _)| tag_source != leading_tag_source);
16079 if let Some(index) = first_mismatch {
16080 templates_with_tags.truncate(index);
16081 }
16082 }
16083
16084 templates_with_tags
16085 })
16086 }
16087
16088 pub fn move_to_enclosing_bracket(
16089 &mut self,
16090 _: &MoveToEnclosingBracket,
16091 window: &mut Window,
16092 cx: &mut Context<Self>,
16093 ) {
16094 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16095 self.change_selections(Default::default(), window, cx, |s| {
16096 s.move_offsets_with(|snapshot, selection| {
16097 let Some(enclosing_bracket_ranges) =
16098 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16099 else {
16100 return;
16101 };
16102
16103 let mut best_length = usize::MAX;
16104 let mut best_inside = false;
16105 let mut best_in_bracket_range = false;
16106 let mut best_destination = None;
16107 for (open, close) in enclosing_bracket_ranges {
16108 let close = close.to_inclusive();
16109 let length = *close.end() - open.start;
16110 let inside = selection.start >= open.end && selection.end <= *close.start();
16111 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16112 || close.contains(&selection.head());
16113
16114 // If best is next to a bracket and current isn't, skip
16115 if !in_bracket_range && best_in_bracket_range {
16116 continue;
16117 }
16118
16119 // Prefer smaller lengths unless best is inside and current isn't
16120 if length > best_length && (best_inside || !inside) {
16121 continue;
16122 }
16123
16124 best_length = length;
16125 best_inside = inside;
16126 best_in_bracket_range = in_bracket_range;
16127 best_destination = Some(
16128 if close.contains(&selection.start) && close.contains(&selection.end) {
16129 if inside { open.end } else { open.start }
16130 } else if inside {
16131 *close.start()
16132 } else {
16133 *close.end()
16134 },
16135 );
16136 }
16137
16138 if let Some(destination) = best_destination {
16139 selection.collapse_to(destination, SelectionGoal::None);
16140 }
16141 })
16142 });
16143 }
16144
16145 pub fn undo_selection(
16146 &mut self,
16147 _: &UndoSelection,
16148 window: &mut Window,
16149 cx: &mut Context<Self>,
16150 ) {
16151 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16152 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16153 self.selection_history.mode = SelectionHistoryMode::Undoing;
16154 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16155 this.end_selection(window, cx);
16156 this.change_selections(
16157 SelectionEffects::scroll(Autoscroll::newest()),
16158 window,
16159 cx,
16160 |s| s.select_anchors(entry.selections.to_vec()),
16161 );
16162 });
16163 self.selection_history.mode = SelectionHistoryMode::Normal;
16164
16165 self.select_next_state = entry.select_next_state;
16166 self.select_prev_state = entry.select_prev_state;
16167 self.add_selections_state = entry.add_selections_state;
16168 }
16169 }
16170
16171 pub fn redo_selection(
16172 &mut self,
16173 _: &RedoSelection,
16174 window: &mut Window,
16175 cx: &mut Context<Self>,
16176 ) {
16177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16178 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16179 self.selection_history.mode = SelectionHistoryMode::Redoing;
16180 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16181 this.end_selection(window, cx);
16182 this.change_selections(
16183 SelectionEffects::scroll(Autoscroll::newest()),
16184 window,
16185 cx,
16186 |s| s.select_anchors(entry.selections.to_vec()),
16187 );
16188 });
16189 self.selection_history.mode = SelectionHistoryMode::Normal;
16190
16191 self.select_next_state = entry.select_next_state;
16192 self.select_prev_state = entry.select_prev_state;
16193 self.add_selections_state = entry.add_selections_state;
16194 }
16195 }
16196
16197 pub fn expand_excerpts(
16198 &mut self,
16199 action: &ExpandExcerpts,
16200 _: &mut Window,
16201 cx: &mut Context<Self>,
16202 ) {
16203 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16204 }
16205
16206 pub fn expand_excerpts_down(
16207 &mut self,
16208 action: &ExpandExcerptsDown,
16209 _: &mut Window,
16210 cx: &mut Context<Self>,
16211 ) {
16212 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16213 }
16214
16215 pub fn expand_excerpts_up(
16216 &mut self,
16217 action: &ExpandExcerptsUp,
16218 _: &mut Window,
16219 cx: &mut Context<Self>,
16220 ) {
16221 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16222 }
16223
16224 pub fn expand_excerpts_for_direction(
16225 &mut self,
16226 lines: u32,
16227 direction: ExpandExcerptDirection,
16228
16229 cx: &mut Context<Self>,
16230 ) {
16231 let selections = self.selections.disjoint_anchors_arc();
16232
16233 let lines = if lines == 0 {
16234 EditorSettings::get_global(cx).expand_excerpt_lines
16235 } else {
16236 lines
16237 };
16238
16239 self.buffer.update(cx, |buffer, cx| {
16240 let snapshot = buffer.snapshot(cx);
16241 let mut excerpt_ids = selections
16242 .iter()
16243 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16244 .collect::<Vec<_>>();
16245 excerpt_ids.sort();
16246 excerpt_ids.dedup();
16247 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16248 })
16249 }
16250
16251 pub fn expand_excerpt(
16252 &mut self,
16253 excerpt: ExcerptId,
16254 direction: ExpandExcerptDirection,
16255 window: &mut Window,
16256 cx: &mut Context<Self>,
16257 ) {
16258 let current_scroll_position = self.scroll_position(cx);
16259 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16260 let mut scroll = None;
16261
16262 if direction == ExpandExcerptDirection::Down {
16263 let multi_buffer = self.buffer.read(cx);
16264 let snapshot = multi_buffer.snapshot(cx);
16265 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16266 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16267 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16268 {
16269 let buffer_snapshot = buffer.read(cx).snapshot();
16270 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16271 let last_row = buffer_snapshot.max_point().row;
16272 let lines_below = last_row.saturating_sub(excerpt_end_row);
16273 if lines_below >= lines_to_expand {
16274 scroll = Some(
16275 current_scroll_position
16276 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16277 );
16278 }
16279 }
16280 }
16281 if direction == ExpandExcerptDirection::Up
16282 && self
16283 .buffer
16284 .read(cx)
16285 .snapshot(cx)
16286 .excerpt_before(excerpt)
16287 .is_none()
16288 {
16289 scroll = Some(current_scroll_position);
16290 }
16291
16292 self.buffer.update(cx, |buffer, cx| {
16293 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16294 });
16295
16296 if let Some(new_scroll_position) = scroll {
16297 self.set_scroll_position(new_scroll_position, window, cx);
16298 }
16299 }
16300
16301 pub fn go_to_singleton_buffer_point(
16302 &mut self,
16303 point: Point,
16304 window: &mut Window,
16305 cx: &mut Context<Self>,
16306 ) {
16307 self.go_to_singleton_buffer_range(point..point, window, cx);
16308 }
16309
16310 pub fn go_to_singleton_buffer_range(
16311 &mut self,
16312 range: Range<Point>,
16313 window: &mut Window,
16314 cx: &mut Context<Self>,
16315 ) {
16316 let multibuffer = self.buffer().read(cx);
16317 let Some(buffer) = multibuffer.as_singleton() else {
16318 return;
16319 };
16320 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16321 return;
16322 };
16323 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16324 return;
16325 };
16326 self.change_selections(
16327 SelectionEffects::default().nav_history(true),
16328 window,
16329 cx,
16330 |s| s.select_anchor_ranges([start..end]),
16331 );
16332 }
16333
16334 pub fn go_to_diagnostic(
16335 &mut self,
16336 action: &GoToDiagnostic,
16337 window: &mut Window,
16338 cx: &mut Context<Self>,
16339 ) {
16340 if !self.diagnostics_enabled() {
16341 return;
16342 }
16343 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16344 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16345 }
16346
16347 pub fn go_to_prev_diagnostic(
16348 &mut self,
16349 action: &GoToPreviousDiagnostic,
16350 window: &mut Window,
16351 cx: &mut Context<Self>,
16352 ) {
16353 if !self.diagnostics_enabled() {
16354 return;
16355 }
16356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16357 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16358 }
16359
16360 pub fn go_to_diagnostic_impl(
16361 &mut self,
16362 direction: Direction,
16363 severity: GoToDiagnosticSeverityFilter,
16364 window: &mut Window,
16365 cx: &mut Context<Self>,
16366 ) {
16367 let buffer = self.buffer.read(cx).snapshot(cx);
16368 let selection = self
16369 .selections
16370 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16371
16372 let mut active_group_id = None;
16373 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16374 && active_group.active_range.start.to_offset(&buffer) == selection.start
16375 {
16376 active_group_id = Some(active_group.group_id);
16377 }
16378
16379 fn filtered<'a>(
16380 severity: GoToDiagnosticSeverityFilter,
16381 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16382 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16383 diagnostics
16384 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16385 .filter(|entry| entry.range.start != entry.range.end)
16386 .filter(|entry| !entry.diagnostic.is_unnecessary)
16387 }
16388
16389 let before = filtered(
16390 severity,
16391 buffer
16392 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16393 .filter(|entry| entry.range.start <= selection.start),
16394 );
16395 let after = filtered(
16396 severity,
16397 buffer
16398 .diagnostics_in_range(selection.start..buffer.len())
16399 .filter(|entry| entry.range.start >= selection.start),
16400 );
16401
16402 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16403 if direction == Direction::Prev {
16404 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16405 {
16406 for diagnostic in prev_diagnostics.into_iter().rev() {
16407 if diagnostic.range.start != selection.start
16408 || active_group_id
16409 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16410 {
16411 found = Some(diagnostic);
16412 break 'outer;
16413 }
16414 }
16415 }
16416 } else {
16417 for diagnostic in after.chain(before) {
16418 if diagnostic.range.start != selection.start
16419 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16420 {
16421 found = Some(diagnostic);
16422 break;
16423 }
16424 }
16425 }
16426 let Some(next_diagnostic) = found else {
16427 return;
16428 };
16429
16430 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16431 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16432 return;
16433 };
16434 let snapshot = self.snapshot(window, cx);
16435 if snapshot.intersects_fold(next_diagnostic.range.start) {
16436 self.unfold_ranges(
16437 std::slice::from_ref(&next_diagnostic.range),
16438 true,
16439 false,
16440 cx,
16441 );
16442 }
16443 self.change_selections(Default::default(), window, cx, |s| {
16444 s.select_ranges(vec![
16445 next_diagnostic.range.start..next_diagnostic.range.start,
16446 ])
16447 });
16448 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16449 self.refresh_edit_prediction(false, true, window, cx);
16450 }
16451
16452 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16453 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16454 let snapshot = self.snapshot(window, cx);
16455 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16456 self.go_to_hunk_before_or_after_position(
16457 &snapshot,
16458 selection.head(),
16459 Direction::Next,
16460 window,
16461 cx,
16462 );
16463 }
16464
16465 pub fn go_to_hunk_before_or_after_position(
16466 &mut self,
16467 snapshot: &EditorSnapshot,
16468 position: Point,
16469 direction: Direction,
16470 window: &mut Window,
16471 cx: &mut Context<Editor>,
16472 ) {
16473 let row = if direction == Direction::Next {
16474 self.hunk_after_position(snapshot, position)
16475 .map(|hunk| hunk.row_range.start)
16476 } else {
16477 self.hunk_before_position(snapshot, position)
16478 };
16479
16480 if let Some(row) = row {
16481 let destination = Point::new(row.0, 0);
16482 let autoscroll = Autoscroll::center();
16483
16484 self.unfold_ranges(&[destination..destination], false, false, cx);
16485 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16486 s.select_ranges([destination..destination]);
16487 });
16488 }
16489 }
16490
16491 fn hunk_after_position(
16492 &mut self,
16493 snapshot: &EditorSnapshot,
16494 position: Point,
16495 ) -> Option<MultiBufferDiffHunk> {
16496 snapshot
16497 .buffer_snapshot()
16498 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16499 .find(|hunk| hunk.row_range.start.0 > position.row)
16500 .or_else(|| {
16501 snapshot
16502 .buffer_snapshot()
16503 .diff_hunks_in_range(Point::zero()..position)
16504 .find(|hunk| hunk.row_range.end.0 < position.row)
16505 })
16506 }
16507
16508 fn go_to_prev_hunk(
16509 &mut self,
16510 _: &GoToPreviousHunk,
16511 window: &mut Window,
16512 cx: &mut Context<Self>,
16513 ) {
16514 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16515 let snapshot = self.snapshot(window, cx);
16516 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16517 self.go_to_hunk_before_or_after_position(
16518 &snapshot,
16519 selection.head(),
16520 Direction::Prev,
16521 window,
16522 cx,
16523 );
16524 }
16525
16526 fn hunk_before_position(
16527 &mut self,
16528 snapshot: &EditorSnapshot,
16529 position: Point,
16530 ) -> Option<MultiBufferRow> {
16531 snapshot
16532 .buffer_snapshot()
16533 .diff_hunk_before(position)
16534 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16535 }
16536
16537 fn go_to_next_change(
16538 &mut self,
16539 _: &GoToNextChange,
16540 window: &mut Window,
16541 cx: &mut Context<Self>,
16542 ) {
16543 if let Some(selections) = self
16544 .change_list
16545 .next_change(1, Direction::Next)
16546 .map(|s| s.to_vec())
16547 {
16548 self.change_selections(Default::default(), window, cx, |s| {
16549 let map = s.display_snapshot();
16550 s.select_display_ranges(selections.iter().map(|a| {
16551 let point = a.to_display_point(&map);
16552 point..point
16553 }))
16554 })
16555 }
16556 }
16557
16558 fn go_to_previous_change(
16559 &mut self,
16560 _: &GoToPreviousChange,
16561 window: &mut Window,
16562 cx: &mut Context<Self>,
16563 ) {
16564 if let Some(selections) = self
16565 .change_list
16566 .next_change(1, Direction::Prev)
16567 .map(|s| s.to_vec())
16568 {
16569 self.change_selections(Default::default(), window, cx, |s| {
16570 let map = s.display_snapshot();
16571 s.select_display_ranges(selections.iter().map(|a| {
16572 let point = a.to_display_point(&map);
16573 point..point
16574 }))
16575 })
16576 }
16577 }
16578
16579 pub fn go_to_next_document_highlight(
16580 &mut self,
16581 _: &GoToNextDocumentHighlight,
16582 window: &mut Window,
16583 cx: &mut Context<Self>,
16584 ) {
16585 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16586 }
16587
16588 pub fn go_to_prev_document_highlight(
16589 &mut self,
16590 _: &GoToPreviousDocumentHighlight,
16591 window: &mut Window,
16592 cx: &mut Context<Self>,
16593 ) {
16594 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16595 }
16596
16597 pub fn go_to_document_highlight_before_or_after_position(
16598 &mut self,
16599 direction: Direction,
16600 window: &mut Window,
16601 cx: &mut Context<Editor>,
16602 ) {
16603 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16604 let snapshot = self.snapshot(window, cx);
16605 let buffer = &snapshot.buffer_snapshot();
16606 let position = self
16607 .selections
16608 .newest::<Point>(&snapshot.display_snapshot)
16609 .head();
16610 let anchor_position = buffer.anchor_after(position);
16611
16612 // Get all document highlights (both read and write)
16613 let mut all_highlights = Vec::new();
16614
16615 if let Some((_, read_highlights)) = self
16616 .background_highlights
16617 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16618 {
16619 all_highlights.extend(read_highlights.iter());
16620 }
16621
16622 if let Some((_, write_highlights)) = self
16623 .background_highlights
16624 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16625 {
16626 all_highlights.extend(write_highlights.iter());
16627 }
16628
16629 if all_highlights.is_empty() {
16630 return;
16631 }
16632
16633 // Sort highlights by position
16634 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16635
16636 let target_highlight = match direction {
16637 Direction::Next => {
16638 // Find the first highlight after the current position
16639 all_highlights
16640 .iter()
16641 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16642 }
16643 Direction::Prev => {
16644 // Find the last highlight before the current position
16645 all_highlights
16646 .iter()
16647 .rev()
16648 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16649 }
16650 };
16651
16652 if let Some(highlight) = target_highlight {
16653 let destination = highlight.start.to_point(buffer);
16654 let autoscroll = Autoscroll::center();
16655
16656 self.unfold_ranges(&[destination..destination], false, false, cx);
16657 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16658 s.select_ranges([destination..destination]);
16659 });
16660 }
16661 }
16662
16663 fn go_to_line<T: 'static>(
16664 &mut self,
16665 position: Anchor,
16666 highlight_color: Option<Hsla>,
16667 window: &mut Window,
16668 cx: &mut Context<Self>,
16669 ) {
16670 let snapshot = self.snapshot(window, cx).display_snapshot;
16671 let position = position.to_point(&snapshot.buffer_snapshot());
16672 let start = snapshot
16673 .buffer_snapshot()
16674 .clip_point(Point::new(position.row, 0), Bias::Left);
16675 let end = start + Point::new(1, 0);
16676 let start = snapshot.buffer_snapshot().anchor_before(start);
16677 let end = snapshot.buffer_snapshot().anchor_before(end);
16678
16679 self.highlight_rows::<T>(
16680 start..end,
16681 highlight_color
16682 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16683 Default::default(),
16684 cx,
16685 );
16686
16687 if self.buffer.read(cx).is_singleton() {
16688 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16689 }
16690 }
16691
16692 pub fn go_to_definition(
16693 &mut self,
16694 _: &GoToDefinition,
16695 window: &mut Window,
16696 cx: &mut Context<Self>,
16697 ) -> Task<Result<Navigated>> {
16698 let definition =
16699 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16700 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16701 cx.spawn_in(window, async move |editor, cx| {
16702 if definition.await? == Navigated::Yes {
16703 return Ok(Navigated::Yes);
16704 }
16705 match fallback_strategy {
16706 GoToDefinitionFallback::None => Ok(Navigated::No),
16707 GoToDefinitionFallback::FindAllReferences => {
16708 match editor.update_in(cx, |editor, window, cx| {
16709 editor.find_all_references(&FindAllReferences, window, cx)
16710 })? {
16711 Some(references) => references.await,
16712 None => Ok(Navigated::No),
16713 }
16714 }
16715 }
16716 })
16717 }
16718
16719 pub fn go_to_declaration(
16720 &mut self,
16721 _: &GoToDeclaration,
16722 window: &mut Window,
16723 cx: &mut Context<Self>,
16724 ) -> Task<Result<Navigated>> {
16725 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16726 }
16727
16728 pub fn go_to_declaration_split(
16729 &mut self,
16730 _: &GoToDeclaration,
16731 window: &mut Window,
16732 cx: &mut Context<Self>,
16733 ) -> Task<Result<Navigated>> {
16734 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16735 }
16736
16737 pub fn go_to_implementation(
16738 &mut self,
16739 _: &GoToImplementation,
16740 window: &mut Window,
16741 cx: &mut Context<Self>,
16742 ) -> Task<Result<Navigated>> {
16743 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16744 }
16745
16746 pub fn go_to_implementation_split(
16747 &mut self,
16748 _: &GoToImplementationSplit,
16749 window: &mut Window,
16750 cx: &mut Context<Self>,
16751 ) -> Task<Result<Navigated>> {
16752 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16753 }
16754
16755 pub fn go_to_type_definition(
16756 &mut self,
16757 _: &GoToTypeDefinition,
16758 window: &mut Window,
16759 cx: &mut Context<Self>,
16760 ) -> Task<Result<Navigated>> {
16761 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16762 }
16763
16764 pub fn go_to_definition_split(
16765 &mut self,
16766 _: &GoToDefinitionSplit,
16767 window: &mut Window,
16768 cx: &mut Context<Self>,
16769 ) -> Task<Result<Navigated>> {
16770 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16771 }
16772
16773 pub fn go_to_type_definition_split(
16774 &mut self,
16775 _: &GoToTypeDefinitionSplit,
16776 window: &mut Window,
16777 cx: &mut Context<Self>,
16778 ) -> Task<Result<Navigated>> {
16779 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16780 }
16781
16782 fn go_to_definition_of_kind(
16783 &mut self,
16784 kind: GotoDefinitionKind,
16785 split: bool,
16786 window: &mut Window,
16787 cx: &mut Context<Self>,
16788 ) -> Task<Result<Navigated>> {
16789 let Some(provider) = self.semantics_provider.clone() else {
16790 return Task::ready(Ok(Navigated::No));
16791 };
16792 let head = self
16793 .selections
16794 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16795 .head();
16796 let buffer = self.buffer.read(cx);
16797 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16798 return Task::ready(Ok(Navigated::No));
16799 };
16800 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16801 return Task::ready(Ok(Navigated::No));
16802 };
16803
16804 cx.spawn_in(window, async move |editor, cx| {
16805 let Some(definitions) = definitions.await? else {
16806 return Ok(Navigated::No);
16807 };
16808 let navigated = editor
16809 .update_in(cx, |editor, window, cx| {
16810 editor.navigate_to_hover_links(
16811 Some(kind),
16812 definitions
16813 .into_iter()
16814 .filter(|location| {
16815 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16816 })
16817 .map(HoverLink::Text)
16818 .collect::<Vec<_>>(),
16819 split,
16820 window,
16821 cx,
16822 )
16823 })?
16824 .await?;
16825 anyhow::Ok(navigated)
16826 })
16827 }
16828
16829 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16830 let selection = self.selections.newest_anchor();
16831 let head = selection.head();
16832 let tail = selection.tail();
16833
16834 let Some((buffer, start_position)) =
16835 self.buffer.read(cx).text_anchor_for_position(head, cx)
16836 else {
16837 return;
16838 };
16839
16840 let end_position = if head != tail {
16841 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16842 return;
16843 };
16844 Some(pos)
16845 } else {
16846 None
16847 };
16848
16849 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16850 let url = if let Some(end_pos) = end_position {
16851 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16852 } else {
16853 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16854 };
16855
16856 if let Some(url) = url {
16857 cx.update(|window, cx| {
16858 if parse_zed_link(&url, cx).is_some() {
16859 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16860 } else {
16861 cx.open_url(&url);
16862 }
16863 })?;
16864 }
16865
16866 anyhow::Ok(())
16867 });
16868
16869 url_finder.detach();
16870 }
16871
16872 pub fn open_selected_filename(
16873 &mut self,
16874 _: &OpenSelectedFilename,
16875 window: &mut Window,
16876 cx: &mut Context<Self>,
16877 ) {
16878 let Some(workspace) = self.workspace() else {
16879 return;
16880 };
16881
16882 let position = self.selections.newest_anchor().head();
16883
16884 let Some((buffer, buffer_position)) =
16885 self.buffer.read(cx).text_anchor_for_position(position, cx)
16886 else {
16887 return;
16888 };
16889
16890 let project = self.project.clone();
16891
16892 cx.spawn_in(window, async move |_, cx| {
16893 let result = find_file(&buffer, project, buffer_position, cx).await;
16894
16895 if let Some((_, path)) = result {
16896 workspace
16897 .update_in(cx, |workspace, window, cx| {
16898 workspace.open_resolved_path(path, window, cx)
16899 })?
16900 .await?;
16901 }
16902 anyhow::Ok(())
16903 })
16904 .detach();
16905 }
16906
16907 pub(crate) fn navigate_to_hover_links(
16908 &mut self,
16909 kind: Option<GotoDefinitionKind>,
16910 definitions: Vec<HoverLink>,
16911 split: bool,
16912 window: &mut Window,
16913 cx: &mut Context<Editor>,
16914 ) -> Task<Result<Navigated>> {
16915 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16916 let mut first_url_or_file = None;
16917 let definitions: Vec<_> = definitions
16918 .into_iter()
16919 .filter_map(|def| match def {
16920 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16921 HoverLink::InlayHint(lsp_location, server_id) => {
16922 let computation =
16923 self.compute_target_location(lsp_location, server_id, window, cx);
16924 Some(cx.background_spawn(computation))
16925 }
16926 HoverLink::Url(url) => {
16927 first_url_or_file = Some(Either::Left(url));
16928 None
16929 }
16930 HoverLink::File(path) => {
16931 first_url_or_file = Some(Either::Right(path));
16932 None
16933 }
16934 })
16935 .collect();
16936
16937 let workspace = self.workspace();
16938
16939 cx.spawn_in(window, async move |editor, cx| {
16940 let locations: Vec<Location> = future::join_all(definitions)
16941 .await
16942 .into_iter()
16943 .filter_map(|location| location.transpose())
16944 .collect::<Result<_>>()
16945 .context("location tasks")?;
16946 let mut locations = cx.update(|_, cx| {
16947 locations
16948 .into_iter()
16949 .map(|location| {
16950 let buffer = location.buffer.read(cx);
16951 (location.buffer, location.range.to_point(buffer))
16952 })
16953 .into_group_map()
16954 })?;
16955 let mut num_locations = 0;
16956 for ranges in locations.values_mut() {
16957 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16958 ranges.dedup();
16959 num_locations += ranges.len();
16960 }
16961
16962 if num_locations > 1 {
16963 let Some(workspace) = workspace else {
16964 return Ok(Navigated::No);
16965 };
16966
16967 let tab_kind = match kind {
16968 Some(GotoDefinitionKind::Implementation) => "Implementations",
16969 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16970 Some(GotoDefinitionKind::Declaration) => "Declarations",
16971 Some(GotoDefinitionKind::Type) => "Types",
16972 };
16973 let title = editor
16974 .update_in(cx, |_, _, cx| {
16975 let target = locations
16976 .iter()
16977 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16978 .map(|(buffer, location)| {
16979 buffer
16980 .read(cx)
16981 .text_for_range(location.clone())
16982 .collect::<String>()
16983 })
16984 .filter(|text| !text.contains('\n'))
16985 .unique()
16986 .take(3)
16987 .join(", ");
16988 if target.is_empty() {
16989 tab_kind.to_owned()
16990 } else {
16991 format!("{tab_kind} for {target}")
16992 }
16993 })
16994 .context("buffer title")?;
16995
16996 let opened = workspace
16997 .update_in(cx, |workspace, window, cx| {
16998 Self::open_locations_in_multibuffer(
16999 workspace,
17000 locations,
17001 title,
17002 split,
17003 MultibufferSelectionMode::First,
17004 window,
17005 cx,
17006 )
17007 })
17008 .is_ok();
17009
17010 anyhow::Ok(Navigated::from_bool(opened))
17011 } else if num_locations == 0 {
17012 // If there is one url or file, open it directly
17013 match first_url_or_file {
17014 Some(Either::Left(url)) => {
17015 cx.update(|_, cx| cx.open_url(&url))?;
17016 Ok(Navigated::Yes)
17017 }
17018 Some(Either::Right(path)) => {
17019 let Some(workspace) = workspace else {
17020 return Ok(Navigated::No);
17021 };
17022
17023 workspace
17024 .update_in(cx, |workspace, window, cx| {
17025 workspace.open_resolved_path(path, window, cx)
17026 })?
17027 .await?;
17028 Ok(Navigated::Yes)
17029 }
17030 None => Ok(Navigated::No),
17031 }
17032 } else {
17033 let Some(workspace) = workspace else {
17034 return Ok(Navigated::No);
17035 };
17036
17037 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17038 let target_range = target_ranges.first().unwrap().clone();
17039
17040 editor.update_in(cx, |editor, window, cx| {
17041 let range = target_range.to_point(target_buffer.read(cx));
17042 let range = editor.range_for_match(&range);
17043 let range = collapse_multiline_range(range);
17044
17045 if !split
17046 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17047 {
17048 editor.go_to_singleton_buffer_range(range, window, cx);
17049 } else {
17050 let pane = workspace.read(cx).active_pane().clone();
17051 window.defer(cx, move |window, cx| {
17052 let target_editor: Entity<Self> =
17053 workspace.update(cx, |workspace, cx| {
17054 let pane = if split {
17055 workspace.adjacent_pane(window, cx)
17056 } else {
17057 workspace.active_pane().clone()
17058 };
17059
17060 workspace.open_project_item(
17061 pane,
17062 target_buffer.clone(),
17063 true,
17064 true,
17065 window,
17066 cx,
17067 )
17068 });
17069 target_editor.update(cx, |target_editor, cx| {
17070 // When selecting a definition in a different buffer, disable the nav history
17071 // to avoid creating a history entry at the previous cursor location.
17072 pane.update(cx, |pane, _| pane.disable_history());
17073 target_editor.go_to_singleton_buffer_range(range, window, cx);
17074 pane.update(cx, |pane, _| pane.enable_history());
17075 });
17076 });
17077 }
17078 Navigated::Yes
17079 })
17080 }
17081 })
17082 }
17083
17084 fn compute_target_location(
17085 &self,
17086 lsp_location: lsp::Location,
17087 server_id: LanguageServerId,
17088 window: &mut Window,
17089 cx: &mut Context<Self>,
17090 ) -> Task<anyhow::Result<Option<Location>>> {
17091 let Some(project) = self.project.clone() else {
17092 return Task::ready(Ok(None));
17093 };
17094
17095 cx.spawn_in(window, async move |editor, cx| {
17096 let location_task = editor.update(cx, |_, cx| {
17097 project.update(cx, |project, cx| {
17098 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17099 })
17100 })?;
17101 let location = Some({
17102 let target_buffer_handle = location_task.await.context("open local buffer")?;
17103 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17104 let target_start = target_buffer
17105 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17106 let target_end = target_buffer
17107 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17108 target_buffer.anchor_after(target_start)
17109 ..target_buffer.anchor_before(target_end)
17110 })?;
17111 Location {
17112 buffer: target_buffer_handle,
17113 range,
17114 }
17115 });
17116 Ok(location)
17117 })
17118 }
17119
17120 fn go_to_next_reference(
17121 &mut self,
17122 _: &GoToNextReference,
17123 window: &mut Window,
17124 cx: &mut Context<Self>,
17125 ) {
17126 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17127 if let Some(task) = task {
17128 task.detach();
17129 };
17130 }
17131
17132 fn go_to_prev_reference(
17133 &mut self,
17134 _: &GoToPreviousReference,
17135 window: &mut Window,
17136 cx: &mut Context<Self>,
17137 ) {
17138 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17139 if let Some(task) = task {
17140 task.detach();
17141 };
17142 }
17143
17144 pub fn go_to_reference_before_or_after_position(
17145 &mut self,
17146 direction: Direction,
17147 count: usize,
17148 window: &mut Window,
17149 cx: &mut Context<Self>,
17150 ) -> Option<Task<Result<()>>> {
17151 let selection = self.selections.newest_anchor();
17152 let head = selection.head();
17153
17154 let multi_buffer = self.buffer.read(cx);
17155
17156 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17157 let workspace = self.workspace()?;
17158 let project = workspace.read(cx).project().clone();
17159 let references =
17160 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17161 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17162 let Some(locations) = references.await? else {
17163 return Ok(());
17164 };
17165
17166 if locations.is_empty() {
17167 // totally normal - the cursor may be on something which is not
17168 // a symbol (e.g. a keyword)
17169 log::info!("no references found under cursor");
17170 return Ok(());
17171 }
17172
17173 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17174
17175 let (locations, current_location_index) =
17176 multi_buffer.update(cx, |multi_buffer, cx| {
17177 let mut locations = locations
17178 .into_iter()
17179 .filter_map(|loc| {
17180 let start = multi_buffer.buffer_anchor_to_anchor(
17181 &loc.buffer,
17182 loc.range.start,
17183 cx,
17184 )?;
17185 let end = multi_buffer.buffer_anchor_to_anchor(
17186 &loc.buffer,
17187 loc.range.end,
17188 cx,
17189 )?;
17190 Some(start..end)
17191 })
17192 .collect::<Vec<_>>();
17193
17194 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17195 // There is an O(n) implementation, but given this list will be
17196 // small (usually <100 items), the extra O(log(n)) factor isn't
17197 // worth the (surprisingly large amount of) extra complexity.
17198 locations
17199 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17200
17201 let head_offset = head.to_offset(&multi_buffer_snapshot);
17202
17203 let current_location_index = locations.iter().position(|loc| {
17204 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17205 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17206 });
17207
17208 (locations, current_location_index)
17209 })?;
17210
17211 let Some(current_location_index) = current_location_index else {
17212 // This indicates something has gone wrong, because we already
17213 // handle the "no references" case above
17214 log::error!(
17215 "failed to find current reference under cursor. Total references: {}",
17216 locations.len()
17217 );
17218 return Ok(());
17219 };
17220
17221 let destination_location_index = match direction {
17222 Direction::Next => (current_location_index + count) % locations.len(),
17223 Direction::Prev => {
17224 (current_location_index + locations.len() - count % locations.len())
17225 % locations.len()
17226 }
17227 };
17228
17229 // TODO(cameron): is this needed?
17230 // the thinking is to avoid "jumping to the current location" (avoid
17231 // polluting "jumplist" in vim terms)
17232 if current_location_index == destination_location_index {
17233 return Ok(());
17234 }
17235
17236 let Range { start, end } = locations[destination_location_index];
17237
17238 editor.update_in(cx, |editor, window, cx| {
17239 let effects = SelectionEffects::default();
17240
17241 editor.unfold_ranges(&[start..end], false, false, cx);
17242 editor.change_selections(effects, window, cx, |s| {
17243 s.select_ranges([start..start]);
17244 });
17245 })?;
17246
17247 Ok(())
17248 }))
17249 }
17250
17251 pub fn find_all_references(
17252 &mut self,
17253 _: &FindAllReferences,
17254 window: &mut Window,
17255 cx: &mut Context<Self>,
17256 ) -> Option<Task<Result<Navigated>>> {
17257 let selection = self
17258 .selections
17259 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17260 let multi_buffer = self.buffer.read(cx);
17261 let head = selection.head();
17262
17263 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17264 let head_anchor = multi_buffer_snapshot.anchor_at(
17265 head,
17266 if head < selection.tail() {
17267 Bias::Right
17268 } else {
17269 Bias::Left
17270 },
17271 );
17272
17273 match self
17274 .find_all_references_task_sources
17275 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17276 {
17277 Ok(_) => {
17278 log::info!(
17279 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17280 );
17281 return None;
17282 }
17283 Err(i) => {
17284 self.find_all_references_task_sources.insert(i, head_anchor);
17285 }
17286 }
17287
17288 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17289 let workspace = self.workspace()?;
17290 let project = workspace.read(cx).project().clone();
17291 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17292 Some(cx.spawn_in(window, async move |editor, cx| {
17293 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17294 if let Ok(i) = editor
17295 .find_all_references_task_sources
17296 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17297 {
17298 editor.find_all_references_task_sources.remove(i);
17299 }
17300 });
17301
17302 let Some(locations) = references.await? else {
17303 return anyhow::Ok(Navigated::No);
17304 };
17305 let mut locations = cx.update(|_, cx| {
17306 locations
17307 .into_iter()
17308 .map(|location| {
17309 let buffer = location.buffer.read(cx);
17310 (location.buffer, location.range.to_point(buffer))
17311 })
17312 .into_group_map()
17313 })?;
17314 if locations.is_empty() {
17315 return anyhow::Ok(Navigated::No);
17316 }
17317 for ranges in locations.values_mut() {
17318 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17319 ranges.dedup();
17320 }
17321
17322 workspace.update_in(cx, |workspace, window, cx| {
17323 let target = locations
17324 .iter()
17325 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17326 .map(|(buffer, location)| {
17327 buffer
17328 .read(cx)
17329 .text_for_range(location.clone())
17330 .collect::<String>()
17331 })
17332 .filter(|text| !text.contains('\n'))
17333 .unique()
17334 .take(3)
17335 .join(", ");
17336 let title = if target.is_empty() {
17337 "References".to_owned()
17338 } else {
17339 format!("References to {target}")
17340 };
17341 Self::open_locations_in_multibuffer(
17342 workspace,
17343 locations,
17344 title,
17345 false,
17346 MultibufferSelectionMode::First,
17347 window,
17348 cx,
17349 );
17350 Navigated::Yes
17351 })
17352 }))
17353 }
17354
17355 /// Opens a multibuffer with the given project locations in it
17356 pub fn open_locations_in_multibuffer(
17357 workspace: &mut Workspace,
17358 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17359 title: String,
17360 split: bool,
17361 multibuffer_selection_mode: MultibufferSelectionMode,
17362 window: &mut Window,
17363 cx: &mut Context<Workspace>,
17364 ) {
17365 if locations.is_empty() {
17366 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17367 return;
17368 }
17369
17370 let capability = workspace.project().read(cx).capability();
17371 let mut ranges = <Vec<Range<Anchor>>>::new();
17372
17373 // a key to find existing multibuffer editors with the same set of locations
17374 // to prevent us from opening more and more multibuffer tabs for searches and the like
17375 let mut key = (title.clone(), vec![]);
17376 let excerpt_buffer = cx.new(|cx| {
17377 let key = &mut key.1;
17378 let mut multibuffer = MultiBuffer::new(capability);
17379 for (buffer, mut ranges_for_buffer) in locations {
17380 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17381 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17382 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17383 PathKey::for_buffer(&buffer, cx),
17384 buffer.clone(),
17385 ranges_for_buffer,
17386 multibuffer_context_lines(cx),
17387 cx,
17388 );
17389 ranges.extend(new_ranges)
17390 }
17391
17392 multibuffer.with_title(title)
17393 });
17394 let existing = workspace.active_pane().update(cx, |pane, cx| {
17395 pane.items()
17396 .filter_map(|item| item.downcast::<Editor>())
17397 .find(|editor| {
17398 editor
17399 .read(cx)
17400 .lookup_key
17401 .as_ref()
17402 .and_then(|it| {
17403 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17404 })
17405 .is_some_and(|it| *it == key)
17406 })
17407 });
17408 let editor = existing.unwrap_or_else(|| {
17409 cx.new(|cx| {
17410 let mut editor = Editor::for_multibuffer(
17411 excerpt_buffer,
17412 Some(workspace.project().clone()),
17413 window,
17414 cx,
17415 );
17416 editor.lookup_key = Some(Box::new(key));
17417 editor
17418 })
17419 });
17420 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17421 MultibufferSelectionMode::First => {
17422 if let Some(first_range) = ranges.first() {
17423 editor.change_selections(
17424 SelectionEffects::no_scroll(),
17425 window,
17426 cx,
17427 |selections| {
17428 selections.clear_disjoint();
17429 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17430 },
17431 );
17432 }
17433 editor.highlight_background::<Self>(
17434 &ranges,
17435 |theme| theme.colors().editor_highlighted_line_background,
17436 cx,
17437 );
17438 }
17439 MultibufferSelectionMode::All => {
17440 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17441 selections.clear_disjoint();
17442 selections.select_anchor_ranges(ranges);
17443 });
17444 }
17445 });
17446
17447 let item = Box::new(editor);
17448 let item_id = item.item_id();
17449
17450 if split {
17451 let pane = workspace.adjacent_pane(window, cx);
17452 workspace.add_item(pane, item, None, true, true, window, cx);
17453 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17454 let (preview_item_id, preview_item_idx) =
17455 workspace.active_pane().read_with(cx, |pane, _| {
17456 (pane.preview_item_id(), pane.preview_item_idx())
17457 });
17458
17459 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17460
17461 if let Some(preview_item_id) = preview_item_id {
17462 workspace.active_pane().update(cx, |pane, cx| {
17463 pane.remove_item(preview_item_id, false, false, window, cx);
17464 });
17465 }
17466 } else {
17467 workspace.add_item_to_active_pane(item, None, true, window, cx);
17468 }
17469 workspace.active_pane().update(cx, |pane, cx| {
17470 pane.set_preview_item_id(Some(item_id), cx);
17471 });
17472 }
17473
17474 pub fn rename(
17475 &mut self,
17476 _: &Rename,
17477 window: &mut Window,
17478 cx: &mut Context<Self>,
17479 ) -> Option<Task<Result<()>>> {
17480 use language::ToOffset as _;
17481
17482 let provider = self.semantics_provider.clone()?;
17483 let selection = self.selections.newest_anchor().clone();
17484 let (cursor_buffer, cursor_buffer_position) = self
17485 .buffer
17486 .read(cx)
17487 .text_anchor_for_position(selection.head(), cx)?;
17488 let (tail_buffer, cursor_buffer_position_end) = self
17489 .buffer
17490 .read(cx)
17491 .text_anchor_for_position(selection.tail(), cx)?;
17492 if tail_buffer != cursor_buffer {
17493 return None;
17494 }
17495
17496 let snapshot = cursor_buffer.read(cx).snapshot();
17497 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17498 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17499 let prepare_rename = provider
17500 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17501 .unwrap_or_else(|| Task::ready(Ok(None)));
17502 drop(snapshot);
17503
17504 Some(cx.spawn_in(window, async move |this, cx| {
17505 let rename_range = if let Some(range) = prepare_rename.await? {
17506 Some(range)
17507 } else {
17508 this.update(cx, |this, cx| {
17509 let buffer = this.buffer.read(cx).snapshot(cx);
17510 let mut buffer_highlights = this
17511 .document_highlights_for_position(selection.head(), &buffer)
17512 .filter(|highlight| {
17513 highlight.start.excerpt_id == selection.head().excerpt_id
17514 && highlight.end.excerpt_id == selection.head().excerpt_id
17515 });
17516 buffer_highlights
17517 .next()
17518 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17519 })?
17520 };
17521 if let Some(rename_range) = rename_range {
17522 this.update_in(cx, |this, window, cx| {
17523 let snapshot = cursor_buffer.read(cx).snapshot();
17524 let rename_buffer_range = rename_range.to_offset(&snapshot);
17525 let cursor_offset_in_rename_range =
17526 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17527 let cursor_offset_in_rename_range_end =
17528 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17529
17530 this.take_rename(false, window, cx);
17531 let buffer = this.buffer.read(cx).read(cx);
17532 let cursor_offset = selection.head().to_offset(&buffer);
17533 let rename_start =
17534 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17535 let rename_end = rename_start + rename_buffer_range.len();
17536 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17537 let mut old_highlight_id = None;
17538 let old_name: Arc<str> = buffer
17539 .chunks(rename_start..rename_end, true)
17540 .map(|chunk| {
17541 if old_highlight_id.is_none() {
17542 old_highlight_id = chunk.syntax_highlight_id;
17543 }
17544 chunk.text
17545 })
17546 .collect::<String>()
17547 .into();
17548
17549 drop(buffer);
17550
17551 // Position the selection in the rename editor so that it matches the current selection.
17552 this.show_local_selections = false;
17553 let rename_editor = cx.new(|cx| {
17554 let mut editor = Editor::single_line(window, cx);
17555 editor.buffer.update(cx, |buffer, cx| {
17556 buffer.edit(
17557 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17558 None,
17559 cx,
17560 )
17561 });
17562 let cursor_offset_in_rename_range =
17563 MultiBufferOffset(cursor_offset_in_rename_range);
17564 let cursor_offset_in_rename_range_end =
17565 MultiBufferOffset(cursor_offset_in_rename_range_end);
17566 let rename_selection_range = match cursor_offset_in_rename_range
17567 .cmp(&cursor_offset_in_rename_range_end)
17568 {
17569 Ordering::Equal => {
17570 editor.select_all(&SelectAll, window, cx);
17571 return editor;
17572 }
17573 Ordering::Less => {
17574 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17575 }
17576 Ordering::Greater => {
17577 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17578 }
17579 };
17580 if rename_selection_range.end.0 > old_name.len() {
17581 editor.select_all(&SelectAll, window, cx);
17582 } else {
17583 editor.change_selections(Default::default(), window, cx, |s| {
17584 s.select_ranges([rename_selection_range]);
17585 });
17586 }
17587 editor
17588 });
17589 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17590 if e == &EditorEvent::Focused {
17591 cx.emit(EditorEvent::FocusedIn)
17592 }
17593 })
17594 .detach();
17595
17596 let write_highlights =
17597 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17598 let read_highlights =
17599 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17600 let ranges = write_highlights
17601 .iter()
17602 .flat_map(|(_, ranges)| ranges.iter())
17603 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17604 .cloned()
17605 .collect();
17606
17607 this.highlight_text::<Rename>(
17608 ranges,
17609 HighlightStyle {
17610 fade_out: Some(0.6),
17611 ..Default::default()
17612 },
17613 cx,
17614 );
17615 let rename_focus_handle = rename_editor.focus_handle(cx);
17616 window.focus(&rename_focus_handle);
17617 let block_id = this.insert_blocks(
17618 [BlockProperties {
17619 style: BlockStyle::Flex,
17620 placement: BlockPlacement::Below(range.start),
17621 height: Some(1),
17622 render: Arc::new({
17623 let rename_editor = rename_editor.clone();
17624 move |cx: &mut BlockContext| {
17625 let mut text_style = cx.editor_style.text.clone();
17626 if let Some(highlight_style) = old_highlight_id
17627 .and_then(|h| h.style(&cx.editor_style.syntax))
17628 {
17629 text_style = text_style.highlight(highlight_style);
17630 }
17631 div()
17632 .block_mouse_except_scroll()
17633 .pl(cx.anchor_x)
17634 .child(EditorElement::new(
17635 &rename_editor,
17636 EditorStyle {
17637 background: cx.theme().system().transparent,
17638 local_player: cx.editor_style.local_player,
17639 text: text_style,
17640 scrollbar_width: cx.editor_style.scrollbar_width,
17641 syntax: cx.editor_style.syntax.clone(),
17642 status: cx.editor_style.status.clone(),
17643 inlay_hints_style: HighlightStyle {
17644 font_weight: Some(FontWeight::BOLD),
17645 ..make_inlay_hints_style(cx.app)
17646 },
17647 edit_prediction_styles: make_suggestion_styles(
17648 cx.app,
17649 ),
17650 ..EditorStyle::default()
17651 },
17652 ))
17653 .into_any_element()
17654 }
17655 }),
17656 priority: 0,
17657 }],
17658 Some(Autoscroll::fit()),
17659 cx,
17660 )[0];
17661 this.pending_rename = Some(RenameState {
17662 range,
17663 old_name,
17664 editor: rename_editor,
17665 block_id,
17666 });
17667 })?;
17668 }
17669
17670 Ok(())
17671 }))
17672 }
17673
17674 pub fn confirm_rename(
17675 &mut self,
17676 _: &ConfirmRename,
17677 window: &mut Window,
17678 cx: &mut Context<Self>,
17679 ) -> Option<Task<Result<()>>> {
17680 let rename = self.take_rename(false, window, cx)?;
17681 let workspace = self.workspace()?.downgrade();
17682 let (buffer, start) = self
17683 .buffer
17684 .read(cx)
17685 .text_anchor_for_position(rename.range.start, cx)?;
17686 let (end_buffer, _) = self
17687 .buffer
17688 .read(cx)
17689 .text_anchor_for_position(rename.range.end, cx)?;
17690 if buffer != end_buffer {
17691 return None;
17692 }
17693
17694 let old_name = rename.old_name;
17695 let new_name = rename.editor.read(cx).text(cx);
17696
17697 let rename = self.semantics_provider.as_ref()?.perform_rename(
17698 &buffer,
17699 start,
17700 new_name.clone(),
17701 cx,
17702 )?;
17703
17704 Some(cx.spawn_in(window, async move |editor, cx| {
17705 let project_transaction = rename.await?;
17706 Self::open_project_transaction(
17707 &editor,
17708 workspace,
17709 project_transaction,
17710 format!("Rename: {} → {}", old_name, new_name),
17711 cx,
17712 )
17713 .await?;
17714
17715 editor.update(cx, |editor, cx| {
17716 editor.refresh_document_highlights(cx);
17717 })?;
17718 Ok(())
17719 }))
17720 }
17721
17722 fn take_rename(
17723 &mut self,
17724 moving_cursor: bool,
17725 window: &mut Window,
17726 cx: &mut Context<Self>,
17727 ) -> Option<RenameState> {
17728 let rename = self.pending_rename.take()?;
17729 if rename.editor.focus_handle(cx).is_focused(window) {
17730 window.focus(&self.focus_handle);
17731 }
17732
17733 self.remove_blocks(
17734 [rename.block_id].into_iter().collect(),
17735 Some(Autoscroll::fit()),
17736 cx,
17737 );
17738 self.clear_highlights::<Rename>(cx);
17739 self.show_local_selections = true;
17740
17741 if moving_cursor {
17742 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17743 editor
17744 .selections
17745 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
17746 .head()
17747 });
17748
17749 // Update the selection to match the position of the selection inside
17750 // the rename editor.
17751 let snapshot = self.buffer.read(cx).read(cx);
17752 let rename_range = rename.range.to_offset(&snapshot);
17753 let cursor_in_editor = snapshot
17754 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17755 .min(rename_range.end);
17756 drop(snapshot);
17757
17758 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17759 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17760 });
17761 } else {
17762 self.refresh_document_highlights(cx);
17763 }
17764
17765 Some(rename)
17766 }
17767
17768 pub fn pending_rename(&self) -> Option<&RenameState> {
17769 self.pending_rename.as_ref()
17770 }
17771
17772 fn format(
17773 &mut self,
17774 _: &Format,
17775 window: &mut Window,
17776 cx: &mut Context<Self>,
17777 ) -> Option<Task<Result<()>>> {
17778 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17779
17780 let project = match &self.project {
17781 Some(project) => project.clone(),
17782 None => return None,
17783 };
17784
17785 Some(self.perform_format(
17786 project,
17787 FormatTrigger::Manual,
17788 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17789 window,
17790 cx,
17791 ))
17792 }
17793
17794 fn format_selections(
17795 &mut self,
17796 _: &FormatSelections,
17797 window: &mut Window,
17798 cx: &mut Context<Self>,
17799 ) -> Option<Task<Result<()>>> {
17800 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17801
17802 let project = match &self.project {
17803 Some(project) => project.clone(),
17804 None => return None,
17805 };
17806
17807 let ranges = self
17808 .selections
17809 .all_adjusted(&self.display_snapshot(cx))
17810 .into_iter()
17811 .map(|selection| selection.range())
17812 .collect_vec();
17813
17814 Some(self.perform_format(
17815 project,
17816 FormatTrigger::Manual,
17817 FormatTarget::Ranges(ranges),
17818 window,
17819 cx,
17820 ))
17821 }
17822
17823 fn perform_format(
17824 &mut self,
17825 project: Entity<Project>,
17826 trigger: FormatTrigger,
17827 target: FormatTarget,
17828 window: &mut Window,
17829 cx: &mut Context<Self>,
17830 ) -> Task<Result<()>> {
17831 let buffer = self.buffer.clone();
17832 let (buffers, target) = match target {
17833 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17834 FormatTarget::Ranges(selection_ranges) => {
17835 let multi_buffer = buffer.read(cx);
17836 let snapshot = multi_buffer.read(cx);
17837 let mut buffers = HashSet::default();
17838 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17839 BTreeMap::new();
17840 for selection_range in selection_ranges {
17841 for (buffer, buffer_range, _) in
17842 snapshot.range_to_buffer_ranges(selection_range)
17843 {
17844 let buffer_id = buffer.remote_id();
17845 let start = buffer.anchor_before(buffer_range.start);
17846 let end = buffer.anchor_after(buffer_range.end);
17847 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17848 buffer_id_to_ranges
17849 .entry(buffer_id)
17850 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17851 .or_insert_with(|| vec![start..end]);
17852 }
17853 }
17854 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17855 }
17856 };
17857
17858 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17859 let selections_prev = transaction_id_prev
17860 .and_then(|transaction_id_prev| {
17861 // default to selections as they were after the last edit, if we have them,
17862 // instead of how they are now.
17863 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17864 // will take you back to where you made the last edit, instead of staying where you scrolled
17865 self.selection_history
17866 .transaction(transaction_id_prev)
17867 .map(|t| t.0.clone())
17868 })
17869 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17870
17871 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17872 let format = project.update(cx, |project, cx| {
17873 project.format(buffers, target, true, trigger, cx)
17874 });
17875
17876 cx.spawn_in(window, async move |editor, cx| {
17877 let transaction = futures::select_biased! {
17878 transaction = format.log_err().fuse() => transaction,
17879 () = timeout => {
17880 log::warn!("timed out waiting for formatting");
17881 None
17882 }
17883 };
17884
17885 buffer
17886 .update(cx, |buffer, cx| {
17887 if let Some(transaction) = transaction
17888 && !buffer.is_singleton()
17889 {
17890 buffer.push_transaction(&transaction.0, cx);
17891 }
17892 cx.notify();
17893 })
17894 .ok();
17895
17896 if let Some(transaction_id_now) =
17897 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17898 {
17899 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17900 if has_new_transaction {
17901 _ = editor.update(cx, |editor, _| {
17902 editor
17903 .selection_history
17904 .insert_transaction(transaction_id_now, selections_prev);
17905 });
17906 }
17907 }
17908
17909 Ok(())
17910 })
17911 }
17912
17913 fn organize_imports(
17914 &mut self,
17915 _: &OrganizeImports,
17916 window: &mut Window,
17917 cx: &mut Context<Self>,
17918 ) -> Option<Task<Result<()>>> {
17919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17920 let project = match &self.project {
17921 Some(project) => project.clone(),
17922 None => return None,
17923 };
17924 Some(self.perform_code_action_kind(
17925 project,
17926 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17927 window,
17928 cx,
17929 ))
17930 }
17931
17932 fn perform_code_action_kind(
17933 &mut self,
17934 project: Entity<Project>,
17935 kind: CodeActionKind,
17936 window: &mut Window,
17937 cx: &mut Context<Self>,
17938 ) -> Task<Result<()>> {
17939 let buffer = self.buffer.clone();
17940 let buffers = buffer.read(cx).all_buffers();
17941 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17942 let apply_action = project.update(cx, |project, cx| {
17943 project.apply_code_action_kind(buffers, kind, true, cx)
17944 });
17945 cx.spawn_in(window, async move |_, cx| {
17946 let transaction = futures::select_biased! {
17947 () = timeout => {
17948 log::warn!("timed out waiting for executing code action");
17949 None
17950 }
17951 transaction = apply_action.log_err().fuse() => transaction,
17952 };
17953 buffer
17954 .update(cx, |buffer, cx| {
17955 // check if we need this
17956 if let Some(transaction) = transaction
17957 && !buffer.is_singleton()
17958 {
17959 buffer.push_transaction(&transaction.0, cx);
17960 }
17961 cx.notify();
17962 })
17963 .ok();
17964 Ok(())
17965 })
17966 }
17967
17968 pub fn restart_language_server(
17969 &mut self,
17970 _: &RestartLanguageServer,
17971 _: &mut Window,
17972 cx: &mut Context<Self>,
17973 ) {
17974 if let Some(project) = self.project.clone() {
17975 self.buffer.update(cx, |multi_buffer, cx| {
17976 project.update(cx, |project, cx| {
17977 project.restart_language_servers_for_buffers(
17978 multi_buffer.all_buffers().into_iter().collect(),
17979 HashSet::default(),
17980 cx,
17981 );
17982 });
17983 })
17984 }
17985 }
17986
17987 pub fn stop_language_server(
17988 &mut self,
17989 _: &StopLanguageServer,
17990 _: &mut Window,
17991 cx: &mut Context<Self>,
17992 ) {
17993 if let Some(project) = self.project.clone() {
17994 self.buffer.update(cx, |multi_buffer, cx| {
17995 project.update(cx, |project, cx| {
17996 project.stop_language_servers_for_buffers(
17997 multi_buffer.all_buffers().into_iter().collect(),
17998 HashSet::default(),
17999 cx,
18000 );
18001 });
18002 });
18003 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18004 }
18005 }
18006
18007 fn cancel_language_server_work(
18008 workspace: &mut Workspace,
18009 _: &actions::CancelLanguageServerWork,
18010 _: &mut Window,
18011 cx: &mut Context<Workspace>,
18012 ) {
18013 let project = workspace.project();
18014 let buffers = workspace
18015 .active_item(cx)
18016 .and_then(|item| item.act_as::<Editor>(cx))
18017 .map_or(HashSet::default(), |editor| {
18018 editor.read(cx).buffer.read(cx).all_buffers()
18019 });
18020 project.update(cx, |project, cx| {
18021 project.cancel_language_server_work_for_buffers(buffers, cx);
18022 });
18023 }
18024
18025 fn show_character_palette(
18026 &mut self,
18027 _: &ShowCharacterPalette,
18028 window: &mut Window,
18029 _: &mut Context<Self>,
18030 ) {
18031 window.show_character_palette();
18032 }
18033
18034 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18035 if !self.diagnostics_enabled() {
18036 return;
18037 }
18038
18039 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18040 let buffer = self.buffer.read(cx).snapshot(cx);
18041 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18042 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18043 let is_valid = buffer
18044 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18045 .any(|entry| {
18046 entry.diagnostic.is_primary
18047 && !entry.range.is_empty()
18048 && entry.range.start == primary_range_start
18049 && entry.diagnostic.message == active_diagnostics.active_message
18050 });
18051
18052 if !is_valid {
18053 self.dismiss_diagnostics(cx);
18054 }
18055 }
18056 }
18057
18058 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18059 match &self.active_diagnostics {
18060 ActiveDiagnostic::Group(group) => Some(group),
18061 _ => None,
18062 }
18063 }
18064
18065 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18066 if !self.diagnostics_enabled() {
18067 return;
18068 }
18069 self.dismiss_diagnostics(cx);
18070 self.active_diagnostics = ActiveDiagnostic::All;
18071 }
18072
18073 fn activate_diagnostics(
18074 &mut self,
18075 buffer_id: BufferId,
18076 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18077 window: &mut Window,
18078 cx: &mut Context<Self>,
18079 ) {
18080 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18081 return;
18082 }
18083 self.dismiss_diagnostics(cx);
18084 let snapshot = self.snapshot(window, cx);
18085 let buffer = self.buffer.read(cx).snapshot(cx);
18086 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18087 return;
18088 };
18089
18090 let diagnostic_group = buffer
18091 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18092 .collect::<Vec<_>>();
18093
18094 let language_registry = self
18095 .project()
18096 .map(|project| project.read(cx).languages().clone());
18097
18098 let blocks = renderer.render_group(
18099 diagnostic_group,
18100 buffer_id,
18101 snapshot,
18102 cx.weak_entity(),
18103 language_registry,
18104 cx,
18105 );
18106
18107 let blocks = self.display_map.update(cx, |display_map, cx| {
18108 display_map.insert_blocks(blocks, cx).into_iter().collect()
18109 });
18110 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18111 active_range: buffer.anchor_before(diagnostic.range.start)
18112 ..buffer.anchor_after(diagnostic.range.end),
18113 active_message: diagnostic.diagnostic.message.clone(),
18114 group_id: diagnostic.diagnostic.group_id,
18115 blocks,
18116 });
18117 cx.notify();
18118 }
18119
18120 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18121 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18122 return;
18123 };
18124
18125 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18126 if let ActiveDiagnostic::Group(group) = prev {
18127 self.display_map.update(cx, |display_map, cx| {
18128 display_map.remove_blocks(group.blocks, cx);
18129 });
18130 cx.notify();
18131 }
18132 }
18133
18134 /// Disable inline diagnostics rendering for this editor.
18135 pub fn disable_inline_diagnostics(&mut self) {
18136 self.inline_diagnostics_enabled = false;
18137 self.inline_diagnostics_update = Task::ready(());
18138 self.inline_diagnostics.clear();
18139 }
18140
18141 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18142 self.diagnostics_enabled = false;
18143 self.dismiss_diagnostics(cx);
18144 self.inline_diagnostics_update = Task::ready(());
18145 self.inline_diagnostics.clear();
18146 }
18147
18148 pub fn disable_word_completions(&mut self) {
18149 self.word_completions_enabled = false;
18150 }
18151
18152 pub fn diagnostics_enabled(&self) -> bool {
18153 self.diagnostics_enabled && self.mode.is_full()
18154 }
18155
18156 pub fn inline_diagnostics_enabled(&self) -> bool {
18157 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18158 }
18159
18160 pub fn show_inline_diagnostics(&self) -> bool {
18161 self.show_inline_diagnostics
18162 }
18163
18164 pub fn toggle_inline_diagnostics(
18165 &mut self,
18166 _: &ToggleInlineDiagnostics,
18167 window: &mut Window,
18168 cx: &mut Context<Editor>,
18169 ) {
18170 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18171 self.refresh_inline_diagnostics(false, window, cx);
18172 }
18173
18174 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18175 self.diagnostics_max_severity = severity;
18176 self.display_map.update(cx, |display_map, _| {
18177 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18178 });
18179 }
18180
18181 pub fn toggle_diagnostics(
18182 &mut self,
18183 _: &ToggleDiagnostics,
18184 window: &mut Window,
18185 cx: &mut Context<Editor>,
18186 ) {
18187 if !self.diagnostics_enabled() {
18188 return;
18189 }
18190
18191 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18192 EditorSettings::get_global(cx)
18193 .diagnostics_max_severity
18194 .filter(|severity| severity != &DiagnosticSeverity::Off)
18195 .unwrap_or(DiagnosticSeverity::Hint)
18196 } else {
18197 DiagnosticSeverity::Off
18198 };
18199 self.set_max_diagnostics_severity(new_severity, cx);
18200 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18201 self.active_diagnostics = ActiveDiagnostic::None;
18202 self.inline_diagnostics_update = Task::ready(());
18203 self.inline_diagnostics.clear();
18204 } else {
18205 self.refresh_inline_diagnostics(false, window, cx);
18206 }
18207
18208 cx.notify();
18209 }
18210
18211 pub fn toggle_minimap(
18212 &mut self,
18213 _: &ToggleMinimap,
18214 window: &mut Window,
18215 cx: &mut Context<Editor>,
18216 ) {
18217 if self.supports_minimap(cx) {
18218 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18219 }
18220 }
18221
18222 fn refresh_inline_diagnostics(
18223 &mut self,
18224 debounce: bool,
18225 window: &mut Window,
18226 cx: &mut Context<Self>,
18227 ) {
18228 let max_severity = ProjectSettings::get_global(cx)
18229 .diagnostics
18230 .inline
18231 .max_severity
18232 .unwrap_or(self.diagnostics_max_severity);
18233
18234 if !self.inline_diagnostics_enabled()
18235 || !self.diagnostics_enabled()
18236 || !self.show_inline_diagnostics
18237 || max_severity == DiagnosticSeverity::Off
18238 {
18239 self.inline_diagnostics_update = Task::ready(());
18240 self.inline_diagnostics.clear();
18241 return;
18242 }
18243
18244 let debounce_ms = ProjectSettings::get_global(cx)
18245 .diagnostics
18246 .inline
18247 .update_debounce_ms;
18248 let debounce = if debounce && debounce_ms > 0 {
18249 Some(Duration::from_millis(debounce_ms))
18250 } else {
18251 None
18252 };
18253 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18254 if let Some(debounce) = debounce {
18255 cx.background_executor().timer(debounce).await;
18256 }
18257 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18258 editor
18259 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18260 .ok()
18261 }) else {
18262 return;
18263 };
18264
18265 let new_inline_diagnostics = cx
18266 .background_spawn(async move {
18267 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18268 for diagnostic_entry in
18269 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18270 {
18271 let message = diagnostic_entry
18272 .diagnostic
18273 .message
18274 .split_once('\n')
18275 .map(|(line, _)| line)
18276 .map(SharedString::new)
18277 .unwrap_or_else(|| {
18278 SharedString::new(&*diagnostic_entry.diagnostic.message)
18279 });
18280 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18281 let (Ok(i) | Err(i)) = inline_diagnostics
18282 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18283 inline_diagnostics.insert(
18284 i,
18285 (
18286 start_anchor,
18287 InlineDiagnostic {
18288 message,
18289 group_id: diagnostic_entry.diagnostic.group_id,
18290 start: diagnostic_entry.range.start.to_point(&snapshot),
18291 is_primary: diagnostic_entry.diagnostic.is_primary,
18292 severity: diagnostic_entry.diagnostic.severity,
18293 },
18294 ),
18295 );
18296 }
18297 inline_diagnostics
18298 })
18299 .await;
18300
18301 editor
18302 .update(cx, |editor, cx| {
18303 editor.inline_diagnostics = new_inline_diagnostics;
18304 cx.notify();
18305 })
18306 .ok();
18307 });
18308 }
18309
18310 fn pull_diagnostics(
18311 &mut self,
18312 buffer_id: Option<BufferId>,
18313 window: &Window,
18314 cx: &mut Context<Self>,
18315 ) -> Option<()> {
18316 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18317 return None;
18318 }
18319 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18320 .diagnostics
18321 .lsp_pull_diagnostics;
18322 if !pull_diagnostics_settings.enabled {
18323 return None;
18324 }
18325 let project = self.project()?.downgrade();
18326 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18327 let mut buffers = self.buffer.read(cx).all_buffers();
18328 buffers.retain(|buffer| {
18329 let buffer_id_to_retain = buffer.read(cx).remote_id();
18330 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18331 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18332 });
18333 if buffers.is_empty() {
18334 self.pull_diagnostics_task = Task::ready(());
18335 return None;
18336 }
18337
18338 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18339 cx.background_executor().timer(debounce).await;
18340
18341 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18342 buffers
18343 .into_iter()
18344 .filter_map(|buffer| {
18345 project
18346 .update(cx, |project, cx| {
18347 project.lsp_store().update(cx, |lsp_store, cx| {
18348 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18349 })
18350 })
18351 .ok()
18352 })
18353 .collect::<FuturesUnordered<_>>()
18354 }) else {
18355 return;
18356 };
18357
18358 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18359 match pull_task {
18360 Ok(()) => {
18361 if editor
18362 .update_in(cx, |editor, window, cx| {
18363 editor.update_diagnostics_state(window, cx);
18364 })
18365 .is_err()
18366 {
18367 return;
18368 }
18369 }
18370 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18371 }
18372 }
18373 });
18374
18375 Some(())
18376 }
18377
18378 pub fn set_selections_from_remote(
18379 &mut self,
18380 selections: Vec<Selection<Anchor>>,
18381 pending_selection: Option<Selection<Anchor>>,
18382 window: &mut Window,
18383 cx: &mut Context<Self>,
18384 ) {
18385 let old_cursor_position = self.selections.newest_anchor().head();
18386 self.selections
18387 .change_with(&self.display_snapshot(cx), |s| {
18388 s.select_anchors(selections);
18389 if let Some(pending_selection) = pending_selection {
18390 s.set_pending(pending_selection, SelectMode::Character);
18391 } else {
18392 s.clear_pending();
18393 }
18394 });
18395 self.selections_did_change(
18396 false,
18397 &old_cursor_position,
18398 SelectionEffects::default(),
18399 window,
18400 cx,
18401 );
18402 }
18403
18404 pub fn transact(
18405 &mut self,
18406 window: &mut Window,
18407 cx: &mut Context<Self>,
18408 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18409 ) -> Option<TransactionId> {
18410 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18411 this.start_transaction_at(Instant::now(), window, cx);
18412 update(this, window, cx);
18413 this.end_transaction_at(Instant::now(), cx)
18414 })
18415 }
18416
18417 pub fn start_transaction_at(
18418 &mut self,
18419 now: Instant,
18420 window: &mut Window,
18421 cx: &mut Context<Self>,
18422 ) -> Option<TransactionId> {
18423 self.end_selection(window, cx);
18424 if let Some(tx_id) = self
18425 .buffer
18426 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18427 {
18428 self.selection_history
18429 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18430 cx.emit(EditorEvent::TransactionBegun {
18431 transaction_id: tx_id,
18432 });
18433 Some(tx_id)
18434 } else {
18435 None
18436 }
18437 }
18438
18439 pub fn end_transaction_at(
18440 &mut self,
18441 now: Instant,
18442 cx: &mut Context<Self>,
18443 ) -> Option<TransactionId> {
18444 if let Some(transaction_id) = self
18445 .buffer
18446 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18447 {
18448 if let Some((_, end_selections)) =
18449 self.selection_history.transaction_mut(transaction_id)
18450 {
18451 *end_selections = Some(self.selections.disjoint_anchors_arc());
18452 } else {
18453 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18454 }
18455
18456 cx.emit(EditorEvent::Edited { transaction_id });
18457 Some(transaction_id)
18458 } else {
18459 None
18460 }
18461 }
18462
18463 pub fn modify_transaction_selection_history(
18464 &mut self,
18465 transaction_id: TransactionId,
18466 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18467 ) -> bool {
18468 self.selection_history
18469 .transaction_mut(transaction_id)
18470 .map(modify)
18471 .is_some()
18472 }
18473
18474 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18475 if self.selection_mark_mode {
18476 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18477 s.move_with(|_, sel| {
18478 sel.collapse_to(sel.head(), SelectionGoal::None);
18479 });
18480 })
18481 }
18482 self.selection_mark_mode = true;
18483 cx.notify();
18484 }
18485
18486 pub fn swap_selection_ends(
18487 &mut self,
18488 _: &actions::SwapSelectionEnds,
18489 window: &mut Window,
18490 cx: &mut Context<Self>,
18491 ) {
18492 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18493 s.move_with(|_, sel| {
18494 if sel.start != sel.end {
18495 sel.reversed = !sel.reversed
18496 }
18497 });
18498 });
18499 self.request_autoscroll(Autoscroll::newest(), cx);
18500 cx.notify();
18501 }
18502
18503 pub fn toggle_focus(
18504 workspace: &mut Workspace,
18505 _: &actions::ToggleFocus,
18506 window: &mut Window,
18507 cx: &mut Context<Workspace>,
18508 ) {
18509 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18510 return;
18511 };
18512 workspace.activate_item(&item, true, true, window, cx);
18513 }
18514
18515 pub fn toggle_fold(
18516 &mut self,
18517 _: &actions::ToggleFold,
18518 window: &mut Window,
18519 cx: &mut Context<Self>,
18520 ) {
18521 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18522 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18523 let selection = self.selections.newest::<Point>(&display_map);
18524
18525 let range = if selection.is_empty() {
18526 let point = selection.head().to_display_point(&display_map);
18527 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18528 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18529 .to_point(&display_map);
18530 start..end
18531 } else {
18532 selection.range()
18533 };
18534 if display_map.folds_in_range(range).next().is_some() {
18535 self.unfold_lines(&Default::default(), window, cx)
18536 } else {
18537 self.fold(&Default::default(), window, cx)
18538 }
18539 } else {
18540 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18541 let buffer_ids: HashSet<_> = self
18542 .selections
18543 .disjoint_anchor_ranges()
18544 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18545 .collect();
18546
18547 let should_unfold = buffer_ids
18548 .iter()
18549 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18550
18551 for buffer_id in buffer_ids {
18552 if should_unfold {
18553 self.unfold_buffer(buffer_id, cx);
18554 } else {
18555 self.fold_buffer(buffer_id, cx);
18556 }
18557 }
18558 }
18559 }
18560
18561 pub fn toggle_fold_recursive(
18562 &mut self,
18563 _: &actions::ToggleFoldRecursive,
18564 window: &mut Window,
18565 cx: &mut Context<Self>,
18566 ) {
18567 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18568
18569 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18570 let range = if selection.is_empty() {
18571 let point = selection.head().to_display_point(&display_map);
18572 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18573 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18574 .to_point(&display_map);
18575 start..end
18576 } else {
18577 selection.range()
18578 };
18579 if display_map.folds_in_range(range).next().is_some() {
18580 self.unfold_recursive(&Default::default(), window, cx)
18581 } else {
18582 self.fold_recursive(&Default::default(), window, cx)
18583 }
18584 }
18585
18586 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18587 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18588 let mut to_fold = Vec::new();
18589 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18590 let selections = self.selections.all_adjusted(&display_map);
18591
18592 for selection in selections {
18593 let range = selection.range().sorted();
18594 let buffer_start_row = range.start.row;
18595
18596 if range.start.row != range.end.row {
18597 let mut found = false;
18598 let mut row = range.start.row;
18599 while row <= range.end.row {
18600 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18601 {
18602 found = true;
18603 row = crease.range().end.row + 1;
18604 to_fold.push(crease);
18605 } else {
18606 row += 1
18607 }
18608 }
18609 if found {
18610 continue;
18611 }
18612 }
18613
18614 for row in (0..=range.start.row).rev() {
18615 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18616 && crease.range().end.row >= buffer_start_row
18617 {
18618 to_fold.push(crease);
18619 if row <= range.start.row {
18620 break;
18621 }
18622 }
18623 }
18624 }
18625
18626 self.fold_creases(to_fold, true, window, cx);
18627 } else {
18628 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18629 let buffer_ids = self
18630 .selections
18631 .disjoint_anchor_ranges()
18632 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18633 .collect::<HashSet<_>>();
18634 for buffer_id in buffer_ids {
18635 self.fold_buffer(buffer_id, cx);
18636 }
18637 }
18638 }
18639
18640 pub fn toggle_fold_all(
18641 &mut self,
18642 _: &actions::ToggleFoldAll,
18643 window: &mut Window,
18644 cx: &mut Context<Self>,
18645 ) {
18646 if self.buffer.read(cx).is_singleton() {
18647 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18648 let has_folds = display_map
18649 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18650 .next()
18651 .is_some();
18652
18653 if has_folds {
18654 self.unfold_all(&actions::UnfoldAll, window, cx);
18655 } else {
18656 self.fold_all(&actions::FoldAll, window, cx);
18657 }
18658 } else {
18659 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18660 let should_unfold = buffer_ids
18661 .iter()
18662 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18663
18664 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18665 editor
18666 .update_in(cx, |editor, _, cx| {
18667 for buffer_id in buffer_ids {
18668 if should_unfold {
18669 editor.unfold_buffer(buffer_id, cx);
18670 } else {
18671 editor.fold_buffer(buffer_id, cx);
18672 }
18673 }
18674 })
18675 .ok();
18676 });
18677 }
18678 }
18679
18680 fn fold_at_level(
18681 &mut self,
18682 fold_at: &FoldAtLevel,
18683 window: &mut Window,
18684 cx: &mut Context<Self>,
18685 ) {
18686 if !self.buffer.read(cx).is_singleton() {
18687 return;
18688 }
18689
18690 let fold_at_level = fold_at.0;
18691 let snapshot = self.buffer.read(cx).snapshot(cx);
18692 let mut to_fold = Vec::new();
18693 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18694
18695 let row_ranges_to_keep: Vec<Range<u32>> = self
18696 .selections
18697 .all::<Point>(&self.display_snapshot(cx))
18698 .into_iter()
18699 .map(|sel| sel.start.row..sel.end.row)
18700 .collect();
18701
18702 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18703 while start_row < end_row {
18704 match self
18705 .snapshot(window, cx)
18706 .crease_for_buffer_row(MultiBufferRow(start_row))
18707 {
18708 Some(crease) => {
18709 let nested_start_row = crease.range().start.row + 1;
18710 let nested_end_row = crease.range().end.row;
18711
18712 if current_level < fold_at_level {
18713 stack.push((nested_start_row, nested_end_row, current_level + 1));
18714 } else if current_level == fold_at_level {
18715 // Fold iff there is no selection completely contained within the fold region
18716 if !row_ranges_to_keep.iter().any(|selection| {
18717 selection.end >= nested_start_row
18718 && selection.start <= nested_end_row
18719 }) {
18720 to_fold.push(crease);
18721 }
18722 }
18723
18724 start_row = nested_end_row + 1;
18725 }
18726 None => start_row += 1,
18727 }
18728 }
18729 }
18730
18731 self.fold_creases(to_fold, true, window, cx);
18732 }
18733
18734 pub fn fold_at_level_1(
18735 &mut self,
18736 _: &actions::FoldAtLevel1,
18737 window: &mut Window,
18738 cx: &mut Context<Self>,
18739 ) {
18740 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18741 }
18742
18743 pub fn fold_at_level_2(
18744 &mut self,
18745 _: &actions::FoldAtLevel2,
18746 window: &mut Window,
18747 cx: &mut Context<Self>,
18748 ) {
18749 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18750 }
18751
18752 pub fn fold_at_level_3(
18753 &mut self,
18754 _: &actions::FoldAtLevel3,
18755 window: &mut Window,
18756 cx: &mut Context<Self>,
18757 ) {
18758 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18759 }
18760
18761 pub fn fold_at_level_4(
18762 &mut self,
18763 _: &actions::FoldAtLevel4,
18764 window: &mut Window,
18765 cx: &mut Context<Self>,
18766 ) {
18767 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18768 }
18769
18770 pub fn fold_at_level_5(
18771 &mut self,
18772 _: &actions::FoldAtLevel5,
18773 window: &mut Window,
18774 cx: &mut Context<Self>,
18775 ) {
18776 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18777 }
18778
18779 pub fn fold_at_level_6(
18780 &mut self,
18781 _: &actions::FoldAtLevel6,
18782 window: &mut Window,
18783 cx: &mut Context<Self>,
18784 ) {
18785 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18786 }
18787
18788 pub fn fold_at_level_7(
18789 &mut self,
18790 _: &actions::FoldAtLevel7,
18791 window: &mut Window,
18792 cx: &mut Context<Self>,
18793 ) {
18794 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18795 }
18796
18797 pub fn fold_at_level_8(
18798 &mut self,
18799 _: &actions::FoldAtLevel8,
18800 window: &mut Window,
18801 cx: &mut Context<Self>,
18802 ) {
18803 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18804 }
18805
18806 pub fn fold_at_level_9(
18807 &mut self,
18808 _: &actions::FoldAtLevel9,
18809 window: &mut Window,
18810 cx: &mut Context<Self>,
18811 ) {
18812 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18813 }
18814
18815 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18816 if self.buffer.read(cx).is_singleton() {
18817 let mut fold_ranges = Vec::new();
18818 let snapshot = self.buffer.read(cx).snapshot(cx);
18819
18820 for row in 0..snapshot.max_row().0 {
18821 if let Some(foldable_range) = self
18822 .snapshot(window, cx)
18823 .crease_for_buffer_row(MultiBufferRow(row))
18824 {
18825 fold_ranges.push(foldable_range);
18826 }
18827 }
18828
18829 self.fold_creases(fold_ranges, true, window, cx);
18830 } else {
18831 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18832 editor
18833 .update_in(cx, |editor, _, cx| {
18834 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18835 editor.fold_buffer(buffer_id, cx);
18836 }
18837 })
18838 .ok();
18839 });
18840 }
18841 }
18842
18843 pub fn fold_function_bodies(
18844 &mut self,
18845 _: &actions::FoldFunctionBodies,
18846 window: &mut Window,
18847 cx: &mut Context<Self>,
18848 ) {
18849 let snapshot = self.buffer.read(cx).snapshot(cx);
18850
18851 let ranges = snapshot
18852 .text_object_ranges(
18853 MultiBufferOffset(0)..snapshot.len(),
18854 TreeSitterOptions::default(),
18855 )
18856 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18857 .collect::<Vec<_>>();
18858
18859 let creases = ranges
18860 .into_iter()
18861 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18862 .collect();
18863
18864 self.fold_creases(creases, true, window, cx);
18865 }
18866
18867 pub fn fold_recursive(
18868 &mut self,
18869 _: &actions::FoldRecursive,
18870 window: &mut Window,
18871 cx: &mut Context<Self>,
18872 ) {
18873 let mut to_fold = Vec::new();
18874 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18875 let selections = self.selections.all_adjusted(&display_map);
18876
18877 for selection in selections {
18878 let range = selection.range().sorted();
18879 let buffer_start_row = range.start.row;
18880
18881 if range.start.row != range.end.row {
18882 let mut found = false;
18883 for row in range.start.row..=range.end.row {
18884 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18885 found = true;
18886 to_fold.push(crease);
18887 }
18888 }
18889 if found {
18890 continue;
18891 }
18892 }
18893
18894 for row in (0..=range.start.row).rev() {
18895 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18896 if crease.range().end.row >= buffer_start_row {
18897 to_fold.push(crease);
18898 } else {
18899 break;
18900 }
18901 }
18902 }
18903 }
18904
18905 self.fold_creases(to_fold, true, window, cx);
18906 }
18907
18908 pub fn fold_at(
18909 &mut self,
18910 buffer_row: MultiBufferRow,
18911 window: &mut Window,
18912 cx: &mut Context<Self>,
18913 ) {
18914 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18915
18916 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18917 let autoscroll = self
18918 .selections
18919 .all::<Point>(&display_map)
18920 .iter()
18921 .any(|selection| crease.range().overlaps(&selection.range()));
18922
18923 self.fold_creases(vec![crease], autoscroll, window, cx);
18924 }
18925 }
18926
18927 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18928 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18929 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18930 let buffer = display_map.buffer_snapshot();
18931 let selections = self.selections.all::<Point>(&display_map);
18932 let ranges = selections
18933 .iter()
18934 .map(|s| {
18935 let range = s.display_range(&display_map).sorted();
18936 let mut start = range.start.to_point(&display_map);
18937 let mut end = range.end.to_point(&display_map);
18938 start.column = 0;
18939 end.column = buffer.line_len(MultiBufferRow(end.row));
18940 start..end
18941 })
18942 .collect::<Vec<_>>();
18943
18944 self.unfold_ranges(&ranges, true, true, cx);
18945 } else {
18946 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18947 let buffer_ids = self
18948 .selections
18949 .disjoint_anchor_ranges()
18950 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18951 .collect::<HashSet<_>>();
18952 for buffer_id in buffer_ids {
18953 self.unfold_buffer(buffer_id, cx);
18954 }
18955 }
18956 }
18957
18958 pub fn unfold_recursive(
18959 &mut self,
18960 _: &UnfoldRecursive,
18961 _window: &mut Window,
18962 cx: &mut Context<Self>,
18963 ) {
18964 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18965 let selections = self.selections.all::<Point>(&display_map);
18966 let ranges = selections
18967 .iter()
18968 .map(|s| {
18969 let mut range = s.display_range(&display_map).sorted();
18970 *range.start.column_mut() = 0;
18971 *range.end.column_mut() = display_map.line_len(range.end.row());
18972 let start = range.start.to_point(&display_map);
18973 let end = range.end.to_point(&display_map);
18974 start..end
18975 })
18976 .collect::<Vec<_>>();
18977
18978 self.unfold_ranges(&ranges, true, true, cx);
18979 }
18980
18981 pub fn unfold_at(
18982 &mut self,
18983 buffer_row: MultiBufferRow,
18984 _window: &mut Window,
18985 cx: &mut Context<Self>,
18986 ) {
18987 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18988
18989 let intersection_range = Point::new(buffer_row.0, 0)
18990 ..Point::new(
18991 buffer_row.0,
18992 display_map.buffer_snapshot().line_len(buffer_row),
18993 );
18994
18995 let autoscroll = self
18996 .selections
18997 .all::<Point>(&display_map)
18998 .iter()
18999 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19000
19001 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19002 }
19003
19004 pub fn unfold_all(
19005 &mut self,
19006 _: &actions::UnfoldAll,
19007 _window: &mut Window,
19008 cx: &mut Context<Self>,
19009 ) {
19010 if self.buffer.read(cx).is_singleton() {
19011 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19012 self.unfold_ranges(
19013 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19014 true,
19015 true,
19016 cx,
19017 );
19018 } else {
19019 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19020 editor
19021 .update(cx, |editor, cx| {
19022 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19023 editor.unfold_buffer(buffer_id, cx);
19024 }
19025 })
19026 .ok();
19027 });
19028 }
19029 }
19030
19031 pub fn fold_selected_ranges(
19032 &mut self,
19033 _: &FoldSelectedRanges,
19034 window: &mut Window,
19035 cx: &mut Context<Self>,
19036 ) {
19037 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19038 let selections = self.selections.all_adjusted(&display_map);
19039 let ranges = selections
19040 .into_iter()
19041 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19042 .collect::<Vec<_>>();
19043 self.fold_creases(ranges, true, window, cx);
19044 }
19045
19046 pub fn fold_ranges<T: ToOffset + Clone>(
19047 &mut self,
19048 ranges: Vec<Range<T>>,
19049 auto_scroll: bool,
19050 window: &mut Window,
19051 cx: &mut Context<Self>,
19052 ) {
19053 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19054 let ranges = ranges
19055 .into_iter()
19056 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19057 .collect::<Vec<_>>();
19058 self.fold_creases(ranges, auto_scroll, window, cx);
19059 }
19060
19061 pub fn fold_creases<T: ToOffset + Clone>(
19062 &mut self,
19063 creases: Vec<Crease<T>>,
19064 auto_scroll: bool,
19065 _window: &mut Window,
19066 cx: &mut Context<Self>,
19067 ) {
19068 if creases.is_empty() {
19069 return;
19070 }
19071
19072 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19073
19074 if auto_scroll {
19075 self.request_autoscroll(Autoscroll::fit(), cx);
19076 }
19077
19078 cx.notify();
19079
19080 self.scrollbar_marker_state.dirty = true;
19081 self.folds_did_change(cx);
19082 }
19083
19084 /// Removes any folds whose ranges intersect any of the given ranges.
19085 pub fn unfold_ranges<T: ToOffset + Clone>(
19086 &mut self,
19087 ranges: &[Range<T>],
19088 inclusive: bool,
19089 auto_scroll: bool,
19090 cx: &mut Context<Self>,
19091 ) {
19092 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19093 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19094 });
19095 self.folds_did_change(cx);
19096 }
19097
19098 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19099 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19100 return;
19101 }
19102
19103 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19104 self.display_map.update(cx, |display_map, cx| {
19105 display_map.fold_buffers([buffer_id], cx)
19106 });
19107
19108 let snapshot = self.display_snapshot(cx);
19109 self.selections.change_with(&snapshot, |selections| {
19110 selections.remove_selections_from_buffer(buffer_id);
19111 });
19112
19113 cx.emit(EditorEvent::BufferFoldToggled {
19114 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19115 folded: true,
19116 });
19117 cx.notify();
19118 }
19119
19120 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19121 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19122 return;
19123 }
19124 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19125 self.display_map.update(cx, |display_map, cx| {
19126 display_map.unfold_buffers([buffer_id], cx);
19127 });
19128 cx.emit(EditorEvent::BufferFoldToggled {
19129 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19130 folded: false,
19131 });
19132 cx.notify();
19133 }
19134
19135 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19136 self.display_map.read(cx).is_buffer_folded(buffer)
19137 }
19138
19139 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19140 self.display_map.read(cx).folded_buffers()
19141 }
19142
19143 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19144 self.display_map.update(cx, |display_map, cx| {
19145 display_map.disable_header_for_buffer(buffer_id, cx);
19146 });
19147 cx.notify();
19148 }
19149
19150 /// Removes any folds with the given ranges.
19151 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19152 &mut self,
19153 ranges: &[Range<T>],
19154 type_id: TypeId,
19155 auto_scroll: bool,
19156 cx: &mut Context<Self>,
19157 ) {
19158 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19159 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19160 });
19161 self.folds_did_change(cx);
19162 }
19163
19164 fn remove_folds_with<T: ToOffset + Clone>(
19165 &mut self,
19166 ranges: &[Range<T>],
19167 auto_scroll: bool,
19168 cx: &mut Context<Self>,
19169 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19170 ) {
19171 if ranges.is_empty() {
19172 return;
19173 }
19174
19175 let mut buffers_affected = HashSet::default();
19176 let multi_buffer = self.buffer().read(cx);
19177 for range in ranges {
19178 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19179 buffers_affected.insert(buffer.read(cx).remote_id());
19180 };
19181 }
19182
19183 self.display_map.update(cx, update);
19184
19185 if auto_scroll {
19186 self.request_autoscroll(Autoscroll::fit(), cx);
19187 }
19188
19189 cx.notify();
19190 self.scrollbar_marker_state.dirty = true;
19191 self.active_indent_guides_state.dirty = true;
19192 }
19193
19194 pub fn update_renderer_widths(
19195 &mut self,
19196 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19197 cx: &mut Context<Self>,
19198 ) -> bool {
19199 self.display_map
19200 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19201 }
19202
19203 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19204 self.display_map.read(cx).fold_placeholder.clone()
19205 }
19206
19207 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19208 self.buffer.update(cx, |buffer, cx| {
19209 buffer.set_all_diff_hunks_expanded(cx);
19210 });
19211 }
19212
19213 pub fn expand_all_diff_hunks(
19214 &mut self,
19215 _: &ExpandAllDiffHunks,
19216 _window: &mut Window,
19217 cx: &mut Context<Self>,
19218 ) {
19219 self.buffer.update(cx, |buffer, cx| {
19220 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19221 });
19222 }
19223
19224 pub fn collapse_all_diff_hunks(
19225 &mut self,
19226 _: &CollapseAllDiffHunks,
19227 _window: &mut Window,
19228 cx: &mut Context<Self>,
19229 ) {
19230 self.buffer.update(cx, |buffer, cx| {
19231 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19232 });
19233 }
19234
19235 pub fn toggle_selected_diff_hunks(
19236 &mut self,
19237 _: &ToggleSelectedDiffHunks,
19238 _window: &mut Window,
19239 cx: &mut Context<Self>,
19240 ) {
19241 let ranges: Vec<_> = self
19242 .selections
19243 .disjoint_anchors()
19244 .iter()
19245 .map(|s| s.range())
19246 .collect();
19247 self.toggle_diff_hunks_in_ranges(ranges, cx);
19248 }
19249
19250 pub fn diff_hunks_in_ranges<'a>(
19251 &'a self,
19252 ranges: &'a [Range<Anchor>],
19253 buffer: &'a MultiBufferSnapshot,
19254 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19255 ranges.iter().flat_map(move |range| {
19256 let end_excerpt_id = range.end.excerpt_id;
19257 let range = range.to_point(buffer);
19258 let mut peek_end = range.end;
19259 if range.end.row < buffer.max_row().0 {
19260 peek_end = Point::new(range.end.row + 1, 0);
19261 }
19262 buffer
19263 .diff_hunks_in_range(range.start..peek_end)
19264 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19265 })
19266 }
19267
19268 pub fn has_stageable_diff_hunks_in_ranges(
19269 &self,
19270 ranges: &[Range<Anchor>],
19271 snapshot: &MultiBufferSnapshot,
19272 ) -> bool {
19273 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19274 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19275 }
19276
19277 pub fn toggle_staged_selected_diff_hunks(
19278 &mut self,
19279 _: &::git::ToggleStaged,
19280 _: &mut Window,
19281 cx: &mut Context<Self>,
19282 ) {
19283 let snapshot = self.buffer.read(cx).snapshot(cx);
19284 let ranges: Vec<_> = self
19285 .selections
19286 .disjoint_anchors()
19287 .iter()
19288 .map(|s| s.range())
19289 .collect();
19290 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19291 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19292 }
19293
19294 pub fn set_render_diff_hunk_controls(
19295 &mut self,
19296 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19297 cx: &mut Context<Self>,
19298 ) {
19299 self.render_diff_hunk_controls = render_diff_hunk_controls;
19300 cx.notify();
19301 }
19302
19303 pub fn stage_and_next(
19304 &mut self,
19305 _: &::git::StageAndNext,
19306 window: &mut Window,
19307 cx: &mut Context<Self>,
19308 ) {
19309 self.do_stage_or_unstage_and_next(true, window, cx);
19310 }
19311
19312 pub fn unstage_and_next(
19313 &mut self,
19314 _: &::git::UnstageAndNext,
19315 window: &mut Window,
19316 cx: &mut Context<Self>,
19317 ) {
19318 self.do_stage_or_unstage_and_next(false, window, cx);
19319 }
19320
19321 pub fn stage_or_unstage_diff_hunks(
19322 &mut self,
19323 stage: bool,
19324 ranges: Vec<Range<Anchor>>,
19325 cx: &mut Context<Self>,
19326 ) {
19327 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19328 cx.spawn(async move |this, cx| {
19329 task.await?;
19330 this.update(cx, |this, cx| {
19331 let snapshot = this.buffer.read(cx).snapshot(cx);
19332 let chunk_by = this
19333 .diff_hunks_in_ranges(&ranges, &snapshot)
19334 .chunk_by(|hunk| hunk.buffer_id);
19335 for (buffer_id, hunks) in &chunk_by {
19336 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19337 }
19338 })
19339 })
19340 .detach_and_log_err(cx);
19341 }
19342
19343 fn save_buffers_for_ranges_if_needed(
19344 &mut self,
19345 ranges: &[Range<Anchor>],
19346 cx: &mut Context<Editor>,
19347 ) -> Task<Result<()>> {
19348 let multibuffer = self.buffer.read(cx);
19349 let snapshot = multibuffer.read(cx);
19350 let buffer_ids: HashSet<_> = ranges
19351 .iter()
19352 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19353 .collect();
19354 drop(snapshot);
19355
19356 let mut buffers = HashSet::default();
19357 for buffer_id in buffer_ids {
19358 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19359 let buffer = buffer_entity.read(cx);
19360 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19361 {
19362 buffers.insert(buffer_entity);
19363 }
19364 }
19365 }
19366
19367 if let Some(project) = &self.project {
19368 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19369 } else {
19370 Task::ready(Ok(()))
19371 }
19372 }
19373
19374 fn do_stage_or_unstage_and_next(
19375 &mut self,
19376 stage: bool,
19377 window: &mut Window,
19378 cx: &mut Context<Self>,
19379 ) {
19380 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19381
19382 if ranges.iter().any(|range| range.start != range.end) {
19383 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19384 return;
19385 }
19386
19387 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19388 let snapshot = self.snapshot(window, cx);
19389 let position = self
19390 .selections
19391 .newest::<Point>(&snapshot.display_snapshot)
19392 .head();
19393 let mut row = snapshot
19394 .buffer_snapshot()
19395 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19396 .find(|hunk| hunk.row_range.start.0 > position.row)
19397 .map(|hunk| hunk.row_range.start);
19398
19399 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19400 // Outside of the project diff editor, wrap around to the beginning.
19401 if !all_diff_hunks_expanded {
19402 row = row.or_else(|| {
19403 snapshot
19404 .buffer_snapshot()
19405 .diff_hunks_in_range(Point::zero()..position)
19406 .find(|hunk| hunk.row_range.end.0 < position.row)
19407 .map(|hunk| hunk.row_range.start)
19408 });
19409 }
19410
19411 if let Some(row) = row {
19412 let destination = Point::new(row.0, 0);
19413 let autoscroll = Autoscroll::center();
19414
19415 self.unfold_ranges(&[destination..destination], false, false, cx);
19416 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19417 s.select_ranges([destination..destination]);
19418 });
19419 }
19420 }
19421
19422 fn do_stage_or_unstage(
19423 &self,
19424 stage: bool,
19425 buffer_id: BufferId,
19426 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19427 cx: &mut App,
19428 ) -> Option<()> {
19429 let project = self.project()?;
19430 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19431 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19432 let buffer_snapshot = buffer.read(cx).snapshot();
19433 let file_exists = buffer_snapshot
19434 .file()
19435 .is_some_and(|file| file.disk_state().exists());
19436 diff.update(cx, |diff, cx| {
19437 diff.stage_or_unstage_hunks(
19438 stage,
19439 &hunks
19440 .map(|hunk| buffer_diff::DiffHunk {
19441 buffer_range: hunk.buffer_range,
19442 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19443 ..hunk.diff_base_byte_range.end.0,
19444 secondary_status: hunk.secondary_status,
19445 range: Point::zero()..Point::zero(), // unused
19446 })
19447 .collect::<Vec<_>>(),
19448 &buffer_snapshot,
19449 file_exists,
19450 cx,
19451 )
19452 });
19453 None
19454 }
19455
19456 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19457 let ranges: Vec<_> = self
19458 .selections
19459 .disjoint_anchors()
19460 .iter()
19461 .map(|s| s.range())
19462 .collect();
19463 self.buffer
19464 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19465 }
19466
19467 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19468 self.buffer.update(cx, |buffer, cx| {
19469 let ranges = vec![Anchor::min()..Anchor::max()];
19470 if !buffer.all_diff_hunks_expanded()
19471 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19472 {
19473 buffer.collapse_diff_hunks(ranges, cx);
19474 true
19475 } else {
19476 false
19477 }
19478 })
19479 }
19480
19481 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19482 if self.buffer.read(cx).all_diff_hunks_expanded() {
19483 return true;
19484 }
19485 let ranges = vec![Anchor::min()..Anchor::max()];
19486 self.buffer
19487 .read(cx)
19488 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19489 }
19490
19491 fn toggle_diff_hunks_in_ranges(
19492 &mut self,
19493 ranges: Vec<Range<Anchor>>,
19494 cx: &mut Context<Editor>,
19495 ) {
19496 self.buffer.update(cx, |buffer, cx| {
19497 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19498 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19499 })
19500 }
19501
19502 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19503 self.buffer.update(cx, |buffer, cx| {
19504 let snapshot = buffer.snapshot(cx);
19505 let excerpt_id = range.end.excerpt_id;
19506 let point_range = range.to_point(&snapshot);
19507 let expand = !buffer.single_hunk_is_expanded(range, cx);
19508 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19509 })
19510 }
19511
19512 pub(crate) fn apply_all_diff_hunks(
19513 &mut self,
19514 _: &ApplyAllDiffHunks,
19515 window: &mut Window,
19516 cx: &mut Context<Self>,
19517 ) {
19518 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19519
19520 let buffers = self.buffer.read(cx).all_buffers();
19521 for branch_buffer in buffers {
19522 branch_buffer.update(cx, |branch_buffer, cx| {
19523 branch_buffer.merge_into_base(Vec::new(), cx);
19524 });
19525 }
19526
19527 if let Some(project) = self.project.clone() {
19528 self.save(
19529 SaveOptions {
19530 format: true,
19531 autosave: false,
19532 },
19533 project,
19534 window,
19535 cx,
19536 )
19537 .detach_and_log_err(cx);
19538 }
19539 }
19540
19541 pub(crate) fn apply_selected_diff_hunks(
19542 &mut self,
19543 _: &ApplyDiffHunk,
19544 window: &mut Window,
19545 cx: &mut Context<Self>,
19546 ) {
19547 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19548 let snapshot = self.snapshot(window, cx);
19549 let hunks = snapshot.hunks_for_ranges(
19550 self.selections
19551 .all(&snapshot.display_snapshot)
19552 .into_iter()
19553 .map(|selection| selection.range()),
19554 );
19555 let mut ranges_by_buffer = HashMap::default();
19556 self.transact(window, cx, |editor, _window, cx| {
19557 for hunk in hunks {
19558 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19559 ranges_by_buffer
19560 .entry(buffer.clone())
19561 .or_insert_with(Vec::new)
19562 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19563 }
19564 }
19565
19566 for (buffer, ranges) in ranges_by_buffer {
19567 buffer.update(cx, |buffer, cx| {
19568 buffer.merge_into_base(ranges, cx);
19569 });
19570 }
19571 });
19572
19573 if let Some(project) = self.project.clone() {
19574 self.save(
19575 SaveOptions {
19576 format: true,
19577 autosave: false,
19578 },
19579 project,
19580 window,
19581 cx,
19582 )
19583 .detach_and_log_err(cx);
19584 }
19585 }
19586
19587 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19588 if hovered != self.gutter_hovered {
19589 self.gutter_hovered = hovered;
19590 cx.notify();
19591 }
19592 }
19593
19594 pub fn insert_blocks(
19595 &mut self,
19596 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19597 autoscroll: Option<Autoscroll>,
19598 cx: &mut Context<Self>,
19599 ) -> Vec<CustomBlockId> {
19600 let blocks = self
19601 .display_map
19602 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19603 if let Some(autoscroll) = autoscroll {
19604 self.request_autoscroll(autoscroll, cx);
19605 }
19606 cx.notify();
19607 blocks
19608 }
19609
19610 pub fn resize_blocks(
19611 &mut self,
19612 heights: HashMap<CustomBlockId, u32>,
19613 autoscroll: Option<Autoscroll>,
19614 cx: &mut Context<Self>,
19615 ) {
19616 self.display_map
19617 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19618 if let Some(autoscroll) = autoscroll {
19619 self.request_autoscroll(autoscroll, cx);
19620 }
19621 cx.notify();
19622 }
19623
19624 pub fn replace_blocks(
19625 &mut self,
19626 renderers: HashMap<CustomBlockId, RenderBlock>,
19627 autoscroll: Option<Autoscroll>,
19628 cx: &mut Context<Self>,
19629 ) {
19630 self.display_map
19631 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19632 if let Some(autoscroll) = autoscroll {
19633 self.request_autoscroll(autoscroll, cx);
19634 }
19635 cx.notify();
19636 }
19637
19638 pub fn remove_blocks(
19639 &mut self,
19640 block_ids: HashSet<CustomBlockId>,
19641 autoscroll: Option<Autoscroll>,
19642 cx: &mut Context<Self>,
19643 ) {
19644 self.display_map.update(cx, |display_map, cx| {
19645 display_map.remove_blocks(block_ids, cx)
19646 });
19647 if let Some(autoscroll) = autoscroll {
19648 self.request_autoscroll(autoscroll, cx);
19649 }
19650 cx.notify();
19651 }
19652
19653 pub fn row_for_block(
19654 &self,
19655 block_id: CustomBlockId,
19656 cx: &mut Context<Self>,
19657 ) -> Option<DisplayRow> {
19658 self.display_map
19659 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19660 }
19661
19662 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19663 self.focused_block = Some(focused_block);
19664 }
19665
19666 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19667 self.focused_block.take()
19668 }
19669
19670 pub fn insert_creases(
19671 &mut self,
19672 creases: impl IntoIterator<Item = Crease<Anchor>>,
19673 cx: &mut Context<Self>,
19674 ) -> Vec<CreaseId> {
19675 self.display_map
19676 .update(cx, |map, cx| map.insert_creases(creases, cx))
19677 }
19678
19679 pub fn remove_creases(
19680 &mut self,
19681 ids: impl IntoIterator<Item = CreaseId>,
19682 cx: &mut Context<Self>,
19683 ) -> Vec<(CreaseId, Range<Anchor>)> {
19684 self.display_map
19685 .update(cx, |map, cx| map.remove_creases(ids, cx))
19686 }
19687
19688 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19689 self.display_map
19690 .update(cx, |map, cx| map.snapshot(cx))
19691 .longest_row()
19692 }
19693
19694 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19695 self.display_map
19696 .update(cx, |map, cx| map.snapshot(cx))
19697 .max_point()
19698 }
19699
19700 pub fn text(&self, cx: &App) -> String {
19701 self.buffer.read(cx).read(cx).text()
19702 }
19703
19704 pub fn is_empty(&self, cx: &App) -> bool {
19705 self.buffer.read(cx).read(cx).is_empty()
19706 }
19707
19708 pub fn text_option(&self, cx: &App) -> Option<String> {
19709 let text = self.text(cx);
19710 let text = text.trim();
19711
19712 if text.is_empty() {
19713 return None;
19714 }
19715
19716 Some(text.to_string())
19717 }
19718
19719 pub fn set_text(
19720 &mut self,
19721 text: impl Into<Arc<str>>,
19722 window: &mut Window,
19723 cx: &mut Context<Self>,
19724 ) {
19725 self.transact(window, cx, |this, _, cx| {
19726 this.buffer
19727 .read(cx)
19728 .as_singleton()
19729 .expect("you can only call set_text on editors for singleton buffers")
19730 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19731 });
19732 }
19733
19734 pub fn display_text(&self, cx: &mut App) -> String {
19735 self.display_map
19736 .update(cx, |map, cx| map.snapshot(cx))
19737 .text()
19738 }
19739
19740 fn create_minimap(
19741 &self,
19742 minimap_settings: MinimapSettings,
19743 window: &mut Window,
19744 cx: &mut Context<Self>,
19745 ) -> Option<Entity<Self>> {
19746 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19747 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19748 }
19749
19750 fn initialize_new_minimap(
19751 &self,
19752 minimap_settings: MinimapSettings,
19753 window: &mut Window,
19754 cx: &mut Context<Self>,
19755 ) -> Entity<Self> {
19756 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19757
19758 let mut minimap = Editor::new_internal(
19759 EditorMode::Minimap {
19760 parent: cx.weak_entity(),
19761 },
19762 self.buffer.clone(),
19763 None,
19764 Some(self.display_map.clone()),
19765 window,
19766 cx,
19767 );
19768 minimap.scroll_manager.clone_state(&self.scroll_manager);
19769 minimap.set_text_style_refinement(TextStyleRefinement {
19770 font_size: Some(MINIMAP_FONT_SIZE),
19771 font_weight: Some(MINIMAP_FONT_WEIGHT),
19772 ..Default::default()
19773 });
19774 minimap.update_minimap_configuration(minimap_settings, cx);
19775 cx.new(|_| minimap)
19776 }
19777
19778 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19779 let current_line_highlight = minimap_settings
19780 .current_line_highlight
19781 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19782 self.set_current_line_highlight(Some(current_line_highlight));
19783 }
19784
19785 pub fn minimap(&self) -> Option<&Entity<Self>> {
19786 self.minimap
19787 .as_ref()
19788 .filter(|_| self.minimap_visibility.visible())
19789 }
19790
19791 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19792 let mut wrap_guides = smallvec![];
19793
19794 if self.show_wrap_guides == Some(false) {
19795 return wrap_guides;
19796 }
19797
19798 let settings = self.buffer.read(cx).language_settings(cx);
19799 if settings.show_wrap_guides {
19800 match self.soft_wrap_mode(cx) {
19801 SoftWrap::Column(soft_wrap) => {
19802 wrap_guides.push((soft_wrap as usize, true));
19803 }
19804 SoftWrap::Bounded(soft_wrap) => {
19805 wrap_guides.push((soft_wrap as usize, true));
19806 }
19807 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19808 }
19809 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19810 }
19811
19812 wrap_guides
19813 }
19814
19815 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19816 let settings = self.buffer.read(cx).language_settings(cx);
19817 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19818 match mode {
19819 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19820 SoftWrap::None
19821 }
19822 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19823 language_settings::SoftWrap::PreferredLineLength => {
19824 SoftWrap::Column(settings.preferred_line_length)
19825 }
19826 language_settings::SoftWrap::Bounded => {
19827 SoftWrap::Bounded(settings.preferred_line_length)
19828 }
19829 }
19830 }
19831
19832 pub fn set_soft_wrap_mode(
19833 &mut self,
19834 mode: language_settings::SoftWrap,
19835
19836 cx: &mut Context<Self>,
19837 ) {
19838 self.soft_wrap_mode_override = Some(mode);
19839 cx.notify();
19840 }
19841
19842 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19843 self.hard_wrap = hard_wrap;
19844 cx.notify();
19845 }
19846
19847 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19848 self.text_style_refinement = Some(style);
19849 }
19850
19851 /// called by the Element so we know what style we were most recently rendered with.
19852 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19853 // We intentionally do not inform the display map about the minimap style
19854 // so that wrapping is not recalculated and stays consistent for the editor
19855 // and its linked minimap.
19856 if !self.mode.is_minimap() {
19857 let font = style.text.font();
19858 let font_size = style.text.font_size.to_pixels(window.rem_size());
19859 let display_map = self
19860 .placeholder_display_map
19861 .as_ref()
19862 .filter(|_| self.is_empty(cx))
19863 .unwrap_or(&self.display_map);
19864
19865 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19866 }
19867 self.style = Some(style);
19868 }
19869
19870 pub fn style(&self) -> Option<&EditorStyle> {
19871 self.style.as_ref()
19872 }
19873
19874 // Called by the element. This method is not designed to be called outside of the editor
19875 // element's layout code because it does not notify when rewrapping is computed synchronously.
19876 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19877 if self.is_empty(cx) {
19878 self.placeholder_display_map
19879 .as_ref()
19880 .map_or(false, |display_map| {
19881 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19882 })
19883 } else {
19884 self.display_map
19885 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19886 }
19887 }
19888
19889 pub fn set_soft_wrap(&mut self) {
19890 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19891 }
19892
19893 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19894 if self.soft_wrap_mode_override.is_some() {
19895 self.soft_wrap_mode_override.take();
19896 } else {
19897 let soft_wrap = match self.soft_wrap_mode(cx) {
19898 SoftWrap::GitDiff => return,
19899 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19900 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19901 language_settings::SoftWrap::None
19902 }
19903 };
19904 self.soft_wrap_mode_override = Some(soft_wrap);
19905 }
19906 cx.notify();
19907 }
19908
19909 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19910 let Some(workspace) = self.workspace() else {
19911 return;
19912 };
19913 let fs = workspace.read(cx).app_state().fs.clone();
19914 let current_show = TabBarSettings::get_global(cx).show;
19915 update_settings_file(fs, cx, move |setting, _| {
19916 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19917 });
19918 }
19919
19920 pub fn toggle_indent_guides(
19921 &mut self,
19922 _: &ToggleIndentGuides,
19923 _: &mut Window,
19924 cx: &mut Context<Self>,
19925 ) {
19926 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19927 self.buffer
19928 .read(cx)
19929 .language_settings(cx)
19930 .indent_guides
19931 .enabled
19932 });
19933 self.show_indent_guides = Some(!currently_enabled);
19934 cx.notify();
19935 }
19936
19937 fn should_show_indent_guides(&self) -> Option<bool> {
19938 self.show_indent_guides
19939 }
19940
19941 pub fn toggle_line_numbers(
19942 &mut self,
19943 _: &ToggleLineNumbers,
19944 _: &mut Window,
19945 cx: &mut Context<Self>,
19946 ) {
19947 let mut editor_settings = EditorSettings::get_global(cx).clone();
19948 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19949 EditorSettings::override_global(editor_settings, cx);
19950 }
19951
19952 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19953 if let Some(show_line_numbers) = self.show_line_numbers {
19954 return show_line_numbers;
19955 }
19956 EditorSettings::get_global(cx).gutter.line_numbers
19957 }
19958
19959 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19960 match (
19961 self.use_relative_line_numbers,
19962 EditorSettings::get_global(cx).relative_line_numbers,
19963 ) {
19964 (None, setting) => setting,
19965 (Some(false), _) => RelativeLineNumbers::Disabled,
19966 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19967 (Some(true), _) => RelativeLineNumbers::Enabled,
19968 }
19969 }
19970
19971 pub fn toggle_relative_line_numbers(
19972 &mut self,
19973 _: &ToggleRelativeLineNumbers,
19974 _: &mut Window,
19975 cx: &mut Context<Self>,
19976 ) {
19977 let is_relative = self.relative_line_numbers(cx);
19978 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19979 }
19980
19981 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19982 self.use_relative_line_numbers = is_relative;
19983 cx.notify();
19984 }
19985
19986 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19987 self.show_gutter = show_gutter;
19988 cx.notify();
19989 }
19990
19991 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19992 self.show_scrollbars = ScrollbarAxes {
19993 horizontal: show,
19994 vertical: show,
19995 };
19996 cx.notify();
19997 }
19998
19999 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20000 self.show_scrollbars.vertical = show;
20001 cx.notify();
20002 }
20003
20004 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20005 self.show_scrollbars.horizontal = show;
20006 cx.notify();
20007 }
20008
20009 pub fn set_minimap_visibility(
20010 &mut self,
20011 minimap_visibility: MinimapVisibility,
20012 window: &mut Window,
20013 cx: &mut Context<Self>,
20014 ) {
20015 if self.minimap_visibility != minimap_visibility {
20016 if minimap_visibility.visible() && self.minimap.is_none() {
20017 let minimap_settings = EditorSettings::get_global(cx).minimap;
20018 self.minimap =
20019 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20020 }
20021 self.minimap_visibility = minimap_visibility;
20022 cx.notify();
20023 }
20024 }
20025
20026 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20027 self.set_show_scrollbars(false, cx);
20028 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20029 }
20030
20031 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20032 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20033 }
20034
20035 /// Normally the text in full mode and auto height editors is padded on the
20036 /// left side by roughly half a character width for improved hit testing.
20037 ///
20038 /// Use this method to disable this for cases where this is not wanted (e.g.
20039 /// if you want to align the editor text with some other text above or below)
20040 /// or if you want to add this padding to single-line editors.
20041 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20042 self.offset_content = offset_content;
20043 cx.notify();
20044 }
20045
20046 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20047 self.show_line_numbers = Some(show_line_numbers);
20048 cx.notify();
20049 }
20050
20051 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20052 self.disable_expand_excerpt_buttons = true;
20053 cx.notify();
20054 }
20055
20056 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20057 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20058 cx.notify();
20059 }
20060
20061 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20062 self.show_code_actions = Some(show_code_actions);
20063 cx.notify();
20064 }
20065
20066 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20067 self.show_runnables = Some(show_runnables);
20068 cx.notify();
20069 }
20070
20071 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20072 self.show_breakpoints = Some(show_breakpoints);
20073 cx.notify();
20074 }
20075
20076 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20077 if self.display_map.read(cx).masked != masked {
20078 self.display_map.update(cx, |map, _| map.masked = masked);
20079 }
20080 cx.notify()
20081 }
20082
20083 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20084 self.show_wrap_guides = Some(show_wrap_guides);
20085 cx.notify();
20086 }
20087
20088 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20089 self.show_indent_guides = Some(show_indent_guides);
20090 cx.notify();
20091 }
20092
20093 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20094 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20095 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20096 && let Some(dir) = file.abs_path(cx).parent()
20097 {
20098 return Some(dir.to_owned());
20099 }
20100 }
20101
20102 None
20103 }
20104
20105 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20106 self.active_excerpt(cx)?
20107 .1
20108 .read(cx)
20109 .file()
20110 .and_then(|f| f.as_local())
20111 }
20112
20113 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20114 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20115 let buffer = buffer.read(cx);
20116 if let Some(project_path) = buffer.project_path(cx) {
20117 let project = self.project()?.read(cx);
20118 project.absolute_path(&project_path, cx)
20119 } else {
20120 buffer
20121 .file()
20122 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20123 }
20124 })
20125 }
20126
20127 pub fn reveal_in_finder(
20128 &mut self,
20129 _: &RevealInFileManager,
20130 _window: &mut Window,
20131 cx: &mut Context<Self>,
20132 ) {
20133 if let Some(target) = self.target_file(cx) {
20134 cx.reveal_path(&target.abs_path(cx));
20135 }
20136 }
20137
20138 pub fn copy_path(
20139 &mut self,
20140 _: &zed_actions::workspace::CopyPath,
20141 _window: &mut Window,
20142 cx: &mut Context<Self>,
20143 ) {
20144 if let Some(path) = self.target_file_abs_path(cx)
20145 && let Some(path) = path.to_str()
20146 {
20147 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20148 } else {
20149 cx.propagate();
20150 }
20151 }
20152
20153 pub fn copy_relative_path(
20154 &mut self,
20155 _: &zed_actions::workspace::CopyRelativePath,
20156 _window: &mut Window,
20157 cx: &mut Context<Self>,
20158 ) {
20159 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20160 let project = self.project()?.read(cx);
20161 let path = buffer.read(cx).file()?.path();
20162 let path = path.display(project.path_style(cx));
20163 Some(path)
20164 }) {
20165 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20166 } else {
20167 cx.propagate();
20168 }
20169 }
20170
20171 /// Returns the project path for the editor's buffer, if any buffer is
20172 /// opened in the editor.
20173 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20174 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20175 buffer.read(cx).project_path(cx)
20176 } else {
20177 None
20178 }
20179 }
20180
20181 // Returns true if the editor handled a go-to-line request
20182 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20183 maybe!({
20184 let breakpoint_store = self.breakpoint_store.as_ref()?;
20185
20186 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20187 else {
20188 self.clear_row_highlights::<ActiveDebugLine>();
20189 return None;
20190 };
20191
20192 let position = active_stack_frame.position;
20193 let buffer_id = position.buffer_id?;
20194 let snapshot = self
20195 .project
20196 .as_ref()?
20197 .read(cx)
20198 .buffer_for_id(buffer_id, cx)?
20199 .read(cx)
20200 .snapshot();
20201
20202 let mut handled = false;
20203 for (id, ExcerptRange { context, .. }) in
20204 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20205 {
20206 if context.start.cmp(&position, &snapshot).is_ge()
20207 || context.end.cmp(&position, &snapshot).is_lt()
20208 {
20209 continue;
20210 }
20211 let snapshot = self.buffer.read(cx).snapshot(cx);
20212 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20213
20214 handled = true;
20215 self.clear_row_highlights::<ActiveDebugLine>();
20216
20217 self.go_to_line::<ActiveDebugLine>(
20218 multibuffer_anchor,
20219 Some(cx.theme().colors().editor_debugger_active_line_background),
20220 window,
20221 cx,
20222 );
20223
20224 cx.notify();
20225 }
20226
20227 handled.then_some(())
20228 })
20229 .is_some()
20230 }
20231
20232 pub fn copy_file_name_without_extension(
20233 &mut self,
20234 _: &CopyFileNameWithoutExtension,
20235 _: &mut Window,
20236 cx: &mut Context<Self>,
20237 ) {
20238 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20239 let file = buffer.read(cx).file()?;
20240 file.path().file_stem()
20241 }) {
20242 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20243 }
20244 }
20245
20246 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20247 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20248 let file = buffer.read(cx).file()?;
20249 Some(file.file_name(cx))
20250 }) {
20251 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20252 }
20253 }
20254
20255 pub fn toggle_git_blame(
20256 &mut self,
20257 _: &::git::Blame,
20258 window: &mut Window,
20259 cx: &mut Context<Self>,
20260 ) {
20261 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20262
20263 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20264 self.start_git_blame(true, window, cx);
20265 }
20266
20267 cx.notify();
20268 }
20269
20270 pub fn toggle_git_blame_inline(
20271 &mut self,
20272 _: &ToggleGitBlameInline,
20273 window: &mut Window,
20274 cx: &mut Context<Self>,
20275 ) {
20276 self.toggle_git_blame_inline_internal(true, window, cx);
20277 cx.notify();
20278 }
20279
20280 pub fn open_git_blame_commit(
20281 &mut self,
20282 _: &OpenGitBlameCommit,
20283 window: &mut Window,
20284 cx: &mut Context<Self>,
20285 ) {
20286 self.open_git_blame_commit_internal(window, cx);
20287 }
20288
20289 fn open_git_blame_commit_internal(
20290 &mut self,
20291 window: &mut Window,
20292 cx: &mut Context<Self>,
20293 ) -> Option<()> {
20294 let blame = self.blame.as_ref()?;
20295 let snapshot = self.snapshot(window, cx);
20296 let cursor = self
20297 .selections
20298 .newest::<Point>(&snapshot.display_snapshot)
20299 .head();
20300 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20301 let (_, blame_entry) = blame
20302 .update(cx, |blame, cx| {
20303 blame
20304 .blame_for_rows(
20305 &[RowInfo {
20306 buffer_id: Some(buffer.remote_id()),
20307 buffer_row: Some(point.row),
20308 ..Default::default()
20309 }],
20310 cx,
20311 )
20312 .next()
20313 })
20314 .flatten()?;
20315 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20316 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20317 let workspace = self.workspace()?.downgrade();
20318 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20319 None
20320 }
20321
20322 pub fn git_blame_inline_enabled(&self) -> bool {
20323 self.git_blame_inline_enabled
20324 }
20325
20326 pub fn toggle_selection_menu(
20327 &mut self,
20328 _: &ToggleSelectionMenu,
20329 _: &mut Window,
20330 cx: &mut Context<Self>,
20331 ) {
20332 self.show_selection_menu = self
20333 .show_selection_menu
20334 .map(|show_selections_menu| !show_selections_menu)
20335 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20336
20337 cx.notify();
20338 }
20339
20340 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20341 self.show_selection_menu
20342 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20343 }
20344
20345 fn start_git_blame(
20346 &mut self,
20347 user_triggered: bool,
20348 window: &mut Window,
20349 cx: &mut Context<Self>,
20350 ) {
20351 if let Some(project) = self.project() {
20352 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20353 && buffer.read(cx).file().is_none()
20354 {
20355 return;
20356 }
20357
20358 let focused = self.focus_handle(cx).contains_focused(window, cx);
20359
20360 let project = project.clone();
20361 let blame = cx
20362 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20363 self.blame_subscription =
20364 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20365 self.blame = Some(blame);
20366 }
20367 }
20368
20369 fn toggle_git_blame_inline_internal(
20370 &mut self,
20371 user_triggered: bool,
20372 window: &mut Window,
20373 cx: &mut Context<Self>,
20374 ) {
20375 if self.git_blame_inline_enabled {
20376 self.git_blame_inline_enabled = false;
20377 self.show_git_blame_inline = false;
20378 self.show_git_blame_inline_delay_task.take();
20379 } else {
20380 self.git_blame_inline_enabled = true;
20381 self.start_git_blame_inline(user_triggered, window, cx);
20382 }
20383
20384 cx.notify();
20385 }
20386
20387 fn start_git_blame_inline(
20388 &mut self,
20389 user_triggered: bool,
20390 window: &mut Window,
20391 cx: &mut Context<Self>,
20392 ) {
20393 self.start_git_blame(user_triggered, window, cx);
20394
20395 if ProjectSettings::get_global(cx)
20396 .git
20397 .inline_blame_delay()
20398 .is_some()
20399 {
20400 self.start_inline_blame_timer(window, cx);
20401 } else {
20402 self.show_git_blame_inline = true
20403 }
20404 }
20405
20406 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20407 self.blame.as_ref()
20408 }
20409
20410 pub fn show_git_blame_gutter(&self) -> bool {
20411 self.show_git_blame_gutter
20412 }
20413
20414 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20415 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20416 }
20417
20418 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20419 self.show_git_blame_inline
20420 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20421 && !self.newest_selection_head_on_empty_line(cx)
20422 && self.has_blame_entries(cx)
20423 }
20424
20425 fn has_blame_entries(&self, cx: &App) -> bool {
20426 self.blame()
20427 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20428 }
20429
20430 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20431 let cursor_anchor = self.selections.newest_anchor().head();
20432
20433 let snapshot = self.buffer.read(cx).snapshot(cx);
20434 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20435
20436 snapshot.line_len(buffer_row) == 0
20437 }
20438
20439 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20440 let buffer_and_selection = maybe!({
20441 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20442 let selection_range = selection.range();
20443
20444 let multi_buffer = self.buffer().read(cx);
20445 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20446 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20447
20448 let (buffer, range, _) = if selection.reversed {
20449 buffer_ranges.first()
20450 } else {
20451 buffer_ranges.last()
20452 }?;
20453
20454 let selection = text::ToPoint::to_point(&range.start, buffer).row
20455 ..text::ToPoint::to_point(&range.end, buffer).row;
20456 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20457 });
20458
20459 let Some((buffer, selection)) = buffer_and_selection else {
20460 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20461 };
20462
20463 let Some(project) = self.project() else {
20464 return Task::ready(Err(anyhow!("editor does not have project")));
20465 };
20466
20467 project.update(cx, |project, cx| {
20468 project.get_permalink_to_line(&buffer, selection, cx)
20469 })
20470 }
20471
20472 pub fn copy_permalink_to_line(
20473 &mut self,
20474 _: &CopyPermalinkToLine,
20475 window: &mut Window,
20476 cx: &mut Context<Self>,
20477 ) {
20478 let permalink_task = self.get_permalink_to_line(cx);
20479 let workspace = self.workspace();
20480
20481 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20482 Ok(permalink) => {
20483 cx.update(|_, cx| {
20484 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20485 })
20486 .ok();
20487 }
20488 Err(err) => {
20489 let message = format!("Failed to copy permalink: {err}");
20490
20491 anyhow::Result::<()>::Err(err).log_err();
20492
20493 if let Some(workspace) = workspace {
20494 workspace
20495 .update_in(cx, |workspace, _, cx| {
20496 struct CopyPermalinkToLine;
20497
20498 workspace.show_toast(
20499 Toast::new(
20500 NotificationId::unique::<CopyPermalinkToLine>(),
20501 message,
20502 ),
20503 cx,
20504 )
20505 })
20506 .ok();
20507 }
20508 }
20509 })
20510 .detach();
20511 }
20512
20513 pub fn copy_file_location(
20514 &mut self,
20515 _: &CopyFileLocation,
20516 _: &mut Window,
20517 cx: &mut Context<Self>,
20518 ) {
20519 let selection = self
20520 .selections
20521 .newest::<Point>(&self.display_snapshot(cx))
20522 .start
20523 .row
20524 + 1;
20525 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20526 let project = self.project()?.read(cx);
20527 let file = buffer.read(cx).file()?;
20528 let path = file.path().display(project.path_style(cx));
20529
20530 Some(format!("{path}:{selection}"))
20531 }) {
20532 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
20533 }
20534 }
20535
20536 pub fn open_permalink_to_line(
20537 &mut self,
20538 _: &OpenPermalinkToLine,
20539 window: &mut Window,
20540 cx: &mut Context<Self>,
20541 ) {
20542 let permalink_task = self.get_permalink_to_line(cx);
20543 let workspace = self.workspace();
20544
20545 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20546 Ok(permalink) => {
20547 cx.update(|_, cx| {
20548 cx.open_url(permalink.as_ref());
20549 })
20550 .ok();
20551 }
20552 Err(err) => {
20553 let message = format!("Failed to open permalink: {err}");
20554
20555 anyhow::Result::<()>::Err(err).log_err();
20556
20557 if let Some(workspace) = workspace {
20558 workspace
20559 .update(cx, |workspace, cx| {
20560 struct OpenPermalinkToLine;
20561
20562 workspace.show_toast(
20563 Toast::new(
20564 NotificationId::unique::<OpenPermalinkToLine>(),
20565 message,
20566 ),
20567 cx,
20568 )
20569 })
20570 .ok();
20571 }
20572 }
20573 })
20574 .detach();
20575 }
20576
20577 pub fn insert_uuid_v4(
20578 &mut self,
20579 _: &InsertUuidV4,
20580 window: &mut Window,
20581 cx: &mut Context<Self>,
20582 ) {
20583 self.insert_uuid(UuidVersion::V4, window, cx);
20584 }
20585
20586 pub fn insert_uuid_v7(
20587 &mut self,
20588 _: &InsertUuidV7,
20589 window: &mut Window,
20590 cx: &mut Context<Self>,
20591 ) {
20592 self.insert_uuid(UuidVersion::V7, window, cx);
20593 }
20594
20595 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20596 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20597 self.transact(window, cx, |this, window, cx| {
20598 let edits = this
20599 .selections
20600 .all::<Point>(&this.display_snapshot(cx))
20601 .into_iter()
20602 .map(|selection| {
20603 let uuid = match version {
20604 UuidVersion::V4 => uuid::Uuid::new_v4(),
20605 UuidVersion::V7 => uuid::Uuid::now_v7(),
20606 };
20607
20608 (selection.range(), uuid.to_string())
20609 });
20610 this.edit(edits, cx);
20611 this.refresh_edit_prediction(true, false, window, cx);
20612 });
20613 }
20614
20615 pub fn open_selections_in_multibuffer(
20616 &mut self,
20617 _: &OpenSelectionsInMultibuffer,
20618 window: &mut Window,
20619 cx: &mut Context<Self>,
20620 ) {
20621 let multibuffer = self.buffer.read(cx);
20622
20623 let Some(buffer) = multibuffer.as_singleton() else {
20624 return;
20625 };
20626
20627 let Some(workspace) = self.workspace() else {
20628 return;
20629 };
20630
20631 let title = multibuffer.title(cx).to_string();
20632
20633 let locations = self
20634 .selections
20635 .all_anchors(&self.display_snapshot(cx))
20636 .iter()
20637 .map(|selection| {
20638 (
20639 buffer.clone(),
20640 (selection.start.text_anchor..selection.end.text_anchor)
20641 .to_point(buffer.read(cx)),
20642 )
20643 })
20644 .into_group_map();
20645
20646 cx.spawn_in(window, async move |_, cx| {
20647 workspace.update_in(cx, |workspace, window, cx| {
20648 Self::open_locations_in_multibuffer(
20649 workspace,
20650 locations,
20651 format!("Selections for '{title}'"),
20652 false,
20653 MultibufferSelectionMode::All,
20654 window,
20655 cx,
20656 );
20657 })
20658 })
20659 .detach();
20660 }
20661
20662 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20663 /// last highlight added will be used.
20664 ///
20665 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20666 pub fn highlight_rows<T: 'static>(
20667 &mut self,
20668 range: Range<Anchor>,
20669 color: Hsla,
20670 options: RowHighlightOptions,
20671 cx: &mut Context<Self>,
20672 ) {
20673 let snapshot = self.buffer().read(cx).snapshot(cx);
20674 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20675 let ix = row_highlights.binary_search_by(|highlight| {
20676 Ordering::Equal
20677 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20678 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20679 });
20680
20681 if let Err(mut ix) = ix {
20682 let index = post_inc(&mut self.highlight_order);
20683
20684 // If this range intersects with the preceding highlight, then merge it with
20685 // the preceding highlight. Otherwise insert a new highlight.
20686 let mut merged = false;
20687 if ix > 0 {
20688 let prev_highlight = &mut row_highlights[ix - 1];
20689 if prev_highlight
20690 .range
20691 .end
20692 .cmp(&range.start, &snapshot)
20693 .is_ge()
20694 {
20695 ix -= 1;
20696 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20697 prev_highlight.range.end = range.end;
20698 }
20699 merged = true;
20700 prev_highlight.index = index;
20701 prev_highlight.color = color;
20702 prev_highlight.options = options;
20703 }
20704 }
20705
20706 if !merged {
20707 row_highlights.insert(
20708 ix,
20709 RowHighlight {
20710 range,
20711 index,
20712 color,
20713 options,
20714 type_id: TypeId::of::<T>(),
20715 },
20716 );
20717 }
20718
20719 // If any of the following highlights intersect with this one, merge them.
20720 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20721 let highlight = &row_highlights[ix];
20722 if next_highlight
20723 .range
20724 .start
20725 .cmp(&highlight.range.end, &snapshot)
20726 .is_le()
20727 {
20728 if next_highlight
20729 .range
20730 .end
20731 .cmp(&highlight.range.end, &snapshot)
20732 .is_gt()
20733 {
20734 row_highlights[ix].range.end = next_highlight.range.end;
20735 }
20736 row_highlights.remove(ix + 1);
20737 } else {
20738 break;
20739 }
20740 }
20741 }
20742 }
20743
20744 /// Remove any highlighted row ranges of the given type that intersect the
20745 /// given ranges.
20746 pub fn remove_highlighted_rows<T: 'static>(
20747 &mut self,
20748 ranges_to_remove: Vec<Range<Anchor>>,
20749 cx: &mut Context<Self>,
20750 ) {
20751 let snapshot = self.buffer().read(cx).snapshot(cx);
20752 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20753 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20754 row_highlights.retain(|highlight| {
20755 while let Some(range_to_remove) = ranges_to_remove.peek() {
20756 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20757 Ordering::Less | Ordering::Equal => {
20758 ranges_to_remove.next();
20759 }
20760 Ordering::Greater => {
20761 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20762 Ordering::Less | Ordering::Equal => {
20763 return false;
20764 }
20765 Ordering::Greater => break,
20766 }
20767 }
20768 }
20769 }
20770
20771 true
20772 })
20773 }
20774
20775 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20776 pub fn clear_row_highlights<T: 'static>(&mut self) {
20777 self.highlighted_rows.remove(&TypeId::of::<T>());
20778 }
20779
20780 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20781 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20782 self.highlighted_rows
20783 .get(&TypeId::of::<T>())
20784 .map_or(&[] as &[_], |vec| vec.as_slice())
20785 .iter()
20786 .map(|highlight| (highlight.range.clone(), highlight.color))
20787 }
20788
20789 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20790 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20791 /// Allows to ignore certain kinds of highlights.
20792 pub fn highlighted_display_rows(
20793 &self,
20794 window: &mut Window,
20795 cx: &mut App,
20796 ) -> BTreeMap<DisplayRow, LineHighlight> {
20797 let snapshot = self.snapshot(window, cx);
20798 let mut used_highlight_orders = HashMap::default();
20799 self.highlighted_rows
20800 .iter()
20801 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20802 .fold(
20803 BTreeMap::<DisplayRow, LineHighlight>::new(),
20804 |mut unique_rows, highlight| {
20805 let start = highlight.range.start.to_display_point(&snapshot);
20806 let end = highlight.range.end.to_display_point(&snapshot);
20807 let start_row = start.row().0;
20808 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
20809 {
20810 end.row().0.saturating_sub(1)
20811 } else {
20812 end.row().0
20813 };
20814 for row in start_row..=end_row {
20815 let used_index =
20816 used_highlight_orders.entry(row).or_insert(highlight.index);
20817 if highlight.index >= *used_index {
20818 *used_index = highlight.index;
20819 unique_rows.insert(
20820 DisplayRow(row),
20821 LineHighlight {
20822 include_gutter: highlight.options.include_gutter,
20823 border: None,
20824 background: highlight.color.into(),
20825 type_id: Some(highlight.type_id),
20826 },
20827 );
20828 }
20829 }
20830 unique_rows
20831 },
20832 )
20833 }
20834
20835 pub fn highlighted_display_row_for_autoscroll(
20836 &self,
20837 snapshot: &DisplaySnapshot,
20838 ) -> Option<DisplayRow> {
20839 self.highlighted_rows
20840 .values()
20841 .flat_map(|highlighted_rows| highlighted_rows.iter())
20842 .filter_map(|highlight| {
20843 if highlight.options.autoscroll {
20844 Some(highlight.range.start.to_display_point(snapshot).row())
20845 } else {
20846 None
20847 }
20848 })
20849 .min()
20850 }
20851
20852 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20853 self.highlight_background::<SearchWithinRange>(
20854 ranges,
20855 |colors| colors.colors().editor_document_highlight_read_background,
20856 cx,
20857 )
20858 }
20859
20860 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20861 self.breadcrumb_header = Some(new_header);
20862 }
20863
20864 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20865 self.clear_background_highlights::<SearchWithinRange>(cx);
20866 }
20867
20868 pub fn highlight_background<T: 'static>(
20869 &mut self,
20870 ranges: &[Range<Anchor>],
20871 color_fetcher: fn(&Theme) -> Hsla,
20872 cx: &mut Context<Self>,
20873 ) {
20874 self.background_highlights.insert(
20875 HighlightKey::Type(TypeId::of::<T>()),
20876 (color_fetcher, Arc::from(ranges)),
20877 );
20878 self.scrollbar_marker_state.dirty = true;
20879 cx.notify();
20880 }
20881
20882 pub fn highlight_background_key<T: 'static>(
20883 &mut self,
20884 key: usize,
20885 ranges: &[Range<Anchor>],
20886 color_fetcher: fn(&Theme) -> Hsla,
20887 cx: &mut Context<Self>,
20888 ) {
20889 self.background_highlights.insert(
20890 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20891 (color_fetcher, Arc::from(ranges)),
20892 );
20893 self.scrollbar_marker_state.dirty = true;
20894 cx.notify();
20895 }
20896
20897 pub fn clear_background_highlights<T: 'static>(
20898 &mut self,
20899 cx: &mut Context<Self>,
20900 ) -> Option<BackgroundHighlight> {
20901 let text_highlights = self
20902 .background_highlights
20903 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20904 if !text_highlights.1.is_empty() {
20905 self.scrollbar_marker_state.dirty = true;
20906 cx.notify();
20907 }
20908 Some(text_highlights)
20909 }
20910
20911 pub fn highlight_gutter<T: 'static>(
20912 &mut self,
20913 ranges: impl Into<Vec<Range<Anchor>>>,
20914 color_fetcher: fn(&App) -> Hsla,
20915 cx: &mut Context<Self>,
20916 ) {
20917 self.gutter_highlights
20918 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20919 cx.notify();
20920 }
20921
20922 pub fn clear_gutter_highlights<T: 'static>(
20923 &mut self,
20924 cx: &mut Context<Self>,
20925 ) -> Option<GutterHighlight> {
20926 cx.notify();
20927 self.gutter_highlights.remove(&TypeId::of::<T>())
20928 }
20929
20930 pub fn insert_gutter_highlight<T: 'static>(
20931 &mut self,
20932 range: Range<Anchor>,
20933 color_fetcher: fn(&App) -> Hsla,
20934 cx: &mut Context<Self>,
20935 ) {
20936 let snapshot = self.buffer().read(cx).snapshot(cx);
20937 let mut highlights = self
20938 .gutter_highlights
20939 .remove(&TypeId::of::<T>())
20940 .map(|(_, highlights)| highlights)
20941 .unwrap_or_default();
20942 let ix = highlights.binary_search_by(|highlight| {
20943 Ordering::Equal
20944 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20945 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20946 });
20947 if let Err(ix) = ix {
20948 highlights.insert(ix, range);
20949 }
20950 self.gutter_highlights
20951 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20952 }
20953
20954 pub fn remove_gutter_highlights<T: 'static>(
20955 &mut self,
20956 ranges_to_remove: Vec<Range<Anchor>>,
20957 cx: &mut Context<Self>,
20958 ) {
20959 let snapshot = self.buffer().read(cx).snapshot(cx);
20960 let Some((color_fetcher, mut gutter_highlights)) =
20961 self.gutter_highlights.remove(&TypeId::of::<T>())
20962 else {
20963 return;
20964 };
20965 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20966 gutter_highlights.retain(|highlight| {
20967 while let Some(range_to_remove) = ranges_to_remove.peek() {
20968 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20969 Ordering::Less | Ordering::Equal => {
20970 ranges_to_remove.next();
20971 }
20972 Ordering::Greater => {
20973 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20974 Ordering::Less | Ordering::Equal => {
20975 return false;
20976 }
20977 Ordering::Greater => break,
20978 }
20979 }
20980 }
20981 }
20982
20983 true
20984 });
20985 self.gutter_highlights
20986 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20987 }
20988
20989 #[cfg(feature = "test-support")]
20990 pub fn all_text_highlights(
20991 &self,
20992 window: &mut Window,
20993 cx: &mut Context<Self>,
20994 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20995 let snapshot = self.snapshot(window, cx);
20996 self.display_map.update(cx, |display_map, _| {
20997 display_map
20998 .all_text_highlights()
20999 .map(|highlight| {
21000 let (style, ranges) = highlight.as_ref();
21001 (
21002 *style,
21003 ranges
21004 .iter()
21005 .map(|range| range.clone().to_display_points(&snapshot))
21006 .collect(),
21007 )
21008 })
21009 .collect()
21010 })
21011 }
21012
21013 #[cfg(feature = "test-support")]
21014 pub fn all_text_background_highlights(
21015 &self,
21016 window: &mut Window,
21017 cx: &mut Context<Self>,
21018 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21019 let snapshot = self.snapshot(window, cx);
21020 let buffer = &snapshot.buffer_snapshot();
21021 let start = buffer.anchor_before(MultiBufferOffset(0));
21022 let end = buffer.anchor_after(buffer.len());
21023 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21024 }
21025
21026 #[cfg(any(test, feature = "test-support"))]
21027 pub fn sorted_background_highlights_in_range(
21028 &self,
21029 search_range: Range<Anchor>,
21030 display_snapshot: &DisplaySnapshot,
21031 theme: &Theme,
21032 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21033 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21034 res.sort_by(|a, b| {
21035 a.0.start
21036 .cmp(&b.0.start)
21037 .then_with(|| a.0.end.cmp(&b.0.end))
21038 .then_with(|| a.1.cmp(&b.1))
21039 });
21040 res
21041 }
21042
21043 #[cfg(feature = "test-support")]
21044 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21045 let snapshot = self.buffer().read(cx).snapshot(cx);
21046
21047 let highlights = self
21048 .background_highlights
21049 .get(&HighlightKey::Type(TypeId::of::<
21050 items::BufferSearchHighlights,
21051 >()));
21052
21053 if let Some((_color, ranges)) = highlights {
21054 ranges
21055 .iter()
21056 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21057 .collect_vec()
21058 } else {
21059 vec![]
21060 }
21061 }
21062
21063 fn document_highlights_for_position<'a>(
21064 &'a self,
21065 position: Anchor,
21066 buffer: &'a MultiBufferSnapshot,
21067 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21068 let read_highlights = self
21069 .background_highlights
21070 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21071 .map(|h| &h.1);
21072 let write_highlights = self
21073 .background_highlights
21074 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21075 .map(|h| &h.1);
21076 let left_position = position.bias_left(buffer);
21077 let right_position = position.bias_right(buffer);
21078 read_highlights
21079 .into_iter()
21080 .chain(write_highlights)
21081 .flat_map(move |ranges| {
21082 let start_ix = match ranges.binary_search_by(|probe| {
21083 let cmp = probe.end.cmp(&left_position, buffer);
21084 if cmp.is_ge() {
21085 Ordering::Greater
21086 } else {
21087 Ordering::Less
21088 }
21089 }) {
21090 Ok(i) | Err(i) => i,
21091 };
21092
21093 ranges[start_ix..]
21094 .iter()
21095 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21096 })
21097 }
21098
21099 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21100 self.background_highlights
21101 .get(&HighlightKey::Type(TypeId::of::<T>()))
21102 .is_some_and(|(_, highlights)| !highlights.is_empty())
21103 }
21104
21105 /// Returns all background highlights for a given range.
21106 ///
21107 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21108 pub fn background_highlights_in_range(
21109 &self,
21110 search_range: Range<Anchor>,
21111 display_snapshot: &DisplaySnapshot,
21112 theme: &Theme,
21113 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21114 let mut results = Vec::new();
21115 for (color_fetcher, ranges) in self.background_highlights.values() {
21116 let color = color_fetcher(theme);
21117 let start_ix = match ranges.binary_search_by(|probe| {
21118 let cmp = probe
21119 .end
21120 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21121 if cmp.is_gt() {
21122 Ordering::Greater
21123 } else {
21124 Ordering::Less
21125 }
21126 }) {
21127 Ok(i) | Err(i) => i,
21128 };
21129 for range in &ranges[start_ix..] {
21130 if range
21131 .start
21132 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21133 .is_ge()
21134 {
21135 break;
21136 }
21137
21138 let start = range.start.to_display_point(display_snapshot);
21139 let end = range.end.to_display_point(display_snapshot);
21140 results.push((start..end, color))
21141 }
21142 }
21143 results
21144 }
21145
21146 pub fn gutter_highlights_in_range(
21147 &self,
21148 search_range: Range<Anchor>,
21149 display_snapshot: &DisplaySnapshot,
21150 cx: &App,
21151 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21152 let mut results = Vec::new();
21153 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21154 let color = color_fetcher(cx);
21155 let start_ix = match ranges.binary_search_by(|probe| {
21156 let cmp = probe
21157 .end
21158 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21159 if cmp.is_gt() {
21160 Ordering::Greater
21161 } else {
21162 Ordering::Less
21163 }
21164 }) {
21165 Ok(i) | Err(i) => i,
21166 };
21167 for range in &ranges[start_ix..] {
21168 if range
21169 .start
21170 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21171 .is_ge()
21172 {
21173 break;
21174 }
21175
21176 let start = range.start.to_display_point(display_snapshot);
21177 let end = range.end.to_display_point(display_snapshot);
21178 results.push((start..end, color))
21179 }
21180 }
21181 results
21182 }
21183
21184 /// Get the text ranges corresponding to the redaction query
21185 pub fn redacted_ranges(
21186 &self,
21187 search_range: Range<Anchor>,
21188 display_snapshot: &DisplaySnapshot,
21189 cx: &App,
21190 ) -> Vec<Range<DisplayPoint>> {
21191 display_snapshot
21192 .buffer_snapshot()
21193 .redacted_ranges(search_range, |file| {
21194 if let Some(file) = file {
21195 file.is_private()
21196 && EditorSettings::get(
21197 Some(SettingsLocation {
21198 worktree_id: file.worktree_id(cx),
21199 path: file.path().as_ref(),
21200 }),
21201 cx,
21202 )
21203 .redact_private_values
21204 } else {
21205 false
21206 }
21207 })
21208 .map(|range| {
21209 range.start.to_display_point(display_snapshot)
21210 ..range.end.to_display_point(display_snapshot)
21211 })
21212 .collect()
21213 }
21214
21215 pub fn highlight_text_key<T: 'static>(
21216 &mut self,
21217 key: usize,
21218 ranges: Vec<Range<Anchor>>,
21219 style: HighlightStyle,
21220 merge: bool,
21221 cx: &mut Context<Self>,
21222 ) {
21223 self.display_map.update(cx, |map, cx| {
21224 map.highlight_text(
21225 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21226 ranges,
21227 style,
21228 merge,
21229 cx,
21230 );
21231 });
21232 cx.notify();
21233 }
21234
21235 pub fn highlight_text<T: 'static>(
21236 &mut self,
21237 ranges: Vec<Range<Anchor>>,
21238 style: HighlightStyle,
21239 cx: &mut Context<Self>,
21240 ) {
21241 self.display_map.update(cx, |map, cx| {
21242 map.highlight_text(
21243 HighlightKey::Type(TypeId::of::<T>()),
21244 ranges,
21245 style,
21246 false,
21247 cx,
21248 )
21249 });
21250 cx.notify();
21251 }
21252
21253 pub fn text_highlights<'a, T: 'static>(
21254 &'a self,
21255 cx: &'a App,
21256 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21257 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21258 }
21259
21260 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21261 let cleared = self
21262 .display_map
21263 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21264 if cleared {
21265 cx.notify();
21266 }
21267 }
21268
21269 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21270 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21271 && self.focus_handle.is_focused(window)
21272 }
21273
21274 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21275 self.show_cursor_when_unfocused = is_enabled;
21276 cx.notify();
21277 }
21278
21279 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21280 cx.notify();
21281 }
21282
21283 fn on_debug_session_event(
21284 &mut self,
21285 _session: Entity<Session>,
21286 event: &SessionEvent,
21287 cx: &mut Context<Self>,
21288 ) {
21289 if let SessionEvent::InvalidateInlineValue = event {
21290 self.refresh_inline_values(cx);
21291 }
21292 }
21293
21294 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21295 let Some(project) = self.project.clone() else {
21296 return;
21297 };
21298
21299 if !self.inline_value_cache.enabled {
21300 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21301 self.splice_inlays(&inlays, Vec::new(), cx);
21302 return;
21303 }
21304
21305 let current_execution_position = self
21306 .highlighted_rows
21307 .get(&TypeId::of::<ActiveDebugLine>())
21308 .and_then(|lines| lines.last().map(|line| line.range.end));
21309
21310 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21311 let inline_values = editor
21312 .update(cx, |editor, cx| {
21313 let Some(current_execution_position) = current_execution_position else {
21314 return Some(Task::ready(Ok(Vec::new())));
21315 };
21316
21317 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21318 let snapshot = buffer.snapshot(cx);
21319
21320 let excerpt = snapshot.excerpt_containing(
21321 current_execution_position..current_execution_position,
21322 )?;
21323
21324 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21325 })?;
21326
21327 let range =
21328 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21329
21330 project.inline_values(buffer, range, cx)
21331 })
21332 .ok()
21333 .flatten()?
21334 .await
21335 .context("refreshing debugger inlays")
21336 .log_err()?;
21337
21338 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21339
21340 for (buffer_id, inline_value) in inline_values
21341 .into_iter()
21342 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21343 {
21344 buffer_inline_values
21345 .entry(buffer_id)
21346 .or_default()
21347 .push(inline_value);
21348 }
21349
21350 editor
21351 .update(cx, |editor, cx| {
21352 let snapshot = editor.buffer.read(cx).snapshot(cx);
21353 let mut new_inlays = Vec::default();
21354
21355 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21356 let buffer_id = buffer_snapshot.remote_id();
21357 buffer_inline_values
21358 .get(&buffer_id)
21359 .into_iter()
21360 .flatten()
21361 .for_each(|hint| {
21362 let inlay = Inlay::debugger(
21363 post_inc(&mut editor.next_inlay_id),
21364 Anchor::in_buffer(excerpt_id, hint.position),
21365 hint.text(),
21366 );
21367 if !inlay.text().chars().contains(&'\n') {
21368 new_inlays.push(inlay);
21369 }
21370 });
21371 }
21372
21373 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21374 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21375
21376 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21377 })
21378 .ok()?;
21379 Some(())
21380 });
21381 }
21382
21383 fn on_buffer_event(
21384 &mut self,
21385 multibuffer: &Entity<MultiBuffer>,
21386 event: &multi_buffer::Event,
21387 window: &mut Window,
21388 cx: &mut Context<Self>,
21389 ) {
21390 match event {
21391 multi_buffer::Event::Edited { edited_buffer } => {
21392 self.scrollbar_marker_state.dirty = true;
21393 self.active_indent_guides_state.dirty = true;
21394 self.refresh_active_diagnostics(cx);
21395 self.refresh_code_actions(window, cx);
21396 self.refresh_single_line_folds(window, cx);
21397 self.refresh_matching_bracket_highlights(window, cx);
21398 if self.has_active_edit_prediction() {
21399 self.update_visible_edit_prediction(window, cx);
21400 }
21401
21402 if let Some(buffer) = edited_buffer {
21403 if buffer.read(cx).file().is_none() {
21404 cx.emit(EditorEvent::TitleChanged);
21405 }
21406
21407 if self.project.is_some() {
21408 let buffer_id = buffer.read(cx).remote_id();
21409 self.register_buffer(buffer_id, cx);
21410 self.update_lsp_data(Some(buffer_id), window, cx);
21411 self.refresh_inlay_hints(
21412 InlayHintRefreshReason::BufferEdited(buffer_id),
21413 cx,
21414 );
21415 }
21416 }
21417
21418 cx.emit(EditorEvent::BufferEdited);
21419 cx.emit(SearchEvent::MatchesInvalidated);
21420
21421 let Some(project) = &self.project else { return };
21422 let (telemetry, is_via_ssh) = {
21423 let project = project.read(cx);
21424 let telemetry = project.client().telemetry().clone();
21425 let is_via_ssh = project.is_via_remote_server();
21426 (telemetry, is_via_ssh)
21427 };
21428 telemetry.log_edit_event("editor", is_via_ssh);
21429 }
21430 multi_buffer::Event::ExcerptsAdded {
21431 buffer,
21432 predecessor,
21433 excerpts,
21434 } => {
21435 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21436 let buffer_id = buffer.read(cx).remote_id();
21437 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21438 && let Some(project) = &self.project
21439 {
21440 update_uncommitted_diff_for_buffer(
21441 cx.entity(),
21442 project,
21443 [buffer.clone()],
21444 self.buffer.clone(),
21445 cx,
21446 )
21447 .detach();
21448 }
21449 self.update_lsp_data(Some(buffer_id), window, cx);
21450 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21451 self.colorize_brackets(false, cx);
21452 cx.emit(EditorEvent::ExcerptsAdded {
21453 buffer: buffer.clone(),
21454 predecessor: *predecessor,
21455 excerpts: excerpts.clone(),
21456 });
21457 }
21458 multi_buffer::Event::ExcerptsRemoved {
21459 ids,
21460 removed_buffer_ids,
21461 } => {
21462 if let Some(inlay_hints) = &mut self.inlay_hints {
21463 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21464 }
21465 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21466 for buffer_id in removed_buffer_ids {
21467 self.registered_buffers.remove(buffer_id);
21468 }
21469 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21470 cx.emit(EditorEvent::ExcerptsRemoved {
21471 ids: ids.clone(),
21472 removed_buffer_ids: removed_buffer_ids.clone(),
21473 });
21474 }
21475 multi_buffer::Event::ExcerptsEdited {
21476 excerpt_ids,
21477 buffer_ids,
21478 } => {
21479 self.display_map.update(cx, |map, cx| {
21480 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21481 });
21482 cx.emit(EditorEvent::ExcerptsEdited {
21483 ids: excerpt_ids.clone(),
21484 });
21485 }
21486 multi_buffer::Event::ExcerptsExpanded { ids } => {
21487 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21488 self.refresh_document_highlights(cx);
21489 for id in ids {
21490 self.fetched_tree_sitter_chunks.remove(id);
21491 }
21492 self.colorize_brackets(false, cx);
21493 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21494 }
21495 multi_buffer::Event::Reparsed(buffer_id) => {
21496 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21497 self.refresh_selected_text_highlights(true, window, cx);
21498 self.colorize_brackets(true, cx);
21499 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21500
21501 cx.emit(EditorEvent::Reparsed(*buffer_id));
21502 }
21503 multi_buffer::Event::DiffHunksToggled => {
21504 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21505 }
21506 multi_buffer::Event::LanguageChanged(buffer_id) => {
21507 self.registered_buffers.remove(&buffer_id);
21508 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21509 cx.emit(EditorEvent::Reparsed(*buffer_id));
21510 cx.notify();
21511 }
21512 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21513 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21514 multi_buffer::Event::FileHandleChanged
21515 | multi_buffer::Event::Reloaded
21516 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21517 multi_buffer::Event::DiagnosticsUpdated => {
21518 self.update_diagnostics_state(window, cx);
21519 }
21520 _ => {}
21521 };
21522 }
21523
21524 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21525 if !self.diagnostics_enabled() {
21526 return;
21527 }
21528 self.refresh_active_diagnostics(cx);
21529 self.refresh_inline_diagnostics(true, window, cx);
21530 self.scrollbar_marker_state.dirty = true;
21531 cx.notify();
21532 }
21533
21534 pub fn start_temporary_diff_override(&mut self) {
21535 self.load_diff_task.take();
21536 self.temporary_diff_override = true;
21537 }
21538
21539 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21540 self.temporary_diff_override = false;
21541 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21542 self.buffer.update(cx, |buffer, cx| {
21543 buffer.set_all_diff_hunks_collapsed(cx);
21544 });
21545
21546 if let Some(project) = self.project.clone() {
21547 self.load_diff_task = Some(
21548 update_uncommitted_diff_for_buffer(
21549 cx.entity(),
21550 &project,
21551 self.buffer.read(cx).all_buffers(),
21552 self.buffer.clone(),
21553 cx,
21554 )
21555 .shared(),
21556 );
21557 }
21558 }
21559
21560 fn on_display_map_changed(
21561 &mut self,
21562 _: Entity<DisplayMap>,
21563 _: &mut Window,
21564 cx: &mut Context<Self>,
21565 ) {
21566 cx.notify();
21567 }
21568
21569 fn fetch_accent_overrides(&self, cx: &App) -> Vec<SharedString> {
21570 if !self.mode.is_full() {
21571 return Vec::new();
21572 }
21573
21574 let theme_settings = theme::ThemeSettings::get_global(cx);
21575
21576 theme_settings
21577 .theme_overrides
21578 .get(cx.theme().name.as_ref())
21579 .map(|theme_style| &theme_style.accents)
21580 .into_iter()
21581 .flatten()
21582 .chain(
21583 theme_settings
21584 .experimental_theme_overrides
21585 .as_ref()
21586 .map(|overrides| &overrides.accents)
21587 .into_iter()
21588 .flatten(),
21589 )
21590 .flat_map(|accent| accent.0.clone())
21591 .collect()
21592 }
21593
21594 fn fetch_applicable_language_settings(
21595 &self,
21596 cx: &App,
21597 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21598 if !self.mode.is_full() {
21599 return HashMap::default();
21600 }
21601
21602 self.buffer().read(cx).all_buffers().into_iter().fold(
21603 HashMap::default(),
21604 |mut acc, buffer| {
21605 let buffer = buffer.read(cx);
21606 let language = buffer.language().map(|language| language.name());
21607 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21608 let file = buffer.file();
21609 v.insert(language_settings(language, file, cx).into_owned());
21610 }
21611 acc
21612 },
21613 )
21614 }
21615
21616 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21617 let new_language_settings = self.fetch_applicable_language_settings(cx);
21618 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21619 self.applicable_language_settings = new_language_settings;
21620
21621 let new_accent_overrides = self.fetch_accent_overrides(cx);
21622 let accent_overrides_changed = new_accent_overrides != self.accent_overrides;
21623 self.accent_overrides = new_accent_overrides;
21624
21625 if self.diagnostics_enabled() {
21626 let new_severity = EditorSettings::get_global(cx)
21627 .diagnostics_max_severity
21628 .unwrap_or(DiagnosticSeverity::Hint);
21629 self.set_max_diagnostics_severity(new_severity, cx);
21630 }
21631 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21632 self.update_edit_prediction_settings(cx);
21633 self.refresh_edit_prediction(true, false, window, cx);
21634 self.refresh_inline_values(cx);
21635 self.refresh_inlay_hints(
21636 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21637 self.selections.newest_anchor().head(),
21638 &self.buffer.read(cx).snapshot(cx),
21639 cx,
21640 )),
21641 cx,
21642 );
21643
21644 let old_cursor_shape = self.cursor_shape;
21645 let old_show_breadcrumbs = self.show_breadcrumbs;
21646
21647 {
21648 let editor_settings = EditorSettings::get_global(cx);
21649 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21650 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21651 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21652 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21653 }
21654
21655 if old_cursor_shape != self.cursor_shape {
21656 cx.emit(EditorEvent::CursorShapeChanged);
21657 }
21658
21659 if old_show_breadcrumbs != self.show_breadcrumbs {
21660 cx.emit(EditorEvent::BreadcrumbsChanged);
21661 }
21662
21663 let project_settings = ProjectSettings::get_global(cx);
21664 self.buffer_serialization = self
21665 .should_serialize_buffer()
21666 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21667
21668 if self.mode.is_full() {
21669 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21670 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21671 if self.show_inline_diagnostics != show_inline_diagnostics {
21672 self.show_inline_diagnostics = show_inline_diagnostics;
21673 self.refresh_inline_diagnostics(false, window, cx);
21674 }
21675
21676 if self.git_blame_inline_enabled != inline_blame_enabled {
21677 self.toggle_git_blame_inline_internal(false, window, cx);
21678 }
21679
21680 let minimap_settings = EditorSettings::get_global(cx).minimap;
21681 if self.minimap_visibility != MinimapVisibility::Disabled {
21682 if self.minimap_visibility.settings_visibility()
21683 != minimap_settings.minimap_enabled()
21684 {
21685 self.set_minimap_visibility(
21686 MinimapVisibility::for_mode(self.mode(), cx),
21687 window,
21688 cx,
21689 );
21690 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21691 minimap_entity.update(cx, |minimap_editor, cx| {
21692 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21693 })
21694 }
21695 }
21696
21697 if language_settings_changed || accent_overrides_changed {
21698 self.colorize_brackets(true, cx);
21699 }
21700
21701 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21702 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21703 }) {
21704 if !inlay_splice.is_empty() {
21705 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21706 }
21707 self.refresh_colors_for_visible_range(None, window, cx);
21708 }
21709 }
21710
21711 cx.notify();
21712 }
21713
21714 pub fn set_searchable(&mut self, searchable: bool) {
21715 self.searchable = searchable;
21716 }
21717
21718 pub fn searchable(&self) -> bool {
21719 self.searchable
21720 }
21721
21722 pub fn open_excerpts_in_split(
21723 &mut self,
21724 _: &OpenExcerptsSplit,
21725 window: &mut Window,
21726 cx: &mut Context<Self>,
21727 ) {
21728 self.open_excerpts_common(None, true, window, cx)
21729 }
21730
21731 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21732 self.open_excerpts_common(None, false, window, cx)
21733 }
21734
21735 fn open_excerpts_common(
21736 &mut self,
21737 jump_data: Option<JumpData>,
21738 split: bool,
21739 window: &mut Window,
21740 cx: &mut Context<Self>,
21741 ) {
21742 let Some(workspace) = self.workspace() else {
21743 cx.propagate();
21744 return;
21745 };
21746
21747 if self.buffer.read(cx).is_singleton() {
21748 cx.propagate();
21749 return;
21750 }
21751
21752 let mut new_selections_by_buffer = HashMap::default();
21753 match &jump_data {
21754 Some(JumpData::MultiBufferPoint {
21755 excerpt_id,
21756 position,
21757 anchor,
21758 line_offset_from_top,
21759 }) => {
21760 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21761 if let Some(buffer) = multi_buffer_snapshot
21762 .buffer_id_for_excerpt(*excerpt_id)
21763 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21764 {
21765 let buffer_snapshot = buffer.read(cx).snapshot();
21766 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21767 language::ToPoint::to_point(anchor, &buffer_snapshot)
21768 } else {
21769 buffer_snapshot.clip_point(*position, Bias::Left)
21770 };
21771 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21772 new_selections_by_buffer.insert(
21773 buffer,
21774 (
21775 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
21776 Some(*line_offset_from_top),
21777 ),
21778 );
21779 }
21780 }
21781 Some(JumpData::MultiBufferRow {
21782 row,
21783 line_offset_from_top,
21784 }) => {
21785 let point = MultiBufferPoint::new(row.0, 0);
21786 if let Some((buffer, buffer_point, _)) =
21787 self.buffer.read(cx).point_to_buffer_point(point, cx)
21788 {
21789 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21790 new_selections_by_buffer
21791 .entry(buffer)
21792 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21793 .0
21794 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
21795 }
21796 }
21797 None => {
21798 let selections = self
21799 .selections
21800 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
21801 let multi_buffer = self.buffer.read(cx);
21802 for selection in selections {
21803 for (snapshot, range, _, anchor) in multi_buffer
21804 .snapshot(cx)
21805 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21806 {
21807 if let Some(anchor) = anchor {
21808 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21809 else {
21810 continue;
21811 };
21812 let offset = text::ToOffset::to_offset(
21813 &anchor.text_anchor,
21814 &buffer_handle.read(cx).snapshot(),
21815 );
21816 let range = BufferOffset(offset)..BufferOffset(offset);
21817 new_selections_by_buffer
21818 .entry(buffer_handle)
21819 .or_insert((Vec::new(), None))
21820 .0
21821 .push(range)
21822 } else {
21823 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21824 else {
21825 continue;
21826 };
21827 new_selections_by_buffer
21828 .entry(buffer_handle)
21829 .or_insert((Vec::new(), None))
21830 .0
21831 .push(range)
21832 }
21833 }
21834 }
21835 }
21836 }
21837
21838 new_selections_by_buffer
21839 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21840
21841 if new_selections_by_buffer.is_empty() {
21842 return;
21843 }
21844
21845 // We defer the pane interaction because we ourselves are a workspace item
21846 // and activating a new item causes the pane to call a method on us reentrantly,
21847 // which panics if we're on the stack.
21848 window.defer(cx, move |window, cx| {
21849 workspace.update(cx, |workspace, cx| {
21850 let pane = if split {
21851 workspace.adjacent_pane(window, cx)
21852 } else {
21853 workspace.active_pane().clone()
21854 };
21855
21856 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21857 let editor = buffer
21858 .read(cx)
21859 .file()
21860 .is_none()
21861 .then(|| {
21862 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21863 // so `workspace.open_project_item` will never find them, always opening a new editor.
21864 // Instead, we try to activate the existing editor in the pane first.
21865 let (editor, pane_item_index) =
21866 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21867 let editor = item.downcast::<Editor>()?;
21868 let singleton_buffer =
21869 editor.read(cx).buffer().read(cx).as_singleton()?;
21870 if singleton_buffer == buffer {
21871 Some((editor, i))
21872 } else {
21873 None
21874 }
21875 })?;
21876 pane.update(cx, |pane, cx| {
21877 pane.activate_item(pane_item_index, true, true, window, cx)
21878 });
21879 Some(editor)
21880 })
21881 .flatten()
21882 .unwrap_or_else(|| {
21883 workspace.open_project_item::<Self>(
21884 pane.clone(),
21885 buffer,
21886 true,
21887 true,
21888 window,
21889 cx,
21890 )
21891 });
21892
21893 editor.update(cx, |editor, cx| {
21894 let autoscroll = match scroll_offset {
21895 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21896 None => Autoscroll::newest(),
21897 };
21898 let nav_history = editor.nav_history.take();
21899 editor.change_selections(
21900 SelectionEffects::scroll(autoscroll),
21901 window,
21902 cx,
21903 |s| {
21904 s.select_ranges(ranges.into_iter().map(|range| {
21905 // we checked that the editor is a singleton editor so the offsets are valid
21906 MultiBufferOffset(range.start.0)..MultiBufferOffset(range.end.0)
21907 }));
21908 },
21909 );
21910 editor.nav_history = nav_history;
21911 });
21912 }
21913 })
21914 });
21915 }
21916
21917 // For now, don't allow opening excerpts in buffers that aren't backed by
21918 // regular project files.
21919 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21920 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21921 }
21922
21923 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
21924 let snapshot = self.buffer.read(cx).read(cx);
21925 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21926 Some(
21927 ranges
21928 .iter()
21929 .map(move |range| {
21930 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21931 })
21932 .collect(),
21933 )
21934 }
21935
21936 fn selection_replacement_ranges(
21937 &self,
21938 range: Range<MultiBufferOffsetUtf16>,
21939 cx: &mut App,
21940 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
21941 let selections = self
21942 .selections
21943 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
21944 let newest_selection = selections
21945 .iter()
21946 .max_by_key(|selection| selection.id)
21947 .unwrap();
21948 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
21949 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
21950 let snapshot = self.buffer.read(cx).read(cx);
21951 selections
21952 .into_iter()
21953 .map(|mut selection| {
21954 selection.start.0.0 =
21955 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
21956 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
21957 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21958 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21959 })
21960 .collect()
21961 }
21962
21963 fn report_editor_event(
21964 &self,
21965 reported_event: ReportEditorEvent,
21966 file_extension: Option<String>,
21967 cx: &App,
21968 ) {
21969 if cfg!(any(test, feature = "test-support")) {
21970 return;
21971 }
21972
21973 let Some(project) = &self.project else { return };
21974
21975 // If None, we are in a file without an extension
21976 let file = self
21977 .buffer
21978 .read(cx)
21979 .as_singleton()
21980 .and_then(|b| b.read(cx).file());
21981 let file_extension = file_extension.or(file
21982 .as_ref()
21983 .and_then(|file| Path::new(file.file_name(cx)).extension())
21984 .and_then(|e| e.to_str())
21985 .map(|a| a.to_string()));
21986
21987 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
21988 .map(|vim_mode| vim_mode.0)
21989 .unwrap_or(false);
21990
21991 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21992 let copilot_enabled = edit_predictions_provider
21993 == language::language_settings::EditPredictionProvider::Copilot;
21994 let copilot_enabled_for_language = self
21995 .buffer
21996 .read(cx)
21997 .language_settings(cx)
21998 .show_edit_predictions;
21999
22000 let project = project.read(cx);
22001 let event_type = reported_event.event_type();
22002
22003 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22004 telemetry::event!(
22005 event_type,
22006 type = if auto_saved {"autosave"} else {"manual"},
22007 file_extension,
22008 vim_mode,
22009 copilot_enabled,
22010 copilot_enabled_for_language,
22011 edit_predictions_provider,
22012 is_via_ssh = project.is_via_remote_server(),
22013 );
22014 } else {
22015 telemetry::event!(
22016 event_type,
22017 file_extension,
22018 vim_mode,
22019 copilot_enabled,
22020 copilot_enabled_for_language,
22021 edit_predictions_provider,
22022 is_via_ssh = project.is_via_remote_server(),
22023 );
22024 };
22025 }
22026
22027 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22028 /// with each line being an array of {text, highlight} objects.
22029 fn copy_highlight_json(
22030 &mut self,
22031 _: &CopyHighlightJson,
22032 window: &mut Window,
22033 cx: &mut Context<Self>,
22034 ) {
22035 #[derive(Serialize)]
22036 struct Chunk<'a> {
22037 text: String,
22038 highlight: Option<&'a str>,
22039 }
22040
22041 let snapshot = self.buffer.read(cx).snapshot(cx);
22042 let range = self
22043 .selected_text_range(false, window, cx)
22044 .and_then(|selection| {
22045 if selection.range.is_empty() {
22046 None
22047 } else {
22048 Some(
22049 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22050 selection.range.start,
22051 )))
22052 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22053 selection.range.end,
22054 ))),
22055 )
22056 }
22057 })
22058 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22059
22060 let chunks = snapshot.chunks(range, true);
22061 let mut lines = Vec::new();
22062 let mut line: VecDeque<Chunk> = VecDeque::new();
22063
22064 let Some(style) = self.style.as_ref() else {
22065 return;
22066 };
22067
22068 for chunk in chunks {
22069 let highlight = chunk
22070 .syntax_highlight_id
22071 .and_then(|id| id.name(&style.syntax));
22072 let mut chunk_lines = chunk.text.split('\n').peekable();
22073 while let Some(text) = chunk_lines.next() {
22074 let mut merged_with_last_token = false;
22075 if let Some(last_token) = line.back_mut()
22076 && last_token.highlight == highlight
22077 {
22078 last_token.text.push_str(text);
22079 merged_with_last_token = true;
22080 }
22081
22082 if !merged_with_last_token {
22083 line.push_back(Chunk {
22084 text: text.into(),
22085 highlight,
22086 });
22087 }
22088
22089 if chunk_lines.peek().is_some() {
22090 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22091 line.pop_front();
22092 }
22093 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22094 line.pop_back();
22095 }
22096
22097 lines.push(mem::take(&mut line));
22098 }
22099 }
22100 }
22101
22102 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22103 return;
22104 };
22105 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22106 }
22107
22108 pub fn open_context_menu(
22109 &mut self,
22110 _: &OpenContextMenu,
22111 window: &mut Window,
22112 cx: &mut Context<Self>,
22113 ) {
22114 self.request_autoscroll(Autoscroll::newest(), cx);
22115 let position = self
22116 .selections
22117 .newest_display(&self.display_snapshot(cx))
22118 .start;
22119 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22120 }
22121
22122 pub fn replay_insert_event(
22123 &mut self,
22124 text: &str,
22125 relative_utf16_range: Option<Range<isize>>,
22126 window: &mut Window,
22127 cx: &mut Context<Self>,
22128 ) {
22129 if !self.input_enabled {
22130 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22131 return;
22132 }
22133 if let Some(relative_utf16_range) = relative_utf16_range {
22134 let selections = self
22135 .selections
22136 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22137 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22138 let new_ranges = selections.into_iter().map(|range| {
22139 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22140 range
22141 .head()
22142 .0
22143 .0
22144 .saturating_add_signed(relative_utf16_range.start),
22145 ));
22146 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22147 range
22148 .head()
22149 .0
22150 .0
22151 .saturating_add_signed(relative_utf16_range.end),
22152 ));
22153 start..end
22154 });
22155 s.select_ranges(new_ranges);
22156 });
22157 }
22158
22159 self.handle_input(text, window, cx);
22160 }
22161
22162 pub fn is_focused(&self, window: &Window) -> bool {
22163 self.focus_handle.is_focused(window)
22164 }
22165
22166 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22167 cx.emit(EditorEvent::Focused);
22168
22169 if let Some(descendant) = self
22170 .last_focused_descendant
22171 .take()
22172 .and_then(|descendant| descendant.upgrade())
22173 {
22174 window.focus(&descendant);
22175 } else {
22176 if let Some(blame) = self.blame.as_ref() {
22177 blame.update(cx, GitBlame::focus)
22178 }
22179
22180 self.blink_manager.update(cx, BlinkManager::enable);
22181 self.show_cursor_names(window, cx);
22182 self.buffer.update(cx, |buffer, cx| {
22183 buffer.finalize_last_transaction(cx);
22184 if self.leader_id.is_none() {
22185 buffer.set_active_selections(
22186 &self.selections.disjoint_anchors_arc(),
22187 self.selections.line_mode(),
22188 self.cursor_shape,
22189 cx,
22190 );
22191 }
22192 });
22193
22194 if let Some(position_map) = self.last_position_map.clone() {
22195 EditorElement::mouse_moved(
22196 self,
22197 &MouseMoveEvent {
22198 position: window.mouse_position(),
22199 pressed_button: None,
22200 modifiers: window.modifiers(),
22201 },
22202 &position_map,
22203 window,
22204 cx,
22205 );
22206 }
22207 }
22208 }
22209
22210 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22211 cx.emit(EditorEvent::FocusedIn)
22212 }
22213
22214 fn handle_focus_out(
22215 &mut self,
22216 event: FocusOutEvent,
22217 _window: &mut Window,
22218 cx: &mut Context<Self>,
22219 ) {
22220 if event.blurred != self.focus_handle {
22221 self.last_focused_descendant = Some(event.blurred);
22222 }
22223 self.selection_drag_state = SelectionDragState::None;
22224 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22225 }
22226
22227 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22228 self.blink_manager.update(cx, BlinkManager::disable);
22229 self.buffer
22230 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22231
22232 if let Some(blame) = self.blame.as_ref() {
22233 blame.update(cx, GitBlame::blur)
22234 }
22235 if !self.hover_state.focused(window, cx) {
22236 hide_hover(self, cx);
22237 }
22238 if !self
22239 .context_menu
22240 .borrow()
22241 .as_ref()
22242 .is_some_and(|context_menu| context_menu.focused(window, cx))
22243 {
22244 self.hide_context_menu(window, cx);
22245 }
22246 self.take_active_edit_prediction(cx);
22247 cx.emit(EditorEvent::Blurred);
22248 cx.notify();
22249 }
22250
22251 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22252 let mut pending: String = window
22253 .pending_input_keystrokes()
22254 .into_iter()
22255 .flatten()
22256 .filter_map(|keystroke| keystroke.key_char.clone())
22257 .collect();
22258
22259 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22260 pending = "".to_string();
22261 }
22262
22263 let existing_pending = self
22264 .text_highlights::<PendingInput>(cx)
22265 .map(|(_, ranges)| ranges.to_vec());
22266 if existing_pending.is_none() && pending.is_empty() {
22267 return;
22268 }
22269 let transaction =
22270 self.transact(window, cx, |this, window, cx| {
22271 let selections = this
22272 .selections
22273 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22274 let edits = selections
22275 .iter()
22276 .map(|selection| (selection.end..selection.end, pending.clone()));
22277 this.edit(edits, cx);
22278 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22279 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22280 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22281 }));
22282 });
22283 if let Some(existing_ranges) = existing_pending {
22284 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22285 this.edit(edits, cx);
22286 }
22287 });
22288
22289 let snapshot = self.snapshot(window, cx);
22290 let ranges = self
22291 .selections
22292 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22293 .into_iter()
22294 .map(|selection| {
22295 snapshot.buffer_snapshot().anchor_after(selection.end)
22296 ..snapshot
22297 .buffer_snapshot()
22298 .anchor_before(selection.end + pending.len())
22299 })
22300 .collect();
22301
22302 if pending.is_empty() {
22303 self.clear_highlights::<PendingInput>(cx);
22304 } else {
22305 self.highlight_text::<PendingInput>(
22306 ranges,
22307 HighlightStyle {
22308 underline: Some(UnderlineStyle {
22309 thickness: px(1.),
22310 color: None,
22311 wavy: false,
22312 }),
22313 ..Default::default()
22314 },
22315 cx,
22316 );
22317 }
22318
22319 self.ime_transaction = self.ime_transaction.or(transaction);
22320 if let Some(transaction) = self.ime_transaction {
22321 self.buffer.update(cx, |buffer, cx| {
22322 buffer.group_until_transaction(transaction, cx);
22323 });
22324 }
22325
22326 if self.text_highlights::<PendingInput>(cx).is_none() {
22327 self.ime_transaction.take();
22328 }
22329 }
22330
22331 pub fn register_action_renderer(
22332 &mut self,
22333 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22334 ) -> Subscription {
22335 let id = self.next_editor_action_id.post_inc();
22336 self.editor_actions
22337 .borrow_mut()
22338 .insert(id, Box::new(listener));
22339
22340 let editor_actions = self.editor_actions.clone();
22341 Subscription::new(move || {
22342 editor_actions.borrow_mut().remove(&id);
22343 })
22344 }
22345
22346 pub fn register_action<A: Action>(
22347 &mut self,
22348 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22349 ) -> Subscription {
22350 let id = self.next_editor_action_id.post_inc();
22351 let listener = Arc::new(listener);
22352 self.editor_actions.borrow_mut().insert(
22353 id,
22354 Box::new(move |_, window, _| {
22355 let listener = listener.clone();
22356 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22357 let action = action.downcast_ref().unwrap();
22358 if phase == DispatchPhase::Bubble {
22359 listener(action, window, cx)
22360 }
22361 })
22362 }),
22363 );
22364
22365 let editor_actions = self.editor_actions.clone();
22366 Subscription::new(move || {
22367 editor_actions.borrow_mut().remove(&id);
22368 })
22369 }
22370
22371 pub fn file_header_size(&self) -> u32 {
22372 FILE_HEADER_HEIGHT
22373 }
22374
22375 pub fn restore(
22376 &mut self,
22377 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22378 window: &mut Window,
22379 cx: &mut Context<Self>,
22380 ) {
22381 let workspace = self.workspace();
22382 let project = self.project();
22383 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22384 let mut tasks = Vec::new();
22385 for (buffer_id, changes) in revert_changes {
22386 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22387 buffer.update(cx, |buffer, cx| {
22388 buffer.edit(
22389 changes
22390 .into_iter()
22391 .map(|(range, text)| (range, text.to_string())),
22392 None,
22393 cx,
22394 );
22395 });
22396
22397 if let Some(project) =
22398 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22399 {
22400 project.update(cx, |project, cx| {
22401 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22402 })
22403 }
22404 }
22405 }
22406 tasks
22407 });
22408 cx.spawn_in(window, async move |_, cx| {
22409 for (buffer, task) in save_tasks {
22410 let result = task.await;
22411 if result.is_err() {
22412 let Some(path) = buffer
22413 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22414 .ok()
22415 else {
22416 continue;
22417 };
22418 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22419 let Some(task) = cx
22420 .update_window_entity(workspace, |workspace, window, cx| {
22421 workspace
22422 .open_path_preview(path, None, false, false, false, window, cx)
22423 })
22424 .ok()
22425 else {
22426 continue;
22427 };
22428 task.await.log_err();
22429 }
22430 }
22431 }
22432 })
22433 .detach();
22434 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22435 selections.refresh()
22436 });
22437 }
22438
22439 pub fn to_pixel_point(
22440 &self,
22441 source: multi_buffer::Anchor,
22442 editor_snapshot: &EditorSnapshot,
22443 window: &mut Window,
22444 ) -> Option<gpui::Point<Pixels>> {
22445 let source_point = source.to_display_point(editor_snapshot);
22446 self.display_to_pixel_point(source_point, editor_snapshot, window)
22447 }
22448
22449 pub fn display_to_pixel_point(
22450 &self,
22451 source: DisplayPoint,
22452 editor_snapshot: &EditorSnapshot,
22453 window: &mut Window,
22454 ) -> Option<gpui::Point<Pixels>> {
22455 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22456 let text_layout_details = self.text_layout_details(window);
22457 let scroll_top = text_layout_details
22458 .scroll_anchor
22459 .scroll_position(editor_snapshot)
22460 .y;
22461
22462 if source.row().as_f64() < scroll_top.floor() {
22463 return None;
22464 }
22465 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22466 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22467 Some(gpui::Point::new(source_x, source_y))
22468 }
22469
22470 pub fn has_visible_completions_menu(&self) -> bool {
22471 !self.edit_prediction_preview_is_active()
22472 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22473 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22474 })
22475 }
22476
22477 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22478 if self.mode.is_minimap() {
22479 return;
22480 }
22481 self.addons
22482 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22483 }
22484
22485 pub fn unregister_addon<T: Addon>(&mut self) {
22486 self.addons.remove(&std::any::TypeId::of::<T>());
22487 }
22488
22489 pub fn addon<T: Addon>(&self) -> Option<&T> {
22490 let type_id = std::any::TypeId::of::<T>();
22491 self.addons
22492 .get(&type_id)
22493 .and_then(|item| item.to_any().downcast_ref::<T>())
22494 }
22495
22496 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22497 let type_id = std::any::TypeId::of::<T>();
22498 self.addons
22499 .get_mut(&type_id)
22500 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22501 }
22502
22503 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22504 let text_layout_details = self.text_layout_details(window);
22505 let style = &text_layout_details.editor_style;
22506 let font_id = window.text_system().resolve_font(&style.text.font());
22507 let font_size = style.text.font_size.to_pixels(window.rem_size());
22508 let line_height = style.text.line_height_in_pixels(window.rem_size());
22509 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22510 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22511
22512 CharacterDimensions {
22513 em_width,
22514 em_advance,
22515 line_height,
22516 }
22517 }
22518
22519 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22520 self.load_diff_task.clone()
22521 }
22522
22523 fn read_metadata_from_db(
22524 &mut self,
22525 item_id: u64,
22526 workspace_id: WorkspaceId,
22527 window: &mut Window,
22528 cx: &mut Context<Editor>,
22529 ) {
22530 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22531 && !self.mode.is_minimap()
22532 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22533 {
22534 let buffer_snapshot = OnceCell::new();
22535
22536 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22537 && !folds.is_empty()
22538 {
22539 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22540 self.fold_ranges(
22541 folds
22542 .into_iter()
22543 .map(|(start, end)| {
22544 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22545 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22546 })
22547 .collect(),
22548 false,
22549 window,
22550 cx,
22551 );
22552 }
22553
22554 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22555 && !selections.is_empty()
22556 {
22557 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22558 // skip adding the initial selection to selection history
22559 self.selection_history.mode = SelectionHistoryMode::Skipping;
22560 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22561 s.select_ranges(selections.into_iter().map(|(start, end)| {
22562 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22563 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22564 }));
22565 });
22566 self.selection_history.mode = SelectionHistoryMode::Normal;
22567 };
22568 }
22569
22570 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22571 }
22572
22573 fn update_lsp_data(
22574 &mut self,
22575 for_buffer: Option<BufferId>,
22576 window: &mut Window,
22577 cx: &mut Context<'_, Self>,
22578 ) {
22579 self.pull_diagnostics(for_buffer, window, cx);
22580 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22581 }
22582
22583 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22584 if self.ignore_lsp_data() {
22585 return;
22586 }
22587 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22588 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22589 }
22590 }
22591
22592 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22593 if self.ignore_lsp_data() {
22594 return;
22595 }
22596
22597 if !self.registered_buffers.contains_key(&buffer_id)
22598 && let Some(project) = self.project.as_ref()
22599 {
22600 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22601 project.update(cx, |project, cx| {
22602 self.registered_buffers.insert(
22603 buffer_id,
22604 project.register_buffer_with_language_servers(&buffer, cx),
22605 );
22606 });
22607 } else {
22608 self.registered_buffers.remove(&buffer_id);
22609 }
22610 }
22611 }
22612
22613 fn ignore_lsp_data(&self) -> bool {
22614 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22615 // skip any LSP updates for it.
22616 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22617 }
22618}
22619
22620fn edit_for_markdown_paste<'a>(
22621 buffer: &MultiBufferSnapshot,
22622 range: Range<MultiBufferOffset>,
22623 to_insert: &'a str,
22624 url: Option<url::Url>,
22625) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
22626 if url.is_none() {
22627 return (range, Cow::Borrowed(to_insert));
22628 };
22629
22630 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22631
22632 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22633 Cow::Borrowed(to_insert)
22634 } else {
22635 Cow::Owned(format!("[{old_text}]({to_insert})"))
22636 };
22637 (range, new_text)
22638}
22639
22640fn process_completion_for_edit(
22641 completion: &Completion,
22642 intent: CompletionIntent,
22643 buffer: &Entity<Buffer>,
22644 cursor_position: &text::Anchor,
22645 cx: &mut Context<Editor>,
22646) -> CompletionEdit {
22647 let buffer = buffer.read(cx);
22648 let buffer_snapshot = buffer.snapshot();
22649 let (snippet, new_text) = if completion.is_snippet() {
22650 let mut snippet_source = completion.new_text.clone();
22651 // Workaround for typescript language server issues so that methods don't expand within
22652 // strings and functions with type expressions. The previous point is used because the query
22653 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22654 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22655 let previous_point = if previous_point.column > 0 {
22656 cursor_position.to_previous_offset(&buffer_snapshot)
22657 } else {
22658 cursor_position.to_offset(&buffer_snapshot)
22659 };
22660 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22661 && scope.prefers_label_for_snippet_in_completion()
22662 && let Some(label) = completion.label()
22663 && matches!(
22664 completion.kind(),
22665 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22666 )
22667 {
22668 snippet_source = label;
22669 }
22670 match Snippet::parse(&snippet_source).log_err() {
22671 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22672 None => (None, completion.new_text.clone()),
22673 }
22674 } else {
22675 (None, completion.new_text.clone())
22676 };
22677
22678 let mut range_to_replace = {
22679 let replace_range = &completion.replace_range;
22680 if let CompletionSource::Lsp {
22681 insert_range: Some(insert_range),
22682 ..
22683 } = &completion.source
22684 {
22685 debug_assert_eq!(
22686 insert_range.start, replace_range.start,
22687 "insert_range and replace_range should start at the same position"
22688 );
22689 debug_assert!(
22690 insert_range
22691 .start
22692 .cmp(cursor_position, &buffer_snapshot)
22693 .is_le(),
22694 "insert_range should start before or at cursor position"
22695 );
22696 debug_assert!(
22697 replace_range
22698 .start
22699 .cmp(cursor_position, &buffer_snapshot)
22700 .is_le(),
22701 "replace_range should start before or at cursor position"
22702 );
22703
22704 let should_replace = match intent {
22705 CompletionIntent::CompleteWithInsert => false,
22706 CompletionIntent::CompleteWithReplace => true,
22707 CompletionIntent::Complete | CompletionIntent::Compose => {
22708 let insert_mode =
22709 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22710 .completions
22711 .lsp_insert_mode;
22712 match insert_mode {
22713 LspInsertMode::Insert => false,
22714 LspInsertMode::Replace => true,
22715 LspInsertMode::ReplaceSubsequence => {
22716 let mut text_to_replace = buffer.chars_for_range(
22717 buffer.anchor_before(replace_range.start)
22718 ..buffer.anchor_after(replace_range.end),
22719 );
22720 let mut current_needle = text_to_replace.next();
22721 for haystack_ch in completion.label.text.chars() {
22722 if let Some(needle_ch) = current_needle
22723 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22724 {
22725 current_needle = text_to_replace.next();
22726 }
22727 }
22728 current_needle.is_none()
22729 }
22730 LspInsertMode::ReplaceSuffix => {
22731 if replace_range
22732 .end
22733 .cmp(cursor_position, &buffer_snapshot)
22734 .is_gt()
22735 {
22736 let range_after_cursor = *cursor_position..replace_range.end;
22737 let text_after_cursor = buffer
22738 .text_for_range(
22739 buffer.anchor_before(range_after_cursor.start)
22740 ..buffer.anchor_after(range_after_cursor.end),
22741 )
22742 .collect::<String>()
22743 .to_ascii_lowercase();
22744 completion
22745 .label
22746 .text
22747 .to_ascii_lowercase()
22748 .ends_with(&text_after_cursor)
22749 } else {
22750 true
22751 }
22752 }
22753 }
22754 }
22755 };
22756
22757 if should_replace {
22758 replace_range.clone()
22759 } else {
22760 insert_range.clone()
22761 }
22762 } else {
22763 replace_range.clone()
22764 }
22765 };
22766
22767 if range_to_replace
22768 .end
22769 .cmp(cursor_position, &buffer_snapshot)
22770 .is_lt()
22771 {
22772 range_to_replace.end = *cursor_position;
22773 }
22774
22775 let replace_range = range_to_replace.to_offset(buffer);
22776 CompletionEdit {
22777 new_text,
22778 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
22779 snippet,
22780 }
22781}
22782
22783struct CompletionEdit {
22784 new_text: String,
22785 replace_range: Range<BufferOffset>,
22786 snippet: Option<Snippet>,
22787}
22788
22789fn insert_extra_newline_brackets(
22790 buffer: &MultiBufferSnapshot,
22791 range: Range<MultiBufferOffset>,
22792 language: &language::LanguageScope,
22793) -> bool {
22794 let leading_whitespace_len = buffer
22795 .reversed_chars_at(range.start)
22796 .take_while(|c| c.is_whitespace() && *c != '\n')
22797 .map(|c| c.len_utf8())
22798 .sum::<usize>();
22799 let trailing_whitespace_len = buffer
22800 .chars_at(range.end)
22801 .take_while(|c| c.is_whitespace() && *c != '\n')
22802 .map(|c| c.len_utf8())
22803 .sum::<usize>();
22804 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22805
22806 language.brackets().any(|(pair, enabled)| {
22807 let pair_start = pair.start.trim_end();
22808 let pair_end = pair.end.trim_start();
22809
22810 enabled
22811 && pair.newline
22812 && buffer.contains_str_at(range.end, pair_end)
22813 && buffer.contains_str_at(
22814 range.start.saturating_sub_usize(pair_start.len()),
22815 pair_start,
22816 )
22817 })
22818}
22819
22820fn insert_extra_newline_tree_sitter(
22821 buffer: &MultiBufferSnapshot,
22822 range: Range<MultiBufferOffset>,
22823) -> bool {
22824 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22825 [(buffer, range, _)] => (*buffer, range.clone()),
22826 _ => return false,
22827 };
22828 let pair = {
22829 let mut result: Option<BracketMatch<usize>> = None;
22830
22831 for pair in buffer
22832 .all_bracket_ranges(range.start.0..range.end.0)
22833 .filter(move |pair| {
22834 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
22835 })
22836 {
22837 let len = pair.close_range.end - pair.open_range.start;
22838
22839 if let Some(existing) = &result {
22840 let existing_len = existing.close_range.end - existing.open_range.start;
22841 if len > existing_len {
22842 continue;
22843 }
22844 }
22845
22846 result = Some(pair);
22847 }
22848
22849 result
22850 };
22851 let Some(pair) = pair else {
22852 return false;
22853 };
22854 pair.newline_only
22855 && buffer
22856 .chars_for_range(pair.open_range.end..range.start.0)
22857 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
22858 .all(|c| c.is_whitespace() && c != '\n')
22859}
22860
22861fn update_uncommitted_diff_for_buffer(
22862 editor: Entity<Editor>,
22863 project: &Entity<Project>,
22864 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22865 buffer: Entity<MultiBuffer>,
22866 cx: &mut App,
22867) -> Task<()> {
22868 let mut tasks = Vec::new();
22869 project.update(cx, |project, cx| {
22870 for buffer in buffers {
22871 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22872 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22873 }
22874 }
22875 });
22876 cx.spawn(async move |cx| {
22877 let diffs = future::join_all(tasks).await;
22878 if editor
22879 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22880 .unwrap_or(false)
22881 {
22882 return;
22883 }
22884
22885 buffer
22886 .update(cx, |buffer, cx| {
22887 for diff in diffs.into_iter().flatten() {
22888 buffer.add_diff(diff, cx);
22889 }
22890 })
22891 .ok();
22892 })
22893}
22894
22895fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22896 let tab_size = tab_size.get() as usize;
22897 let mut width = offset;
22898
22899 for ch in text.chars() {
22900 width += if ch == '\t' {
22901 tab_size - (width % tab_size)
22902 } else {
22903 1
22904 };
22905 }
22906
22907 width - offset
22908}
22909
22910#[cfg(test)]
22911mod tests {
22912 use super::*;
22913
22914 #[test]
22915 fn test_string_size_with_expanded_tabs() {
22916 let nz = |val| NonZeroU32::new(val).unwrap();
22917 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22918 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22919 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22920 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22921 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22922 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22923 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22924 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22925 }
22926}
22927
22928/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22929struct WordBreakingTokenizer<'a> {
22930 input: &'a str,
22931}
22932
22933impl<'a> WordBreakingTokenizer<'a> {
22934 fn new(input: &'a str) -> Self {
22935 Self { input }
22936 }
22937}
22938
22939fn is_char_ideographic(ch: char) -> bool {
22940 use unicode_script::Script::*;
22941 use unicode_script::UnicodeScript;
22942 matches!(ch.script(), Han | Tangut | Yi)
22943}
22944
22945fn is_grapheme_ideographic(text: &str) -> bool {
22946 text.chars().any(is_char_ideographic)
22947}
22948
22949fn is_grapheme_whitespace(text: &str) -> bool {
22950 text.chars().any(|x| x.is_whitespace())
22951}
22952
22953fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22954 text.chars()
22955 .next()
22956 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22957}
22958
22959#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22960enum WordBreakToken<'a> {
22961 Word { token: &'a str, grapheme_len: usize },
22962 InlineWhitespace { token: &'a str, grapheme_len: usize },
22963 Newline,
22964}
22965
22966impl<'a> Iterator for WordBreakingTokenizer<'a> {
22967 /// Yields a span, the count of graphemes in the token, and whether it was
22968 /// whitespace. Note that it also breaks at word boundaries.
22969 type Item = WordBreakToken<'a>;
22970
22971 fn next(&mut self) -> Option<Self::Item> {
22972 use unicode_segmentation::UnicodeSegmentation;
22973 if self.input.is_empty() {
22974 return None;
22975 }
22976
22977 let mut iter = self.input.graphemes(true).peekable();
22978 let mut offset = 0;
22979 let mut grapheme_len = 0;
22980 if let Some(first_grapheme) = iter.next() {
22981 let is_newline = first_grapheme == "\n";
22982 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22983 offset += first_grapheme.len();
22984 grapheme_len += 1;
22985 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22986 if let Some(grapheme) = iter.peek().copied()
22987 && should_stay_with_preceding_ideograph(grapheme)
22988 {
22989 offset += grapheme.len();
22990 grapheme_len += 1;
22991 }
22992 } else {
22993 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22994 let mut next_word_bound = words.peek().copied();
22995 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22996 next_word_bound = words.next();
22997 }
22998 while let Some(grapheme) = iter.peek().copied() {
22999 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23000 break;
23001 };
23002 if is_grapheme_whitespace(grapheme) != is_whitespace
23003 || (grapheme == "\n") != is_newline
23004 {
23005 break;
23006 };
23007 offset += grapheme.len();
23008 grapheme_len += 1;
23009 iter.next();
23010 }
23011 }
23012 let token = &self.input[..offset];
23013 self.input = &self.input[offset..];
23014 if token == "\n" {
23015 Some(WordBreakToken::Newline)
23016 } else if is_whitespace {
23017 Some(WordBreakToken::InlineWhitespace {
23018 token,
23019 grapheme_len,
23020 })
23021 } else {
23022 Some(WordBreakToken::Word {
23023 token,
23024 grapheme_len,
23025 })
23026 }
23027 } else {
23028 None
23029 }
23030 }
23031}
23032
23033#[test]
23034fn test_word_breaking_tokenizer() {
23035 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23036 ("", &[]),
23037 (" ", &[whitespace(" ", 2)]),
23038 ("Ʒ", &[word("Ʒ", 1)]),
23039 ("Ǽ", &[word("Ǽ", 1)]),
23040 ("⋑", &[word("⋑", 1)]),
23041 ("⋑⋑", &[word("⋑⋑", 2)]),
23042 (
23043 "原理,进而",
23044 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23045 ),
23046 (
23047 "hello world",
23048 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23049 ),
23050 (
23051 "hello, world",
23052 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23053 ),
23054 (
23055 " hello world",
23056 &[
23057 whitespace(" ", 2),
23058 word("hello", 5),
23059 whitespace(" ", 1),
23060 word("world", 5),
23061 ],
23062 ),
23063 (
23064 "这是什么 \n 钢笔",
23065 &[
23066 word("这", 1),
23067 word("是", 1),
23068 word("什", 1),
23069 word("么", 1),
23070 whitespace(" ", 1),
23071 newline(),
23072 whitespace(" ", 1),
23073 word("钢", 1),
23074 word("笔", 1),
23075 ],
23076 ),
23077 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23078 ];
23079
23080 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23081 WordBreakToken::Word {
23082 token,
23083 grapheme_len,
23084 }
23085 }
23086
23087 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23088 WordBreakToken::InlineWhitespace {
23089 token,
23090 grapheme_len,
23091 }
23092 }
23093
23094 fn newline() -> WordBreakToken<'static> {
23095 WordBreakToken::Newline
23096 }
23097
23098 for (input, result) in tests {
23099 assert_eq!(
23100 WordBreakingTokenizer::new(input)
23101 .collect::<Vec<_>>()
23102 .as_slice(),
23103 *result,
23104 );
23105 }
23106}
23107
23108fn wrap_with_prefix(
23109 first_line_prefix: String,
23110 subsequent_lines_prefix: String,
23111 unwrapped_text: String,
23112 wrap_column: usize,
23113 tab_size: NonZeroU32,
23114 preserve_existing_whitespace: bool,
23115) -> String {
23116 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23117 let subsequent_lines_prefix_len =
23118 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23119 let mut wrapped_text = String::new();
23120 let mut current_line = first_line_prefix;
23121 let mut is_first_line = true;
23122
23123 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23124 let mut current_line_len = first_line_prefix_len;
23125 let mut in_whitespace = false;
23126 for token in tokenizer {
23127 let have_preceding_whitespace = in_whitespace;
23128 match token {
23129 WordBreakToken::Word {
23130 token,
23131 grapheme_len,
23132 } => {
23133 in_whitespace = false;
23134 let current_prefix_len = if is_first_line {
23135 first_line_prefix_len
23136 } else {
23137 subsequent_lines_prefix_len
23138 };
23139 if current_line_len + grapheme_len > wrap_column
23140 && current_line_len != current_prefix_len
23141 {
23142 wrapped_text.push_str(current_line.trim_end());
23143 wrapped_text.push('\n');
23144 is_first_line = false;
23145 current_line = subsequent_lines_prefix.clone();
23146 current_line_len = subsequent_lines_prefix_len;
23147 }
23148 current_line.push_str(token);
23149 current_line_len += grapheme_len;
23150 }
23151 WordBreakToken::InlineWhitespace {
23152 mut token,
23153 mut grapheme_len,
23154 } => {
23155 in_whitespace = true;
23156 if have_preceding_whitespace && !preserve_existing_whitespace {
23157 continue;
23158 }
23159 if !preserve_existing_whitespace {
23160 // Keep a single whitespace grapheme as-is
23161 if let Some(first) =
23162 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23163 {
23164 token = first;
23165 } else {
23166 token = " ";
23167 }
23168 grapheme_len = 1;
23169 }
23170 let current_prefix_len = if is_first_line {
23171 first_line_prefix_len
23172 } else {
23173 subsequent_lines_prefix_len
23174 };
23175 if current_line_len + grapheme_len > wrap_column {
23176 wrapped_text.push_str(current_line.trim_end());
23177 wrapped_text.push('\n');
23178 is_first_line = false;
23179 current_line = subsequent_lines_prefix.clone();
23180 current_line_len = subsequent_lines_prefix_len;
23181 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23182 current_line.push_str(token);
23183 current_line_len += grapheme_len;
23184 }
23185 }
23186 WordBreakToken::Newline => {
23187 in_whitespace = true;
23188 let current_prefix_len = if is_first_line {
23189 first_line_prefix_len
23190 } else {
23191 subsequent_lines_prefix_len
23192 };
23193 if preserve_existing_whitespace {
23194 wrapped_text.push_str(current_line.trim_end());
23195 wrapped_text.push('\n');
23196 is_first_line = false;
23197 current_line = subsequent_lines_prefix.clone();
23198 current_line_len = subsequent_lines_prefix_len;
23199 } else if have_preceding_whitespace {
23200 continue;
23201 } else if current_line_len + 1 > wrap_column
23202 && current_line_len != current_prefix_len
23203 {
23204 wrapped_text.push_str(current_line.trim_end());
23205 wrapped_text.push('\n');
23206 is_first_line = false;
23207 current_line = subsequent_lines_prefix.clone();
23208 current_line_len = subsequent_lines_prefix_len;
23209 } else if current_line_len != current_prefix_len {
23210 current_line.push(' ');
23211 current_line_len += 1;
23212 }
23213 }
23214 }
23215 }
23216
23217 if !current_line.is_empty() {
23218 wrapped_text.push_str(¤t_line);
23219 }
23220 wrapped_text
23221}
23222
23223#[test]
23224fn test_wrap_with_prefix() {
23225 assert_eq!(
23226 wrap_with_prefix(
23227 "# ".to_string(),
23228 "# ".to_string(),
23229 "abcdefg".to_string(),
23230 4,
23231 NonZeroU32::new(4).unwrap(),
23232 false,
23233 ),
23234 "# abcdefg"
23235 );
23236 assert_eq!(
23237 wrap_with_prefix(
23238 "".to_string(),
23239 "".to_string(),
23240 "\thello world".to_string(),
23241 8,
23242 NonZeroU32::new(4).unwrap(),
23243 false,
23244 ),
23245 "hello\nworld"
23246 );
23247 assert_eq!(
23248 wrap_with_prefix(
23249 "// ".to_string(),
23250 "// ".to_string(),
23251 "xx \nyy zz aa bb cc".to_string(),
23252 12,
23253 NonZeroU32::new(4).unwrap(),
23254 false,
23255 ),
23256 "// xx yy zz\n// aa bb cc"
23257 );
23258 assert_eq!(
23259 wrap_with_prefix(
23260 String::new(),
23261 String::new(),
23262 "这是什么 \n 钢笔".to_string(),
23263 3,
23264 NonZeroU32::new(4).unwrap(),
23265 false,
23266 ),
23267 "这是什\n么 钢\n笔"
23268 );
23269 assert_eq!(
23270 wrap_with_prefix(
23271 String::new(),
23272 String::new(),
23273 format!("foo{}bar", '\u{2009}'), // thin space
23274 80,
23275 NonZeroU32::new(4).unwrap(),
23276 false,
23277 ),
23278 format!("foo{}bar", '\u{2009}')
23279 );
23280}
23281
23282pub trait CollaborationHub {
23283 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23284 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23285 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23286}
23287
23288impl CollaborationHub for Entity<Project> {
23289 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23290 self.read(cx).collaborators()
23291 }
23292
23293 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23294 self.read(cx).user_store().read(cx).participant_indices()
23295 }
23296
23297 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23298 let this = self.read(cx);
23299 let user_ids = this.collaborators().values().map(|c| c.user_id);
23300 this.user_store().read(cx).participant_names(user_ids, cx)
23301 }
23302}
23303
23304pub trait SemanticsProvider {
23305 fn hover(
23306 &self,
23307 buffer: &Entity<Buffer>,
23308 position: text::Anchor,
23309 cx: &mut App,
23310 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23311
23312 fn inline_values(
23313 &self,
23314 buffer_handle: Entity<Buffer>,
23315 range: Range<text::Anchor>,
23316 cx: &mut App,
23317 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23318
23319 fn applicable_inlay_chunks(
23320 &self,
23321 buffer: &Entity<Buffer>,
23322 ranges: &[Range<text::Anchor>],
23323 cx: &mut App,
23324 ) -> Vec<Range<BufferRow>>;
23325
23326 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23327
23328 fn inlay_hints(
23329 &self,
23330 invalidate: InvalidationStrategy,
23331 buffer: Entity<Buffer>,
23332 ranges: Vec<Range<text::Anchor>>,
23333 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23334 cx: &mut App,
23335 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23336
23337 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23338
23339 fn document_highlights(
23340 &self,
23341 buffer: &Entity<Buffer>,
23342 position: text::Anchor,
23343 cx: &mut App,
23344 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23345
23346 fn definitions(
23347 &self,
23348 buffer: &Entity<Buffer>,
23349 position: text::Anchor,
23350 kind: GotoDefinitionKind,
23351 cx: &mut App,
23352 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23353
23354 fn range_for_rename(
23355 &self,
23356 buffer: &Entity<Buffer>,
23357 position: text::Anchor,
23358 cx: &mut App,
23359 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23360
23361 fn perform_rename(
23362 &self,
23363 buffer: &Entity<Buffer>,
23364 position: text::Anchor,
23365 new_name: String,
23366 cx: &mut App,
23367 ) -> Option<Task<Result<ProjectTransaction>>>;
23368}
23369
23370pub trait CompletionProvider {
23371 fn completions(
23372 &self,
23373 excerpt_id: ExcerptId,
23374 buffer: &Entity<Buffer>,
23375 buffer_position: text::Anchor,
23376 trigger: CompletionContext,
23377 window: &mut Window,
23378 cx: &mut Context<Editor>,
23379 ) -> Task<Result<Vec<CompletionResponse>>>;
23380
23381 fn resolve_completions(
23382 &self,
23383 _buffer: Entity<Buffer>,
23384 _completion_indices: Vec<usize>,
23385 _completions: Rc<RefCell<Box<[Completion]>>>,
23386 _cx: &mut Context<Editor>,
23387 ) -> Task<Result<bool>> {
23388 Task::ready(Ok(false))
23389 }
23390
23391 fn apply_additional_edits_for_completion(
23392 &self,
23393 _buffer: Entity<Buffer>,
23394 _completions: Rc<RefCell<Box<[Completion]>>>,
23395 _completion_index: usize,
23396 _push_to_history: bool,
23397 _cx: &mut Context<Editor>,
23398 ) -> Task<Result<Option<language::Transaction>>> {
23399 Task::ready(Ok(None))
23400 }
23401
23402 fn is_completion_trigger(
23403 &self,
23404 buffer: &Entity<Buffer>,
23405 position: language::Anchor,
23406 text: &str,
23407 trigger_in_words: bool,
23408 menu_is_open: bool,
23409 cx: &mut Context<Editor>,
23410 ) -> bool;
23411
23412 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23413
23414 fn sort_completions(&self) -> bool {
23415 true
23416 }
23417
23418 fn filter_completions(&self) -> bool {
23419 true
23420 }
23421
23422 fn show_snippets(&self) -> bool {
23423 false
23424 }
23425}
23426
23427pub trait CodeActionProvider {
23428 fn id(&self) -> Arc<str>;
23429
23430 fn code_actions(
23431 &self,
23432 buffer: &Entity<Buffer>,
23433 range: Range<text::Anchor>,
23434 window: &mut Window,
23435 cx: &mut App,
23436 ) -> Task<Result<Vec<CodeAction>>>;
23437
23438 fn apply_code_action(
23439 &self,
23440 buffer_handle: Entity<Buffer>,
23441 action: CodeAction,
23442 excerpt_id: ExcerptId,
23443 push_to_history: bool,
23444 window: &mut Window,
23445 cx: &mut App,
23446 ) -> Task<Result<ProjectTransaction>>;
23447}
23448
23449impl CodeActionProvider for Entity<Project> {
23450 fn id(&self) -> Arc<str> {
23451 "project".into()
23452 }
23453
23454 fn code_actions(
23455 &self,
23456 buffer: &Entity<Buffer>,
23457 range: Range<text::Anchor>,
23458 _window: &mut Window,
23459 cx: &mut App,
23460 ) -> Task<Result<Vec<CodeAction>>> {
23461 self.update(cx, |project, cx| {
23462 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23463 let code_actions = project.code_actions(buffer, range, None, cx);
23464 cx.background_spawn(async move {
23465 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23466 Ok(code_lens_actions
23467 .context("code lens fetch")?
23468 .into_iter()
23469 .flatten()
23470 .chain(
23471 code_actions
23472 .context("code action fetch")?
23473 .into_iter()
23474 .flatten(),
23475 )
23476 .collect())
23477 })
23478 })
23479 }
23480
23481 fn apply_code_action(
23482 &self,
23483 buffer_handle: Entity<Buffer>,
23484 action: CodeAction,
23485 _excerpt_id: ExcerptId,
23486 push_to_history: bool,
23487 _window: &mut Window,
23488 cx: &mut App,
23489 ) -> Task<Result<ProjectTransaction>> {
23490 self.update(cx, |project, cx| {
23491 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23492 })
23493 }
23494}
23495
23496fn snippet_completions(
23497 project: &Project,
23498 buffer: &Entity<Buffer>,
23499 buffer_anchor: text::Anchor,
23500 classifier: CharClassifier,
23501 cx: &mut App,
23502) -> Task<Result<CompletionResponse>> {
23503 let languages = buffer.read(cx).languages_at(buffer_anchor);
23504 let snippet_store = project.snippets().read(cx);
23505
23506 let scopes: Vec<_> = languages
23507 .iter()
23508 .filter_map(|language| {
23509 let language_name = language.lsp_id();
23510 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23511
23512 if snippets.is_empty() {
23513 None
23514 } else {
23515 Some((language.default_scope(), snippets))
23516 }
23517 })
23518 .collect();
23519
23520 if scopes.is_empty() {
23521 return Task::ready(Ok(CompletionResponse {
23522 completions: vec![],
23523 display_options: CompletionDisplayOptions::default(),
23524 is_incomplete: false,
23525 }));
23526 }
23527
23528 let snapshot = buffer.read(cx).text_snapshot();
23529 let executor = cx.background_executor().clone();
23530
23531 cx.background_spawn(async move {
23532 let is_word_char = |c| classifier.is_word(c);
23533
23534 let mut is_incomplete = false;
23535 let mut completions: Vec<Completion> = Vec::new();
23536
23537 const MAX_PREFIX_LEN: usize = 128;
23538 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23539 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23540 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23541
23542 let max_buffer_window: String = snapshot
23543 .text_for_range(window_start..buffer_offset)
23544 .collect();
23545
23546 if max_buffer_window.is_empty() {
23547 return Ok(CompletionResponse {
23548 completions: vec![],
23549 display_options: CompletionDisplayOptions::default(),
23550 is_incomplete: true,
23551 });
23552 }
23553
23554 for (_scope, snippets) in scopes.into_iter() {
23555 // Sort snippets by word count to match longer snippet prefixes first.
23556 let mut sorted_snippet_candidates = snippets
23557 .iter()
23558 .enumerate()
23559 .flat_map(|(snippet_ix, snippet)| {
23560 snippet
23561 .prefix
23562 .iter()
23563 .enumerate()
23564 .map(move |(prefix_ix, prefix)| {
23565 let word_count =
23566 snippet_candidate_suffixes(prefix, is_word_char).count();
23567 ((snippet_ix, prefix_ix), prefix, word_count)
23568 })
23569 })
23570 .collect_vec();
23571 sorted_snippet_candidates
23572 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23573
23574 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23575
23576 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23577 .take(
23578 sorted_snippet_candidates
23579 .first()
23580 .map(|(_, _, word_count)| *word_count)
23581 .unwrap_or_default(),
23582 )
23583 .collect_vec();
23584
23585 const MAX_RESULTS: usize = 100;
23586 // Each match also remembers how many characters from the buffer it consumed
23587 let mut matches: Vec<(StringMatch, usize)> = vec![];
23588
23589 let mut snippet_list_cutoff_index = 0;
23590 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23591 let word_count = buffer_index + 1;
23592 // Increase `snippet_list_cutoff_index` until we have all of the
23593 // snippets with sufficiently many words.
23594 while sorted_snippet_candidates
23595 .get(snippet_list_cutoff_index)
23596 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23597 *snippet_word_count >= word_count
23598 })
23599 {
23600 snippet_list_cutoff_index += 1;
23601 }
23602
23603 // Take only the candidates with at least `word_count` many words
23604 let snippet_candidates_at_word_len =
23605 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23606
23607 let candidates = snippet_candidates_at_word_len
23608 .iter()
23609 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23610 .enumerate() // index in `sorted_snippet_candidates`
23611 // First char must match
23612 .filter(|(_ix, prefix)| {
23613 itertools::equal(
23614 prefix
23615 .chars()
23616 .next()
23617 .into_iter()
23618 .flat_map(|c| c.to_lowercase()),
23619 buffer_window
23620 .chars()
23621 .next()
23622 .into_iter()
23623 .flat_map(|c| c.to_lowercase()),
23624 )
23625 })
23626 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23627 .collect::<Vec<StringMatchCandidate>>();
23628
23629 matches.extend(
23630 fuzzy::match_strings(
23631 &candidates,
23632 &buffer_window,
23633 buffer_window.chars().any(|c| c.is_uppercase()),
23634 true,
23635 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23636 &Default::default(),
23637 executor.clone(),
23638 )
23639 .await
23640 .into_iter()
23641 .map(|string_match| (string_match, buffer_window.len())),
23642 );
23643
23644 if matches.len() >= MAX_RESULTS {
23645 break;
23646 }
23647 }
23648
23649 let to_lsp = |point: &text::Anchor| {
23650 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23651 point_to_lsp(end)
23652 };
23653 let lsp_end = to_lsp(&buffer_anchor);
23654
23655 if matches.len() >= MAX_RESULTS {
23656 is_incomplete = true;
23657 }
23658
23659 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23660 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23661 sorted_snippet_candidates[string_match.candidate_id];
23662 let snippet = &snippets[snippet_index];
23663 let start = buffer_offset - buffer_window_len;
23664 let start = snapshot.anchor_before(start);
23665 let range = start..buffer_anchor;
23666 let lsp_start = to_lsp(&start);
23667 let lsp_range = lsp::Range {
23668 start: lsp_start,
23669 end: lsp_end,
23670 };
23671 Completion {
23672 replace_range: range,
23673 new_text: snippet.body.clone(),
23674 source: CompletionSource::Lsp {
23675 insert_range: None,
23676 server_id: LanguageServerId(usize::MAX),
23677 resolved: true,
23678 lsp_completion: Box::new(lsp::CompletionItem {
23679 label: snippet.prefix.first().unwrap().clone(),
23680 kind: Some(CompletionItemKind::SNIPPET),
23681 label_details: snippet.description.as_ref().map(|description| {
23682 lsp::CompletionItemLabelDetails {
23683 detail: Some(description.clone()),
23684 description: None,
23685 }
23686 }),
23687 insert_text_format: Some(InsertTextFormat::SNIPPET),
23688 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23689 lsp::InsertReplaceEdit {
23690 new_text: snippet.body.clone(),
23691 insert: lsp_range,
23692 replace: lsp_range,
23693 },
23694 )),
23695 filter_text: Some(snippet.body.clone()),
23696 sort_text: Some(char::MAX.to_string()),
23697 ..lsp::CompletionItem::default()
23698 }),
23699 lsp_defaults: None,
23700 },
23701 label: CodeLabel {
23702 text: matching_prefix.clone(),
23703 runs: Vec::new(),
23704 filter_range: 0..matching_prefix.len(),
23705 },
23706 icon_path: None,
23707 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23708 single_line: snippet.name.clone().into(),
23709 plain_text: snippet
23710 .description
23711 .clone()
23712 .map(|description| description.into()),
23713 }),
23714 insert_text_mode: None,
23715 confirm: None,
23716 match_start: Some(start),
23717 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23718 }
23719 }));
23720 }
23721
23722 Ok(CompletionResponse {
23723 completions,
23724 display_options: CompletionDisplayOptions::default(),
23725 is_incomplete,
23726 })
23727 })
23728}
23729
23730impl CompletionProvider for Entity<Project> {
23731 fn completions(
23732 &self,
23733 _excerpt_id: ExcerptId,
23734 buffer: &Entity<Buffer>,
23735 buffer_position: text::Anchor,
23736 options: CompletionContext,
23737 _window: &mut Window,
23738 cx: &mut Context<Editor>,
23739 ) -> Task<Result<Vec<CompletionResponse>>> {
23740 self.update(cx, |project, cx| {
23741 let task = project.completions(buffer, buffer_position, options, cx);
23742 cx.background_spawn(task)
23743 })
23744 }
23745
23746 fn resolve_completions(
23747 &self,
23748 buffer: Entity<Buffer>,
23749 completion_indices: Vec<usize>,
23750 completions: Rc<RefCell<Box<[Completion]>>>,
23751 cx: &mut Context<Editor>,
23752 ) -> Task<Result<bool>> {
23753 self.update(cx, |project, cx| {
23754 project.lsp_store().update(cx, |lsp_store, cx| {
23755 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23756 })
23757 })
23758 }
23759
23760 fn apply_additional_edits_for_completion(
23761 &self,
23762 buffer: Entity<Buffer>,
23763 completions: Rc<RefCell<Box<[Completion]>>>,
23764 completion_index: usize,
23765 push_to_history: bool,
23766 cx: &mut Context<Editor>,
23767 ) -> Task<Result<Option<language::Transaction>>> {
23768 self.update(cx, |project, cx| {
23769 project.lsp_store().update(cx, |lsp_store, cx| {
23770 lsp_store.apply_additional_edits_for_completion(
23771 buffer,
23772 completions,
23773 completion_index,
23774 push_to_history,
23775 cx,
23776 )
23777 })
23778 })
23779 }
23780
23781 fn is_completion_trigger(
23782 &self,
23783 buffer: &Entity<Buffer>,
23784 position: language::Anchor,
23785 text: &str,
23786 trigger_in_words: bool,
23787 menu_is_open: bool,
23788 cx: &mut Context<Editor>,
23789 ) -> bool {
23790 let mut chars = text.chars();
23791 let char = if let Some(char) = chars.next() {
23792 char
23793 } else {
23794 return false;
23795 };
23796 if chars.next().is_some() {
23797 return false;
23798 }
23799
23800 let buffer = buffer.read(cx);
23801 let snapshot = buffer.snapshot();
23802 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23803 return false;
23804 }
23805 let classifier = snapshot
23806 .char_classifier_at(position)
23807 .scope_context(Some(CharScopeContext::Completion));
23808 if trigger_in_words && classifier.is_word(char) {
23809 return true;
23810 }
23811
23812 buffer.completion_triggers().contains(text)
23813 }
23814
23815 fn show_snippets(&self) -> bool {
23816 true
23817 }
23818}
23819
23820impl SemanticsProvider for Entity<Project> {
23821 fn hover(
23822 &self,
23823 buffer: &Entity<Buffer>,
23824 position: text::Anchor,
23825 cx: &mut App,
23826 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23827 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23828 }
23829
23830 fn document_highlights(
23831 &self,
23832 buffer: &Entity<Buffer>,
23833 position: text::Anchor,
23834 cx: &mut App,
23835 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23836 Some(self.update(cx, |project, cx| {
23837 project.document_highlights(buffer, position, cx)
23838 }))
23839 }
23840
23841 fn definitions(
23842 &self,
23843 buffer: &Entity<Buffer>,
23844 position: text::Anchor,
23845 kind: GotoDefinitionKind,
23846 cx: &mut App,
23847 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23848 Some(self.update(cx, |project, cx| match kind {
23849 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23850 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23851 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23852 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23853 }))
23854 }
23855
23856 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23857 self.update(cx, |project, cx| {
23858 if project
23859 .active_debug_session(cx)
23860 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23861 {
23862 return true;
23863 }
23864
23865 buffer.update(cx, |buffer, cx| {
23866 project.any_language_server_supports_inlay_hints(buffer, cx)
23867 })
23868 })
23869 }
23870
23871 fn inline_values(
23872 &self,
23873 buffer_handle: Entity<Buffer>,
23874 range: Range<text::Anchor>,
23875 cx: &mut App,
23876 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23877 self.update(cx, |project, cx| {
23878 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23879
23880 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23881 })
23882 }
23883
23884 fn applicable_inlay_chunks(
23885 &self,
23886 buffer: &Entity<Buffer>,
23887 ranges: &[Range<text::Anchor>],
23888 cx: &mut App,
23889 ) -> Vec<Range<BufferRow>> {
23890 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23891 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23892 })
23893 }
23894
23895 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23896 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23897 lsp_store.invalidate_inlay_hints(for_buffers)
23898 });
23899 }
23900
23901 fn inlay_hints(
23902 &self,
23903 invalidate: InvalidationStrategy,
23904 buffer: Entity<Buffer>,
23905 ranges: Vec<Range<text::Anchor>>,
23906 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23907 cx: &mut App,
23908 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23909 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23910 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23911 }))
23912 }
23913
23914 fn range_for_rename(
23915 &self,
23916 buffer: &Entity<Buffer>,
23917 position: text::Anchor,
23918 cx: &mut App,
23919 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23920 Some(self.update(cx, |project, cx| {
23921 let buffer = buffer.clone();
23922 let task = project.prepare_rename(buffer.clone(), position, cx);
23923 cx.spawn(async move |_, cx| {
23924 Ok(match task.await? {
23925 PrepareRenameResponse::Success(range) => Some(range),
23926 PrepareRenameResponse::InvalidPosition => None,
23927 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23928 // Fallback on using TreeSitter info to determine identifier range
23929 buffer.read_with(cx, |buffer, _| {
23930 let snapshot = buffer.snapshot();
23931 let (range, kind) = snapshot.surrounding_word(position, None);
23932 if kind != Some(CharKind::Word) {
23933 return None;
23934 }
23935 Some(
23936 snapshot.anchor_before(range.start)
23937 ..snapshot.anchor_after(range.end),
23938 )
23939 })?
23940 }
23941 })
23942 })
23943 }))
23944 }
23945
23946 fn perform_rename(
23947 &self,
23948 buffer: &Entity<Buffer>,
23949 position: text::Anchor,
23950 new_name: String,
23951 cx: &mut App,
23952 ) -> Option<Task<Result<ProjectTransaction>>> {
23953 Some(self.update(cx, |project, cx| {
23954 project.perform_rename(buffer.clone(), position, new_name, cx)
23955 }))
23956 }
23957}
23958
23959fn consume_contiguous_rows(
23960 contiguous_row_selections: &mut Vec<Selection<Point>>,
23961 selection: &Selection<Point>,
23962 display_map: &DisplaySnapshot,
23963 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23964) -> (MultiBufferRow, MultiBufferRow) {
23965 contiguous_row_selections.push(selection.clone());
23966 let start_row = starting_row(selection, display_map);
23967 let mut end_row = ending_row(selection, display_map);
23968
23969 while let Some(next_selection) = selections.peek() {
23970 if next_selection.start.row <= end_row.0 {
23971 end_row = ending_row(next_selection, display_map);
23972 contiguous_row_selections.push(selections.next().unwrap().clone());
23973 } else {
23974 break;
23975 }
23976 }
23977 (start_row, end_row)
23978}
23979
23980fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23981 if selection.start.column > 0 {
23982 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23983 } else {
23984 MultiBufferRow(selection.start.row)
23985 }
23986}
23987
23988fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23989 if next_selection.end.column > 0 || next_selection.is_empty() {
23990 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23991 } else {
23992 MultiBufferRow(next_selection.end.row)
23993 }
23994}
23995
23996impl EditorSnapshot {
23997 pub fn remote_selections_in_range<'a>(
23998 &'a self,
23999 range: &'a Range<Anchor>,
24000 collaboration_hub: &dyn CollaborationHub,
24001 cx: &'a App,
24002 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24003 let participant_names = collaboration_hub.user_names(cx);
24004 let participant_indices = collaboration_hub.user_participant_indices(cx);
24005 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24006 let collaborators_by_replica_id = collaborators_by_peer_id
24007 .values()
24008 .map(|collaborator| (collaborator.replica_id, collaborator))
24009 .collect::<HashMap<_, _>>();
24010 self.buffer_snapshot()
24011 .selections_in_range(range, false)
24012 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24013 if replica_id == ReplicaId::AGENT {
24014 Some(RemoteSelection {
24015 replica_id,
24016 selection,
24017 cursor_shape,
24018 line_mode,
24019 collaborator_id: CollaboratorId::Agent,
24020 user_name: Some("Agent".into()),
24021 color: cx.theme().players().agent(),
24022 })
24023 } else {
24024 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24025 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24026 let user_name = participant_names.get(&collaborator.user_id).cloned();
24027 Some(RemoteSelection {
24028 replica_id,
24029 selection,
24030 cursor_shape,
24031 line_mode,
24032 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24033 user_name,
24034 color: if let Some(index) = participant_index {
24035 cx.theme().players().color_for_participant(index.0)
24036 } else {
24037 cx.theme().players().absent()
24038 },
24039 })
24040 }
24041 })
24042 }
24043
24044 pub fn hunks_for_ranges(
24045 &self,
24046 ranges: impl IntoIterator<Item = Range<Point>>,
24047 ) -> Vec<MultiBufferDiffHunk> {
24048 let mut hunks = Vec::new();
24049 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24050 HashMap::default();
24051 for query_range in ranges {
24052 let query_rows =
24053 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24054 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24055 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24056 ) {
24057 // Include deleted hunks that are adjacent to the query range, because
24058 // otherwise they would be missed.
24059 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24060 if hunk.status().is_deleted() {
24061 intersects_range |= hunk.row_range.start == query_rows.end;
24062 intersects_range |= hunk.row_range.end == query_rows.start;
24063 }
24064 if intersects_range {
24065 if !processed_buffer_rows
24066 .entry(hunk.buffer_id)
24067 .or_default()
24068 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24069 {
24070 continue;
24071 }
24072 hunks.push(hunk);
24073 }
24074 }
24075 }
24076
24077 hunks
24078 }
24079
24080 fn display_diff_hunks_for_rows<'a>(
24081 &'a self,
24082 display_rows: Range<DisplayRow>,
24083 folded_buffers: &'a HashSet<BufferId>,
24084 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24085 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24086 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24087
24088 self.buffer_snapshot()
24089 .diff_hunks_in_range(buffer_start..buffer_end)
24090 .filter_map(|hunk| {
24091 if folded_buffers.contains(&hunk.buffer_id) {
24092 return None;
24093 }
24094
24095 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24096 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24097
24098 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24099 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24100
24101 let display_hunk = if hunk_display_start.column() != 0 {
24102 DisplayDiffHunk::Folded {
24103 display_row: hunk_display_start.row(),
24104 }
24105 } else {
24106 let mut end_row = hunk_display_end.row();
24107 if hunk_display_end.column() > 0 {
24108 end_row.0 += 1;
24109 }
24110 let is_created_file = hunk.is_created_file();
24111 DisplayDiffHunk::Unfolded {
24112 status: hunk.status(),
24113 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24114 ..hunk.diff_base_byte_range.end.0,
24115 display_row_range: hunk_display_start.row()..end_row,
24116 multi_buffer_range: Anchor::range_in_buffer(
24117 hunk.excerpt_id,
24118 hunk.buffer_range,
24119 ),
24120 is_created_file,
24121 }
24122 };
24123
24124 Some(display_hunk)
24125 })
24126 }
24127
24128 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24129 self.display_snapshot
24130 .buffer_snapshot()
24131 .language_at(position)
24132 }
24133
24134 pub fn is_focused(&self) -> bool {
24135 self.is_focused
24136 }
24137
24138 pub fn placeholder_text(&self) -> Option<String> {
24139 self.placeholder_display_snapshot
24140 .as_ref()
24141 .map(|display_map| display_map.text())
24142 }
24143
24144 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24145 self.scroll_anchor.scroll_position(&self.display_snapshot)
24146 }
24147
24148 fn gutter_dimensions(
24149 &self,
24150 font_id: FontId,
24151 font_size: Pixels,
24152 max_line_number_width: Pixels,
24153 cx: &App,
24154 ) -> Option<GutterDimensions> {
24155 if !self.show_gutter {
24156 return None;
24157 }
24158
24159 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
24160 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
24161
24162 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24163 matches!(
24164 ProjectSettings::get_global(cx).git.git_gutter,
24165 GitGutterSetting::TrackedFiles
24166 )
24167 });
24168 let gutter_settings = EditorSettings::get_global(cx).gutter;
24169 let show_line_numbers = self
24170 .show_line_numbers
24171 .unwrap_or(gutter_settings.line_numbers);
24172 let line_gutter_width = if show_line_numbers {
24173 // Avoid flicker-like gutter resizes when the line number gains another digit by
24174 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24175 let min_width_for_number_on_gutter =
24176 ch_advance * gutter_settings.min_line_number_digits as f32;
24177 max_line_number_width.max(min_width_for_number_on_gutter)
24178 } else {
24179 0.0.into()
24180 };
24181
24182 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24183 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24184
24185 let git_blame_entries_width =
24186 self.git_blame_gutter_max_author_length
24187 .map(|max_author_length| {
24188 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24189 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24190
24191 /// The number of characters to dedicate to gaps and margins.
24192 const SPACING_WIDTH: usize = 4;
24193
24194 let max_char_count = max_author_length.min(renderer.max_author_length())
24195 + ::git::SHORT_SHA_LENGTH
24196 + MAX_RELATIVE_TIMESTAMP.len()
24197 + SPACING_WIDTH;
24198
24199 ch_advance * max_char_count
24200 });
24201
24202 let is_singleton = self.buffer_snapshot().is_singleton();
24203
24204 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24205 left_padding += if !is_singleton {
24206 ch_width * 4.0
24207 } else if show_runnables || show_breakpoints {
24208 ch_width * 3.0
24209 } else if show_git_gutter && show_line_numbers {
24210 ch_width * 2.0
24211 } else if show_git_gutter || show_line_numbers {
24212 ch_width
24213 } else {
24214 px(0.)
24215 };
24216
24217 let shows_folds = is_singleton && gutter_settings.folds;
24218
24219 let right_padding = if shows_folds && show_line_numbers {
24220 ch_width * 4.0
24221 } else if shows_folds || (!is_singleton && show_line_numbers) {
24222 ch_width * 3.0
24223 } else if show_line_numbers {
24224 ch_width
24225 } else {
24226 px(0.)
24227 };
24228
24229 Some(GutterDimensions {
24230 left_padding,
24231 right_padding,
24232 width: line_gutter_width + left_padding + right_padding,
24233 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24234 git_blame_entries_width,
24235 })
24236 }
24237
24238 pub fn render_crease_toggle(
24239 &self,
24240 buffer_row: MultiBufferRow,
24241 row_contains_cursor: bool,
24242 editor: Entity<Editor>,
24243 window: &mut Window,
24244 cx: &mut App,
24245 ) -> Option<AnyElement> {
24246 let folded = self.is_line_folded(buffer_row);
24247 let mut is_foldable = false;
24248
24249 if let Some(crease) = self
24250 .crease_snapshot
24251 .query_row(buffer_row, self.buffer_snapshot())
24252 {
24253 is_foldable = true;
24254 match crease {
24255 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24256 if let Some(render_toggle) = render_toggle {
24257 let toggle_callback =
24258 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24259 if folded {
24260 editor.update(cx, |editor, cx| {
24261 editor.fold_at(buffer_row, window, cx)
24262 });
24263 } else {
24264 editor.update(cx, |editor, cx| {
24265 editor.unfold_at(buffer_row, window, cx)
24266 });
24267 }
24268 });
24269 return Some((render_toggle)(
24270 buffer_row,
24271 folded,
24272 toggle_callback,
24273 window,
24274 cx,
24275 ));
24276 }
24277 }
24278 }
24279 }
24280
24281 is_foldable |= self.starts_indent(buffer_row);
24282
24283 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24284 Some(
24285 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24286 .toggle_state(folded)
24287 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24288 if folded {
24289 this.unfold_at(buffer_row, window, cx);
24290 } else {
24291 this.fold_at(buffer_row, window, cx);
24292 }
24293 }))
24294 .into_any_element(),
24295 )
24296 } else {
24297 None
24298 }
24299 }
24300
24301 pub fn render_crease_trailer(
24302 &self,
24303 buffer_row: MultiBufferRow,
24304 window: &mut Window,
24305 cx: &mut App,
24306 ) -> Option<AnyElement> {
24307 let folded = self.is_line_folded(buffer_row);
24308 if let Crease::Inline { render_trailer, .. } = self
24309 .crease_snapshot
24310 .query_row(buffer_row, self.buffer_snapshot())?
24311 {
24312 let render_trailer = render_trailer.as_ref()?;
24313 Some(render_trailer(buffer_row, folded, window, cx))
24314 } else {
24315 None
24316 }
24317 }
24318}
24319
24320impl Deref for EditorSnapshot {
24321 type Target = DisplaySnapshot;
24322
24323 fn deref(&self) -> &Self::Target {
24324 &self.display_snapshot
24325 }
24326}
24327
24328#[derive(Clone, Debug, PartialEq, Eq)]
24329pub enum EditorEvent {
24330 InputIgnored {
24331 text: Arc<str>,
24332 },
24333 InputHandled {
24334 utf16_range_to_replace: Option<Range<isize>>,
24335 text: Arc<str>,
24336 },
24337 ExcerptsAdded {
24338 buffer: Entity<Buffer>,
24339 predecessor: ExcerptId,
24340 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24341 },
24342 ExcerptsRemoved {
24343 ids: Vec<ExcerptId>,
24344 removed_buffer_ids: Vec<BufferId>,
24345 },
24346 BufferFoldToggled {
24347 ids: Vec<ExcerptId>,
24348 folded: bool,
24349 },
24350 ExcerptsEdited {
24351 ids: Vec<ExcerptId>,
24352 },
24353 ExcerptsExpanded {
24354 ids: Vec<ExcerptId>,
24355 },
24356 BufferEdited,
24357 Edited {
24358 transaction_id: clock::Lamport,
24359 },
24360 Reparsed(BufferId),
24361 Focused,
24362 FocusedIn,
24363 Blurred,
24364 DirtyChanged,
24365 Saved,
24366 TitleChanged,
24367 SelectionsChanged {
24368 local: bool,
24369 },
24370 ScrollPositionChanged {
24371 local: bool,
24372 autoscroll: bool,
24373 },
24374 TransactionUndone {
24375 transaction_id: clock::Lamport,
24376 },
24377 TransactionBegun {
24378 transaction_id: clock::Lamport,
24379 },
24380 CursorShapeChanged,
24381 BreadcrumbsChanged,
24382 PushedToNavHistory {
24383 anchor: Anchor,
24384 is_deactivate: bool,
24385 },
24386}
24387
24388impl EventEmitter<EditorEvent> for Editor {}
24389
24390impl Focusable for Editor {
24391 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24392 self.focus_handle.clone()
24393 }
24394}
24395
24396impl Render for Editor {
24397 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24398 let settings = ThemeSettings::get_global(cx);
24399
24400 let mut text_style = match self.mode {
24401 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24402 color: cx.theme().colors().editor_foreground,
24403 font_family: settings.ui_font.family.clone(),
24404 font_features: settings.ui_font.features.clone(),
24405 font_fallbacks: settings.ui_font.fallbacks.clone(),
24406 font_size: rems(0.875).into(),
24407 font_weight: settings.ui_font.weight,
24408 line_height: relative(settings.buffer_line_height.value()),
24409 ..Default::default()
24410 },
24411 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24412 color: cx.theme().colors().editor_foreground,
24413 font_family: settings.buffer_font.family.clone(),
24414 font_features: settings.buffer_font.features.clone(),
24415 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24416 font_size: settings.buffer_font_size(cx).into(),
24417 font_weight: settings.buffer_font.weight,
24418 line_height: relative(settings.buffer_line_height.value()),
24419 ..Default::default()
24420 },
24421 };
24422 if let Some(text_style_refinement) = &self.text_style_refinement {
24423 text_style.refine(text_style_refinement)
24424 }
24425
24426 let background = match self.mode {
24427 EditorMode::SingleLine => cx.theme().system().transparent,
24428 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24429 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24430 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24431 };
24432
24433 EditorElement::new(
24434 &cx.entity(),
24435 EditorStyle {
24436 background,
24437 border: cx.theme().colors().border,
24438 local_player: cx.theme().players().local(),
24439 text: text_style,
24440 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24441 syntax: cx.theme().syntax().clone(),
24442 status: cx.theme().status().clone(),
24443 inlay_hints_style: make_inlay_hints_style(cx),
24444 edit_prediction_styles: make_suggestion_styles(cx),
24445 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24446 show_underlines: self.diagnostics_enabled(),
24447 },
24448 )
24449 }
24450}
24451
24452impl EntityInputHandler for Editor {
24453 fn text_for_range(
24454 &mut self,
24455 range_utf16: Range<usize>,
24456 adjusted_range: &mut Option<Range<usize>>,
24457 _: &mut Window,
24458 cx: &mut Context<Self>,
24459 ) -> Option<String> {
24460 let snapshot = self.buffer.read(cx).read(cx);
24461 let start = snapshot.clip_offset_utf16(
24462 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
24463 Bias::Left,
24464 );
24465 let end = snapshot.clip_offset_utf16(
24466 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
24467 Bias::Right,
24468 );
24469 if (start.0.0..end.0.0) != range_utf16 {
24470 adjusted_range.replace(start.0.0..end.0.0);
24471 }
24472 Some(snapshot.text_for_range(start..end).collect())
24473 }
24474
24475 fn selected_text_range(
24476 &mut self,
24477 ignore_disabled_input: bool,
24478 _: &mut Window,
24479 cx: &mut Context<Self>,
24480 ) -> Option<UTF16Selection> {
24481 // Prevent the IME menu from appearing when holding down an alphabetic key
24482 // while input is disabled.
24483 if !ignore_disabled_input && !self.input_enabled {
24484 return None;
24485 }
24486
24487 let selection = self
24488 .selections
24489 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24490 let range = selection.range();
24491
24492 Some(UTF16Selection {
24493 range: range.start.0.0..range.end.0.0,
24494 reversed: selection.reversed,
24495 })
24496 }
24497
24498 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24499 let snapshot = self.buffer.read(cx).read(cx);
24500 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24501 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
24502 }
24503
24504 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24505 self.clear_highlights::<InputComposition>(cx);
24506 self.ime_transaction.take();
24507 }
24508
24509 fn replace_text_in_range(
24510 &mut self,
24511 range_utf16: Option<Range<usize>>,
24512 text: &str,
24513 window: &mut Window,
24514 cx: &mut Context<Self>,
24515 ) {
24516 if !self.input_enabled {
24517 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24518 return;
24519 }
24520
24521 self.transact(window, cx, |this, window, cx| {
24522 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24523 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24524 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24525 Some(this.selection_replacement_ranges(range_utf16, cx))
24526 } else {
24527 this.marked_text_ranges(cx)
24528 };
24529
24530 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24531 let newest_selection_id = this.selections.newest_anchor().id;
24532 this.selections
24533 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24534 .iter()
24535 .zip(ranges_to_replace.iter())
24536 .find_map(|(selection, range)| {
24537 if selection.id == newest_selection_id {
24538 Some(
24539 (range.start.0.0 as isize - selection.head().0.0 as isize)
24540 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24541 )
24542 } else {
24543 None
24544 }
24545 })
24546 });
24547
24548 cx.emit(EditorEvent::InputHandled {
24549 utf16_range_to_replace: range_to_replace,
24550 text: text.into(),
24551 });
24552
24553 if let Some(new_selected_ranges) = new_selected_ranges {
24554 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24555 selections.select_ranges(new_selected_ranges)
24556 });
24557 this.backspace(&Default::default(), window, cx);
24558 }
24559
24560 this.handle_input(text, window, cx);
24561 });
24562
24563 if let Some(transaction) = self.ime_transaction {
24564 self.buffer.update(cx, |buffer, cx| {
24565 buffer.group_until_transaction(transaction, cx);
24566 });
24567 }
24568
24569 self.unmark_text(window, cx);
24570 }
24571
24572 fn replace_and_mark_text_in_range(
24573 &mut self,
24574 range_utf16: Option<Range<usize>>,
24575 text: &str,
24576 new_selected_range_utf16: Option<Range<usize>>,
24577 window: &mut Window,
24578 cx: &mut Context<Self>,
24579 ) {
24580 if !self.input_enabled {
24581 return;
24582 }
24583
24584 let transaction = self.transact(window, cx, |this, window, cx| {
24585 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24586 let snapshot = this.buffer.read(cx).read(cx);
24587 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24588 for marked_range in &mut marked_ranges {
24589 marked_range.end = marked_range.start + relative_range_utf16.end;
24590 marked_range.start += relative_range_utf16.start;
24591 marked_range.start =
24592 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24593 marked_range.end =
24594 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24595 }
24596 }
24597 Some(marked_ranges)
24598 } else if let Some(range_utf16) = range_utf16 {
24599 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24600 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24601 Some(this.selection_replacement_ranges(range_utf16, cx))
24602 } else {
24603 None
24604 };
24605
24606 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24607 let newest_selection_id = this.selections.newest_anchor().id;
24608 this.selections
24609 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24610 .iter()
24611 .zip(ranges_to_replace.iter())
24612 .find_map(|(selection, range)| {
24613 if selection.id == newest_selection_id {
24614 Some(
24615 (range.start.0.0 as isize - selection.head().0.0 as isize)
24616 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24617 )
24618 } else {
24619 None
24620 }
24621 })
24622 });
24623
24624 cx.emit(EditorEvent::InputHandled {
24625 utf16_range_to_replace: range_to_replace,
24626 text: text.into(),
24627 });
24628
24629 if let Some(ranges) = ranges_to_replace {
24630 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24631 s.select_ranges(ranges)
24632 });
24633 }
24634
24635 let marked_ranges = {
24636 let snapshot = this.buffer.read(cx).read(cx);
24637 this.selections
24638 .disjoint_anchors_arc()
24639 .iter()
24640 .map(|selection| {
24641 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24642 })
24643 .collect::<Vec<_>>()
24644 };
24645
24646 if text.is_empty() {
24647 this.unmark_text(window, cx);
24648 } else {
24649 this.highlight_text::<InputComposition>(
24650 marked_ranges.clone(),
24651 HighlightStyle {
24652 underline: Some(UnderlineStyle {
24653 thickness: px(1.),
24654 color: None,
24655 wavy: false,
24656 }),
24657 ..Default::default()
24658 },
24659 cx,
24660 );
24661 }
24662
24663 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24664 let use_autoclose = this.use_autoclose;
24665 let use_auto_surround = this.use_auto_surround;
24666 this.set_use_autoclose(false);
24667 this.set_use_auto_surround(false);
24668 this.handle_input(text, window, cx);
24669 this.set_use_autoclose(use_autoclose);
24670 this.set_use_auto_surround(use_auto_surround);
24671
24672 if let Some(new_selected_range) = new_selected_range_utf16 {
24673 let snapshot = this.buffer.read(cx).read(cx);
24674 let new_selected_ranges = marked_ranges
24675 .into_iter()
24676 .map(|marked_range| {
24677 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24678 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
24679 insertion_start.0 + new_selected_range.start,
24680 ));
24681 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
24682 insertion_start.0 + new_selected_range.end,
24683 ));
24684 snapshot.clip_offset_utf16(new_start, Bias::Left)
24685 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24686 })
24687 .collect::<Vec<_>>();
24688
24689 drop(snapshot);
24690 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24691 selections.select_ranges(new_selected_ranges)
24692 });
24693 }
24694 });
24695
24696 self.ime_transaction = self.ime_transaction.or(transaction);
24697 if let Some(transaction) = self.ime_transaction {
24698 self.buffer.update(cx, |buffer, cx| {
24699 buffer.group_until_transaction(transaction, cx);
24700 });
24701 }
24702
24703 if self.text_highlights::<InputComposition>(cx).is_none() {
24704 self.ime_transaction.take();
24705 }
24706 }
24707
24708 fn bounds_for_range(
24709 &mut self,
24710 range_utf16: Range<usize>,
24711 element_bounds: gpui::Bounds<Pixels>,
24712 window: &mut Window,
24713 cx: &mut Context<Self>,
24714 ) -> Option<gpui::Bounds<Pixels>> {
24715 let text_layout_details = self.text_layout_details(window);
24716 let CharacterDimensions {
24717 em_width,
24718 em_advance,
24719 line_height,
24720 } = self.character_dimensions(window);
24721
24722 let snapshot = self.snapshot(window, cx);
24723 let scroll_position = snapshot.scroll_position();
24724 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24725
24726 let start =
24727 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
24728 let x = Pixels::from(
24729 ScrollOffset::from(
24730 snapshot.x_for_display_point(start, &text_layout_details)
24731 + self.gutter_dimensions.full_width(),
24732 ) - scroll_left,
24733 );
24734 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24735
24736 Some(Bounds {
24737 origin: element_bounds.origin + point(x, y),
24738 size: size(em_width, line_height),
24739 })
24740 }
24741
24742 fn character_index_for_point(
24743 &mut self,
24744 point: gpui::Point<Pixels>,
24745 _window: &mut Window,
24746 _cx: &mut Context<Self>,
24747 ) -> Option<usize> {
24748 let position_map = self.last_position_map.as_ref()?;
24749 if !position_map.text_hitbox.contains(&point) {
24750 return None;
24751 }
24752 let display_point = position_map.point_for_position(point).previous_valid;
24753 let anchor = position_map
24754 .snapshot
24755 .display_point_to_anchor(display_point, Bias::Left);
24756 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24757 Some(utf16_offset.0.0)
24758 }
24759
24760 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24761 self.input_enabled
24762 }
24763}
24764
24765trait SelectionExt {
24766 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24767 fn spanned_rows(
24768 &self,
24769 include_end_if_at_line_start: bool,
24770 map: &DisplaySnapshot,
24771 ) -> Range<MultiBufferRow>;
24772}
24773
24774impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24775 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24776 let start = self
24777 .start
24778 .to_point(map.buffer_snapshot())
24779 .to_display_point(map);
24780 let end = self
24781 .end
24782 .to_point(map.buffer_snapshot())
24783 .to_display_point(map);
24784 if self.reversed {
24785 end..start
24786 } else {
24787 start..end
24788 }
24789 }
24790
24791 fn spanned_rows(
24792 &self,
24793 include_end_if_at_line_start: bool,
24794 map: &DisplaySnapshot,
24795 ) -> Range<MultiBufferRow> {
24796 let start = self.start.to_point(map.buffer_snapshot());
24797 let mut end = self.end.to_point(map.buffer_snapshot());
24798 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24799 end.row -= 1;
24800 }
24801
24802 let buffer_start = map.prev_line_boundary(start).0;
24803 let buffer_end = map.next_line_boundary(end).0;
24804 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24805 }
24806}
24807
24808impl<T: InvalidationRegion> InvalidationStack<T> {
24809 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24810 where
24811 S: Clone + ToOffset,
24812 {
24813 while let Some(region) = self.last() {
24814 let all_selections_inside_invalidation_ranges =
24815 if selections.len() == region.ranges().len() {
24816 selections
24817 .iter()
24818 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24819 .all(|(selection, invalidation_range)| {
24820 let head = selection.head().to_offset(buffer);
24821 invalidation_range.start <= head && invalidation_range.end >= head
24822 })
24823 } else {
24824 false
24825 };
24826
24827 if all_selections_inside_invalidation_ranges {
24828 break;
24829 } else {
24830 self.pop();
24831 }
24832 }
24833 }
24834}
24835
24836impl<T> Default for InvalidationStack<T> {
24837 fn default() -> Self {
24838 Self(Default::default())
24839 }
24840}
24841
24842impl<T> Deref for InvalidationStack<T> {
24843 type Target = Vec<T>;
24844
24845 fn deref(&self) -> &Self::Target {
24846 &self.0
24847 }
24848}
24849
24850impl<T> DerefMut for InvalidationStack<T> {
24851 fn deref_mut(&mut self) -> &mut Self::Target {
24852 &mut self.0
24853 }
24854}
24855
24856impl InvalidationRegion for SnippetState {
24857 fn ranges(&self) -> &[Range<Anchor>] {
24858 &self.ranges[self.active_index]
24859 }
24860}
24861
24862fn edit_prediction_edit_text(
24863 current_snapshot: &BufferSnapshot,
24864 edits: &[(Range<Anchor>, impl AsRef<str>)],
24865 edit_preview: &EditPreview,
24866 include_deletions: bool,
24867 cx: &App,
24868) -> HighlightedText {
24869 let edits = edits
24870 .iter()
24871 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24872 .collect::<Vec<_>>();
24873
24874 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24875}
24876
24877fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24878 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24879 // Just show the raw edit text with basic styling
24880 let mut text = String::new();
24881 let mut highlights = Vec::new();
24882
24883 let insertion_highlight_style = HighlightStyle {
24884 color: Some(cx.theme().colors().text),
24885 ..Default::default()
24886 };
24887
24888 for (_, edit_text) in edits {
24889 let start_offset = text.len();
24890 text.push_str(edit_text);
24891 let end_offset = text.len();
24892
24893 if start_offset < end_offset {
24894 highlights.push((start_offset..end_offset, insertion_highlight_style));
24895 }
24896 }
24897
24898 HighlightedText {
24899 text: text.into(),
24900 highlights,
24901 }
24902}
24903
24904pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24905 match severity {
24906 lsp::DiagnosticSeverity::ERROR => colors.error,
24907 lsp::DiagnosticSeverity::WARNING => colors.warning,
24908 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24909 lsp::DiagnosticSeverity::HINT => colors.info,
24910 _ => colors.ignored,
24911 }
24912}
24913
24914pub fn styled_runs_for_code_label<'a>(
24915 label: &'a CodeLabel,
24916 syntax_theme: &'a theme::SyntaxTheme,
24917) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24918 let fade_out = HighlightStyle {
24919 fade_out: Some(0.35),
24920 ..Default::default()
24921 };
24922
24923 let mut prev_end = label.filter_range.end;
24924 label
24925 .runs
24926 .iter()
24927 .enumerate()
24928 .flat_map(move |(ix, (range, highlight_id))| {
24929 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24930 style
24931 } else {
24932 return Default::default();
24933 };
24934 let muted_style = style.highlight(fade_out);
24935
24936 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24937 if range.start >= label.filter_range.end {
24938 if range.start > prev_end {
24939 runs.push((prev_end..range.start, fade_out));
24940 }
24941 runs.push((range.clone(), muted_style));
24942 } else if range.end <= label.filter_range.end {
24943 runs.push((range.clone(), style));
24944 } else {
24945 runs.push((range.start..label.filter_range.end, style));
24946 runs.push((label.filter_range.end..range.end, muted_style));
24947 }
24948 prev_end = cmp::max(prev_end, range.end);
24949
24950 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24951 runs.push((prev_end..label.text.len(), fade_out));
24952 }
24953
24954 runs
24955 })
24956}
24957
24958pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24959 let mut prev_index = 0;
24960 let mut prev_codepoint: Option<char> = None;
24961 text.char_indices()
24962 .chain([(text.len(), '\0')])
24963 .filter_map(move |(index, codepoint)| {
24964 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24965 let is_boundary = index == text.len()
24966 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24967 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24968 if is_boundary {
24969 let chunk = &text[prev_index..index];
24970 prev_index = index;
24971 Some(chunk)
24972 } else {
24973 None
24974 }
24975 })
24976}
24977
24978/// Given a string of text immediately before the cursor, iterates over possible
24979/// strings a snippet could match to. More precisely: returns an iterator over
24980/// suffixes of `text` created by splitting at word boundaries (before & after
24981/// every non-word character).
24982///
24983/// Shorter suffixes are returned first.
24984pub(crate) fn snippet_candidate_suffixes(
24985 text: &str,
24986 is_word_char: impl Fn(char) -> bool,
24987) -> impl std::iter::Iterator<Item = &str> {
24988 let mut prev_index = text.len();
24989 let mut prev_codepoint = None;
24990 text.char_indices()
24991 .rev()
24992 .chain([(0, '\0')])
24993 .filter_map(move |(index, codepoint)| {
24994 let prev_index = std::mem::replace(&mut prev_index, index);
24995 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24996 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
24997 None
24998 } else {
24999 let chunk = &text[prev_index..]; // go to end of string
25000 Some(chunk)
25001 }
25002 })
25003}
25004
25005pub trait RangeToAnchorExt: Sized {
25006 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25007
25008 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25009 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25010 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25011 }
25012}
25013
25014impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25015 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25016 let start_offset = self.start.to_offset(snapshot);
25017 let end_offset = self.end.to_offset(snapshot);
25018 if start_offset == end_offset {
25019 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25020 } else {
25021 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25022 }
25023 }
25024}
25025
25026pub trait RowExt {
25027 fn as_f64(&self) -> f64;
25028
25029 fn next_row(&self) -> Self;
25030
25031 fn previous_row(&self) -> Self;
25032
25033 fn minus(&self, other: Self) -> u32;
25034}
25035
25036impl RowExt for DisplayRow {
25037 fn as_f64(&self) -> f64 {
25038 self.0 as _
25039 }
25040
25041 fn next_row(&self) -> Self {
25042 Self(self.0 + 1)
25043 }
25044
25045 fn previous_row(&self) -> Self {
25046 Self(self.0.saturating_sub(1))
25047 }
25048
25049 fn minus(&self, other: Self) -> u32 {
25050 self.0 - other.0
25051 }
25052}
25053
25054impl RowExt for MultiBufferRow {
25055 fn as_f64(&self) -> f64 {
25056 self.0 as _
25057 }
25058
25059 fn next_row(&self) -> Self {
25060 Self(self.0 + 1)
25061 }
25062
25063 fn previous_row(&self) -> Self {
25064 Self(self.0.saturating_sub(1))
25065 }
25066
25067 fn minus(&self, other: Self) -> u32 {
25068 self.0 - other.0
25069 }
25070}
25071
25072trait RowRangeExt {
25073 type Row;
25074
25075 fn len(&self) -> usize;
25076
25077 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25078}
25079
25080impl RowRangeExt for Range<MultiBufferRow> {
25081 type Row = MultiBufferRow;
25082
25083 fn len(&self) -> usize {
25084 (self.end.0 - self.start.0) as usize
25085 }
25086
25087 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25088 (self.start.0..self.end.0).map(MultiBufferRow)
25089 }
25090}
25091
25092impl RowRangeExt for Range<DisplayRow> {
25093 type Row = DisplayRow;
25094
25095 fn len(&self) -> usize {
25096 (self.end.0 - self.start.0) as usize
25097 }
25098
25099 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25100 (self.start.0..self.end.0).map(DisplayRow)
25101 }
25102}
25103
25104/// If select range has more than one line, we
25105/// just point the cursor to range.start.
25106fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25107 if range.start.row == range.end.row {
25108 range
25109 } else {
25110 range.start..range.start
25111 }
25112}
25113pub struct KillRing(ClipboardItem);
25114impl Global for KillRing {}
25115
25116const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25117
25118enum BreakpointPromptEditAction {
25119 Log,
25120 Condition,
25121 HitCondition,
25122}
25123
25124struct BreakpointPromptEditor {
25125 pub(crate) prompt: Entity<Editor>,
25126 editor: WeakEntity<Editor>,
25127 breakpoint_anchor: Anchor,
25128 breakpoint: Breakpoint,
25129 edit_action: BreakpointPromptEditAction,
25130 block_ids: HashSet<CustomBlockId>,
25131 editor_margins: Arc<Mutex<EditorMargins>>,
25132 _subscriptions: Vec<Subscription>,
25133}
25134
25135impl BreakpointPromptEditor {
25136 const MAX_LINES: u8 = 4;
25137
25138 fn new(
25139 editor: WeakEntity<Editor>,
25140 breakpoint_anchor: Anchor,
25141 breakpoint: Breakpoint,
25142 edit_action: BreakpointPromptEditAction,
25143 window: &mut Window,
25144 cx: &mut Context<Self>,
25145 ) -> Self {
25146 let base_text = match edit_action {
25147 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25148 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25149 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25150 }
25151 .map(|msg| msg.to_string())
25152 .unwrap_or_default();
25153
25154 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25155 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25156
25157 let prompt = cx.new(|cx| {
25158 let mut prompt = Editor::new(
25159 EditorMode::AutoHeight {
25160 min_lines: 1,
25161 max_lines: Some(Self::MAX_LINES as usize),
25162 },
25163 buffer,
25164 None,
25165 window,
25166 cx,
25167 );
25168 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25169 prompt.set_show_cursor_when_unfocused(false, cx);
25170 prompt.set_placeholder_text(
25171 match edit_action {
25172 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25173 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25174 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25175 },
25176 window,
25177 cx,
25178 );
25179
25180 prompt
25181 });
25182
25183 Self {
25184 prompt,
25185 editor,
25186 breakpoint_anchor,
25187 breakpoint,
25188 edit_action,
25189 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25190 block_ids: Default::default(),
25191 _subscriptions: vec![],
25192 }
25193 }
25194
25195 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25196 self.block_ids.extend(block_ids)
25197 }
25198
25199 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25200 if let Some(editor) = self.editor.upgrade() {
25201 let message = self
25202 .prompt
25203 .read(cx)
25204 .buffer
25205 .read(cx)
25206 .as_singleton()
25207 .expect("A multi buffer in breakpoint prompt isn't possible")
25208 .read(cx)
25209 .as_rope()
25210 .to_string();
25211
25212 editor.update(cx, |editor, cx| {
25213 editor.edit_breakpoint_at_anchor(
25214 self.breakpoint_anchor,
25215 self.breakpoint.clone(),
25216 match self.edit_action {
25217 BreakpointPromptEditAction::Log => {
25218 BreakpointEditAction::EditLogMessage(message.into())
25219 }
25220 BreakpointPromptEditAction::Condition => {
25221 BreakpointEditAction::EditCondition(message.into())
25222 }
25223 BreakpointPromptEditAction::HitCondition => {
25224 BreakpointEditAction::EditHitCondition(message.into())
25225 }
25226 },
25227 cx,
25228 );
25229
25230 editor.remove_blocks(self.block_ids.clone(), None, cx);
25231 cx.focus_self(window);
25232 });
25233 }
25234 }
25235
25236 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25237 self.editor
25238 .update(cx, |editor, cx| {
25239 editor.remove_blocks(self.block_ids.clone(), None, cx);
25240 window.focus(&editor.focus_handle);
25241 })
25242 .log_err();
25243 }
25244
25245 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25246 let settings = ThemeSettings::get_global(cx);
25247 let text_style = TextStyle {
25248 color: if self.prompt.read(cx).read_only(cx) {
25249 cx.theme().colors().text_disabled
25250 } else {
25251 cx.theme().colors().text
25252 },
25253 font_family: settings.buffer_font.family.clone(),
25254 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25255 font_size: settings.buffer_font_size(cx).into(),
25256 font_weight: settings.buffer_font.weight,
25257 line_height: relative(settings.buffer_line_height.value()),
25258 ..Default::default()
25259 };
25260 EditorElement::new(
25261 &self.prompt,
25262 EditorStyle {
25263 background: cx.theme().colors().editor_background,
25264 local_player: cx.theme().players().local(),
25265 text: text_style,
25266 ..Default::default()
25267 },
25268 )
25269 }
25270}
25271
25272impl Render for BreakpointPromptEditor {
25273 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25274 let editor_margins = *self.editor_margins.lock();
25275 let gutter_dimensions = editor_margins.gutter;
25276 h_flex()
25277 .key_context("Editor")
25278 .bg(cx.theme().colors().editor_background)
25279 .border_y_1()
25280 .border_color(cx.theme().status().info_border)
25281 .size_full()
25282 .py(window.line_height() / 2.5)
25283 .on_action(cx.listener(Self::confirm))
25284 .on_action(cx.listener(Self::cancel))
25285 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25286 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25287 }
25288}
25289
25290impl Focusable for BreakpointPromptEditor {
25291 fn focus_handle(&self, cx: &App) -> FocusHandle {
25292 self.prompt.focus_handle(cx)
25293 }
25294}
25295
25296fn all_edits_insertions_or_deletions(
25297 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25298 snapshot: &MultiBufferSnapshot,
25299) -> bool {
25300 let mut all_insertions = true;
25301 let mut all_deletions = true;
25302
25303 for (range, new_text) in edits.iter() {
25304 let range_is_empty = range.to_offset(snapshot).is_empty();
25305 let text_is_empty = new_text.is_empty();
25306
25307 if range_is_empty != text_is_empty {
25308 if range_is_empty {
25309 all_deletions = false;
25310 } else {
25311 all_insertions = false;
25312 }
25313 } else {
25314 return false;
25315 }
25316
25317 if !all_insertions && !all_deletions {
25318 return false;
25319 }
25320 }
25321 all_insertions || all_deletions
25322}
25323
25324struct MissingEditPredictionKeybindingTooltip;
25325
25326impl Render for MissingEditPredictionKeybindingTooltip {
25327 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25328 ui::tooltip_container(cx, |container, cx| {
25329 container
25330 .flex_shrink_0()
25331 .max_w_80()
25332 .min_h(rems_from_px(124.))
25333 .justify_between()
25334 .child(
25335 v_flex()
25336 .flex_1()
25337 .text_ui_sm(cx)
25338 .child(Label::new("Conflict with Accept Keybinding"))
25339 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25340 )
25341 .child(
25342 h_flex()
25343 .pb_1()
25344 .gap_1()
25345 .items_end()
25346 .w_full()
25347 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25348 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25349 }))
25350 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25351 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25352 })),
25353 )
25354 })
25355 }
25356}
25357
25358#[derive(Debug, Clone, Copy, PartialEq)]
25359pub struct LineHighlight {
25360 pub background: Background,
25361 pub border: Option<gpui::Hsla>,
25362 pub include_gutter: bool,
25363 pub type_id: Option<TypeId>,
25364}
25365
25366struct LineManipulationResult {
25367 pub new_text: String,
25368 pub line_count_before: usize,
25369 pub line_count_after: usize,
25370}
25371
25372fn render_diff_hunk_controls(
25373 row: u32,
25374 status: &DiffHunkStatus,
25375 hunk_range: Range<Anchor>,
25376 is_created_file: bool,
25377 line_height: Pixels,
25378 editor: &Entity<Editor>,
25379 _window: &mut Window,
25380 cx: &mut App,
25381) -> AnyElement {
25382 h_flex()
25383 .h(line_height)
25384 .mr_1()
25385 .gap_1()
25386 .px_0p5()
25387 .pb_1()
25388 .border_x_1()
25389 .border_b_1()
25390 .border_color(cx.theme().colors().border_variant)
25391 .rounded_b_lg()
25392 .bg(cx.theme().colors().editor_background)
25393 .gap_1()
25394 .block_mouse_except_scroll()
25395 .shadow_md()
25396 .child(if status.has_secondary_hunk() {
25397 Button::new(("stage", row as u64), "Stage")
25398 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25399 .tooltip({
25400 let focus_handle = editor.focus_handle(cx);
25401 move |_window, cx| {
25402 Tooltip::for_action_in(
25403 "Stage Hunk",
25404 &::git::ToggleStaged,
25405 &focus_handle,
25406 cx,
25407 )
25408 }
25409 })
25410 .on_click({
25411 let editor = editor.clone();
25412 move |_event, _window, cx| {
25413 editor.update(cx, |editor, cx| {
25414 editor.stage_or_unstage_diff_hunks(
25415 true,
25416 vec![hunk_range.start..hunk_range.start],
25417 cx,
25418 );
25419 });
25420 }
25421 })
25422 } else {
25423 Button::new(("unstage", row as u64), "Unstage")
25424 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25425 .tooltip({
25426 let focus_handle = editor.focus_handle(cx);
25427 move |_window, cx| {
25428 Tooltip::for_action_in(
25429 "Unstage Hunk",
25430 &::git::ToggleStaged,
25431 &focus_handle,
25432 cx,
25433 )
25434 }
25435 })
25436 .on_click({
25437 let editor = editor.clone();
25438 move |_event, _window, cx| {
25439 editor.update(cx, |editor, cx| {
25440 editor.stage_or_unstage_diff_hunks(
25441 false,
25442 vec![hunk_range.start..hunk_range.start],
25443 cx,
25444 );
25445 });
25446 }
25447 })
25448 })
25449 .child(
25450 Button::new(("restore", row as u64), "Restore")
25451 .tooltip({
25452 let focus_handle = editor.focus_handle(cx);
25453 move |_window, cx| {
25454 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25455 }
25456 })
25457 .on_click({
25458 let editor = editor.clone();
25459 move |_event, window, cx| {
25460 editor.update(cx, |editor, cx| {
25461 let snapshot = editor.snapshot(window, cx);
25462 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25463 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25464 });
25465 }
25466 })
25467 .disabled(is_created_file),
25468 )
25469 .when(
25470 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25471 |el| {
25472 el.child(
25473 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25474 .shape(IconButtonShape::Square)
25475 .icon_size(IconSize::Small)
25476 // .disabled(!has_multiple_hunks)
25477 .tooltip({
25478 let focus_handle = editor.focus_handle(cx);
25479 move |_window, cx| {
25480 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25481 }
25482 })
25483 .on_click({
25484 let editor = editor.clone();
25485 move |_event, window, cx| {
25486 editor.update(cx, |editor, cx| {
25487 let snapshot = editor.snapshot(window, cx);
25488 let position =
25489 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25490 editor.go_to_hunk_before_or_after_position(
25491 &snapshot,
25492 position,
25493 Direction::Next,
25494 window,
25495 cx,
25496 );
25497 editor.expand_selected_diff_hunks(cx);
25498 });
25499 }
25500 }),
25501 )
25502 .child(
25503 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25504 .shape(IconButtonShape::Square)
25505 .icon_size(IconSize::Small)
25506 // .disabled(!has_multiple_hunks)
25507 .tooltip({
25508 let focus_handle = editor.focus_handle(cx);
25509 move |_window, cx| {
25510 Tooltip::for_action_in(
25511 "Previous Hunk",
25512 &GoToPreviousHunk,
25513 &focus_handle,
25514 cx,
25515 )
25516 }
25517 })
25518 .on_click({
25519 let editor = editor.clone();
25520 move |_event, window, cx| {
25521 editor.update(cx, |editor, cx| {
25522 let snapshot = editor.snapshot(window, cx);
25523 let point =
25524 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25525 editor.go_to_hunk_before_or_after_position(
25526 &snapshot,
25527 point,
25528 Direction::Prev,
25529 window,
25530 cx,
25531 );
25532 editor.expand_selected_diff_hunks(cx);
25533 });
25534 }
25535 }),
25536 )
25537 },
25538 )
25539 .into_any_element()
25540}
25541
25542pub fn multibuffer_context_lines(cx: &App) -> u32 {
25543 EditorSettings::try_get(cx)
25544 .map(|settings| settings.excerpt_context_lines)
25545 .unwrap_or(2)
25546 .min(32)
25547}