1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15mod blink_manager;
16mod 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, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
109 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
110 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
111 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_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, buffer_id, outline_item.range),
1794 source_range_for_text: Anchor::range_in_buffer(
1795 *excerpt_id,
1796 buffer_id,
1797 outline_item.source_range_for_text,
1798 ),
1799 text: outline_item.text,
1800 highlight_ranges: outline_item.highlight_ranges,
1801 name_ranges: outline_item.name_ranges,
1802 body_range: outline_item
1803 .body_range
1804 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1805 annotation_range: outline_item
1806 .annotation_range
1807 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1808 });
1809 return Some(outline_items.collect());
1810 }
1811
1812 None
1813 }
1814
1815 fn new_internal(
1816 mode: EditorMode,
1817 multi_buffer: Entity<MultiBuffer>,
1818 project: Option<Entity<Project>>,
1819 display_map: Option<Entity<DisplayMap>>,
1820 window: &mut Window,
1821 cx: &mut Context<Self>,
1822 ) -> Self {
1823 debug_assert!(
1824 display_map.is_none() || mode.is_minimap(),
1825 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1826 );
1827
1828 let full_mode = mode.is_full();
1829 let is_minimap = mode.is_minimap();
1830 let diagnostics_max_severity = if full_mode {
1831 EditorSettings::get_global(cx)
1832 .diagnostics_max_severity
1833 .unwrap_or(DiagnosticSeverity::Hint)
1834 } else {
1835 DiagnosticSeverity::Off
1836 };
1837 let style = window.text_style();
1838 let font_size = style.font_size.to_pixels(window.rem_size());
1839 let editor = cx.entity().downgrade();
1840 let fold_placeholder = FoldPlaceholder {
1841 constrain_width: false,
1842 render: Arc::new(move |fold_id, fold_range, cx| {
1843 let editor = editor.clone();
1844 div()
1845 .id(fold_id)
1846 .bg(cx.theme().colors().ghost_element_background)
1847 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1848 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1849 .rounded_xs()
1850 .size_full()
1851 .cursor_pointer()
1852 .child("⋯")
1853 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1854 .on_click(move |_, _window, cx| {
1855 editor
1856 .update(cx, |editor, cx| {
1857 editor.unfold_ranges(
1858 &[fold_range.start..fold_range.end],
1859 true,
1860 false,
1861 cx,
1862 );
1863 cx.stop_propagation();
1864 })
1865 .ok();
1866 })
1867 .into_any()
1868 }),
1869 merge_adjacent: true,
1870 ..FoldPlaceholder::default()
1871 };
1872 let display_map = display_map.unwrap_or_else(|| {
1873 cx.new(|cx| {
1874 DisplayMap::new(
1875 multi_buffer.clone(),
1876 style.font(),
1877 font_size,
1878 None,
1879 FILE_HEADER_HEIGHT,
1880 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1881 fold_placeholder,
1882 diagnostics_max_severity,
1883 cx,
1884 )
1885 })
1886 });
1887
1888 let selections = SelectionsCollection::new();
1889
1890 let blink_manager = cx.new(|cx| {
1891 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1892 if is_minimap {
1893 blink_manager.disable(cx);
1894 }
1895 blink_manager
1896 });
1897
1898 let soft_wrap_mode_override =
1899 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1900
1901 let mut project_subscriptions = Vec::new();
1902 if full_mode && let Some(project) = project.as_ref() {
1903 project_subscriptions.push(cx.subscribe_in(
1904 project,
1905 window,
1906 |editor, _, event, window, cx| match event {
1907 project::Event::RefreshCodeLens => {
1908 // we always query lens with actions, without storing them, always refreshing them
1909 }
1910 project::Event::RefreshInlayHints {
1911 server_id,
1912 request_id,
1913 } => {
1914 editor.refresh_inlay_hints(
1915 InlayHintRefreshReason::RefreshRequested {
1916 server_id: *server_id,
1917 request_id: *request_id,
1918 },
1919 cx,
1920 );
1921 }
1922 project::Event::LanguageServerRemoved(..) => {
1923 if editor.tasks_update_task.is_none() {
1924 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1925 }
1926 editor.registered_buffers.clear();
1927 editor.register_visible_buffers(cx);
1928 }
1929 project::Event::LanguageServerAdded(..) => {
1930 if editor.tasks_update_task.is_none() {
1931 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1932 }
1933 }
1934 project::Event::SnippetEdit(id, snippet_edits) => {
1935 // todo(lw): Non singletons
1936 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
1937 let snapshot = buffer.read(cx).snapshot();
1938 let focus_handle = editor.focus_handle(cx);
1939 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
1940 for (range, snippet) in snippet_edits {
1941 let buffer_range =
1942 language::range_from_lsp(*range).to_offset(&snapshot);
1943 editor
1944 .insert_snippet(
1945 &[MultiBufferOffset(buffer_range.start)
1946 ..MultiBufferOffset(buffer_range.end)],
1947 snippet.clone(),
1948 window,
1949 cx,
1950 )
1951 .ok();
1952 }
1953 }
1954 }
1955 }
1956 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1957 let buffer_id = *buffer_id;
1958 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1959 editor.register_buffer(buffer_id, cx);
1960 editor.update_lsp_data(Some(buffer_id), window, cx);
1961 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1962 refresh_linked_ranges(editor, window, cx);
1963 editor.refresh_code_actions(window, cx);
1964 editor.refresh_document_highlights(cx);
1965 }
1966 }
1967
1968 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1969 let Some(workspace) = editor.workspace() else {
1970 return;
1971 };
1972 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1973 else {
1974 return;
1975 };
1976
1977 if active_editor.entity_id() == cx.entity_id() {
1978 let entity_id = cx.entity_id();
1979 workspace.update(cx, |this, cx| {
1980 this.panes_mut()
1981 .iter_mut()
1982 .filter(|pane| pane.entity_id() != entity_id)
1983 .for_each(|p| {
1984 p.update(cx, |pane, _| {
1985 pane.nav_history_mut().rename_item(
1986 entity_id,
1987 project_path.clone(),
1988 abs_path.clone().into(),
1989 );
1990 })
1991 });
1992 });
1993 let edited_buffers_already_open = {
1994 let other_editors: Vec<Entity<Editor>> = workspace
1995 .read(cx)
1996 .panes()
1997 .iter()
1998 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1999 .filter(|editor| editor.entity_id() != cx.entity_id())
2000 .collect();
2001
2002 transaction.0.keys().all(|buffer| {
2003 other_editors.iter().any(|editor| {
2004 let multi_buffer = editor.read(cx).buffer();
2005 multi_buffer.read(cx).is_singleton()
2006 && multi_buffer.read(cx).as_singleton().map_or(
2007 false,
2008 |singleton| {
2009 singleton.entity_id() == buffer.entity_id()
2010 },
2011 )
2012 })
2013 })
2014 };
2015 if !edited_buffers_already_open {
2016 let workspace = workspace.downgrade();
2017 let transaction = transaction.clone();
2018 cx.defer_in(window, move |_, window, cx| {
2019 cx.spawn_in(window, async move |editor, cx| {
2020 Self::open_project_transaction(
2021 &editor,
2022 workspace,
2023 transaction,
2024 "Rename".to_string(),
2025 cx,
2026 )
2027 .await
2028 .ok()
2029 })
2030 .detach();
2031 });
2032 }
2033 }
2034 }
2035
2036 _ => {}
2037 },
2038 ));
2039 if let Some(task_inventory) = project
2040 .read(cx)
2041 .task_store()
2042 .read(cx)
2043 .task_inventory()
2044 .cloned()
2045 {
2046 project_subscriptions.push(cx.observe_in(
2047 &task_inventory,
2048 window,
2049 |editor, _, window, cx| {
2050 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2051 },
2052 ));
2053 };
2054
2055 project_subscriptions.push(cx.subscribe_in(
2056 &project.read(cx).breakpoint_store(),
2057 window,
2058 |editor, _, event, window, cx| match event {
2059 BreakpointStoreEvent::ClearDebugLines => {
2060 editor.clear_row_highlights::<ActiveDebugLine>();
2061 editor.refresh_inline_values(cx);
2062 }
2063 BreakpointStoreEvent::SetDebugLine => {
2064 if editor.go_to_active_debug_line(window, cx) {
2065 cx.stop_propagation();
2066 }
2067
2068 editor.refresh_inline_values(cx);
2069 }
2070 _ => {}
2071 },
2072 ));
2073 let git_store = project.read(cx).git_store().clone();
2074 let project = project.clone();
2075 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2076 if let GitStoreEvent::RepositoryAdded = event {
2077 this.load_diff_task = Some(
2078 update_uncommitted_diff_for_buffer(
2079 cx.entity(),
2080 &project,
2081 this.buffer.read(cx).all_buffers(),
2082 this.buffer.clone(),
2083 cx,
2084 )
2085 .shared(),
2086 );
2087 }
2088 }));
2089 }
2090
2091 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2092
2093 let inlay_hint_settings =
2094 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2095 let focus_handle = cx.focus_handle();
2096 if !is_minimap {
2097 cx.on_focus(&focus_handle, window, Self::handle_focus)
2098 .detach();
2099 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2100 .detach();
2101 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2102 .detach();
2103 cx.on_blur(&focus_handle, window, Self::handle_blur)
2104 .detach();
2105 cx.observe_pending_input(window, Self::observe_pending_input)
2106 .detach();
2107 }
2108
2109 let show_indent_guides =
2110 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2111 Some(false)
2112 } else {
2113 None
2114 };
2115
2116 let breakpoint_store = match (&mode, project.as_ref()) {
2117 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2118 _ => None,
2119 };
2120
2121 let mut code_action_providers = Vec::new();
2122 let mut load_uncommitted_diff = None;
2123 if let Some(project) = project.clone() {
2124 load_uncommitted_diff = Some(
2125 update_uncommitted_diff_for_buffer(
2126 cx.entity(),
2127 &project,
2128 multi_buffer.read(cx).all_buffers(),
2129 multi_buffer.clone(),
2130 cx,
2131 )
2132 .shared(),
2133 );
2134 code_action_providers.push(Rc::new(project) as Rc<_>);
2135 }
2136
2137 let mut editor = Self {
2138 focus_handle,
2139 show_cursor_when_unfocused: false,
2140 last_focused_descendant: None,
2141 buffer: multi_buffer.clone(),
2142 display_map: display_map.clone(),
2143 placeholder_display_map: None,
2144 selections,
2145 scroll_manager: ScrollManager::new(cx),
2146 columnar_selection_state: None,
2147 add_selections_state: None,
2148 select_next_state: None,
2149 select_prev_state: None,
2150 selection_history: SelectionHistory::default(),
2151 defer_selection_effects: false,
2152 deferred_selection_effects_state: None,
2153 autoclose_regions: Vec::new(),
2154 snippet_stack: InvalidationStack::default(),
2155 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2156 ime_transaction: None,
2157 active_diagnostics: ActiveDiagnostic::None,
2158 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2159 inline_diagnostics_update: Task::ready(()),
2160 inline_diagnostics: Vec::new(),
2161 soft_wrap_mode_override,
2162 diagnostics_max_severity,
2163 hard_wrap: None,
2164 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2165 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2166 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2167 project,
2168 blink_manager: blink_manager.clone(),
2169 show_local_selections: true,
2170 show_scrollbars: ScrollbarAxes {
2171 horizontal: full_mode,
2172 vertical: full_mode,
2173 },
2174 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2175 offset_content: !matches!(mode, EditorMode::SingleLine),
2176 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2177 show_gutter: full_mode,
2178 show_line_numbers: (!full_mode).then_some(false),
2179 use_relative_line_numbers: None,
2180 disable_expand_excerpt_buttons: !full_mode,
2181 show_git_diff_gutter: None,
2182 show_code_actions: None,
2183 show_runnables: None,
2184 show_breakpoints: None,
2185 show_wrap_guides: None,
2186 show_indent_guides,
2187 highlight_order: 0,
2188 highlighted_rows: HashMap::default(),
2189 background_highlights: HashMap::default(),
2190 gutter_highlights: HashMap::default(),
2191 scrollbar_marker_state: ScrollbarMarkerState::default(),
2192 active_indent_guides_state: ActiveIndentGuidesState::default(),
2193 nav_history: None,
2194 context_menu: RefCell::new(None),
2195 context_menu_options: None,
2196 mouse_context_menu: None,
2197 completion_tasks: Vec::new(),
2198 inline_blame_popover: None,
2199 inline_blame_popover_show_task: None,
2200 signature_help_state: SignatureHelpState::default(),
2201 auto_signature_help: None,
2202 find_all_references_task_sources: Vec::new(),
2203 next_completion_id: 0,
2204 next_inlay_id: 0,
2205 code_action_providers,
2206 available_code_actions: None,
2207 code_actions_task: None,
2208 quick_selection_highlight_task: None,
2209 debounced_selection_highlight_task: None,
2210 document_highlights_task: None,
2211 linked_editing_range_task: None,
2212 pending_rename: None,
2213 searchable: !is_minimap,
2214 cursor_shape: EditorSettings::get_global(cx)
2215 .cursor_shape
2216 .unwrap_or_default(),
2217 current_line_highlight: None,
2218 autoindent_mode: Some(AutoindentMode::EachLine),
2219 collapse_matches: false,
2220 workspace: None,
2221 input_enabled: !is_minimap,
2222 use_modal_editing: full_mode,
2223 read_only: is_minimap,
2224 use_autoclose: true,
2225 use_auto_surround: true,
2226 auto_replace_emoji_shortcode: false,
2227 jsx_tag_auto_close_enabled_in_any_buffer: false,
2228 leader_id: None,
2229 remote_id: None,
2230 hover_state: HoverState::default(),
2231 pending_mouse_down: None,
2232 hovered_link_state: None,
2233 edit_prediction_provider: None,
2234 active_edit_prediction: None,
2235 stale_edit_prediction_in_menu: None,
2236 edit_prediction_preview: EditPredictionPreview::Inactive {
2237 released_too_fast: false,
2238 },
2239 inline_diagnostics_enabled: full_mode,
2240 diagnostics_enabled: full_mode,
2241 word_completions_enabled: full_mode,
2242 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2243 gutter_hovered: false,
2244 pixel_position_of_newest_cursor: None,
2245 last_bounds: None,
2246 last_position_map: None,
2247 expect_bounds_change: None,
2248 gutter_dimensions: GutterDimensions::default(),
2249 style: None,
2250 show_cursor_names: false,
2251 hovered_cursors: HashMap::default(),
2252 next_editor_action_id: EditorActionId::default(),
2253 editor_actions: Rc::default(),
2254 edit_predictions_hidden_for_vim_mode: false,
2255 show_edit_predictions_override: None,
2256 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2257 edit_prediction_settings: EditPredictionSettings::Disabled,
2258 edit_prediction_indent_conflict: false,
2259 edit_prediction_requires_modifier_in_indent_conflict: true,
2260 custom_context_menu: None,
2261 show_git_blame_gutter: false,
2262 show_git_blame_inline: false,
2263 show_selection_menu: None,
2264 show_git_blame_inline_delay_task: None,
2265 git_blame_inline_enabled: full_mode
2266 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2267 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2268 buffer_serialization: is_minimap.not().then(|| {
2269 BufferSerialization::new(
2270 ProjectSettings::get_global(cx)
2271 .session
2272 .restore_unsaved_buffers,
2273 )
2274 }),
2275 blame: None,
2276 blame_subscription: None,
2277 tasks: BTreeMap::default(),
2278
2279 breakpoint_store,
2280 gutter_breakpoint_indicator: (None, None),
2281 hovered_diff_hunk_row: None,
2282 _subscriptions: (!is_minimap)
2283 .then(|| {
2284 vec![
2285 cx.observe(&multi_buffer, Self::on_buffer_changed),
2286 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2287 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2288 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2289 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2290 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2291 cx.observe_window_activation(window, |editor, window, cx| {
2292 let active = window.is_window_active();
2293 editor.blink_manager.update(cx, |blink_manager, cx| {
2294 if active {
2295 blink_manager.enable(cx);
2296 } else {
2297 blink_manager.disable(cx);
2298 }
2299 });
2300 if active {
2301 editor.show_mouse_cursor(cx);
2302 }
2303 }),
2304 ]
2305 })
2306 .unwrap_or_default(),
2307 tasks_update_task: None,
2308 pull_diagnostics_task: Task::ready(()),
2309 colors: None,
2310 refresh_colors_task: Task::ready(()),
2311 inlay_hints: None,
2312 next_color_inlay_id: 0,
2313 post_scroll_update: Task::ready(()),
2314 linked_edit_ranges: Default::default(),
2315 in_project_search: false,
2316 previous_search_ranges: None,
2317 breadcrumb_header: None,
2318 focused_block: None,
2319 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2320 addons: HashMap::default(),
2321 registered_buffers: HashMap::default(),
2322 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2323 selection_mark_mode: false,
2324 toggle_fold_multiple_buffers: Task::ready(()),
2325 serialize_selections: Task::ready(()),
2326 serialize_folds: Task::ready(()),
2327 text_style_refinement: None,
2328 load_diff_task: load_uncommitted_diff,
2329 temporary_diff_override: false,
2330 mouse_cursor_hidden: false,
2331 minimap: None,
2332 hide_mouse_mode: EditorSettings::get_global(cx)
2333 .hide_mouse
2334 .unwrap_or_default(),
2335 change_list: ChangeList::new(),
2336 mode,
2337 selection_drag_state: SelectionDragState::None,
2338 folding_newlines: Task::ready(()),
2339 lookup_key: None,
2340 select_next_is_case_sensitive: None,
2341 applicable_language_settings: HashMap::default(),
2342 accent_overrides: Vec::new(),
2343 fetched_tree_sitter_chunks: HashMap::default(),
2344 };
2345
2346 if is_minimap {
2347 return editor;
2348 }
2349
2350 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2351 editor.accent_overrides = editor.fetch_accent_overrides(cx);
2352
2353 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2354 editor
2355 ._subscriptions
2356 .push(cx.observe(breakpoints, |_, _, cx| {
2357 cx.notify();
2358 }));
2359 }
2360 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2361 editor._subscriptions.extend(project_subscriptions);
2362
2363 editor._subscriptions.push(cx.subscribe_in(
2364 &cx.entity(),
2365 window,
2366 |editor, _, e: &EditorEvent, window, cx| match e {
2367 EditorEvent::ScrollPositionChanged { local, .. } => {
2368 if *local {
2369 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2370 editor.inline_blame_popover.take();
2371 let new_anchor = editor.scroll_manager.anchor();
2372 let snapshot = editor.snapshot(window, cx);
2373 editor.update_restoration_data(cx, move |data| {
2374 data.scroll_position = (
2375 new_anchor.top_row(snapshot.buffer_snapshot()),
2376 new_anchor.offset,
2377 );
2378 });
2379
2380 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2381 cx.background_executor()
2382 .timer(Duration::from_millis(50))
2383 .await;
2384 editor
2385 .update_in(cx, |editor, window, cx| {
2386 editor.register_visible_buffers(cx);
2387 editor.refresh_colors_for_visible_range(None, window, cx);
2388 editor.refresh_inlay_hints(
2389 InlayHintRefreshReason::NewLinesShown,
2390 cx,
2391 );
2392 editor.colorize_brackets(false, cx);
2393 })
2394 .ok();
2395 });
2396 }
2397 }
2398 EditorEvent::Edited { .. } => {
2399 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2400 .map(|vim_mode| vim_mode.0)
2401 .unwrap_or(false);
2402 if !vim_mode {
2403 let display_map = editor.display_snapshot(cx);
2404 let selections = editor.selections.all_adjusted_display(&display_map);
2405 let pop_state = editor
2406 .change_list
2407 .last()
2408 .map(|previous| {
2409 previous.len() == selections.len()
2410 && previous.iter().enumerate().all(|(ix, p)| {
2411 p.to_display_point(&display_map).row()
2412 == selections[ix].head().row()
2413 })
2414 })
2415 .unwrap_or(false);
2416 let new_positions = selections
2417 .into_iter()
2418 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2419 .collect();
2420 editor
2421 .change_list
2422 .push_to_change_list(pop_state, new_positions);
2423 }
2424 }
2425 _ => (),
2426 },
2427 ));
2428
2429 if let Some(dap_store) = editor
2430 .project
2431 .as_ref()
2432 .map(|project| project.read(cx).dap_store())
2433 {
2434 let weak_editor = cx.weak_entity();
2435
2436 editor
2437 ._subscriptions
2438 .push(
2439 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2440 let session_entity = cx.entity();
2441 weak_editor
2442 .update(cx, |editor, cx| {
2443 editor._subscriptions.push(
2444 cx.subscribe(&session_entity, Self::on_debug_session_event),
2445 );
2446 })
2447 .ok();
2448 }),
2449 );
2450
2451 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2452 editor
2453 ._subscriptions
2454 .push(cx.subscribe(&session, Self::on_debug_session_event));
2455 }
2456 }
2457
2458 // skip adding the initial selection to selection history
2459 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2460 editor.end_selection(window, cx);
2461 editor.selection_history.mode = SelectionHistoryMode::Normal;
2462
2463 editor.scroll_manager.show_scrollbars(window, cx);
2464 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2465
2466 if full_mode {
2467 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2468 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2469
2470 if editor.git_blame_inline_enabled {
2471 editor.start_git_blame_inline(false, window, cx);
2472 }
2473
2474 editor.go_to_active_debug_line(window, cx);
2475
2476 editor.minimap =
2477 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2478 editor.colors = Some(LspColorData::new(cx));
2479 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2480
2481 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2482 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2483 }
2484 editor.update_lsp_data(None, window, cx);
2485 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2486 }
2487
2488 editor
2489 }
2490
2491 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2492 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2493 }
2494
2495 pub fn deploy_mouse_context_menu(
2496 &mut self,
2497 position: gpui::Point<Pixels>,
2498 context_menu: Entity<ContextMenu>,
2499 window: &mut Window,
2500 cx: &mut Context<Self>,
2501 ) {
2502 self.mouse_context_menu = Some(MouseContextMenu::new(
2503 self,
2504 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2505 context_menu,
2506 window,
2507 cx,
2508 ));
2509 }
2510
2511 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2512 self.mouse_context_menu
2513 .as_ref()
2514 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2515 }
2516
2517 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2518 if self
2519 .selections
2520 .pending_anchor()
2521 .is_some_and(|pending_selection| {
2522 let snapshot = self.buffer().read(cx).snapshot(cx);
2523 pending_selection.range().includes(range, &snapshot)
2524 })
2525 {
2526 return true;
2527 }
2528
2529 self.selections
2530 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2531 .into_iter()
2532 .any(|selection| {
2533 // This is needed to cover a corner case, if we just check for an existing
2534 // selection in the fold range, having a cursor at the start of the fold
2535 // marks it as selected. Non-empty selections don't cause this.
2536 let length = selection.end - selection.start;
2537 length > 0
2538 })
2539 }
2540
2541 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2542 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2543 }
2544
2545 fn key_context_internal(
2546 &self,
2547 has_active_edit_prediction: bool,
2548 window: &mut Window,
2549 cx: &mut App,
2550 ) -> KeyContext {
2551 let mut key_context = KeyContext::new_with_defaults();
2552 key_context.add("Editor");
2553 let mode = match self.mode {
2554 EditorMode::SingleLine => "single_line",
2555 EditorMode::AutoHeight { .. } => "auto_height",
2556 EditorMode::Minimap { .. } => "minimap",
2557 EditorMode::Full { .. } => "full",
2558 };
2559
2560 if EditorSettings::jupyter_enabled(cx) {
2561 key_context.add("jupyter");
2562 }
2563
2564 key_context.set("mode", mode);
2565 if self.pending_rename.is_some() {
2566 key_context.add("renaming");
2567 }
2568
2569 if let Some(snippet_stack) = self.snippet_stack.last() {
2570 key_context.add("in_snippet");
2571
2572 if snippet_stack.active_index > 0 {
2573 key_context.add("has_previous_tabstop");
2574 }
2575
2576 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2577 key_context.add("has_next_tabstop");
2578 }
2579 }
2580
2581 match self.context_menu.borrow().as_ref() {
2582 Some(CodeContextMenu::Completions(menu)) => {
2583 if menu.visible() {
2584 key_context.add("menu");
2585 key_context.add("showing_completions");
2586 }
2587 }
2588 Some(CodeContextMenu::CodeActions(menu)) => {
2589 if menu.visible() {
2590 key_context.add("menu");
2591 key_context.add("showing_code_actions")
2592 }
2593 }
2594 None => {}
2595 }
2596
2597 if self.signature_help_state.has_multiple_signatures() {
2598 key_context.add("showing_signature_help");
2599 }
2600
2601 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2602 if !self.focus_handle(cx).contains_focused(window, cx)
2603 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2604 {
2605 for addon in self.addons.values() {
2606 addon.extend_key_context(&mut key_context, cx)
2607 }
2608 }
2609
2610 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2611 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2612 Some(
2613 file.full_path(cx)
2614 .extension()?
2615 .to_string_lossy()
2616 .into_owned(),
2617 )
2618 }) {
2619 key_context.set("extension", extension);
2620 }
2621 } else {
2622 key_context.add("multibuffer");
2623 }
2624
2625 if has_active_edit_prediction {
2626 if self.edit_prediction_in_conflict() {
2627 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2628 } else {
2629 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2630 key_context.add("copilot_suggestion");
2631 }
2632 }
2633
2634 if self.selection_mark_mode {
2635 key_context.add("selection_mode");
2636 }
2637
2638 let disjoint = self.selections.disjoint_anchors();
2639 let snapshot = self.snapshot(window, cx);
2640 let snapshot = snapshot.buffer_snapshot();
2641 if self.mode == EditorMode::SingleLine
2642 && let [selection] = disjoint
2643 && selection.start == selection.end
2644 && selection.end.to_offset(snapshot) == snapshot.len()
2645 {
2646 key_context.add("end_of_input");
2647 }
2648
2649 if self.has_any_expanded_diff_hunks(cx) {
2650 key_context.add("diffs_expanded");
2651 }
2652
2653 key_context
2654 }
2655
2656 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2657 self.last_bounds.as_ref()
2658 }
2659
2660 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2661 if self.mouse_cursor_hidden {
2662 self.mouse_cursor_hidden = false;
2663 cx.notify();
2664 }
2665 }
2666
2667 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2668 let hide_mouse_cursor = match origin {
2669 HideMouseCursorOrigin::TypingAction => {
2670 matches!(
2671 self.hide_mouse_mode,
2672 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2673 )
2674 }
2675 HideMouseCursorOrigin::MovementAction => {
2676 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2677 }
2678 };
2679 if self.mouse_cursor_hidden != hide_mouse_cursor {
2680 self.mouse_cursor_hidden = hide_mouse_cursor;
2681 cx.notify();
2682 }
2683 }
2684
2685 pub fn edit_prediction_in_conflict(&self) -> bool {
2686 if !self.show_edit_predictions_in_menu() {
2687 return false;
2688 }
2689
2690 let showing_completions = self
2691 .context_menu
2692 .borrow()
2693 .as_ref()
2694 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2695
2696 showing_completions
2697 || self.edit_prediction_requires_modifier()
2698 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2699 // bindings to insert tab characters.
2700 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2701 }
2702
2703 pub fn accept_edit_prediction_keybind(
2704 &self,
2705 accept_partial: bool,
2706 window: &mut Window,
2707 cx: &mut App,
2708 ) -> AcceptEditPredictionBinding {
2709 let key_context = self.key_context_internal(true, window, cx);
2710 let in_conflict = self.edit_prediction_in_conflict();
2711
2712 let bindings = if accept_partial {
2713 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2714 } else {
2715 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2716 };
2717
2718 // TODO: if the binding contains multiple keystrokes, display all of them, not
2719 // just the first one.
2720 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2721 !in_conflict
2722 || binding
2723 .keystrokes()
2724 .first()
2725 .is_some_and(|keystroke| keystroke.modifiers().modified())
2726 }))
2727 }
2728
2729 pub fn new_file(
2730 workspace: &mut Workspace,
2731 _: &workspace::NewFile,
2732 window: &mut Window,
2733 cx: &mut Context<Workspace>,
2734 ) {
2735 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2736 "Failed to create buffer",
2737 window,
2738 cx,
2739 |e, _, _| match e.error_code() {
2740 ErrorCode::RemoteUpgradeRequired => Some(format!(
2741 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2742 e.error_tag("required").unwrap_or("the latest version")
2743 )),
2744 _ => None,
2745 },
2746 );
2747 }
2748
2749 pub fn new_in_workspace(
2750 workspace: &mut Workspace,
2751 window: &mut Window,
2752 cx: &mut Context<Workspace>,
2753 ) -> Task<Result<Entity<Editor>>> {
2754 let project = workspace.project().clone();
2755 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2756
2757 cx.spawn_in(window, async move |workspace, cx| {
2758 let buffer = create.await?;
2759 workspace.update_in(cx, |workspace, window, cx| {
2760 let editor =
2761 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2762 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2763 editor
2764 })
2765 })
2766 }
2767
2768 fn new_file_vertical(
2769 workspace: &mut Workspace,
2770 _: &workspace::NewFileSplitVertical,
2771 window: &mut Window,
2772 cx: &mut Context<Workspace>,
2773 ) {
2774 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2775 }
2776
2777 fn new_file_horizontal(
2778 workspace: &mut Workspace,
2779 _: &workspace::NewFileSplitHorizontal,
2780 window: &mut Window,
2781 cx: &mut Context<Workspace>,
2782 ) {
2783 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2784 }
2785
2786 fn new_file_split(
2787 workspace: &mut Workspace,
2788 action: &workspace::NewFileSplit,
2789 window: &mut Window,
2790 cx: &mut Context<Workspace>,
2791 ) {
2792 Self::new_file_in_direction(workspace, action.0, window, cx)
2793 }
2794
2795 fn new_file_in_direction(
2796 workspace: &mut Workspace,
2797 direction: SplitDirection,
2798 window: &mut Window,
2799 cx: &mut Context<Workspace>,
2800 ) {
2801 let project = workspace.project().clone();
2802 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2803
2804 cx.spawn_in(window, async move |workspace, cx| {
2805 let buffer = create.await?;
2806 workspace.update_in(cx, move |workspace, window, cx| {
2807 workspace.split_item(
2808 direction,
2809 Box::new(
2810 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2811 ),
2812 window,
2813 cx,
2814 )
2815 })?;
2816 anyhow::Ok(())
2817 })
2818 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2819 match e.error_code() {
2820 ErrorCode::RemoteUpgradeRequired => Some(format!(
2821 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2822 e.error_tag("required").unwrap_or("the latest version")
2823 )),
2824 _ => None,
2825 }
2826 });
2827 }
2828
2829 pub fn leader_id(&self) -> Option<CollaboratorId> {
2830 self.leader_id
2831 }
2832
2833 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2834 &self.buffer
2835 }
2836
2837 pub fn project(&self) -> Option<&Entity<Project>> {
2838 self.project.as_ref()
2839 }
2840
2841 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2842 self.workspace.as_ref()?.0.upgrade()
2843 }
2844
2845 /// Returns the workspace serialization ID if this editor should be serialized.
2846 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2847 self.workspace
2848 .as_ref()
2849 .filter(|_| self.should_serialize_buffer())
2850 .and_then(|workspace| workspace.1)
2851 }
2852
2853 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2854 self.buffer().read(cx).title(cx)
2855 }
2856
2857 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2858 let git_blame_gutter_max_author_length = self
2859 .render_git_blame_gutter(cx)
2860 .then(|| {
2861 if let Some(blame) = self.blame.as_ref() {
2862 let max_author_length =
2863 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2864 Some(max_author_length)
2865 } else {
2866 None
2867 }
2868 })
2869 .flatten();
2870
2871 EditorSnapshot {
2872 mode: self.mode.clone(),
2873 show_gutter: self.show_gutter,
2874 show_line_numbers: self.show_line_numbers,
2875 show_git_diff_gutter: self.show_git_diff_gutter,
2876 show_code_actions: self.show_code_actions,
2877 show_runnables: self.show_runnables,
2878 show_breakpoints: self.show_breakpoints,
2879 git_blame_gutter_max_author_length,
2880 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2881 placeholder_display_snapshot: self
2882 .placeholder_display_map
2883 .as_ref()
2884 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2885 scroll_anchor: self.scroll_manager.anchor(),
2886 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2887 is_focused: self.focus_handle.is_focused(window),
2888 current_line_highlight: self
2889 .current_line_highlight
2890 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2891 gutter_hovered: self.gutter_hovered,
2892 }
2893 }
2894
2895 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2896 self.buffer.read(cx).language_at(point, cx)
2897 }
2898
2899 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2900 self.buffer.read(cx).read(cx).file_at(point).cloned()
2901 }
2902
2903 pub fn active_excerpt(
2904 &self,
2905 cx: &App,
2906 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2907 self.buffer
2908 .read(cx)
2909 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2910 }
2911
2912 pub fn mode(&self) -> &EditorMode {
2913 &self.mode
2914 }
2915
2916 pub fn set_mode(&mut self, mode: EditorMode) {
2917 self.mode = mode;
2918 }
2919
2920 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2921 self.collaboration_hub.as_deref()
2922 }
2923
2924 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2925 self.collaboration_hub = Some(hub);
2926 }
2927
2928 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2929 self.in_project_search = in_project_search;
2930 }
2931
2932 pub fn set_custom_context_menu(
2933 &mut self,
2934 f: impl 'static
2935 + Fn(
2936 &mut Self,
2937 DisplayPoint,
2938 &mut Window,
2939 &mut Context<Self>,
2940 ) -> Option<Entity<ui::ContextMenu>>,
2941 ) {
2942 self.custom_context_menu = Some(Box::new(f))
2943 }
2944
2945 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2946 self.completion_provider = provider;
2947 }
2948
2949 #[cfg(any(test, feature = "test-support"))]
2950 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2951 self.completion_provider.clone()
2952 }
2953
2954 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2955 self.semantics_provider.clone()
2956 }
2957
2958 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2959 self.semantics_provider = provider;
2960 }
2961
2962 pub fn set_edit_prediction_provider<T>(
2963 &mut self,
2964 provider: Option<Entity<T>>,
2965 window: &mut Window,
2966 cx: &mut Context<Self>,
2967 ) where
2968 T: EditPredictionProvider,
2969 {
2970 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2971 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2972 if this.focus_handle.is_focused(window) {
2973 this.update_visible_edit_prediction(window, cx);
2974 }
2975 }),
2976 provider: Arc::new(provider),
2977 });
2978 self.update_edit_prediction_settings(cx);
2979 self.refresh_edit_prediction(false, false, window, cx);
2980 }
2981
2982 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2983 self.placeholder_display_map
2984 .as_ref()
2985 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2986 }
2987
2988 pub fn set_placeholder_text(
2989 &mut self,
2990 placeholder_text: &str,
2991 window: &mut Window,
2992 cx: &mut Context<Self>,
2993 ) {
2994 let multibuffer = cx
2995 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2996
2997 let style = window.text_style();
2998
2999 self.placeholder_display_map = Some(cx.new(|cx| {
3000 DisplayMap::new(
3001 multibuffer,
3002 style.font(),
3003 style.font_size.to_pixels(window.rem_size()),
3004 None,
3005 FILE_HEADER_HEIGHT,
3006 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3007 Default::default(),
3008 DiagnosticSeverity::Off,
3009 cx,
3010 )
3011 }));
3012 cx.notify();
3013 }
3014
3015 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3016 self.cursor_shape = cursor_shape;
3017
3018 // Disrupt blink for immediate user feedback that the cursor shape has changed
3019 self.blink_manager.update(cx, BlinkManager::show_cursor);
3020
3021 cx.notify();
3022 }
3023
3024 pub fn set_current_line_highlight(
3025 &mut self,
3026 current_line_highlight: Option<CurrentLineHighlight>,
3027 ) {
3028 self.current_line_highlight = current_line_highlight;
3029 }
3030
3031 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3032 self.collapse_matches = collapse_matches;
3033 }
3034
3035 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3036 if self.collapse_matches {
3037 return range.start..range.start;
3038 }
3039 range.clone()
3040 }
3041
3042 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3043 self.display_map.read(cx).clip_at_line_ends
3044 }
3045
3046 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3047 if self.display_map.read(cx).clip_at_line_ends != clip {
3048 self.display_map
3049 .update(cx, |map, _| map.clip_at_line_ends = clip);
3050 }
3051 }
3052
3053 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3054 self.input_enabled = input_enabled;
3055 }
3056
3057 pub fn set_edit_predictions_hidden_for_vim_mode(
3058 &mut self,
3059 hidden: bool,
3060 window: &mut Window,
3061 cx: &mut Context<Self>,
3062 ) {
3063 if hidden != self.edit_predictions_hidden_for_vim_mode {
3064 self.edit_predictions_hidden_for_vim_mode = hidden;
3065 if hidden {
3066 self.update_visible_edit_prediction(window, cx);
3067 } else {
3068 self.refresh_edit_prediction(true, false, window, cx);
3069 }
3070 }
3071 }
3072
3073 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3074 self.menu_edit_predictions_policy = value;
3075 }
3076
3077 pub fn set_autoindent(&mut self, autoindent: bool) {
3078 if autoindent {
3079 self.autoindent_mode = Some(AutoindentMode::EachLine);
3080 } else {
3081 self.autoindent_mode = None;
3082 }
3083 }
3084
3085 pub fn read_only(&self, cx: &App) -> bool {
3086 self.read_only || self.buffer.read(cx).read_only()
3087 }
3088
3089 pub fn set_read_only(&mut self, read_only: bool) {
3090 self.read_only = read_only;
3091 }
3092
3093 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3094 self.use_autoclose = autoclose;
3095 }
3096
3097 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3098 self.use_auto_surround = auto_surround;
3099 }
3100
3101 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3102 self.auto_replace_emoji_shortcode = auto_replace;
3103 }
3104
3105 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3106 self.buffer_serialization = should_serialize.then(|| {
3107 BufferSerialization::new(
3108 ProjectSettings::get_global(cx)
3109 .session
3110 .restore_unsaved_buffers,
3111 )
3112 })
3113 }
3114
3115 fn should_serialize_buffer(&self) -> bool {
3116 self.buffer_serialization.is_some()
3117 }
3118
3119 pub fn toggle_edit_predictions(
3120 &mut self,
3121 _: &ToggleEditPrediction,
3122 window: &mut Window,
3123 cx: &mut Context<Self>,
3124 ) {
3125 if self.show_edit_predictions_override.is_some() {
3126 self.set_show_edit_predictions(None, window, cx);
3127 } else {
3128 let show_edit_predictions = !self.edit_predictions_enabled();
3129 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3130 }
3131 }
3132
3133 pub fn set_show_edit_predictions(
3134 &mut self,
3135 show_edit_predictions: Option<bool>,
3136 window: &mut Window,
3137 cx: &mut Context<Self>,
3138 ) {
3139 self.show_edit_predictions_override = show_edit_predictions;
3140 self.update_edit_prediction_settings(cx);
3141
3142 if let Some(false) = show_edit_predictions {
3143 self.discard_edit_prediction(false, cx);
3144 } else {
3145 self.refresh_edit_prediction(false, true, window, cx);
3146 }
3147 }
3148
3149 fn edit_predictions_disabled_in_scope(
3150 &self,
3151 buffer: &Entity<Buffer>,
3152 buffer_position: language::Anchor,
3153 cx: &App,
3154 ) -> bool {
3155 let snapshot = buffer.read(cx).snapshot();
3156 let settings = snapshot.settings_at(buffer_position, cx);
3157
3158 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3159 return false;
3160 };
3161
3162 scope.override_name().is_some_and(|scope_name| {
3163 settings
3164 .edit_predictions_disabled_in
3165 .iter()
3166 .any(|s| s == scope_name)
3167 })
3168 }
3169
3170 pub fn set_use_modal_editing(&mut self, to: bool) {
3171 self.use_modal_editing = to;
3172 }
3173
3174 pub fn use_modal_editing(&self) -> bool {
3175 self.use_modal_editing
3176 }
3177
3178 fn selections_did_change(
3179 &mut self,
3180 local: bool,
3181 old_cursor_position: &Anchor,
3182 effects: SelectionEffects,
3183 window: &mut Window,
3184 cx: &mut Context<Self>,
3185 ) {
3186 window.invalidate_character_coordinates();
3187
3188 // Copy selections to primary selection buffer
3189 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3190 if local {
3191 let selections = self
3192 .selections
3193 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3194 let buffer_handle = self.buffer.read(cx).read(cx);
3195
3196 let mut text = String::new();
3197 for (index, selection) in selections.iter().enumerate() {
3198 let text_for_selection = buffer_handle
3199 .text_for_range(selection.start..selection.end)
3200 .collect::<String>();
3201
3202 text.push_str(&text_for_selection);
3203 if index != selections.len() - 1 {
3204 text.push('\n');
3205 }
3206 }
3207
3208 if !text.is_empty() {
3209 cx.write_to_primary(ClipboardItem::new_string(text));
3210 }
3211 }
3212
3213 let selection_anchors = self.selections.disjoint_anchors_arc();
3214
3215 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3216 self.buffer.update(cx, |buffer, cx| {
3217 buffer.set_active_selections(
3218 &selection_anchors,
3219 self.selections.line_mode(),
3220 self.cursor_shape,
3221 cx,
3222 )
3223 });
3224 }
3225 let display_map = self
3226 .display_map
3227 .update(cx, |display_map, cx| display_map.snapshot(cx));
3228 let buffer = display_map.buffer_snapshot();
3229 if self.selections.count() == 1 {
3230 self.add_selections_state = None;
3231 }
3232 self.select_next_state = None;
3233 self.select_prev_state = None;
3234 self.select_syntax_node_history.try_clear();
3235 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3236 self.snippet_stack.invalidate(&selection_anchors, buffer);
3237 self.take_rename(false, window, cx);
3238
3239 let newest_selection = self.selections.newest_anchor();
3240 let new_cursor_position = newest_selection.head();
3241 let selection_start = newest_selection.start;
3242
3243 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3244 self.push_to_nav_history(
3245 *old_cursor_position,
3246 Some(new_cursor_position.to_point(buffer)),
3247 false,
3248 effects.nav_history == Some(true),
3249 cx,
3250 );
3251 }
3252
3253 if local {
3254 if let Some(buffer_id) = new_cursor_position.buffer_id {
3255 self.register_buffer(buffer_id, cx);
3256 }
3257
3258 let mut context_menu = self.context_menu.borrow_mut();
3259 let completion_menu = match context_menu.as_ref() {
3260 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3261 Some(CodeContextMenu::CodeActions(_)) => {
3262 *context_menu = None;
3263 None
3264 }
3265 None => None,
3266 };
3267 let completion_position = completion_menu.map(|menu| menu.initial_position);
3268 drop(context_menu);
3269
3270 if effects.completions
3271 && let Some(completion_position) = completion_position
3272 {
3273 let start_offset = selection_start.to_offset(buffer);
3274 let position_matches = start_offset == completion_position.to_offset(buffer);
3275 let continue_showing = if position_matches {
3276 if self.snippet_stack.is_empty() {
3277 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3278 == Some(CharKind::Word)
3279 } else {
3280 // Snippet choices can be shown even when the cursor is in whitespace.
3281 // Dismissing the menu with actions like backspace is handled by
3282 // invalidation regions.
3283 true
3284 }
3285 } else {
3286 false
3287 };
3288
3289 if continue_showing {
3290 self.open_or_update_completions_menu(None, None, false, window, cx);
3291 } else {
3292 self.hide_context_menu(window, cx);
3293 }
3294 }
3295
3296 hide_hover(self, cx);
3297
3298 if old_cursor_position.to_display_point(&display_map).row()
3299 != new_cursor_position.to_display_point(&display_map).row()
3300 {
3301 self.available_code_actions.take();
3302 }
3303 self.refresh_code_actions(window, cx);
3304 self.refresh_document_highlights(cx);
3305 refresh_linked_ranges(self, window, cx);
3306
3307 self.refresh_selected_text_highlights(false, window, cx);
3308 self.refresh_matching_bracket_highlights(window, cx);
3309 self.update_visible_edit_prediction(window, cx);
3310 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3311 self.inline_blame_popover.take();
3312 if self.git_blame_inline_enabled {
3313 self.start_inline_blame_timer(window, cx);
3314 }
3315 }
3316
3317 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3318 cx.emit(EditorEvent::SelectionsChanged { local });
3319
3320 let selections = &self.selections.disjoint_anchors_arc();
3321 if selections.len() == 1 {
3322 cx.emit(SearchEvent::ActiveMatchChanged)
3323 }
3324 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3325 let inmemory_selections = selections
3326 .iter()
3327 .map(|s| {
3328 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3329 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3330 })
3331 .collect();
3332 self.update_restoration_data(cx, |data| {
3333 data.selections = inmemory_selections;
3334 });
3335
3336 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3337 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3338 {
3339 let snapshot = self.buffer().read(cx).snapshot(cx);
3340 let selections = selections.clone();
3341 let background_executor = cx.background_executor().clone();
3342 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3343 self.serialize_selections = cx.background_spawn(async move {
3344 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3345 let db_selections = selections
3346 .iter()
3347 .map(|selection| {
3348 (
3349 selection.start.to_offset(&snapshot).0,
3350 selection.end.to_offset(&snapshot).0,
3351 )
3352 })
3353 .collect();
3354
3355 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3356 .await
3357 .with_context(|| {
3358 format!(
3359 "persisting editor selections for editor {editor_id}, \
3360 workspace {workspace_id:?}"
3361 )
3362 })
3363 .log_err();
3364 });
3365 }
3366 }
3367
3368 cx.notify();
3369 }
3370
3371 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3372 use text::ToOffset as _;
3373 use text::ToPoint as _;
3374
3375 if self.mode.is_minimap()
3376 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3377 {
3378 return;
3379 }
3380
3381 if !self.buffer().read(cx).is_singleton() {
3382 return;
3383 }
3384
3385 let display_snapshot = self
3386 .display_map
3387 .update(cx, |display_map, cx| display_map.snapshot(cx));
3388 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3389 return;
3390 };
3391 let inmemory_folds = display_snapshot
3392 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3393 .map(|fold| {
3394 fold.range.start.text_anchor.to_point(&snapshot)
3395 ..fold.range.end.text_anchor.to_point(&snapshot)
3396 })
3397 .collect();
3398 self.update_restoration_data(cx, |data| {
3399 data.folds = inmemory_folds;
3400 });
3401
3402 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3403 return;
3404 };
3405 let background_executor = cx.background_executor().clone();
3406 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3407 let db_folds = display_snapshot
3408 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3409 .map(|fold| {
3410 (
3411 fold.range.start.text_anchor.to_offset(&snapshot),
3412 fold.range.end.text_anchor.to_offset(&snapshot),
3413 )
3414 })
3415 .collect();
3416 self.serialize_folds = cx.background_spawn(async move {
3417 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3418 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3419 .await
3420 .with_context(|| {
3421 format!(
3422 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3423 )
3424 })
3425 .log_err();
3426 });
3427 }
3428
3429 pub fn sync_selections(
3430 &mut self,
3431 other: Entity<Editor>,
3432 cx: &mut Context<Self>,
3433 ) -> gpui::Subscription {
3434 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3435 if !other_selections.is_empty() {
3436 self.selections
3437 .change_with(&self.display_snapshot(cx), |selections| {
3438 selections.select_anchors(other_selections);
3439 });
3440 }
3441
3442 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3443 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3444 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3445 if other_selections.is_empty() {
3446 return;
3447 }
3448 let snapshot = this.display_snapshot(cx);
3449 this.selections.change_with(&snapshot, |selections| {
3450 selections.select_anchors(other_selections);
3451 });
3452 }
3453 });
3454
3455 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3456 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3457 let these_selections = this.selections.disjoint_anchors().to_vec();
3458 if these_selections.is_empty() {
3459 return;
3460 }
3461 other.update(cx, |other_editor, cx| {
3462 let snapshot = other_editor.display_snapshot(cx);
3463 other_editor
3464 .selections
3465 .change_with(&snapshot, |selections| {
3466 selections.select_anchors(these_selections);
3467 })
3468 });
3469 }
3470 });
3471
3472 Subscription::join(other_subscription, this_subscription)
3473 }
3474
3475 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3476 if self.buffer().read(cx).is_singleton() {
3477 return;
3478 }
3479 let snapshot = self.buffer.read(cx).snapshot(cx);
3480 let buffer_ids: HashSet<BufferId> = self
3481 .selections
3482 .disjoint_anchor_ranges()
3483 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3484 .collect();
3485 for buffer_id in buffer_ids {
3486 self.unfold_buffer(buffer_id, cx);
3487 }
3488 }
3489
3490 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3491 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3492 /// effects of selection change occur at the end of the transaction.
3493 pub fn change_selections<R>(
3494 &mut self,
3495 effects: SelectionEffects,
3496 window: &mut Window,
3497 cx: &mut Context<Self>,
3498 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3499 ) -> R {
3500 let snapshot = self.display_snapshot(cx);
3501 if let Some(state) = &mut self.deferred_selection_effects_state {
3502 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3503 state.effects.completions = effects.completions;
3504 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3505 let (changed, result) = self.selections.change_with(&snapshot, change);
3506 state.changed |= changed;
3507 return result;
3508 }
3509 let mut state = DeferredSelectionEffectsState {
3510 changed: false,
3511 effects,
3512 old_cursor_position: self.selections.newest_anchor().head(),
3513 history_entry: SelectionHistoryEntry {
3514 selections: self.selections.disjoint_anchors_arc(),
3515 select_next_state: self.select_next_state.clone(),
3516 select_prev_state: self.select_prev_state.clone(),
3517 add_selections_state: self.add_selections_state.clone(),
3518 },
3519 };
3520 let (changed, result) = self.selections.change_with(&snapshot, change);
3521 state.changed = state.changed || changed;
3522 if self.defer_selection_effects {
3523 self.deferred_selection_effects_state = Some(state);
3524 } else {
3525 self.apply_selection_effects(state, window, cx);
3526 }
3527 result
3528 }
3529
3530 /// Defers the effects of selection change, so that the effects of multiple calls to
3531 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3532 /// to selection history and the state of popovers based on selection position aren't
3533 /// erroneously updated.
3534 pub fn with_selection_effects_deferred<R>(
3535 &mut self,
3536 window: &mut Window,
3537 cx: &mut Context<Self>,
3538 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3539 ) -> R {
3540 let already_deferred = self.defer_selection_effects;
3541 self.defer_selection_effects = true;
3542 let result = update(self, window, cx);
3543 if !already_deferred {
3544 self.defer_selection_effects = false;
3545 if let Some(state) = self.deferred_selection_effects_state.take() {
3546 self.apply_selection_effects(state, window, cx);
3547 }
3548 }
3549 result
3550 }
3551
3552 fn apply_selection_effects(
3553 &mut self,
3554 state: DeferredSelectionEffectsState,
3555 window: &mut Window,
3556 cx: &mut Context<Self>,
3557 ) {
3558 if state.changed {
3559 self.selection_history.push(state.history_entry);
3560
3561 if let Some(autoscroll) = state.effects.scroll {
3562 self.request_autoscroll(autoscroll, cx);
3563 }
3564
3565 let old_cursor_position = &state.old_cursor_position;
3566
3567 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3568
3569 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3570 self.show_signature_help(&ShowSignatureHelp, window, cx);
3571 }
3572 }
3573 }
3574
3575 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3576 where
3577 I: IntoIterator<Item = (Range<S>, T)>,
3578 S: ToOffset,
3579 T: Into<Arc<str>>,
3580 {
3581 if self.read_only(cx) {
3582 return;
3583 }
3584
3585 self.buffer
3586 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3587 }
3588
3589 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3590 where
3591 I: IntoIterator<Item = (Range<S>, T)>,
3592 S: ToOffset,
3593 T: Into<Arc<str>>,
3594 {
3595 if self.read_only(cx) {
3596 return;
3597 }
3598
3599 self.buffer.update(cx, |buffer, cx| {
3600 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3601 });
3602 }
3603
3604 pub fn edit_with_block_indent<I, S, T>(
3605 &mut self,
3606 edits: I,
3607 original_indent_columns: Vec<Option<u32>>,
3608 cx: &mut Context<Self>,
3609 ) where
3610 I: IntoIterator<Item = (Range<S>, T)>,
3611 S: ToOffset,
3612 T: Into<Arc<str>>,
3613 {
3614 if self.read_only(cx) {
3615 return;
3616 }
3617
3618 self.buffer.update(cx, |buffer, cx| {
3619 buffer.edit(
3620 edits,
3621 Some(AutoindentMode::Block {
3622 original_indent_columns,
3623 }),
3624 cx,
3625 )
3626 });
3627 }
3628
3629 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3630 self.hide_context_menu(window, cx);
3631
3632 match phase {
3633 SelectPhase::Begin {
3634 position,
3635 add,
3636 click_count,
3637 } => self.begin_selection(position, add, click_count, window, cx),
3638 SelectPhase::BeginColumnar {
3639 position,
3640 goal_column,
3641 reset,
3642 mode,
3643 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3644 SelectPhase::Extend {
3645 position,
3646 click_count,
3647 } => self.extend_selection(position, click_count, window, cx),
3648 SelectPhase::Update {
3649 position,
3650 goal_column,
3651 scroll_delta,
3652 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3653 SelectPhase::End => self.end_selection(window, cx),
3654 }
3655 }
3656
3657 fn extend_selection(
3658 &mut self,
3659 position: DisplayPoint,
3660 click_count: usize,
3661 window: &mut Window,
3662 cx: &mut Context<Self>,
3663 ) {
3664 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3665 let tail = self
3666 .selections
3667 .newest::<MultiBufferOffset>(&display_map)
3668 .tail();
3669 let click_count = click_count.max(match self.selections.select_mode() {
3670 SelectMode::Character => 1,
3671 SelectMode::Word(_) => 2,
3672 SelectMode::Line(_) => 3,
3673 SelectMode::All => 4,
3674 });
3675 self.begin_selection(position, false, click_count, window, cx);
3676
3677 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3678
3679 let current_selection = match self.selections.select_mode() {
3680 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3681 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3682 };
3683
3684 let mut pending_selection = self
3685 .selections
3686 .pending_anchor()
3687 .cloned()
3688 .expect("extend_selection not called with pending selection");
3689
3690 if pending_selection
3691 .start
3692 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3693 == Ordering::Greater
3694 {
3695 pending_selection.start = current_selection.start;
3696 }
3697 if pending_selection
3698 .end
3699 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3700 == Ordering::Less
3701 {
3702 pending_selection.end = current_selection.end;
3703 pending_selection.reversed = true;
3704 }
3705
3706 let mut pending_mode = self.selections.pending_mode().unwrap();
3707 match &mut pending_mode {
3708 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3709 _ => {}
3710 }
3711
3712 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3713 SelectionEffects::scroll(Autoscroll::fit())
3714 } else {
3715 SelectionEffects::no_scroll()
3716 };
3717
3718 self.change_selections(effects, window, cx, |s| {
3719 s.set_pending(pending_selection.clone(), pending_mode);
3720 s.set_is_extending(true);
3721 });
3722 }
3723
3724 fn begin_selection(
3725 &mut self,
3726 position: DisplayPoint,
3727 add: bool,
3728 click_count: usize,
3729 window: &mut Window,
3730 cx: &mut Context<Self>,
3731 ) {
3732 if !self.focus_handle.is_focused(window) {
3733 self.last_focused_descendant = None;
3734 window.focus(&self.focus_handle);
3735 }
3736
3737 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3738 let buffer = display_map.buffer_snapshot();
3739 let position = display_map.clip_point(position, Bias::Left);
3740
3741 let start;
3742 let end;
3743 let mode;
3744 let mut auto_scroll;
3745 match click_count {
3746 1 => {
3747 start = buffer.anchor_before(position.to_point(&display_map));
3748 end = start;
3749 mode = SelectMode::Character;
3750 auto_scroll = true;
3751 }
3752 2 => {
3753 let position = display_map
3754 .clip_point(position, Bias::Left)
3755 .to_offset(&display_map, Bias::Left);
3756 let (range, _) = buffer.surrounding_word(position, None);
3757 start = buffer.anchor_before(range.start);
3758 end = buffer.anchor_before(range.end);
3759 mode = SelectMode::Word(start..end);
3760 auto_scroll = true;
3761 }
3762 3 => {
3763 let position = display_map
3764 .clip_point(position, Bias::Left)
3765 .to_point(&display_map);
3766 let line_start = display_map.prev_line_boundary(position).0;
3767 let next_line_start = buffer.clip_point(
3768 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3769 Bias::Left,
3770 );
3771 start = buffer.anchor_before(line_start);
3772 end = buffer.anchor_before(next_line_start);
3773 mode = SelectMode::Line(start..end);
3774 auto_scroll = true;
3775 }
3776 _ => {
3777 start = buffer.anchor_before(MultiBufferOffset(0));
3778 end = buffer.anchor_before(buffer.len());
3779 mode = SelectMode::All;
3780 auto_scroll = false;
3781 }
3782 }
3783 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3784
3785 let point_to_delete: Option<usize> = {
3786 let selected_points: Vec<Selection<Point>> =
3787 self.selections.disjoint_in_range(start..end, &display_map);
3788
3789 if !add || click_count > 1 {
3790 None
3791 } else if !selected_points.is_empty() {
3792 Some(selected_points[0].id)
3793 } else {
3794 let clicked_point_already_selected =
3795 self.selections.disjoint_anchors().iter().find(|selection| {
3796 selection.start.to_point(buffer) == start.to_point(buffer)
3797 || selection.end.to_point(buffer) == end.to_point(buffer)
3798 });
3799
3800 clicked_point_already_selected.map(|selection| selection.id)
3801 }
3802 };
3803
3804 let selections_count = self.selections.count();
3805 let effects = if auto_scroll {
3806 SelectionEffects::default()
3807 } else {
3808 SelectionEffects::no_scroll()
3809 };
3810
3811 self.change_selections(effects, window, cx, |s| {
3812 if let Some(point_to_delete) = point_to_delete {
3813 s.delete(point_to_delete);
3814
3815 if selections_count == 1 {
3816 s.set_pending_anchor_range(start..end, mode);
3817 }
3818 } else {
3819 if !add {
3820 s.clear_disjoint();
3821 }
3822
3823 s.set_pending_anchor_range(start..end, mode);
3824 }
3825 });
3826 }
3827
3828 fn begin_columnar_selection(
3829 &mut self,
3830 position: DisplayPoint,
3831 goal_column: u32,
3832 reset: bool,
3833 mode: ColumnarMode,
3834 window: &mut Window,
3835 cx: &mut Context<Self>,
3836 ) {
3837 if !self.focus_handle.is_focused(window) {
3838 self.last_focused_descendant = None;
3839 window.focus(&self.focus_handle);
3840 }
3841
3842 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3843
3844 if reset {
3845 let pointer_position = display_map
3846 .buffer_snapshot()
3847 .anchor_before(position.to_point(&display_map));
3848
3849 self.change_selections(
3850 SelectionEffects::scroll(Autoscroll::newest()),
3851 window,
3852 cx,
3853 |s| {
3854 s.clear_disjoint();
3855 s.set_pending_anchor_range(
3856 pointer_position..pointer_position,
3857 SelectMode::Character,
3858 );
3859 },
3860 );
3861 };
3862
3863 let tail = self.selections.newest::<Point>(&display_map).tail();
3864 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3865 self.columnar_selection_state = match mode {
3866 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3867 selection_tail: selection_anchor,
3868 display_point: if reset {
3869 if position.column() != goal_column {
3870 Some(DisplayPoint::new(position.row(), goal_column))
3871 } else {
3872 None
3873 }
3874 } else {
3875 None
3876 },
3877 }),
3878 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3879 selection_tail: selection_anchor,
3880 }),
3881 };
3882
3883 if !reset {
3884 self.select_columns(position, goal_column, &display_map, window, cx);
3885 }
3886 }
3887
3888 fn update_selection(
3889 &mut self,
3890 position: DisplayPoint,
3891 goal_column: u32,
3892 scroll_delta: gpui::Point<f32>,
3893 window: &mut Window,
3894 cx: &mut Context<Self>,
3895 ) {
3896 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3897
3898 if self.columnar_selection_state.is_some() {
3899 self.select_columns(position, goal_column, &display_map, window, cx);
3900 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3901 let buffer = display_map.buffer_snapshot();
3902 let head;
3903 let tail;
3904 let mode = self.selections.pending_mode().unwrap();
3905 match &mode {
3906 SelectMode::Character => {
3907 head = position.to_point(&display_map);
3908 tail = pending.tail().to_point(buffer);
3909 }
3910 SelectMode::Word(original_range) => {
3911 let offset = display_map
3912 .clip_point(position, Bias::Left)
3913 .to_offset(&display_map, Bias::Left);
3914 let original_range = original_range.to_offset(buffer);
3915
3916 let head_offset = if buffer.is_inside_word(offset, None)
3917 || original_range.contains(&offset)
3918 {
3919 let (word_range, _) = buffer.surrounding_word(offset, None);
3920 if word_range.start < original_range.start {
3921 word_range.start
3922 } else {
3923 word_range.end
3924 }
3925 } else {
3926 offset
3927 };
3928
3929 head = head_offset.to_point(buffer);
3930 if head_offset <= original_range.start {
3931 tail = original_range.end.to_point(buffer);
3932 } else {
3933 tail = original_range.start.to_point(buffer);
3934 }
3935 }
3936 SelectMode::Line(original_range) => {
3937 let original_range = original_range.to_point(display_map.buffer_snapshot());
3938
3939 let position = display_map
3940 .clip_point(position, Bias::Left)
3941 .to_point(&display_map);
3942 let line_start = display_map.prev_line_boundary(position).0;
3943 let next_line_start = buffer.clip_point(
3944 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3945 Bias::Left,
3946 );
3947
3948 if line_start < original_range.start {
3949 head = line_start
3950 } else {
3951 head = next_line_start
3952 }
3953
3954 if head <= original_range.start {
3955 tail = original_range.end;
3956 } else {
3957 tail = original_range.start;
3958 }
3959 }
3960 SelectMode::All => {
3961 return;
3962 }
3963 };
3964
3965 if head < tail {
3966 pending.start = buffer.anchor_before(head);
3967 pending.end = buffer.anchor_before(tail);
3968 pending.reversed = true;
3969 } else {
3970 pending.start = buffer.anchor_before(tail);
3971 pending.end = buffer.anchor_before(head);
3972 pending.reversed = false;
3973 }
3974
3975 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3976 s.set_pending(pending.clone(), mode);
3977 });
3978 } else {
3979 log::error!("update_selection dispatched with no pending selection");
3980 return;
3981 }
3982
3983 self.apply_scroll_delta(scroll_delta, window, cx);
3984 cx.notify();
3985 }
3986
3987 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3988 self.columnar_selection_state.take();
3989 if let Some(pending_mode) = self.selections.pending_mode() {
3990 let selections = self
3991 .selections
3992 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3993 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3994 s.select(selections);
3995 s.clear_pending();
3996 if s.is_extending() {
3997 s.set_is_extending(false);
3998 } else {
3999 s.set_select_mode(pending_mode);
4000 }
4001 });
4002 }
4003 }
4004
4005 fn select_columns(
4006 &mut self,
4007 head: DisplayPoint,
4008 goal_column: u32,
4009 display_map: &DisplaySnapshot,
4010 window: &mut Window,
4011 cx: &mut Context<Self>,
4012 ) {
4013 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4014 return;
4015 };
4016
4017 let tail = match columnar_state {
4018 ColumnarSelectionState::FromMouse {
4019 selection_tail,
4020 display_point,
4021 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4022 ColumnarSelectionState::FromSelection { selection_tail } => {
4023 selection_tail.to_display_point(display_map)
4024 }
4025 };
4026
4027 let start_row = cmp::min(tail.row(), head.row());
4028 let end_row = cmp::max(tail.row(), head.row());
4029 let start_column = cmp::min(tail.column(), goal_column);
4030 let end_column = cmp::max(tail.column(), goal_column);
4031 let reversed = start_column < tail.column();
4032
4033 let selection_ranges = (start_row.0..=end_row.0)
4034 .map(DisplayRow)
4035 .filter_map(|row| {
4036 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4037 || start_column <= display_map.line_len(row))
4038 && !display_map.is_block_line(row)
4039 {
4040 let start = display_map
4041 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4042 .to_point(display_map);
4043 let end = display_map
4044 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4045 .to_point(display_map);
4046 if reversed {
4047 Some(end..start)
4048 } else {
4049 Some(start..end)
4050 }
4051 } else {
4052 None
4053 }
4054 })
4055 .collect::<Vec<_>>();
4056 if selection_ranges.is_empty() {
4057 return;
4058 }
4059
4060 let ranges = match columnar_state {
4061 ColumnarSelectionState::FromMouse { .. } => {
4062 let mut non_empty_ranges = selection_ranges
4063 .iter()
4064 .filter(|selection_range| selection_range.start != selection_range.end)
4065 .peekable();
4066 if non_empty_ranges.peek().is_some() {
4067 non_empty_ranges.cloned().collect()
4068 } else {
4069 selection_ranges
4070 }
4071 }
4072 _ => selection_ranges,
4073 };
4074
4075 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4076 s.select_ranges(ranges);
4077 });
4078 cx.notify();
4079 }
4080
4081 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4082 self.selections
4083 .all_adjusted(snapshot)
4084 .iter()
4085 .any(|selection| !selection.is_empty())
4086 }
4087
4088 pub fn has_pending_nonempty_selection(&self) -> bool {
4089 let pending_nonempty_selection = match self.selections.pending_anchor() {
4090 Some(Selection { start, end, .. }) => start != end,
4091 None => false,
4092 };
4093
4094 pending_nonempty_selection
4095 || (self.columnar_selection_state.is_some()
4096 && self.selections.disjoint_anchors().len() > 1)
4097 }
4098
4099 pub fn has_pending_selection(&self) -> bool {
4100 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4101 }
4102
4103 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4104 self.selection_mark_mode = false;
4105 self.selection_drag_state = SelectionDragState::None;
4106
4107 if self.dismiss_menus_and_popups(true, window, cx) {
4108 cx.notify();
4109 return;
4110 }
4111 if self.clear_expanded_diff_hunks(cx) {
4112 cx.notify();
4113 return;
4114 }
4115 if self.show_git_blame_gutter {
4116 self.show_git_blame_gutter = false;
4117 cx.notify();
4118 return;
4119 }
4120
4121 if self.mode.is_full()
4122 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4123 {
4124 cx.notify();
4125 return;
4126 }
4127
4128 cx.propagate();
4129 }
4130
4131 pub fn dismiss_menus_and_popups(
4132 &mut self,
4133 is_user_requested: bool,
4134 window: &mut Window,
4135 cx: &mut Context<Self>,
4136 ) -> bool {
4137 let mut dismissed = false;
4138
4139 dismissed |= self.take_rename(false, window, cx).is_some();
4140 dismissed |= self.hide_blame_popover(true, cx);
4141 dismissed |= hide_hover(self, cx);
4142 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4143 dismissed |= self.hide_context_menu(window, cx).is_some();
4144 dismissed |= self.mouse_context_menu.take().is_some();
4145 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4146 dismissed |= self.snippet_stack.pop().is_some();
4147
4148 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4149 self.dismiss_diagnostics(cx);
4150 dismissed = true;
4151 }
4152
4153 dismissed
4154 }
4155
4156 fn linked_editing_ranges_for(
4157 &self,
4158 selection: Range<text::Anchor>,
4159 cx: &App,
4160 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4161 if self.linked_edit_ranges.is_empty() {
4162 return None;
4163 }
4164 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4165 selection.end.buffer_id.and_then(|end_buffer_id| {
4166 if selection.start.buffer_id != Some(end_buffer_id) {
4167 return None;
4168 }
4169 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4170 let snapshot = buffer.read(cx).snapshot();
4171 self.linked_edit_ranges
4172 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4173 .map(|ranges| (ranges, snapshot, buffer))
4174 })?;
4175 use text::ToOffset as TO;
4176 // find offset from the start of current range to current cursor position
4177 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4178
4179 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4180 let start_difference = start_offset - start_byte_offset;
4181 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4182 let end_difference = end_offset - start_byte_offset;
4183 // Current range has associated linked ranges.
4184 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4185 for range in linked_ranges.iter() {
4186 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4187 let end_offset = start_offset + end_difference;
4188 let start_offset = start_offset + start_difference;
4189 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4190 continue;
4191 }
4192 if self.selections.disjoint_anchor_ranges().any(|s| {
4193 if s.start.buffer_id != selection.start.buffer_id
4194 || s.end.buffer_id != selection.end.buffer_id
4195 {
4196 return false;
4197 }
4198 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4199 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4200 }) {
4201 continue;
4202 }
4203 let start = buffer_snapshot.anchor_after(start_offset);
4204 let end = buffer_snapshot.anchor_after(end_offset);
4205 linked_edits
4206 .entry(buffer.clone())
4207 .or_default()
4208 .push(start..end);
4209 }
4210 Some(linked_edits)
4211 }
4212
4213 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4214 let text: Arc<str> = text.into();
4215
4216 if self.read_only(cx) {
4217 return;
4218 }
4219
4220 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4221
4222 self.unfold_buffers_with_selections(cx);
4223
4224 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4225 let mut bracket_inserted = false;
4226 let mut edits = Vec::new();
4227 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4228 let mut new_selections = Vec::with_capacity(selections.len());
4229 let mut new_autoclose_regions = Vec::new();
4230 let snapshot = self.buffer.read(cx).read(cx);
4231 let mut clear_linked_edit_ranges = false;
4232
4233 for (selection, autoclose_region) in
4234 self.selections_with_autoclose_regions(selections, &snapshot)
4235 {
4236 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4237 // Determine if the inserted text matches the opening or closing
4238 // bracket of any of this language's bracket pairs.
4239 let mut bracket_pair = None;
4240 let mut is_bracket_pair_start = false;
4241 let mut is_bracket_pair_end = false;
4242 if !text.is_empty() {
4243 let mut bracket_pair_matching_end = None;
4244 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4245 // and they are removing the character that triggered IME popup.
4246 for (pair, enabled) in scope.brackets() {
4247 if !pair.close && !pair.surround {
4248 continue;
4249 }
4250
4251 if enabled && pair.start.ends_with(text.as_ref()) {
4252 let prefix_len = pair.start.len() - text.len();
4253 let preceding_text_matches_prefix = prefix_len == 0
4254 || (selection.start.column >= (prefix_len as u32)
4255 && snapshot.contains_str_at(
4256 Point::new(
4257 selection.start.row,
4258 selection.start.column - (prefix_len as u32),
4259 ),
4260 &pair.start[..prefix_len],
4261 ));
4262 if preceding_text_matches_prefix {
4263 bracket_pair = Some(pair.clone());
4264 is_bracket_pair_start = true;
4265 break;
4266 }
4267 }
4268 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4269 {
4270 // take first bracket pair matching end, but don't break in case a later bracket
4271 // pair matches start
4272 bracket_pair_matching_end = Some(pair.clone());
4273 }
4274 }
4275 if let Some(end) = bracket_pair_matching_end
4276 && bracket_pair.is_none()
4277 {
4278 bracket_pair = Some(end);
4279 is_bracket_pair_end = true;
4280 }
4281 }
4282
4283 if let Some(bracket_pair) = bracket_pair {
4284 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4285 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4286 let auto_surround =
4287 self.use_auto_surround && snapshot_settings.use_auto_surround;
4288 if selection.is_empty() {
4289 if is_bracket_pair_start {
4290 // If the inserted text is a suffix of an opening bracket and the
4291 // selection is preceded by the rest of the opening bracket, then
4292 // insert the closing bracket.
4293 let following_text_allows_autoclose = snapshot
4294 .chars_at(selection.start)
4295 .next()
4296 .is_none_or(|c| scope.should_autoclose_before(c));
4297
4298 let preceding_text_allows_autoclose = selection.start.column == 0
4299 || snapshot
4300 .reversed_chars_at(selection.start)
4301 .next()
4302 .is_none_or(|c| {
4303 bracket_pair.start != bracket_pair.end
4304 || !snapshot
4305 .char_classifier_at(selection.start)
4306 .is_word(c)
4307 });
4308
4309 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4310 && bracket_pair.start.len() == 1
4311 {
4312 let target = bracket_pair.start.chars().next().unwrap();
4313 let current_line_count = snapshot
4314 .reversed_chars_at(selection.start)
4315 .take_while(|&c| c != '\n')
4316 .filter(|&c| c == target)
4317 .count();
4318 current_line_count % 2 == 1
4319 } else {
4320 false
4321 };
4322
4323 if autoclose
4324 && bracket_pair.close
4325 && following_text_allows_autoclose
4326 && preceding_text_allows_autoclose
4327 && !is_closing_quote
4328 {
4329 let anchor = snapshot.anchor_before(selection.end);
4330 new_selections.push((selection.map(|_| anchor), text.len()));
4331 new_autoclose_regions.push((
4332 anchor,
4333 text.len(),
4334 selection.id,
4335 bracket_pair.clone(),
4336 ));
4337 edits.push((
4338 selection.range(),
4339 format!("{}{}", text, bracket_pair.end).into(),
4340 ));
4341 bracket_inserted = true;
4342 continue;
4343 }
4344 }
4345
4346 if let Some(region) = autoclose_region {
4347 // If the selection is followed by an auto-inserted closing bracket,
4348 // then don't insert that closing bracket again; just move the selection
4349 // past the closing bracket.
4350 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4351 && text.as_ref() == region.pair.end.as_str()
4352 && snapshot.contains_str_at(region.range.end, text.as_ref());
4353 if should_skip {
4354 let anchor = snapshot.anchor_after(selection.end);
4355 new_selections
4356 .push((selection.map(|_| anchor), region.pair.end.len()));
4357 continue;
4358 }
4359 }
4360
4361 let always_treat_brackets_as_autoclosed = snapshot
4362 .language_settings_at(selection.start, cx)
4363 .always_treat_brackets_as_autoclosed;
4364 if always_treat_brackets_as_autoclosed
4365 && is_bracket_pair_end
4366 && snapshot.contains_str_at(selection.end, text.as_ref())
4367 {
4368 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4369 // and the inserted text is a closing bracket and the selection is followed
4370 // by the closing bracket then move the selection past the closing bracket.
4371 let anchor = snapshot.anchor_after(selection.end);
4372 new_selections.push((selection.map(|_| anchor), text.len()));
4373 continue;
4374 }
4375 }
4376 // If an opening bracket is 1 character long and is typed while
4377 // text is selected, then surround that text with the bracket pair.
4378 else if auto_surround
4379 && bracket_pair.surround
4380 && is_bracket_pair_start
4381 && bracket_pair.start.chars().count() == 1
4382 {
4383 edits.push((selection.start..selection.start, text.clone()));
4384 edits.push((
4385 selection.end..selection.end,
4386 bracket_pair.end.as_str().into(),
4387 ));
4388 bracket_inserted = true;
4389 new_selections.push((
4390 Selection {
4391 id: selection.id,
4392 start: snapshot.anchor_after(selection.start),
4393 end: snapshot.anchor_before(selection.end),
4394 reversed: selection.reversed,
4395 goal: selection.goal,
4396 },
4397 0,
4398 ));
4399 continue;
4400 }
4401 }
4402 }
4403
4404 if self.auto_replace_emoji_shortcode
4405 && selection.is_empty()
4406 && text.as_ref().ends_with(':')
4407 && let Some(possible_emoji_short_code) =
4408 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4409 && !possible_emoji_short_code.is_empty()
4410 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4411 {
4412 let emoji_shortcode_start = Point::new(
4413 selection.start.row,
4414 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4415 );
4416
4417 // Remove shortcode from buffer
4418 edits.push((
4419 emoji_shortcode_start..selection.start,
4420 "".to_string().into(),
4421 ));
4422 new_selections.push((
4423 Selection {
4424 id: selection.id,
4425 start: snapshot.anchor_after(emoji_shortcode_start),
4426 end: snapshot.anchor_before(selection.start),
4427 reversed: selection.reversed,
4428 goal: selection.goal,
4429 },
4430 0,
4431 ));
4432
4433 // Insert emoji
4434 let selection_start_anchor = snapshot.anchor_after(selection.start);
4435 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4436 edits.push((selection.start..selection.end, emoji.to_string().into()));
4437
4438 continue;
4439 }
4440
4441 // If not handling any auto-close operation, then just replace the selected
4442 // text with the given input and move the selection to the end of the
4443 // newly inserted text.
4444 let anchor = snapshot.anchor_after(selection.end);
4445 if !self.linked_edit_ranges.is_empty() {
4446 let start_anchor = snapshot.anchor_before(selection.start);
4447
4448 let is_word_char = text.chars().next().is_none_or(|char| {
4449 let classifier = snapshot
4450 .char_classifier_at(start_anchor.to_offset(&snapshot))
4451 .scope_context(Some(CharScopeContext::LinkedEdit));
4452 classifier.is_word(char)
4453 });
4454
4455 if is_word_char {
4456 if let Some(ranges) = self
4457 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4458 {
4459 for (buffer, edits) in ranges {
4460 linked_edits
4461 .entry(buffer.clone())
4462 .or_default()
4463 .extend(edits.into_iter().map(|range| (range, text.clone())));
4464 }
4465 }
4466 } else {
4467 clear_linked_edit_ranges = true;
4468 }
4469 }
4470
4471 new_selections.push((selection.map(|_| anchor), 0));
4472 edits.push((selection.start..selection.end, text.clone()));
4473 }
4474
4475 drop(snapshot);
4476
4477 self.transact(window, cx, |this, window, cx| {
4478 if clear_linked_edit_ranges {
4479 this.linked_edit_ranges.clear();
4480 }
4481 let initial_buffer_versions =
4482 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4483
4484 this.buffer.update(cx, |buffer, cx| {
4485 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4486 });
4487 for (buffer, edits) in linked_edits {
4488 buffer.update(cx, |buffer, cx| {
4489 let snapshot = buffer.snapshot();
4490 let edits = edits
4491 .into_iter()
4492 .map(|(range, text)| {
4493 use text::ToPoint as TP;
4494 let end_point = TP::to_point(&range.end, &snapshot);
4495 let start_point = TP::to_point(&range.start, &snapshot);
4496 (start_point..end_point, text)
4497 })
4498 .sorted_by_key(|(range, _)| range.start);
4499 buffer.edit(edits, None, cx);
4500 })
4501 }
4502 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4503 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4504 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4505 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4506 new_anchor_selections,
4507 &map,
4508 )
4509 .zip(new_selection_deltas)
4510 .map(|(selection, delta)| Selection {
4511 id: selection.id,
4512 start: selection.start + delta,
4513 end: selection.end + delta,
4514 reversed: selection.reversed,
4515 goal: SelectionGoal::None,
4516 })
4517 .collect::<Vec<_>>();
4518
4519 let mut i = 0;
4520 for (position, delta, selection_id, pair) in new_autoclose_regions {
4521 let position = position.to_offset(map.buffer_snapshot()) + delta;
4522 let start = map.buffer_snapshot().anchor_before(position);
4523 let end = map.buffer_snapshot().anchor_after(position);
4524 while let Some(existing_state) = this.autoclose_regions.get(i) {
4525 match existing_state
4526 .range
4527 .start
4528 .cmp(&start, map.buffer_snapshot())
4529 {
4530 Ordering::Less => i += 1,
4531 Ordering::Greater => break,
4532 Ordering::Equal => {
4533 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4534 Ordering::Less => i += 1,
4535 Ordering::Equal => break,
4536 Ordering::Greater => break,
4537 }
4538 }
4539 }
4540 }
4541 this.autoclose_regions.insert(
4542 i,
4543 AutocloseRegion {
4544 selection_id,
4545 range: start..end,
4546 pair,
4547 },
4548 );
4549 }
4550
4551 let had_active_edit_prediction = this.has_active_edit_prediction();
4552 this.change_selections(
4553 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4554 window,
4555 cx,
4556 |s| s.select(new_selections),
4557 );
4558
4559 if !bracket_inserted
4560 && let Some(on_type_format_task) =
4561 this.trigger_on_type_formatting(text.to_string(), window, cx)
4562 {
4563 on_type_format_task.detach_and_log_err(cx);
4564 }
4565
4566 let editor_settings = EditorSettings::get_global(cx);
4567 if bracket_inserted
4568 && (editor_settings.auto_signature_help
4569 || editor_settings.show_signature_help_after_edits)
4570 {
4571 this.show_signature_help(&ShowSignatureHelp, window, cx);
4572 }
4573
4574 let trigger_in_words =
4575 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4576 if this.hard_wrap.is_some() {
4577 let latest: Range<Point> = this.selections.newest(&map).range();
4578 if latest.is_empty()
4579 && this
4580 .buffer()
4581 .read(cx)
4582 .snapshot(cx)
4583 .line_len(MultiBufferRow(latest.start.row))
4584 == latest.start.column
4585 {
4586 this.rewrap_impl(
4587 RewrapOptions {
4588 override_language_settings: true,
4589 preserve_existing_whitespace: true,
4590 },
4591 cx,
4592 )
4593 }
4594 }
4595 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4596 refresh_linked_ranges(this, window, cx);
4597 this.refresh_edit_prediction(true, false, window, cx);
4598 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4599 });
4600 }
4601
4602 fn find_possible_emoji_shortcode_at_position(
4603 snapshot: &MultiBufferSnapshot,
4604 position: Point,
4605 ) -> Option<String> {
4606 let mut chars = Vec::new();
4607 let mut found_colon = false;
4608 for char in snapshot.reversed_chars_at(position).take(100) {
4609 // Found a possible emoji shortcode in the middle of the buffer
4610 if found_colon {
4611 if char.is_whitespace() {
4612 chars.reverse();
4613 return Some(chars.iter().collect());
4614 }
4615 // If the previous character is not a whitespace, we are in the middle of a word
4616 // and we only want to complete the shortcode if the word is made up of other emojis
4617 let mut containing_word = String::new();
4618 for ch in snapshot
4619 .reversed_chars_at(position)
4620 .skip(chars.len() + 1)
4621 .take(100)
4622 {
4623 if ch.is_whitespace() {
4624 break;
4625 }
4626 containing_word.push(ch);
4627 }
4628 let containing_word = containing_word.chars().rev().collect::<String>();
4629 if util::word_consists_of_emojis(containing_word.as_str()) {
4630 chars.reverse();
4631 return Some(chars.iter().collect());
4632 }
4633 }
4634
4635 if char.is_whitespace() || !char.is_ascii() {
4636 return None;
4637 }
4638 if char == ':' {
4639 found_colon = true;
4640 } else {
4641 chars.push(char);
4642 }
4643 }
4644 // Found a possible emoji shortcode at the beginning of the buffer
4645 chars.reverse();
4646 Some(chars.iter().collect())
4647 }
4648
4649 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4650 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4651 self.transact(window, cx, |this, window, cx| {
4652 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4653 let selections = this
4654 .selections
4655 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4656 let multi_buffer = this.buffer.read(cx);
4657 let buffer = multi_buffer.snapshot(cx);
4658 selections
4659 .iter()
4660 .map(|selection| {
4661 let start_point = selection.start.to_point(&buffer);
4662 let mut existing_indent =
4663 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4664 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4665 let start = selection.start;
4666 let end = selection.end;
4667 let selection_is_empty = start == end;
4668 let language_scope = buffer.language_scope_at(start);
4669 let (
4670 comment_delimiter,
4671 doc_delimiter,
4672 insert_extra_newline,
4673 indent_on_newline,
4674 indent_on_extra_newline,
4675 ) = if let Some(language) = &language_scope {
4676 let mut insert_extra_newline =
4677 insert_extra_newline_brackets(&buffer, start..end, language)
4678 || insert_extra_newline_tree_sitter(&buffer, start..end);
4679
4680 // Comment extension on newline is allowed only for cursor selections
4681 let comment_delimiter = maybe!({
4682 if !selection_is_empty {
4683 return None;
4684 }
4685
4686 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4687 return None;
4688 }
4689
4690 let delimiters = language.line_comment_prefixes();
4691 let max_len_of_delimiter =
4692 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4693 let (snapshot, range) =
4694 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4695
4696 let num_of_whitespaces = snapshot
4697 .chars_for_range(range.clone())
4698 .take_while(|c| c.is_whitespace())
4699 .count();
4700 let comment_candidate = snapshot
4701 .chars_for_range(range.clone())
4702 .skip(num_of_whitespaces)
4703 .take(max_len_of_delimiter)
4704 .collect::<String>();
4705 let (delimiter, trimmed_len) = delimiters
4706 .iter()
4707 .filter_map(|delimiter| {
4708 let prefix = delimiter.trim_end();
4709 if comment_candidate.starts_with(prefix) {
4710 Some((delimiter, prefix.len()))
4711 } else {
4712 None
4713 }
4714 })
4715 .max_by_key(|(_, len)| *len)?;
4716
4717 if let Some(BlockCommentConfig {
4718 start: block_start, ..
4719 }) = language.block_comment()
4720 {
4721 let block_start_trimmed = block_start.trim_end();
4722 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4723 let line_content = snapshot
4724 .chars_for_range(range)
4725 .skip(num_of_whitespaces)
4726 .take(block_start_trimmed.len())
4727 .collect::<String>();
4728
4729 if line_content.starts_with(block_start_trimmed) {
4730 return None;
4731 }
4732 }
4733 }
4734
4735 let cursor_is_placed_after_comment_marker =
4736 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4737 if cursor_is_placed_after_comment_marker {
4738 Some(delimiter.clone())
4739 } else {
4740 None
4741 }
4742 });
4743
4744 let mut indent_on_newline = IndentSize::spaces(0);
4745 let mut indent_on_extra_newline = IndentSize::spaces(0);
4746
4747 let doc_delimiter = maybe!({
4748 if !selection_is_empty {
4749 return None;
4750 }
4751
4752 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4753 return None;
4754 }
4755
4756 let BlockCommentConfig {
4757 start: start_tag,
4758 end: end_tag,
4759 prefix: delimiter,
4760 tab_size: len,
4761 } = language.documentation_comment()?;
4762 let is_within_block_comment = buffer
4763 .language_scope_at(start_point)
4764 .is_some_and(|scope| scope.override_name() == Some("comment"));
4765 if !is_within_block_comment {
4766 return None;
4767 }
4768
4769 let (snapshot, range) =
4770 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4771
4772 let num_of_whitespaces = snapshot
4773 .chars_for_range(range.clone())
4774 .take_while(|c| c.is_whitespace())
4775 .count();
4776
4777 // 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.
4778 let column = start_point.column;
4779 let cursor_is_after_start_tag = {
4780 let start_tag_len = start_tag.len();
4781 let start_tag_line = snapshot
4782 .chars_for_range(range.clone())
4783 .skip(num_of_whitespaces)
4784 .take(start_tag_len)
4785 .collect::<String>();
4786 if start_tag_line.starts_with(start_tag.as_ref()) {
4787 num_of_whitespaces + start_tag_len <= column as usize
4788 } else {
4789 false
4790 }
4791 };
4792
4793 let cursor_is_after_delimiter = {
4794 let delimiter_trim = delimiter.trim_end();
4795 let delimiter_line = snapshot
4796 .chars_for_range(range.clone())
4797 .skip(num_of_whitespaces)
4798 .take(delimiter_trim.len())
4799 .collect::<String>();
4800 if delimiter_line.starts_with(delimiter_trim) {
4801 num_of_whitespaces + delimiter_trim.len() <= column as usize
4802 } else {
4803 false
4804 }
4805 };
4806
4807 let cursor_is_before_end_tag_if_exists = {
4808 let mut char_position = 0u32;
4809 let mut end_tag_offset = None;
4810
4811 'outer: for chunk in snapshot.text_for_range(range) {
4812 if let Some(byte_pos) = chunk.find(&**end_tag) {
4813 let chars_before_match =
4814 chunk[..byte_pos].chars().count() as u32;
4815 end_tag_offset =
4816 Some(char_position + chars_before_match);
4817 break 'outer;
4818 }
4819 char_position += chunk.chars().count() as u32;
4820 }
4821
4822 if let Some(end_tag_offset) = end_tag_offset {
4823 let cursor_is_before_end_tag = column <= end_tag_offset;
4824 if cursor_is_after_start_tag {
4825 if cursor_is_before_end_tag {
4826 insert_extra_newline = true;
4827 }
4828 let cursor_is_at_start_of_end_tag =
4829 column == end_tag_offset;
4830 if cursor_is_at_start_of_end_tag {
4831 indent_on_extra_newline.len = *len;
4832 }
4833 }
4834 cursor_is_before_end_tag
4835 } else {
4836 true
4837 }
4838 };
4839
4840 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4841 && cursor_is_before_end_tag_if_exists
4842 {
4843 if cursor_is_after_start_tag {
4844 indent_on_newline.len = *len;
4845 }
4846 Some(delimiter.clone())
4847 } else {
4848 None
4849 }
4850 });
4851
4852 (
4853 comment_delimiter,
4854 doc_delimiter,
4855 insert_extra_newline,
4856 indent_on_newline,
4857 indent_on_extra_newline,
4858 )
4859 } else {
4860 (
4861 None,
4862 None,
4863 false,
4864 IndentSize::default(),
4865 IndentSize::default(),
4866 )
4867 };
4868
4869 let prevent_auto_indent = doc_delimiter.is_some();
4870 let delimiter = comment_delimiter.or(doc_delimiter);
4871
4872 let capacity_for_delimiter =
4873 delimiter.as_deref().map(str::len).unwrap_or_default();
4874 let mut new_text = String::with_capacity(
4875 1 + capacity_for_delimiter
4876 + existing_indent.len as usize
4877 + indent_on_newline.len as usize
4878 + indent_on_extra_newline.len as usize,
4879 );
4880 new_text.push('\n');
4881 new_text.extend(existing_indent.chars());
4882 new_text.extend(indent_on_newline.chars());
4883
4884 if let Some(delimiter) = &delimiter {
4885 new_text.push_str(delimiter);
4886 }
4887
4888 if insert_extra_newline {
4889 new_text.push('\n');
4890 new_text.extend(existing_indent.chars());
4891 new_text.extend(indent_on_extra_newline.chars());
4892 }
4893
4894 let anchor = buffer.anchor_after(end);
4895 let new_selection = selection.map(|_| anchor);
4896 (
4897 ((start..end, new_text), prevent_auto_indent),
4898 (insert_extra_newline, new_selection),
4899 )
4900 })
4901 .unzip()
4902 };
4903
4904 let mut auto_indent_edits = Vec::new();
4905 let mut edits = Vec::new();
4906 for (edit, prevent_auto_indent) in edits_with_flags {
4907 if prevent_auto_indent {
4908 edits.push(edit);
4909 } else {
4910 auto_indent_edits.push(edit);
4911 }
4912 }
4913 if !edits.is_empty() {
4914 this.edit(edits, cx);
4915 }
4916 if !auto_indent_edits.is_empty() {
4917 this.edit_with_autoindent(auto_indent_edits, cx);
4918 }
4919
4920 let buffer = this.buffer.read(cx).snapshot(cx);
4921 let new_selections = selection_info
4922 .into_iter()
4923 .map(|(extra_newline_inserted, new_selection)| {
4924 let mut cursor = new_selection.end.to_point(&buffer);
4925 if extra_newline_inserted {
4926 cursor.row -= 1;
4927 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4928 }
4929 new_selection.map(|_| cursor)
4930 })
4931 .collect();
4932
4933 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4934 this.refresh_edit_prediction(true, false, window, cx);
4935 });
4936 }
4937
4938 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4940
4941 let buffer = self.buffer.read(cx);
4942 let snapshot = buffer.snapshot(cx);
4943
4944 let mut edits = Vec::new();
4945 let mut rows = Vec::new();
4946
4947 for (rows_inserted, selection) in self
4948 .selections
4949 .all_adjusted(&self.display_snapshot(cx))
4950 .into_iter()
4951 .enumerate()
4952 {
4953 let cursor = selection.head();
4954 let row = cursor.row;
4955
4956 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4957
4958 let newline = "\n".to_string();
4959 edits.push((start_of_line..start_of_line, newline));
4960
4961 rows.push(row + rows_inserted as u32);
4962 }
4963
4964 self.transact(window, cx, |editor, window, cx| {
4965 editor.edit(edits, cx);
4966
4967 editor.change_selections(Default::default(), window, cx, |s| {
4968 let mut index = 0;
4969 s.move_cursors_with(|map, _, _| {
4970 let row = rows[index];
4971 index += 1;
4972
4973 let point = Point::new(row, 0);
4974 let boundary = map.next_line_boundary(point).1;
4975 let clipped = map.clip_point(boundary, Bias::Left);
4976
4977 (clipped, SelectionGoal::None)
4978 });
4979 });
4980
4981 let mut indent_edits = Vec::new();
4982 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4983 for row in rows {
4984 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4985 for (row, indent) in indents {
4986 if indent.len == 0 {
4987 continue;
4988 }
4989
4990 let text = match indent.kind {
4991 IndentKind::Space => " ".repeat(indent.len as usize),
4992 IndentKind::Tab => "\t".repeat(indent.len as usize),
4993 };
4994 let point = Point::new(row.0, 0);
4995 indent_edits.push((point..point, text));
4996 }
4997 }
4998 editor.edit(indent_edits, cx);
4999 });
5000 }
5001
5002 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5003 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5004
5005 let buffer = self.buffer.read(cx);
5006 let snapshot = buffer.snapshot(cx);
5007
5008 let mut edits = Vec::new();
5009 let mut rows = Vec::new();
5010 let mut rows_inserted = 0;
5011
5012 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5013 let cursor = selection.head();
5014 let row = cursor.row;
5015
5016 let point = Point::new(row + 1, 0);
5017 let start_of_line = snapshot.clip_point(point, Bias::Left);
5018
5019 let newline = "\n".to_string();
5020 edits.push((start_of_line..start_of_line, newline));
5021
5022 rows_inserted += 1;
5023 rows.push(row + rows_inserted);
5024 }
5025
5026 self.transact(window, cx, |editor, window, cx| {
5027 editor.edit(edits, cx);
5028
5029 editor.change_selections(Default::default(), window, cx, |s| {
5030 let mut index = 0;
5031 s.move_cursors_with(|map, _, _| {
5032 let row = rows[index];
5033 index += 1;
5034
5035 let point = Point::new(row, 0);
5036 let boundary = map.next_line_boundary(point).1;
5037 let clipped = map.clip_point(boundary, Bias::Left);
5038
5039 (clipped, SelectionGoal::None)
5040 });
5041 });
5042
5043 let mut indent_edits = Vec::new();
5044 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5045 for row in rows {
5046 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5047 for (row, indent) in indents {
5048 if indent.len == 0 {
5049 continue;
5050 }
5051
5052 let text = match indent.kind {
5053 IndentKind::Space => " ".repeat(indent.len as usize),
5054 IndentKind::Tab => "\t".repeat(indent.len as usize),
5055 };
5056 let point = Point::new(row.0, 0);
5057 indent_edits.push((point..point, text));
5058 }
5059 }
5060 editor.edit(indent_edits, cx);
5061 });
5062 }
5063
5064 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5065 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5066 original_indent_columns: Vec::new(),
5067 });
5068 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5069 }
5070
5071 fn insert_with_autoindent_mode(
5072 &mut self,
5073 text: &str,
5074 autoindent_mode: Option<AutoindentMode>,
5075 window: &mut Window,
5076 cx: &mut Context<Self>,
5077 ) {
5078 if self.read_only(cx) {
5079 return;
5080 }
5081
5082 let text: Arc<str> = text.into();
5083 self.transact(window, cx, |this, window, cx| {
5084 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5085 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5086 let anchors = {
5087 let snapshot = buffer.read(cx);
5088 old_selections
5089 .iter()
5090 .map(|s| {
5091 let anchor = snapshot.anchor_after(s.head());
5092 s.map(|_| anchor)
5093 })
5094 .collect::<Vec<_>>()
5095 };
5096 buffer.edit(
5097 old_selections
5098 .iter()
5099 .map(|s| (s.start..s.end, text.clone())),
5100 autoindent_mode,
5101 cx,
5102 );
5103 anchors
5104 });
5105
5106 this.change_selections(Default::default(), window, cx, |s| {
5107 s.select_anchors(selection_anchors);
5108 });
5109
5110 cx.notify();
5111 });
5112 }
5113
5114 fn trigger_completion_on_input(
5115 &mut self,
5116 text: &str,
5117 trigger_in_words: bool,
5118 window: &mut Window,
5119 cx: &mut Context<Self>,
5120 ) {
5121 let completions_source = self
5122 .context_menu
5123 .borrow()
5124 .as_ref()
5125 .and_then(|menu| match menu {
5126 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5127 CodeContextMenu::CodeActions(_) => None,
5128 });
5129
5130 match completions_source {
5131 Some(CompletionsMenuSource::Words { .. }) => {
5132 self.open_or_update_completions_menu(
5133 Some(CompletionsMenuSource::Words {
5134 ignore_threshold: false,
5135 }),
5136 None,
5137 trigger_in_words,
5138 window,
5139 cx,
5140 );
5141 }
5142 _ => self.open_or_update_completions_menu(
5143 None,
5144 Some(text.to_owned()).filter(|x| !x.is_empty()),
5145 true,
5146 window,
5147 cx,
5148 ),
5149 }
5150 }
5151
5152 /// If any empty selections is touching the start of its innermost containing autoclose
5153 /// region, expand it to select the brackets.
5154 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5155 let selections = self
5156 .selections
5157 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5158 let buffer = self.buffer.read(cx).read(cx);
5159 let new_selections = self
5160 .selections_with_autoclose_regions(selections, &buffer)
5161 .map(|(mut selection, region)| {
5162 if !selection.is_empty() {
5163 return selection;
5164 }
5165
5166 if let Some(region) = region {
5167 let mut range = region.range.to_offset(&buffer);
5168 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5169 range.start -= region.pair.start.len();
5170 if buffer.contains_str_at(range.start, ®ion.pair.start)
5171 && buffer.contains_str_at(range.end, ®ion.pair.end)
5172 {
5173 range.end += region.pair.end.len();
5174 selection.start = range.start;
5175 selection.end = range.end;
5176
5177 return selection;
5178 }
5179 }
5180 }
5181
5182 let always_treat_brackets_as_autoclosed = buffer
5183 .language_settings_at(selection.start, cx)
5184 .always_treat_brackets_as_autoclosed;
5185
5186 if !always_treat_brackets_as_autoclosed {
5187 return selection;
5188 }
5189
5190 if let Some(scope) = buffer.language_scope_at(selection.start) {
5191 for (pair, enabled) in scope.brackets() {
5192 if !enabled || !pair.close {
5193 continue;
5194 }
5195
5196 if buffer.contains_str_at(selection.start, &pair.end) {
5197 let pair_start_len = pair.start.len();
5198 if buffer.contains_str_at(
5199 selection.start.saturating_sub_usize(pair_start_len),
5200 &pair.start,
5201 ) {
5202 selection.start -= pair_start_len;
5203 selection.end += pair.end.len();
5204
5205 return selection;
5206 }
5207 }
5208 }
5209 }
5210
5211 selection
5212 })
5213 .collect();
5214
5215 drop(buffer);
5216 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5217 selections.select(new_selections)
5218 });
5219 }
5220
5221 /// Iterate the given selections, and for each one, find the smallest surrounding
5222 /// autoclose region. This uses the ordering of the selections and the autoclose
5223 /// regions to avoid repeated comparisons.
5224 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5225 &'a self,
5226 selections: impl IntoIterator<Item = Selection<D>>,
5227 buffer: &'a MultiBufferSnapshot,
5228 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5229 let mut i = 0;
5230 let mut regions = self.autoclose_regions.as_slice();
5231 selections.into_iter().map(move |selection| {
5232 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5233
5234 let mut enclosing = None;
5235 while let Some(pair_state) = regions.get(i) {
5236 if pair_state.range.end.to_offset(buffer) < range.start {
5237 regions = ®ions[i + 1..];
5238 i = 0;
5239 } else if pair_state.range.start.to_offset(buffer) > range.end {
5240 break;
5241 } else {
5242 if pair_state.selection_id == selection.id {
5243 enclosing = Some(pair_state);
5244 }
5245 i += 1;
5246 }
5247 }
5248
5249 (selection, enclosing)
5250 })
5251 }
5252
5253 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5254 fn invalidate_autoclose_regions(
5255 &mut self,
5256 mut selections: &[Selection<Anchor>],
5257 buffer: &MultiBufferSnapshot,
5258 ) {
5259 self.autoclose_regions.retain(|state| {
5260 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5261 return false;
5262 }
5263
5264 let mut i = 0;
5265 while let Some(selection) = selections.get(i) {
5266 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5267 selections = &selections[1..];
5268 continue;
5269 }
5270 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5271 break;
5272 }
5273 if selection.id == state.selection_id {
5274 return true;
5275 } else {
5276 i += 1;
5277 }
5278 }
5279 false
5280 });
5281 }
5282
5283 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5284 let offset = position.to_offset(buffer);
5285 let (word_range, kind) =
5286 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5287 if offset > word_range.start && kind == Some(CharKind::Word) {
5288 Some(
5289 buffer
5290 .text_for_range(word_range.start..offset)
5291 .collect::<String>(),
5292 )
5293 } else {
5294 None
5295 }
5296 }
5297
5298 pub fn visible_excerpts(
5299 &self,
5300 cx: &mut Context<Editor>,
5301 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5302 let Some(project) = self.project() else {
5303 return HashMap::default();
5304 };
5305 let project = project.read(cx);
5306 let multi_buffer = self.buffer().read(cx);
5307 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5308 let multi_buffer_visible_start = self
5309 .scroll_manager
5310 .anchor()
5311 .anchor
5312 .to_point(&multi_buffer_snapshot);
5313 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5314 multi_buffer_visible_start
5315 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5316 Bias::Left,
5317 );
5318 multi_buffer_snapshot
5319 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5320 .into_iter()
5321 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5322 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5323 let buffer_file = project::File::from_dyn(buffer.file())?;
5324 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5325 let worktree_entry = buffer_worktree
5326 .read(cx)
5327 .entry_for_id(buffer_file.project_entry_id()?)?;
5328 if worktree_entry.is_ignored {
5329 None
5330 } else {
5331 Some((
5332 excerpt_id,
5333 (
5334 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5335 buffer.version().clone(),
5336 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5337 ),
5338 ))
5339 }
5340 })
5341 .collect()
5342 }
5343
5344 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5345 TextLayoutDetails {
5346 text_system: window.text_system().clone(),
5347 editor_style: self.style.clone().unwrap(),
5348 rem_size: window.rem_size(),
5349 scroll_anchor: self.scroll_manager.anchor(),
5350 visible_rows: self.visible_line_count(),
5351 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5352 }
5353 }
5354
5355 fn trigger_on_type_formatting(
5356 &self,
5357 input: String,
5358 window: &mut Window,
5359 cx: &mut Context<Self>,
5360 ) -> Option<Task<Result<()>>> {
5361 if input.len() != 1 {
5362 return None;
5363 }
5364
5365 let project = self.project()?;
5366 let position = self.selections.newest_anchor().head();
5367 let (buffer, buffer_position) = self
5368 .buffer
5369 .read(cx)
5370 .text_anchor_for_position(position, cx)?;
5371
5372 let settings = language_settings::language_settings(
5373 buffer
5374 .read(cx)
5375 .language_at(buffer_position)
5376 .map(|l| l.name()),
5377 buffer.read(cx).file(),
5378 cx,
5379 );
5380 if !settings.use_on_type_format {
5381 return None;
5382 }
5383
5384 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5385 // hence we do LSP request & edit on host side only — add formats to host's history.
5386 let push_to_lsp_host_history = true;
5387 // If this is not the host, append its history with new edits.
5388 let push_to_client_history = project.read(cx).is_via_collab();
5389
5390 let on_type_formatting = project.update(cx, |project, cx| {
5391 project.on_type_format(
5392 buffer.clone(),
5393 buffer_position,
5394 input,
5395 push_to_lsp_host_history,
5396 cx,
5397 )
5398 });
5399 Some(cx.spawn_in(window, async move |editor, cx| {
5400 if let Some(transaction) = on_type_formatting.await? {
5401 if push_to_client_history {
5402 buffer
5403 .update(cx, |buffer, _| {
5404 buffer.push_transaction(transaction, Instant::now());
5405 buffer.finalize_last_transaction();
5406 })
5407 .ok();
5408 }
5409 editor.update(cx, |editor, cx| {
5410 editor.refresh_document_highlights(cx);
5411 })?;
5412 }
5413 Ok(())
5414 }))
5415 }
5416
5417 pub fn show_word_completions(
5418 &mut self,
5419 _: &ShowWordCompletions,
5420 window: &mut Window,
5421 cx: &mut Context<Self>,
5422 ) {
5423 self.open_or_update_completions_menu(
5424 Some(CompletionsMenuSource::Words {
5425 ignore_threshold: true,
5426 }),
5427 None,
5428 false,
5429 window,
5430 cx,
5431 );
5432 }
5433
5434 pub fn show_completions(
5435 &mut self,
5436 _: &ShowCompletions,
5437 window: &mut Window,
5438 cx: &mut Context<Self>,
5439 ) {
5440 self.open_or_update_completions_menu(None, None, false, window, cx);
5441 }
5442
5443 fn open_or_update_completions_menu(
5444 &mut self,
5445 requested_source: Option<CompletionsMenuSource>,
5446 trigger: Option<String>,
5447 trigger_in_words: bool,
5448 window: &mut Window,
5449 cx: &mut Context<Self>,
5450 ) {
5451 if self.pending_rename.is_some() {
5452 return;
5453 }
5454
5455 let completions_source = self
5456 .context_menu
5457 .borrow()
5458 .as_ref()
5459 .and_then(|menu| match menu {
5460 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5461 CodeContextMenu::CodeActions(_) => None,
5462 });
5463
5464 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5465
5466 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5467 // inserted and selected. To handle that case, the start of the selection is used so that
5468 // the menu starts with all choices.
5469 let position = self
5470 .selections
5471 .newest_anchor()
5472 .start
5473 .bias_right(&multibuffer_snapshot);
5474 if position.diff_base_anchor.is_some() {
5475 return;
5476 }
5477 let buffer_position = multibuffer_snapshot.anchor_before(position);
5478 let Some(buffer) = buffer_position
5479 .buffer_id
5480 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5481 else {
5482 return;
5483 };
5484 let buffer_snapshot = buffer.read(cx).snapshot();
5485
5486 let query: Option<Arc<String>> =
5487 Self::completion_query(&multibuffer_snapshot, buffer_position)
5488 .map(|query| query.into());
5489
5490 drop(multibuffer_snapshot);
5491
5492 // Hide the current completions menu when query is empty. Without this, cached
5493 // completions from before the trigger char may be reused (#32774).
5494 if query.is_none() {
5495 let menu_is_open = matches!(
5496 self.context_menu.borrow().as_ref(),
5497 Some(CodeContextMenu::Completions(_))
5498 );
5499 if menu_is_open {
5500 self.hide_context_menu(window, cx);
5501 }
5502 }
5503
5504 let mut ignore_word_threshold = false;
5505 let provider = match requested_source {
5506 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5507 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5508 ignore_word_threshold = ignore_threshold;
5509 None
5510 }
5511 Some(CompletionsMenuSource::SnippetChoices)
5512 | Some(CompletionsMenuSource::SnippetsOnly) => {
5513 log::error!("bug: SnippetChoices requested_source is not handled");
5514 None
5515 }
5516 };
5517
5518 let sort_completions = provider
5519 .as_ref()
5520 .is_some_and(|provider| provider.sort_completions());
5521
5522 let filter_completions = provider
5523 .as_ref()
5524 .is_none_or(|provider| provider.filter_completions());
5525
5526 let was_snippets_only = matches!(
5527 completions_source,
5528 Some(CompletionsMenuSource::SnippetsOnly)
5529 );
5530
5531 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5532 if filter_completions {
5533 menu.filter(
5534 query.clone().unwrap_or_default(),
5535 buffer_position.text_anchor,
5536 &buffer,
5537 provider.clone(),
5538 window,
5539 cx,
5540 );
5541 }
5542 // When `is_incomplete` is false, no need to re-query completions when the current query
5543 // is a suffix of the initial query.
5544 let was_complete = !menu.is_incomplete;
5545 if was_complete && !was_snippets_only {
5546 // If the new query is a suffix of the old query (typing more characters) and
5547 // the previous result was complete, the existing completions can be filtered.
5548 //
5549 // Note that snippet completions are always complete.
5550 let query_matches = match (&menu.initial_query, &query) {
5551 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5552 (None, _) => true,
5553 _ => false,
5554 };
5555 if query_matches {
5556 let position_matches = if menu.initial_position == position {
5557 true
5558 } else {
5559 let snapshot = self.buffer.read(cx).read(cx);
5560 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5561 };
5562 if position_matches {
5563 return;
5564 }
5565 }
5566 }
5567 };
5568
5569 let Anchor {
5570 excerpt_id: buffer_excerpt_id,
5571 text_anchor: buffer_position,
5572 ..
5573 } = buffer_position;
5574
5575 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5576 buffer_snapshot.surrounding_word(buffer_position, None)
5577 {
5578 let word_to_exclude = buffer_snapshot
5579 .text_for_range(word_range.clone())
5580 .collect::<String>();
5581 (
5582 buffer_snapshot.anchor_before(word_range.start)
5583 ..buffer_snapshot.anchor_after(buffer_position),
5584 Some(word_to_exclude),
5585 )
5586 } else {
5587 (buffer_position..buffer_position, None)
5588 };
5589
5590 let language = buffer_snapshot
5591 .language_at(buffer_position)
5592 .map(|language| language.name());
5593
5594 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5595 .completions
5596 .clone();
5597
5598 let show_completion_documentation = buffer_snapshot
5599 .settings_at(buffer_position, cx)
5600 .show_completion_documentation;
5601
5602 // The document can be large, so stay in reasonable bounds when searching for words,
5603 // otherwise completion pop-up might be slow to appear.
5604 const WORD_LOOKUP_ROWS: u32 = 5_000;
5605 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5606 let min_word_search = buffer_snapshot.clip_point(
5607 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5608 Bias::Left,
5609 );
5610 let max_word_search = buffer_snapshot.clip_point(
5611 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5612 Bias::Right,
5613 );
5614 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5615 ..buffer_snapshot.point_to_offset(max_word_search);
5616
5617 let skip_digits = query
5618 .as_ref()
5619 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5620
5621 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5622 trigger.as_ref().is_none_or(|trigger| {
5623 provider.is_completion_trigger(
5624 &buffer,
5625 position.text_anchor,
5626 trigger,
5627 trigger_in_words,
5628 completions_source.is_some(),
5629 cx,
5630 )
5631 })
5632 });
5633
5634 let provider_responses = if let Some(provider) = &provider
5635 && load_provider_completions
5636 {
5637 let trigger_character =
5638 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5639 let completion_context = CompletionContext {
5640 trigger_kind: match &trigger_character {
5641 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5642 None => CompletionTriggerKind::INVOKED,
5643 },
5644 trigger_character,
5645 };
5646
5647 provider.completions(
5648 buffer_excerpt_id,
5649 &buffer,
5650 buffer_position,
5651 completion_context,
5652 window,
5653 cx,
5654 )
5655 } else {
5656 Task::ready(Ok(Vec::new()))
5657 };
5658
5659 let load_word_completions = if !self.word_completions_enabled {
5660 false
5661 } else if requested_source
5662 == Some(CompletionsMenuSource::Words {
5663 ignore_threshold: true,
5664 })
5665 {
5666 true
5667 } else {
5668 load_provider_completions
5669 && completion_settings.words != WordsCompletionMode::Disabled
5670 && (ignore_word_threshold || {
5671 let words_min_length = completion_settings.words_min_length;
5672 // check whether word has at least `words_min_length` characters
5673 let query_chars = query.iter().flat_map(|q| q.chars());
5674 query_chars.take(words_min_length).count() == words_min_length
5675 })
5676 };
5677
5678 let mut words = if load_word_completions {
5679 cx.background_spawn({
5680 let buffer_snapshot = buffer_snapshot.clone();
5681 async move {
5682 buffer_snapshot.words_in_range(WordsQuery {
5683 fuzzy_contents: None,
5684 range: word_search_range,
5685 skip_digits,
5686 })
5687 }
5688 })
5689 } else {
5690 Task::ready(BTreeMap::default())
5691 };
5692
5693 let snippets = if let Some(provider) = &provider
5694 && provider.show_snippets()
5695 && let Some(project) = self.project()
5696 {
5697 let char_classifier = buffer_snapshot
5698 .char_classifier_at(buffer_position)
5699 .scope_context(Some(CharScopeContext::Completion));
5700 project.update(cx, |project, cx| {
5701 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5702 })
5703 } else {
5704 Task::ready(Ok(CompletionResponse {
5705 completions: Vec::new(),
5706 display_options: Default::default(),
5707 is_incomplete: false,
5708 }))
5709 };
5710
5711 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5712
5713 let id = post_inc(&mut self.next_completion_id);
5714 let task = cx.spawn_in(window, async move |editor, cx| {
5715 let Ok(()) = editor.update(cx, |this, _| {
5716 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5717 }) else {
5718 return;
5719 };
5720
5721 // TODO: Ideally completions from different sources would be selectively re-queried, so
5722 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5723 let mut completions = Vec::new();
5724 let mut is_incomplete = false;
5725 let mut display_options: Option<CompletionDisplayOptions> = None;
5726 if let Some(provider_responses) = provider_responses.await.log_err()
5727 && !provider_responses.is_empty()
5728 {
5729 for response in provider_responses {
5730 completions.extend(response.completions);
5731 is_incomplete = is_incomplete || response.is_incomplete;
5732 match display_options.as_mut() {
5733 None => {
5734 display_options = Some(response.display_options);
5735 }
5736 Some(options) => options.merge(&response.display_options),
5737 }
5738 }
5739 if completion_settings.words == WordsCompletionMode::Fallback {
5740 words = Task::ready(BTreeMap::default());
5741 }
5742 }
5743 let display_options = display_options.unwrap_or_default();
5744
5745 let mut words = words.await;
5746 if let Some(word_to_exclude) = &word_to_exclude {
5747 words.remove(word_to_exclude);
5748 }
5749 for lsp_completion in &completions {
5750 words.remove(&lsp_completion.new_text);
5751 }
5752 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5753 replace_range: word_replace_range.clone(),
5754 new_text: word.clone(),
5755 label: CodeLabel::plain(word, None),
5756 match_start: None,
5757 snippet_deduplication_key: None,
5758 icon_path: None,
5759 documentation: None,
5760 source: CompletionSource::BufferWord {
5761 word_range,
5762 resolved: false,
5763 },
5764 insert_text_mode: Some(InsertTextMode::AS_IS),
5765 confirm: None,
5766 }));
5767
5768 completions.extend(
5769 snippets
5770 .await
5771 .into_iter()
5772 .flat_map(|response| response.completions),
5773 );
5774
5775 let menu = if completions.is_empty() {
5776 None
5777 } else {
5778 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5779 let languages = editor
5780 .workspace
5781 .as_ref()
5782 .and_then(|(workspace, _)| workspace.upgrade())
5783 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5784 let menu = CompletionsMenu::new(
5785 id,
5786 requested_source.unwrap_or(if load_provider_completions {
5787 CompletionsMenuSource::Normal
5788 } else {
5789 CompletionsMenuSource::SnippetsOnly
5790 }),
5791 sort_completions,
5792 show_completion_documentation,
5793 position,
5794 query.clone(),
5795 is_incomplete,
5796 buffer.clone(),
5797 completions.into(),
5798 display_options,
5799 snippet_sort_order,
5800 languages,
5801 language,
5802 cx,
5803 );
5804
5805 let query = if filter_completions { query } else { None };
5806 let matches_task = menu.do_async_filtering(
5807 query.unwrap_or_default(),
5808 buffer_position,
5809 &buffer,
5810 cx,
5811 );
5812 (menu, matches_task)
5813 }) else {
5814 return;
5815 };
5816
5817 let matches = matches_task.await;
5818
5819 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5820 // Newer menu already set, so exit.
5821 if let Some(CodeContextMenu::Completions(prev_menu)) =
5822 editor.context_menu.borrow().as_ref()
5823 && prev_menu.id > id
5824 {
5825 return;
5826 };
5827
5828 // Only valid to take prev_menu because either the new menu is immediately set
5829 // below, or the menu is hidden.
5830 if let Some(CodeContextMenu::Completions(prev_menu)) =
5831 editor.context_menu.borrow_mut().take()
5832 {
5833 let position_matches =
5834 if prev_menu.initial_position == menu.initial_position {
5835 true
5836 } else {
5837 let snapshot = editor.buffer.read(cx).read(cx);
5838 prev_menu.initial_position.to_offset(&snapshot)
5839 == menu.initial_position.to_offset(&snapshot)
5840 };
5841 if position_matches {
5842 // Preserve markdown cache before `set_filter_results` because it will
5843 // try to populate the documentation cache.
5844 menu.preserve_markdown_cache(prev_menu);
5845 }
5846 };
5847
5848 menu.set_filter_results(matches, provider, window, cx);
5849 }) else {
5850 return;
5851 };
5852
5853 menu.visible().then_some(menu)
5854 };
5855
5856 editor
5857 .update_in(cx, |editor, window, cx| {
5858 if editor.focus_handle.is_focused(window)
5859 && let Some(menu) = menu
5860 {
5861 *editor.context_menu.borrow_mut() =
5862 Some(CodeContextMenu::Completions(menu));
5863
5864 crate::hover_popover::hide_hover(editor, cx);
5865 if editor.show_edit_predictions_in_menu() {
5866 editor.update_visible_edit_prediction(window, cx);
5867 } else {
5868 editor.discard_edit_prediction(false, cx);
5869 }
5870
5871 cx.notify();
5872 return;
5873 }
5874
5875 if editor.completion_tasks.len() <= 1 {
5876 // If there are no more completion tasks and the last menu was empty, we should hide it.
5877 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5878 // If it was already hidden and we don't show edit predictions in the menu,
5879 // we should also show the edit prediction when available.
5880 if was_hidden && editor.show_edit_predictions_in_menu() {
5881 editor.update_visible_edit_prediction(window, cx);
5882 }
5883 }
5884 })
5885 .ok();
5886 });
5887
5888 self.completion_tasks.push((id, task));
5889 }
5890
5891 #[cfg(feature = "test-support")]
5892 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5893 let menu = self.context_menu.borrow();
5894 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5895 let completions = menu.completions.borrow();
5896 Some(completions.to_vec())
5897 } else {
5898 None
5899 }
5900 }
5901
5902 pub fn with_completions_menu_matching_id<R>(
5903 &self,
5904 id: CompletionId,
5905 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5906 ) -> R {
5907 let mut context_menu = self.context_menu.borrow_mut();
5908 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5909 return f(None);
5910 };
5911 if completions_menu.id != id {
5912 return f(None);
5913 }
5914 f(Some(completions_menu))
5915 }
5916
5917 pub fn confirm_completion(
5918 &mut self,
5919 action: &ConfirmCompletion,
5920 window: &mut Window,
5921 cx: &mut Context<Self>,
5922 ) -> Option<Task<Result<()>>> {
5923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5924 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5925 }
5926
5927 pub fn confirm_completion_insert(
5928 &mut self,
5929 _: &ConfirmCompletionInsert,
5930 window: &mut Window,
5931 cx: &mut Context<Self>,
5932 ) -> Option<Task<Result<()>>> {
5933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5934 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5935 }
5936
5937 pub fn confirm_completion_replace(
5938 &mut self,
5939 _: &ConfirmCompletionReplace,
5940 window: &mut Window,
5941 cx: &mut Context<Self>,
5942 ) -> Option<Task<Result<()>>> {
5943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5944 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5945 }
5946
5947 pub fn compose_completion(
5948 &mut self,
5949 action: &ComposeCompletion,
5950 window: &mut Window,
5951 cx: &mut Context<Self>,
5952 ) -> Option<Task<Result<()>>> {
5953 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5954 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5955 }
5956
5957 fn do_completion(
5958 &mut self,
5959 item_ix: Option<usize>,
5960 intent: CompletionIntent,
5961 window: &mut Window,
5962 cx: &mut Context<Editor>,
5963 ) -> Option<Task<Result<()>>> {
5964 use language::ToOffset as _;
5965
5966 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5967 else {
5968 return None;
5969 };
5970
5971 let candidate_id = {
5972 let entries = completions_menu.entries.borrow();
5973 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5974 if self.show_edit_predictions_in_menu() {
5975 self.discard_edit_prediction(true, cx);
5976 }
5977 mat.candidate_id
5978 };
5979
5980 let completion = completions_menu
5981 .completions
5982 .borrow()
5983 .get(candidate_id)?
5984 .clone();
5985 cx.stop_propagation();
5986
5987 let buffer_handle = completions_menu.buffer.clone();
5988
5989 let CompletionEdit {
5990 new_text,
5991 snippet,
5992 replace_range,
5993 } = process_completion_for_edit(
5994 &completion,
5995 intent,
5996 &buffer_handle,
5997 &completions_menu.initial_position.text_anchor,
5998 cx,
5999 );
6000
6001 let buffer = buffer_handle.read(cx);
6002 let snapshot = self.buffer.read(cx).snapshot(cx);
6003 let newest_anchor = self.selections.newest_anchor();
6004 let replace_range_multibuffer = {
6005 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6006 excerpt.map_range_from_buffer(replace_range.clone())
6007 };
6008 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6009 return None;
6010 }
6011
6012 let old_text = buffer
6013 .text_for_range(replace_range.clone())
6014 .collect::<String>();
6015 let lookbehind = newest_anchor
6016 .start
6017 .text_anchor
6018 .to_offset(buffer)
6019 .saturating_sub(replace_range.start.0);
6020 let lookahead = replace_range
6021 .end
6022 .0
6023 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6024 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6025 let suffix = &old_text[lookbehind.min(old_text.len())..];
6026
6027 let selections = self
6028 .selections
6029 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6030 let mut ranges = Vec::new();
6031 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6032
6033 for selection in &selections {
6034 let range = if selection.id == newest_anchor.id {
6035 replace_range_multibuffer.clone()
6036 } else {
6037 let mut range = selection.range();
6038
6039 // if prefix is present, don't duplicate it
6040 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6041 range.start = range.start.saturating_sub_usize(lookbehind);
6042
6043 // if suffix is also present, mimic the newest cursor and replace it
6044 if selection.id != newest_anchor.id
6045 && snapshot.contains_str_at(range.end, suffix)
6046 {
6047 range.end += lookahead;
6048 }
6049 }
6050 range
6051 };
6052
6053 ranges.push(range.clone());
6054
6055 if !self.linked_edit_ranges.is_empty() {
6056 let start_anchor = snapshot.anchor_before(range.start);
6057 let end_anchor = snapshot.anchor_after(range.end);
6058 if let Some(ranges) = self
6059 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6060 {
6061 for (buffer, edits) in ranges {
6062 linked_edits
6063 .entry(buffer.clone())
6064 .or_default()
6065 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6066 }
6067 }
6068 }
6069 }
6070
6071 let common_prefix_len = old_text
6072 .chars()
6073 .zip(new_text.chars())
6074 .take_while(|(a, b)| a == b)
6075 .map(|(a, _)| a.len_utf8())
6076 .sum::<usize>();
6077
6078 cx.emit(EditorEvent::InputHandled {
6079 utf16_range_to_replace: None,
6080 text: new_text[common_prefix_len..].into(),
6081 });
6082
6083 self.transact(window, cx, |editor, window, cx| {
6084 if let Some(mut snippet) = snippet {
6085 snippet.text = new_text.to_string();
6086 editor
6087 .insert_snippet(&ranges, snippet, window, cx)
6088 .log_err();
6089 } else {
6090 editor.buffer.update(cx, |multi_buffer, cx| {
6091 let auto_indent = match completion.insert_text_mode {
6092 Some(InsertTextMode::AS_IS) => None,
6093 _ => editor.autoindent_mode.clone(),
6094 };
6095 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6096 multi_buffer.edit(edits, auto_indent, cx);
6097 });
6098 }
6099 for (buffer, edits) in linked_edits {
6100 buffer.update(cx, |buffer, cx| {
6101 let snapshot = buffer.snapshot();
6102 let edits = edits
6103 .into_iter()
6104 .map(|(range, text)| {
6105 use text::ToPoint as TP;
6106 let end_point = TP::to_point(&range.end, &snapshot);
6107 let start_point = TP::to_point(&range.start, &snapshot);
6108 (start_point..end_point, text)
6109 })
6110 .sorted_by_key(|(range, _)| range.start);
6111 buffer.edit(edits, None, cx);
6112 })
6113 }
6114
6115 editor.refresh_edit_prediction(true, false, window, cx);
6116 });
6117 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6118
6119 let show_new_completions_on_confirm = completion
6120 .confirm
6121 .as_ref()
6122 .is_some_and(|confirm| confirm(intent, window, cx));
6123 if show_new_completions_on_confirm {
6124 self.open_or_update_completions_menu(None, None, false, window, cx);
6125 }
6126
6127 let provider = self.completion_provider.as_ref()?;
6128 drop(completion);
6129 let apply_edits = provider.apply_additional_edits_for_completion(
6130 buffer_handle,
6131 completions_menu.completions.clone(),
6132 candidate_id,
6133 true,
6134 cx,
6135 );
6136
6137 let editor_settings = EditorSettings::get_global(cx);
6138 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6139 // After the code completion is finished, users often want to know what signatures are needed.
6140 // so we should automatically call signature_help
6141 self.show_signature_help(&ShowSignatureHelp, window, cx);
6142 }
6143
6144 Some(cx.foreground_executor().spawn(async move {
6145 apply_edits.await?;
6146 Ok(())
6147 }))
6148 }
6149
6150 pub fn toggle_code_actions(
6151 &mut self,
6152 action: &ToggleCodeActions,
6153 window: &mut Window,
6154 cx: &mut Context<Self>,
6155 ) {
6156 let quick_launch = action.quick_launch;
6157 let mut context_menu = self.context_menu.borrow_mut();
6158 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6159 if code_actions.deployed_from == action.deployed_from {
6160 // Toggle if we're selecting the same one
6161 *context_menu = None;
6162 cx.notify();
6163 return;
6164 } else {
6165 // Otherwise, clear it and start a new one
6166 *context_menu = None;
6167 cx.notify();
6168 }
6169 }
6170 drop(context_menu);
6171 let snapshot = self.snapshot(window, cx);
6172 let deployed_from = action.deployed_from.clone();
6173 let action = action.clone();
6174 self.completion_tasks.clear();
6175 self.discard_edit_prediction(false, cx);
6176
6177 let multibuffer_point = match &action.deployed_from {
6178 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6179 DisplayPoint::new(*row, 0).to_point(&snapshot)
6180 }
6181 _ => self
6182 .selections
6183 .newest::<Point>(&snapshot.display_snapshot)
6184 .head(),
6185 };
6186 let Some((buffer, buffer_row)) = snapshot
6187 .buffer_snapshot()
6188 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6189 .and_then(|(buffer_snapshot, range)| {
6190 self.buffer()
6191 .read(cx)
6192 .buffer(buffer_snapshot.remote_id())
6193 .map(|buffer| (buffer, range.start.row))
6194 })
6195 else {
6196 return;
6197 };
6198 let buffer_id = buffer.read(cx).remote_id();
6199 let tasks = self
6200 .tasks
6201 .get(&(buffer_id, buffer_row))
6202 .map(|t| Arc::new(t.to_owned()));
6203
6204 if !self.focus_handle.is_focused(window) {
6205 return;
6206 }
6207 let project = self.project.clone();
6208
6209 let code_actions_task = match deployed_from {
6210 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6211 _ => self.code_actions(buffer_row, window, cx),
6212 };
6213
6214 let runnable_task = match deployed_from {
6215 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6216 _ => {
6217 let mut task_context_task = Task::ready(None);
6218 if let Some(tasks) = &tasks
6219 && let Some(project) = project
6220 {
6221 task_context_task =
6222 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6223 }
6224
6225 cx.spawn_in(window, {
6226 let buffer = buffer.clone();
6227 async move |editor, cx| {
6228 let task_context = task_context_task.await;
6229
6230 let resolved_tasks =
6231 tasks
6232 .zip(task_context.clone())
6233 .map(|(tasks, task_context)| ResolvedTasks {
6234 templates: tasks.resolve(&task_context).collect(),
6235 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6236 multibuffer_point.row,
6237 tasks.column,
6238 )),
6239 });
6240 let debug_scenarios = editor
6241 .update(cx, |editor, cx| {
6242 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6243 })?
6244 .await;
6245 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6246 }
6247 })
6248 }
6249 };
6250
6251 cx.spawn_in(window, async move |editor, cx| {
6252 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6253 let code_actions = code_actions_task.await;
6254 let spawn_straight_away = quick_launch
6255 && resolved_tasks
6256 .as_ref()
6257 .is_some_and(|tasks| tasks.templates.len() == 1)
6258 && code_actions
6259 .as_ref()
6260 .is_none_or(|actions| actions.is_empty())
6261 && debug_scenarios.is_empty();
6262
6263 editor.update_in(cx, |editor, window, cx| {
6264 crate::hover_popover::hide_hover(editor, cx);
6265 let actions = CodeActionContents::new(
6266 resolved_tasks,
6267 code_actions,
6268 debug_scenarios,
6269 task_context.unwrap_or_default(),
6270 );
6271
6272 // Don't show the menu if there are no actions available
6273 if actions.is_empty() {
6274 cx.notify();
6275 return Task::ready(Ok(()));
6276 }
6277
6278 *editor.context_menu.borrow_mut() =
6279 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6280 buffer,
6281 actions,
6282 selected_item: Default::default(),
6283 scroll_handle: UniformListScrollHandle::default(),
6284 deployed_from,
6285 }));
6286 cx.notify();
6287 if spawn_straight_away
6288 && let Some(task) = editor.confirm_code_action(
6289 &ConfirmCodeAction { item_ix: Some(0) },
6290 window,
6291 cx,
6292 )
6293 {
6294 return task;
6295 }
6296
6297 Task::ready(Ok(()))
6298 })
6299 })
6300 .detach_and_log_err(cx);
6301 }
6302
6303 fn debug_scenarios(
6304 &mut self,
6305 resolved_tasks: &Option<ResolvedTasks>,
6306 buffer: &Entity<Buffer>,
6307 cx: &mut App,
6308 ) -> Task<Vec<task::DebugScenario>> {
6309 maybe!({
6310 let project = self.project()?;
6311 let dap_store = project.read(cx).dap_store();
6312 let mut scenarios = vec![];
6313 let resolved_tasks = resolved_tasks.as_ref()?;
6314 let buffer = buffer.read(cx);
6315 let language = buffer.language()?;
6316 let file = buffer.file();
6317 let debug_adapter = language_settings(language.name().into(), file, cx)
6318 .debuggers
6319 .first()
6320 .map(SharedString::from)
6321 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6322
6323 dap_store.update(cx, |dap_store, cx| {
6324 for (_, task) in &resolved_tasks.templates {
6325 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6326 task.original_task().clone(),
6327 debug_adapter.clone().into(),
6328 task.display_label().to_owned().into(),
6329 cx,
6330 );
6331 scenarios.push(maybe_scenario);
6332 }
6333 });
6334 Some(cx.background_spawn(async move {
6335 futures::future::join_all(scenarios)
6336 .await
6337 .into_iter()
6338 .flatten()
6339 .collect::<Vec<_>>()
6340 }))
6341 })
6342 .unwrap_or_else(|| Task::ready(vec![]))
6343 }
6344
6345 fn code_actions(
6346 &mut self,
6347 buffer_row: u32,
6348 window: &mut Window,
6349 cx: &mut Context<Self>,
6350 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6351 let mut task = self.code_actions_task.take();
6352 cx.spawn_in(window, async move |editor, cx| {
6353 while let Some(prev_task) = task {
6354 prev_task.await.log_err();
6355 task = editor
6356 .update(cx, |this, _| this.code_actions_task.take())
6357 .ok()?;
6358 }
6359
6360 editor
6361 .update(cx, |editor, cx| {
6362 editor
6363 .available_code_actions
6364 .clone()
6365 .and_then(|(location, code_actions)| {
6366 let snapshot = location.buffer.read(cx).snapshot();
6367 let point_range = location.range.to_point(&snapshot);
6368 let point_range = point_range.start.row..=point_range.end.row;
6369 if point_range.contains(&buffer_row) {
6370 Some(code_actions)
6371 } else {
6372 None
6373 }
6374 })
6375 })
6376 .ok()
6377 .flatten()
6378 })
6379 }
6380
6381 pub fn confirm_code_action(
6382 &mut self,
6383 action: &ConfirmCodeAction,
6384 window: &mut Window,
6385 cx: &mut Context<Self>,
6386 ) -> Option<Task<Result<()>>> {
6387 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6388
6389 let actions_menu =
6390 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6391 menu
6392 } else {
6393 return None;
6394 };
6395
6396 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6397 let action = actions_menu.actions.get(action_ix)?;
6398 let title = action.label();
6399 let buffer = actions_menu.buffer;
6400 let workspace = self.workspace()?;
6401
6402 match action {
6403 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6404 workspace.update(cx, |workspace, cx| {
6405 workspace.schedule_resolved_task(
6406 task_source_kind,
6407 resolved_task,
6408 false,
6409 window,
6410 cx,
6411 );
6412
6413 Some(Task::ready(Ok(())))
6414 })
6415 }
6416 CodeActionsItem::CodeAction {
6417 excerpt_id,
6418 action,
6419 provider,
6420 } => {
6421 let apply_code_action =
6422 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6423 let workspace = workspace.downgrade();
6424 Some(cx.spawn_in(window, async move |editor, cx| {
6425 let project_transaction = apply_code_action.await?;
6426 Self::open_project_transaction(
6427 &editor,
6428 workspace,
6429 project_transaction,
6430 title,
6431 cx,
6432 )
6433 .await
6434 }))
6435 }
6436 CodeActionsItem::DebugScenario(scenario) => {
6437 let context = actions_menu.actions.context;
6438
6439 workspace.update(cx, |workspace, cx| {
6440 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6441 workspace.start_debug_session(
6442 scenario,
6443 context,
6444 Some(buffer),
6445 None,
6446 window,
6447 cx,
6448 );
6449 });
6450 Some(Task::ready(Ok(())))
6451 }
6452 }
6453 }
6454
6455 pub async fn open_project_transaction(
6456 editor: &WeakEntity<Editor>,
6457 workspace: WeakEntity<Workspace>,
6458 transaction: ProjectTransaction,
6459 title: String,
6460 cx: &mut AsyncWindowContext,
6461 ) -> Result<()> {
6462 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6463 cx.update(|_, cx| {
6464 entries.sort_unstable_by_key(|(buffer, _)| {
6465 buffer.read(cx).file().map(|f| f.path().clone())
6466 });
6467 })?;
6468 if entries.is_empty() {
6469 return Ok(());
6470 }
6471
6472 // If the project transaction's edits are all contained within this editor, then
6473 // avoid opening a new editor to display them.
6474
6475 if let [(buffer, transaction)] = &*entries {
6476 let excerpt = editor.update(cx, |editor, cx| {
6477 editor
6478 .buffer()
6479 .read(cx)
6480 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6481 })?;
6482 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6483 && excerpted_buffer == *buffer
6484 {
6485 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6486 let excerpt_range = excerpt_range.to_offset(buffer);
6487 buffer
6488 .edited_ranges_for_transaction::<usize>(transaction)
6489 .all(|range| {
6490 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6491 })
6492 })?;
6493
6494 if all_edits_within_excerpt {
6495 return Ok(());
6496 }
6497 }
6498 }
6499
6500 let mut ranges_to_highlight = Vec::new();
6501 let excerpt_buffer = cx.new(|cx| {
6502 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6503 for (buffer_handle, transaction) in &entries {
6504 let edited_ranges = buffer_handle
6505 .read(cx)
6506 .edited_ranges_for_transaction::<Point>(transaction)
6507 .collect::<Vec<_>>();
6508 let (ranges, _) = multibuffer.set_excerpts_for_path(
6509 PathKey::for_buffer(buffer_handle, cx),
6510 buffer_handle.clone(),
6511 edited_ranges,
6512 multibuffer_context_lines(cx),
6513 cx,
6514 );
6515
6516 ranges_to_highlight.extend(ranges);
6517 }
6518 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6519 multibuffer
6520 })?;
6521
6522 workspace.update_in(cx, |workspace, window, cx| {
6523 let project = workspace.project().clone();
6524 let editor =
6525 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6526 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6527 editor.update(cx, |editor, cx| {
6528 editor.highlight_background::<Self>(
6529 &ranges_to_highlight,
6530 |theme| theme.colors().editor_highlighted_line_background,
6531 cx,
6532 );
6533 });
6534 })?;
6535
6536 Ok(())
6537 }
6538
6539 pub fn clear_code_action_providers(&mut self) {
6540 self.code_action_providers.clear();
6541 self.available_code_actions.take();
6542 }
6543
6544 pub fn add_code_action_provider(
6545 &mut self,
6546 provider: Rc<dyn CodeActionProvider>,
6547 window: &mut Window,
6548 cx: &mut Context<Self>,
6549 ) {
6550 if self
6551 .code_action_providers
6552 .iter()
6553 .any(|existing_provider| existing_provider.id() == provider.id())
6554 {
6555 return;
6556 }
6557
6558 self.code_action_providers.push(provider);
6559 self.refresh_code_actions(window, cx);
6560 }
6561
6562 pub fn remove_code_action_provider(
6563 &mut self,
6564 id: Arc<str>,
6565 window: &mut Window,
6566 cx: &mut Context<Self>,
6567 ) {
6568 self.code_action_providers
6569 .retain(|provider| provider.id() != id);
6570 self.refresh_code_actions(window, cx);
6571 }
6572
6573 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6574 !self.code_action_providers.is_empty()
6575 && EditorSettings::get_global(cx).toolbar.code_actions
6576 }
6577
6578 pub fn has_available_code_actions(&self) -> bool {
6579 self.available_code_actions
6580 .as_ref()
6581 .is_some_and(|(_, actions)| !actions.is_empty())
6582 }
6583
6584 fn render_inline_code_actions(
6585 &self,
6586 icon_size: ui::IconSize,
6587 display_row: DisplayRow,
6588 is_active: bool,
6589 cx: &mut Context<Self>,
6590 ) -> AnyElement {
6591 let show_tooltip = !self.context_menu_visible();
6592 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6593 .icon_size(icon_size)
6594 .shape(ui::IconButtonShape::Square)
6595 .icon_color(ui::Color::Hidden)
6596 .toggle_state(is_active)
6597 .when(show_tooltip, |this| {
6598 this.tooltip({
6599 let focus_handle = self.focus_handle.clone();
6600 move |_window, cx| {
6601 Tooltip::for_action_in(
6602 "Toggle Code Actions",
6603 &ToggleCodeActions {
6604 deployed_from: None,
6605 quick_launch: false,
6606 },
6607 &focus_handle,
6608 cx,
6609 )
6610 }
6611 })
6612 })
6613 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6614 window.focus(&editor.focus_handle(cx));
6615 editor.toggle_code_actions(
6616 &crate::actions::ToggleCodeActions {
6617 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6618 display_row,
6619 )),
6620 quick_launch: false,
6621 },
6622 window,
6623 cx,
6624 );
6625 }))
6626 .into_any_element()
6627 }
6628
6629 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6630 &self.context_menu
6631 }
6632
6633 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6634 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6635 cx.background_executor()
6636 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6637 .await;
6638
6639 let (start_buffer, start, _, end, newest_selection) = this
6640 .update(cx, |this, cx| {
6641 let newest_selection = this.selections.newest_anchor().clone();
6642 if newest_selection.head().diff_base_anchor.is_some() {
6643 return None;
6644 }
6645 let display_snapshot = this.display_snapshot(cx);
6646 let newest_selection_adjusted =
6647 this.selections.newest_adjusted(&display_snapshot);
6648 let buffer = this.buffer.read(cx);
6649
6650 let (start_buffer, start) =
6651 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6652 let (end_buffer, end) =
6653 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6654
6655 Some((start_buffer, start, end_buffer, end, newest_selection))
6656 })?
6657 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6658 .context(
6659 "Expected selection to lie in a single buffer when refreshing code actions",
6660 )?;
6661 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6662 let providers = this.code_action_providers.clone();
6663 let tasks = this
6664 .code_action_providers
6665 .iter()
6666 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6667 .collect::<Vec<_>>();
6668 (providers, tasks)
6669 })?;
6670
6671 let mut actions = Vec::new();
6672 for (provider, provider_actions) in
6673 providers.into_iter().zip(future::join_all(tasks).await)
6674 {
6675 if let Some(provider_actions) = provider_actions.log_err() {
6676 actions.extend(provider_actions.into_iter().map(|action| {
6677 AvailableCodeAction {
6678 excerpt_id: newest_selection.start.excerpt_id,
6679 action,
6680 provider: provider.clone(),
6681 }
6682 }));
6683 }
6684 }
6685
6686 this.update(cx, |this, cx| {
6687 this.available_code_actions = if actions.is_empty() {
6688 None
6689 } else {
6690 Some((
6691 Location {
6692 buffer: start_buffer,
6693 range: start..end,
6694 },
6695 actions.into(),
6696 ))
6697 };
6698 cx.notify();
6699 })
6700 }));
6701 }
6702
6703 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6704 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6705 self.show_git_blame_inline = false;
6706
6707 self.show_git_blame_inline_delay_task =
6708 Some(cx.spawn_in(window, async move |this, cx| {
6709 cx.background_executor().timer(delay).await;
6710
6711 this.update(cx, |this, cx| {
6712 this.show_git_blame_inline = true;
6713 cx.notify();
6714 })
6715 .log_err();
6716 }));
6717 }
6718 }
6719
6720 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6721 let snapshot = self.snapshot(window, cx);
6722 let cursor = self
6723 .selections
6724 .newest::<Point>(&snapshot.display_snapshot)
6725 .head();
6726 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6727 else {
6728 return;
6729 };
6730
6731 let Some(blame) = self.blame.as_ref() else {
6732 return;
6733 };
6734
6735 let row_info = RowInfo {
6736 buffer_id: Some(buffer.remote_id()),
6737 buffer_row: Some(point.row),
6738 ..Default::default()
6739 };
6740 let Some((buffer, blame_entry)) = blame
6741 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6742 .flatten()
6743 else {
6744 return;
6745 };
6746
6747 let anchor = self.selections.newest_anchor().head();
6748 let position = self.to_pixel_point(anchor, &snapshot, window);
6749 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6750 self.show_blame_popover(
6751 buffer,
6752 &blame_entry,
6753 position + last_bounds.origin,
6754 true,
6755 cx,
6756 );
6757 };
6758 }
6759
6760 fn show_blame_popover(
6761 &mut self,
6762 buffer: BufferId,
6763 blame_entry: &BlameEntry,
6764 position: gpui::Point<Pixels>,
6765 ignore_timeout: bool,
6766 cx: &mut Context<Self>,
6767 ) {
6768 if let Some(state) = &mut self.inline_blame_popover {
6769 state.hide_task.take();
6770 } else {
6771 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6772 let blame_entry = blame_entry.clone();
6773 let show_task = cx.spawn(async move |editor, cx| {
6774 if !ignore_timeout {
6775 cx.background_executor()
6776 .timer(std::time::Duration::from_millis(blame_popover_delay))
6777 .await;
6778 }
6779 editor
6780 .update(cx, |editor, cx| {
6781 editor.inline_blame_popover_show_task.take();
6782 let Some(blame) = editor.blame.as_ref() else {
6783 return;
6784 };
6785 let blame = blame.read(cx);
6786 let details = blame.details_for_entry(buffer, &blame_entry);
6787 let markdown = cx.new(|cx| {
6788 Markdown::new(
6789 details
6790 .as_ref()
6791 .map(|message| message.message.clone())
6792 .unwrap_or_default(),
6793 None,
6794 None,
6795 cx,
6796 )
6797 });
6798 editor.inline_blame_popover = Some(InlineBlamePopover {
6799 position,
6800 hide_task: None,
6801 popover_bounds: None,
6802 popover_state: InlineBlamePopoverState {
6803 scroll_handle: ScrollHandle::new(),
6804 commit_message: details,
6805 markdown,
6806 },
6807 keyboard_grace: ignore_timeout,
6808 });
6809 cx.notify();
6810 })
6811 .ok();
6812 });
6813 self.inline_blame_popover_show_task = Some(show_task);
6814 }
6815 }
6816
6817 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6818 self.inline_blame_popover_show_task.take();
6819 if let Some(state) = &mut self.inline_blame_popover {
6820 let hide_task = cx.spawn(async move |editor, cx| {
6821 if !ignore_timeout {
6822 cx.background_executor()
6823 .timer(std::time::Duration::from_millis(100))
6824 .await;
6825 }
6826 editor
6827 .update(cx, |editor, cx| {
6828 editor.inline_blame_popover.take();
6829 cx.notify();
6830 })
6831 .ok();
6832 });
6833 state.hide_task = Some(hide_task);
6834 true
6835 } else {
6836 false
6837 }
6838 }
6839
6840 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6841 if self.pending_rename.is_some() {
6842 return None;
6843 }
6844
6845 let provider = self.semantics_provider.clone()?;
6846 let buffer = self.buffer.read(cx);
6847 let newest_selection = self.selections.newest_anchor().clone();
6848 let cursor_position = newest_selection.head();
6849 let (cursor_buffer, cursor_buffer_position) =
6850 buffer.text_anchor_for_position(cursor_position, cx)?;
6851 let (tail_buffer, tail_buffer_position) =
6852 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6853 if cursor_buffer != tail_buffer {
6854 return None;
6855 }
6856
6857 let snapshot = cursor_buffer.read(cx).snapshot();
6858 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6859 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6860 if start_word_range != end_word_range {
6861 self.document_highlights_task.take();
6862 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6863 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6864 return None;
6865 }
6866
6867 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6868 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6869 cx.background_executor()
6870 .timer(Duration::from_millis(debounce))
6871 .await;
6872
6873 let highlights = if let Some(highlights) = cx
6874 .update(|cx| {
6875 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6876 })
6877 .ok()
6878 .flatten()
6879 {
6880 highlights.await.log_err()
6881 } else {
6882 None
6883 };
6884
6885 if let Some(highlights) = highlights {
6886 this.update(cx, |this, cx| {
6887 if this.pending_rename.is_some() {
6888 return;
6889 }
6890
6891 let buffer = this.buffer.read(cx);
6892 if buffer
6893 .text_anchor_for_position(cursor_position, cx)
6894 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6895 {
6896 return;
6897 }
6898
6899 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6900 let mut write_ranges = Vec::new();
6901 let mut read_ranges = Vec::new();
6902 for highlight in highlights {
6903 let buffer_id = cursor_buffer.read(cx).remote_id();
6904 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6905 {
6906 let start = highlight
6907 .range
6908 .start
6909 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6910 let end = highlight
6911 .range
6912 .end
6913 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6914 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6915 continue;
6916 }
6917
6918 let range =
6919 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6920 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6921 write_ranges.push(range);
6922 } else {
6923 read_ranges.push(range);
6924 }
6925 }
6926 }
6927
6928 this.highlight_background::<DocumentHighlightRead>(
6929 &read_ranges,
6930 |theme| theme.colors().editor_document_highlight_read_background,
6931 cx,
6932 );
6933 this.highlight_background::<DocumentHighlightWrite>(
6934 &write_ranges,
6935 |theme| theme.colors().editor_document_highlight_write_background,
6936 cx,
6937 );
6938 cx.notify();
6939 })
6940 .log_err();
6941 }
6942 }));
6943 None
6944 }
6945
6946 fn prepare_highlight_query_from_selection(
6947 &mut self,
6948 window: &Window,
6949 cx: &mut Context<Editor>,
6950 ) -> Option<(String, Range<Anchor>)> {
6951 if matches!(self.mode, EditorMode::SingleLine) {
6952 return None;
6953 }
6954 if !EditorSettings::get_global(cx).selection_highlight {
6955 return None;
6956 }
6957 if self.selections.count() != 1 || self.selections.line_mode() {
6958 return None;
6959 }
6960 let snapshot = self.snapshot(window, cx);
6961 let selection = self.selections.newest::<Point>(&snapshot);
6962 // If the selection spans multiple rows OR it is empty
6963 if selection.start.row != selection.end.row
6964 || selection.start.column == selection.end.column
6965 {
6966 return None;
6967 }
6968 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6969 let query = snapshot
6970 .buffer_snapshot()
6971 .text_for_range(selection_anchor_range.clone())
6972 .collect::<String>();
6973 if query.trim().is_empty() {
6974 return None;
6975 }
6976 Some((query, selection_anchor_range))
6977 }
6978
6979 fn update_selection_occurrence_highlights(
6980 &mut self,
6981 query_text: String,
6982 query_range: Range<Anchor>,
6983 multi_buffer_range_to_query: Range<Point>,
6984 use_debounce: bool,
6985 window: &mut Window,
6986 cx: &mut Context<Editor>,
6987 ) -> Task<()> {
6988 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6989 cx.spawn_in(window, async move |editor, cx| {
6990 if use_debounce {
6991 cx.background_executor()
6992 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6993 .await;
6994 }
6995 let match_task = cx.background_spawn(async move {
6996 let buffer_ranges = multi_buffer_snapshot
6997 .range_to_buffer_ranges(multi_buffer_range_to_query)
6998 .into_iter()
6999 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7000 let mut match_ranges = Vec::new();
7001 let Ok(regex) = project::search::SearchQuery::text(
7002 query_text.clone(),
7003 false,
7004 false,
7005 false,
7006 Default::default(),
7007 Default::default(),
7008 false,
7009 None,
7010 ) else {
7011 return Vec::default();
7012 };
7013 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7014 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7015 match_ranges.extend(
7016 regex
7017 .search(
7018 buffer_snapshot,
7019 Some(search_range.start.0..search_range.end.0),
7020 )
7021 .await
7022 .into_iter()
7023 .filter_map(|match_range| {
7024 let match_start = buffer_snapshot
7025 .anchor_after(search_range.start + match_range.start);
7026 let match_end = buffer_snapshot
7027 .anchor_before(search_range.start + match_range.end);
7028 let match_anchor_range = Anchor::range_in_buffer(
7029 excerpt_id,
7030 buffer_snapshot.remote_id(),
7031 match_start..match_end,
7032 );
7033 (match_anchor_range != query_range).then_some(match_anchor_range)
7034 }),
7035 );
7036 }
7037 match_ranges
7038 });
7039 let match_ranges = match_task.await;
7040 editor
7041 .update_in(cx, |editor, _, cx| {
7042 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7043 if !match_ranges.is_empty() {
7044 editor.highlight_background::<SelectedTextHighlight>(
7045 &match_ranges,
7046 |theme| theme.colors().editor_document_highlight_bracket_background,
7047 cx,
7048 )
7049 }
7050 })
7051 .log_err();
7052 })
7053 }
7054
7055 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7056 struct NewlineFold;
7057 let type_id = std::any::TypeId::of::<NewlineFold>();
7058 if !self.mode.is_single_line() {
7059 return;
7060 }
7061 let snapshot = self.snapshot(window, cx);
7062 if snapshot.buffer_snapshot().max_point().row == 0 {
7063 return;
7064 }
7065 let task = cx.background_spawn(async move {
7066 let new_newlines = snapshot
7067 .buffer_chars_at(MultiBufferOffset(0))
7068 .filter_map(|(c, i)| {
7069 if c == '\n' {
7070 Some(
7071 snapshot.buffer_snapshot().anchor_after(i)
7072 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7073 )
7074 } else {
7075 None
7076 }
7077 })
7078 .collect::<Vec<_>>();
7079 let existing_newlines = snapshot
7080 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7081 .filter_map(|fold| {
7082 if fold.placeholder.type_tag == Some(type_id) {
7083 Some(fold.range.start..fold.range.end)
7084 } else {
7085 None
7086 }
7087 })
7088 .collect::<Vec<_>>();
7089
7090 (new_newlines, existing_newlines)
7091 });
7092 self.folding_newlines = cx.spawn(async move |this, cx| {
7093 let (new_newlines, existing_newlines) = task.await;
7094 if new_newlines == existing_newlines {
7095 return;
7096 }
7097 let placeholder = FoldPlaceholder {
7098 render: Arc::new(move |_, _, cx| {
7099 div()
7100 .bg(cx.theme().status().hint_background)
7101 .border_b_1()
7102 .size_full()
7103 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7104 .border_color(cx.theme().status().hint)
7105 .child("\\n")
7106 .into_any()
7107 }),
7108 constrain_width: false,
7109 merge_adjacent: false,
7110 type_tag: Some(type_id),
7111 };
7112 let creases = new_newlines
7113 .into_iter()
7114 .map(|range| Crease::simple(range, placeholder.clone()))
7115 .collect();
7116 this.update(cx, |this, cx| {
7117 this.display_map.update(cx, |display_map, cx| {
7118 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7119 display_map.fold(creases, cx);
7120 });
7121 })
7122 .ok();
7123 });
7124 }
7125
7126 fn refresh_selected_text_highlights(
7127 &mut self,
7128 on_buffer_edit: bool,
7129 window: &mut Window,
7130 cx: &mut Context<Editor>,
7131 ) {
7132 let Some((query_text, query_range)) =
7133 self.prepare_highlight_query_from_selection(window, cx)
7134 else {
7135 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7136 self.quick_selection_highlight_task.take();
7137 self.debounced_selection_highlight_task.take();
7138 return;
7139 };
7140 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7141 if on_buffer_edit
7142 || self
7143 .quick_selection_highlight_task
7144 .as_ref()
7145 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7146 {
7147 let multi_buffer_visible_start = self
7148 .scroll_manager
7149 .anchor()
7150 .anchor
7151 .to_point(&multi_buffer_snapshot);
7152 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7153 multi_buffer_visible_start
7154 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7155 Bias::Left,
7156 );
7157 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7158 self.quick_selection_highlight_task = Some((
7159 query_range.clone(),
7160 self.update_selection_occurrence_highlights(
7161 query_text.clone(),
7162 query_range.clone(),
7163 multi_buffer_visible_range,
7164 false,
7165 window,
7166 cx,
7167 ),
7168 ));
7169 }
7170 if on_buffer_edit
7171 || self
7172 .debounced_selection_highlight_task
7173 .as_ref()
7174 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7175 {
7176 let multi_buffer_start = multi_buffer_snapshot
7177 .anchor_before(MultiBufferOffset(0))
7178 .to_point(&multi_buffer_snapshot);
7179 let multi_buffer_end = multi_buffer_snapshot
7180 .anchor_after(multi_buffer_snapshot.len())
7181 .to_point(&multi_buffer_snapshot);
7182 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7183 self.debounced_selection_highlight_task = Some((
7184 query_range.clone(),
7185 self.update_selection_occurrence_highlights(
7186 query_text,
7187 query_range,
7188 multi_buffer_full_range,
7189 true,
7190 window,
7191 cx,
7192 ),
7193 ));
7194 }
7195 }
7196
7197 pub fn refresh_edit_prediction(
7198 &mut self,
7199 debounce: bool,
7200 user_requested: bool,
7201 window: &mut Window,
7202 cx: &mut Context<Self>,
7203 ) -> Option<()> {
7204 if DisableAiSettings::get_global(cx).disable_ai {
7205 return None;
7206 }
7207
7208 let provider = self.edit_prediction_provider()?;
7209 let cursor = self.selections.newest_anchor().head();
7210 let (buffer, cursor_buffer_position) =
7211 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7212
7213 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7214 self.discard_edit_prediction(false, cx);
7215 return None;
7216 }
7217
7218 self.update_visible_edit_prediction(window, cx);
7219
7220 if !user_requested
7221 && (!self.should_show_edit_predictions()
7222 || !self.is_focused(window)
7223 || buffer.read(cx).is_empty())
7224 {
7225 self.discard_edit_prediction(false, cx);
7226 return None;
7227 }
7228
7229 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7230 Some(())
7231 }
7232
7233 fn show_edit_predictions_in_menu(&self) -> bool {
7234 match self.edit_prediction_settings {
7235 EditPredictionSettings::Disabled => false,
7236 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7237 }
7238 }
7239
7240 pub fn edit_predictions_enabled(&self) -> bool {
7241 match self.edit_prediction_settings {
7242 EditPredictionSettings::Disabled => false,
7243 EditPredictionSettings::Enabled { .. } => true,
7244 }
7245 }
7246
7247 fn edit_prediction_requires_modifier(&self) -> bool {
7248 match self.edit_prediction_settings {
7249 EditPredictionSettings::Disabled => false,
7250 EditPredictionSettings::Enabled {
7251 preview_requires_modifier,
7252 ..
7253 } => preview_requires_modifier,
7254 }
7255 }
7256
7257 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7258 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7259 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7260 self.discard_edit_prediction(false, cx);
7261 } else {
7262 let selection = self.selections.newest_anchor();
7263 let cursor = selection.head();
7264
7265 if let Some((buffer, cursor_buffer_position)) =
7266 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7267 {
7268 self.edit_prediction_settings =
7269 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7270 }
7271 }
7272 }
7273
7274 fn edit_prediction_settings_at_position(
7275 &self,
7276 buffer: &Entity<Buffer>,
7277 buffer_position: language::Anchor,
7278 cx: &App,
7279 ) -> EditPredictionSettings {
7280 if !self.mode.is_full()
7281 || !self.show_edit_predictions_override.unwrap_or(true)
7282 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7283 {
7284 return EditPredictionSettings::Disabled;
7285 }
7286
7287 let buffer = buffer.read(cx);
7288
7289 let file = buffer.file();
7290
7291 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7292 return EditPredictionSettings::Disabled;
7293 };
7294
7295 let by_provider = matches!(
7296 self.menu_edit_predictions_policy,
7297 MenuEditPredictionsPolicy::ByProvider
7298 );
7299
7300 let show_in_menu = by_provider
7301 && self
7302 .edit_prediction_provider
7303 .as_ref()
7304 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7305
7306 let preview_requires_modifier =
7307 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7308
7309 EditPredictionSettings::Enabled {
7310 show_in_menu,
7311 preview_requires_modifier,
7312 }
7313 }
7314
7315 fn should_show_edit_predictions(&self) -> bool {
7316 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7317 }
7318
7319 pub fn edit_prediction_preview_is_active(&self) -> bool {
7320 matches!(
7321 self.edit_prediction_preview,
7322 EditPredictionPreview::Active { .. }
7323 )
7324 }
7325
7326 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7327 let cursor = self.selections.newest_anchor().head();
7328 if let Some((buffer, cursor_position)) =
7329 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7330 {
7331 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7332 } else {
7333 false
7334 }
7335 }
7336
7337 pub fn supports_minimap(&self, cx: &App) -> bool {
7338 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7339 }
7340
7341 fn edit_predictions_enabled_in_buffer(
7342 &self,
7343 buffer: &Entity<Buffer>,
7344 buffer_position: language::Anchor,
7345 cx: &App,
7346 ) -> bool {
7347 maybe!({
7348 if self.read_only(cx) {
7349 return Some(false);
7350 }
7351 let provider = self.edit_prediction_provider()?;
7352 if !provider.is_enabled(buffer, buffer_position, cx) {
7353 return Some(false);
7354 }
7355 let buffer = buffer.read(cx);
7356 let Some(file) = buffer.file() else {
7357 return Some(true);
7358 };
7359 let settings = all_language_settings(Some(file), cx);
7360 Some(settings.edit_predictions_enabled_for_file(file, cx))
7361 })
7362 .unwrap_or(false)
7363 }
7364
7365 fn cycle_edit_prediction(
7366 &mut self,
7367 direction: Direction,
7368 window: &mut Window,
7369 cx: &mut Context<Self>,
7370 ) -> Option<()> {
7371 let provider = self.edit_prediction_provider()?;
7372 let cursor = self.selections.newest_anchor().head();
7373 let (buffer, cursor_buffer_position) =
7374 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7375 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7376 return None;
7377 }
7378
7379 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7380 self.update_visible_edit_prediction(window, cx);
7381
7382 Some(())
7383 }
7384
7385 pub fn show_edit_prediction(
7386 &mut self,
7387 _: &ShowEditPrediction,
7388 window: &mut Window,
7389 cx: &mut Context<Self>,
7390 ) {
7391 if !self.has_active_edit_prediction() {
7392 self.refresh_edit_prediction(false, true, window, cx);
7393 return;
7394 }
7395
7396 self.update_visible_edit_prediction(window, cx);
7397 }
7398
7399 pub fn display_cursor_names(
7400 &mut self,
7401 _: &DisplayCursorNames,
7402 window: &mut Window,
7403 cx: &mut Context<Self>,
7404 ) {
7405 self.show_cursor_names(window, cx);
7406 }
7407
7408 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7409 self.show_cursor_names = true;
7410 cx.notify();
7411 cx.spawn_in(window, async move |this, cx| {
7412 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7413 this.update(cx, |this, cx| {
7414 this.show_cursor_names = false;
7415 cx.notify()
7416 })
7417 .ok()
7418 })
7419 .detach();
7420 }
7421
7422 pub fn next_edit_prediction(
7423 &mut self,
7424 _: &NextEditPrediction,
7425 window: &mut Window,
7426 cx: &mut Context<Self>,
7427 ) {
7428 if self.has_active_edit_prediction() {
7429 self.cycle_edit_prediction(Direction::Next, window, cx);
7430 } else {
7431 let is_copilot_disabled = self
7432 .refresh_edit_prediction(false, true, window, cx)
7433 .is_none();
7434 if is_copilot_disabled {
7435 cx.propagate();
7436 }
7437 }
7438 }
7439
7440 pub fn previous_edit_prediction(
7441 &mut self,
7442 _: &PreviousEditPrediction,
7443 window: &mut Window,
7444 cx: &mut Context<Self>,
7445 ) {
7446 if self.has_active_edit_prediction() {
7447 self.cycle_edit_prediction(Direction::Prev, window, cx);
7448 } else {
7449 let is_copilot_disabled = self
7450 .refresh_edit_prediction(false, true, window, cx)
7451 .is_none();
7452 if is_copilot_disabled {
7453 cx.propagate();
7454 }
7455 }
7456 }
7457
7458 pub fn accept_edit_prediction(
7459 &mut self,
7460 _: &AcceptEditPrediction,
7461 window: &mut Window,
7462 cx: &mut Context<Self>,
7463 ) {
7464 if self.show_edit_predictions_in_menu() {
7465 self.hide_context_menu(window, cx);
7466 }
7467
7468 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7469 return;
7470 };
7471
7472 match &active_edit_prediction.completion {
7473 EditPrediction::MoveWithin { target, .. } => {
7474 let target = *target;
7475
7476 if let Some(position_map) = &self.last_position_map {
7477 if position_map
7478 .visible_row_range
7479 .contains(&target.to_display_point(&position_map.snapshot).row())
7480 || !self.edit_prediction_requires_modifier()
7481 {
7482 self.unfold_ranges(&[target..target], true, false, cx);
7483 // Note that this is also done in vim's handler of the Tab action.
7484 self.change_selections(
7485 SelectionEffects::scroll(Autoscroll::newest()),
7486 window,
7487 cx,
7488 |selections| {
7489 selections.select_anchor_ranges([target..target]);
7490 },
7491 );
7492 self.clear_row_highlights::<EditPredictionPreview>();
7493
7494 self.edit_prediction_preview
7495 .set_previous_scroll_position(None);
7496 } else {
7497 self.edit_prediction_preview
7498 .set_previous_scroll_position(Some(
7499 position_map.snapshot.scroll_anchor,
7500 ));
7501
7502 self.highlight_rows::<EditPredictionPreview>(
7503 target..target,
7504 cx.theme().colors().editor_highlighted_line_background,
7505 RowHighlightOptions {
7506 autoscroll: true,
7507 ..Default::default()
7508 },
7509 cx,
7510 );
7511 self.request_autoscroll(Autoscroll::fit(), cx);
7512 }
7513 }
7514 }
7515 EditPrediction::MoveOutside { snapshot, target } => {
7516 if let Some(workspace) = self.workspace() {
7517 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7518 .detach_and_log_err(cx);
7519 }
7520 }
7521 EditPrediction::Edit { edits, .. } => {
7522 self.report_edit_prediction_event(
7523 active_edit_prediction.completion_id.clone(),
7524 true,
7525 cx,
7526 );
7527
7528 if let Some(provider) = self.edit_prediction_provider() {
7529 provider.accept(cx);
7530 }
7531
7532 // Store the transaction ID and selections before applying the edit
7533 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7534
7535 let snapshot = self.buffer.read(cx).snapshot(cx);
7536 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7537
7538 self.buffer.update(cx, |buffer, cx| {
7539 buffer.edit(edits.iter().cloned(), None, cx)
7540 });
7541
7542 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7543 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7544 });
7545
7546 let selections = self.selections.disjoint_anchors_arc();
7547 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7548 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7549 if has_new_transaction {
7550 self.selection_history
7551 .insert_transaction(transaction_id_now, selections);
7552 }
7553 }
7554
7555 self.update_visible_edit_prediction(window, cx);
7556 if self.active_edit_prediction.is_none() {
7557 self.refresh_edit_prediction(true, true, window, cx);
7558 }
7559
7560 cx.notify();
7561 }
7562 }
7563
7564 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7565 }
7566
7567 pub fn accept_partial_edit_prediction(
7568 &mut self,
7569 _: &AcceptPartialEditPrediction,
7570 window: &mut Window,
7571 cx: &mut Context<Self>,
7572 ) {
7573 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7574 return;
7575 };
7576 if self.selections.count() != 1 {
7577 return;
7578 }
7579
7580 match &active_edit_prediction.completion {
7581 EditPrediction::MoveWithin { target, .. } => {
7582 let target = *target;
7583 self.change_selections(
7584 SelectionEffects::scroll(Autoscroll::newest()),
7585 window,
7586 cx,
7587 |selections| {
7588 selections.select_anchor_ranges([target..target]);
7589 },
7590 );
7591 }
7592 EditPrediction::MoveOutside { snapshot, target } => {
7593 if let Some(workspace) = self.workspace() {
7594 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7595 .detach_and_log_err(cx);
7596 }
7597 }
7598 EditPrediction::Edit { edits, .. } => {
7599 self.report_edit_prediction_event(
7600 active_edit_prediction.completion_id.clone(),
7601 true,
7602 cx,
7603 );
7604
7605 // Find an insertion that starts at the cursor position.
7606 let snapshot = self.buffer.read(cx).snapshot(cx);
7607 let cursor_offset = self
7608 .selections
7609 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7610 .head();
7611 let insertion = edits.iter().find_map(|(range, text)| {
7612 let range = range.to_offset(&snapshot);
7613 if range.is_empty() && range.start == cursor_offset {
7614 Some(text)
7615 } else {
7616 None
7617 }
7618 });
7619
7620 if let Some(text) = insertion {
7621 let mut partial_completion = text
7622 .chars()
7623 .by_ref()
7624 .take_while(|c| c.is_alphabetic())
7625 .collect::<String>();
7626 if partial_completion.is_empty() {
7627 partial_completion = text
7628 .chars()
7629 .by_ref()
7630 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7631 .collect::<String>();
7632 }
7633
7634 cx.emit(EditorEvent::InputHandled {
7635 utf16_range_to_replace: None,
7636 text: partial_completion.clone().into(),
7637 });
7638
7639 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7640
7641 self.refresh_edit_prediction(true, true, window, cx);
7642 cx.notify();
7643 } else {
7644 self.accept_edit_prediction(&Default::default(), window, cx);
7645 }
7646 }
7647 }
7648 }
7649
7650 fn discard_edit_prediction(
7651 &mut self,
7652 should_report_edit_prediction_event: bool,
7653 cx: &mut Context<Self>,
7654 ) -> bool {
7655 if should_report_edit_prediction_event {
7656 let completion_id = self
7657 .active_edit_prediction
7658 .as_ref()
7659 .and_then(|active_completion| active_completion.completion_id.clone());
7660
7661 self.report_edit_prediction_event(completion_id, false, cx);
7662 }
7663
7664 if let Some(provider) = self.edit_prediction_provider() {
7665 provider.discard(cx);
7666 }
7667
7668 self.take_active_edit_prediction(cx)
7669 }
7670
7671 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7672 let Some(provider) = self.edit_prediction_provider() else {
7673 return;
7674 };
7675
7676 let Some((_, buffer, _)) = self
7677 .buffer
7678 .read(cx)
7679 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7680 else {
7681 return;
7682 };
7683
7684 let extension = buffer
7685 .read(cx)
7686 .file()
7687 .and_then(|file| Some(file.path().extension()?.to_string()));
7688
7689 let event_type = match accepted {
7690 true => "Edit Prediction Accepted",
7691 false => "Edit Prediction Discarded",
7692 };
7693 telemetry::event!(
7694 event_type,
7695 provider = provider.name(),
7696 prediction_id = id,
7697 suggestion_accepted = accepted,
7698 file_extension = extension,
7699 );
7700 }
7701
7702 fn open_editor_at_anchor(
7703 snapshot: &language::BufferSnapshot,
7704 target: language::Anchor,
7705 workspace: &Entity<Workspace>,
7706 window: &mut Window,
7707 cx: &mut App,
7708 ) -> Task<Result<()>> {
7709 workspace.update(cx, |workspace, cx| {
7710 let path = snapshot.file().map(|file| file.full_path(cx));
7711 let Some(path) =
7712 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7713 else {
7714 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7715 };
7716 let target = text::ToPoint::to_point(&target, snapshot);
7717 let item = workspace.open_path(path, None, true, window, cx);
7718 window.spawn(cx, async move |cx| {
7719 let Some(editor) = item.await?.downcast::<Editor>() else {
7720 return Ok(());
7721 };
7722 editor
7723 .update_in(cx, |editor, window, cx| {
7724 editor.go_to_singleton_buffer_point(target, window, cx);
7725 })
7726 .ok();
7727 anyhow::Ok(())
7728 })
7729 })
7730 }
7731
7732 pub fn has_active_edit_prediction(&self) -> bool {
7733 self.active_edit_prediction.is_some()
7734 }
7735
7736 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7737 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7738 return false;
7739 };
7740
7741 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7742 self.clear_highlights::<EditPredictionHighlight>(cx);
7743 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7744 true
7745 }
7746
7747 /// Returns true when we're displaying the edit prediction popover below the cursor
7748 /// like we are not previewing and the LSP autocomplete menu is visible
7749 /// or we are in `when_holding_modifier` mode.
7750 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7751 if self.edit_prediction_preview_is_active()
7752 || !self.show_edit_predictions_in_menu()
7753 || !self.edit_predictions_enabled()
7754 {
7755 return false;
7756 }
7757
7758 if self.has_visible_completions_menu() {
7759 return true;
7760 }
7761
7762 has_completion && self.edit_prediction_requires_modifier()
7763 }
7764
7765 fn handle_modifiers_changed(
7766 &mut self,
7767 modifiers: Modifiers,
7768 position_map: &PositionMap,
7769 window: &mut Window,
7770 cx: &mut Context<Self>,
7771 ) {
7772 // Ensure that the edit prediction preview is updated, even when not
7773 // enabled, if there's an active edit prediction preview.
7774 if self.show_edit_predictions_in_menu()
7775 || matches!(
7776 self.edit_prediction_preview,
7777 EditPredictionPreview::Active { .. }
7778 )
7779 {
7780 self.update_edit_prediction_preview(&modifiers, window, cx);
7781 }
7782
7783 self.update_selection_mode(&modifiers, position_map, window, cx);
7784
7785 let mouse_position = window.mouse_position();
7786 if !position_map.text_hitbox.is_hovered(window) {
7787 return;
7788 }
7789
7790 self.update_hovered_link(
7791 position_map.point_for_position(mouse_position),
7792 &position_map.snapshot,
7793 modifiers,
7794 window,
7795 cx,
7796 )
7797 }
7798
7799 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7800 match EditorSettings::get_global(cx).multi_cursor_modifier {
7801 MultiCursorModifier::Alt => modifiers.secondary(),
7802 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7803 }
7804 }
7805
7806 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7807 match EditorSettings::get_global(cx).multi_cursor_modifier {
7808 MultiCursorModifier::Alt => modifiers.alt,
7809 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7810 }
7811 }
7812
7813 fn columnar_selection_mode(
7814 modifiers: &Modifiers,
7815 cx: &mut Context<Self>,
7816 ) -> Option<ColumnarMode> {
7817 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7818 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7819 Some(ColumnarMode::FromMouse)
7820 } else if Self::is_alt_pressed(modifiers, cx) {
7821 Some(ColumnarMode::FromSelection)
7822 } else {
7823 None
7824 }
7825 } else {
7826 None
7827 }
7828 }
7829
7830 fn update_selection_mode(
7831 &mut self,
7832 modifiers: &Modifiers,
7833 position_map: &PositionMap,
7834 window: &mut Window,
7835 cx: &mut Context<Self>,
7836 ) {
7837 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7838 return;
7839 };
7840 if self.selections.pending_anchor().is_none() {
7841 return;
7842 }
7843
7844 let mouse_position = window.mouse_position();
7845 let point_for_position = position_map.point_for_position(mouse_position);
7846 let position = point_for_position.previous_valid;
7847
7848 self.select(
7849 SelectPhase::BeginColumnar {
7850 position,
7851 reset: false,
7852 mode,
7853 goal_column: point_for_position.exact_unclipped.column(),
7854 },
7855 window,
7856 cx,
7857 );
7858 }
7859
7860 fn update_edit_prediction_preview(
7861 &mut self,
7862 modifiers: &Modifiers,
7863 window: &mut Window,
7864 cx: &mut Context<Self>,
7865 ) {
7866 let mut modifiers_held = false;
7867 if let Some(accept_keystroke) = self
7868 .accept_edit_prediction_keybind(false, window, cx)
7869 .keystroke()
7870 {
7871 modifiers_held = modifiers_held
7872 || (accept_keystroke.modifiers() == modifiers
7873 && accept_keystroke.modifiers().modified());
7874 };
7875 if let Some(accept_partial_keystroke) = self
7876 .accept_edit_prediction_keybind(true, window, cx)
7877 .keystroke()
7878 {
7879 modifiers_held = modifiers_held
7880 || (accept_partial_keystroke.modifiers() == modifiers
7881 && accept_partial_keystroke.modifiers().modified());
7882 }
7883
7884 if modifiers_held {
7885 if matches!(
7886 self.edit_prediction_preview,
7887 EditPredictionPreview::Inactive { .. }
7888 ) {
7889 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7890 provider.provider.did_show(cx)
7891 }
7892
7893 self.edit_prediction_preview = EditPredictionPreview::Active {
7894 previous_scroll_position: None,
7895 since: Instant::now(),
7896 };
7897
7898 self.update_visible_edit_prediction(window, cx);
7899 cx.notify();
7900 }
7901 } else if let EditPredictionPreview::Active {
7902 previous_scroll_position,
7903 since,
7904 } = self.edit_prediction_preview
7905 {
7906 if let (Some(previous_scroll_position), Some(position_map)) =
7907 (previous_scroll_position, self.last_position_map.as_ref())
7908 {
7909 self.set_scroll_position(
7910 previous_scroll_position
7911 .scroll_position(&position_map.snapshot.display_snapshot),
7912 window,
7913 cx,
7914 );
7915 }
7916
7917 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7918 released_too_fast: since.elapsed() < Duration::from_millis(200),
7919 };
7920 self.clear_row_highlights::<EditPredictionPreview>();
7921 self.update_visible_edit_prediction(window, cx);
7922 cx.notify();
7923 }
7924 }
7925
7926 fn update_visible_edit_prediction(
7927 &mut self,
7928 _window: &mut Window,
7929 cx: &mut Context<Self>,
7930 ) -> Option<()> {
7931 if DisableAiSettings::get_global(cx).disable_ai {
7932 return None;
7933 }
7934
7935 if self.ime_transaction.is_some() {
7936 self.discard_edit_prediction(false, cx);
7937 return None;
7938 }
7939
7940 let selection = self.selections.newest_anchor();
7941 let cursor = selection.head();
7942 let multibuffer = self.buffer.read(cx).snapshot(cx);
7943 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7944 let excerpt_id = cursor.excerpt_id;
7945
7946 let show_in_menu = self.show_edit_predictions_in_menu();
7947 let completions_menu_has_precedence = !show_in_menu
7948 && (self.context_menu.borrow().is_some()
7949 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7950
7951 if completions_menu_has_precedence
7952 || !offset_selection.is_empty()
7953 || self
7954 .active_edit_prediction
7955 .as_ref()
7956 .is_some_and(|completion| {
7957 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7958 return false;
7959 };
7960 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7961 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7962 !invalidation_range.contains(&offset_selection.head())
7963 })
7964 {
7965 self.discard_edit_prediction(false, cx);
7966 return None;
7967 }
7968
7969 self.take_active_edit_prediction(cx);
7970 let Some(provider) = self.edit_prediction_provider() else {
7971 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7972 return None;
7973 };
7974
7975 let (buffer, cursor_buffer_position) =
7976 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7977
7978 self.edit_prediction_settings =
7979 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7980
7981 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7982
7983 if self.edit_prediction_indent_conflict {
7984 let cursor_point = cursor.to_point(&multibuffer);
7985
7986 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7987
7988 if let Some((_, indent)) = indents.iter().next()
7989 && indent.len == cursor_point.column
7990 {
7991 self.edit_prediction_indent_conflict = false;
7992 }
7993 }
7994
7995 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7996
7997 let (completion_id, edits, edit_preview) = match edit_prediction {
7998 edit_prediction::EditPrediction::Local {
7999 id,
8000 edits,
8001 edit_preview,
8002 } => (id, edits, edit_preview),
8003 edit_prediction::EditPrediction::Jump {
8004 id,
8005 snapshot,
8006 target,
8007 } => {
8008 self.stale_edit_prediction_in_menu = None;
8009 self.active_edit_prediction = Some(EditPredictionState {
8010 inlay_ids: vec![],
8011 completion: EditPrediction::MoveOutside { snapshot, target },
8012 completion_id: id,
8013 invalidation_range: None,
8014 });
8015 cx.notify();
8016 return Some(());
8017 }
8018 };
8019
8020 let edits = edits
8021 .into_iter()
8022 .flat_map(|(range, new_text)| {
8023 Some((
8024 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8025 new_text,
8026 ))
8027 })
8028 .collect::<Vec<_>>();
8029 if edits.is_empty() {
8030 return None;
8031 }
8032
8033 let first_edit_start = edits.first().unwrap().0.start;
8034 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8035 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8036
8037 let last_edit_end = edits.last().unwrap().0.end;
8038 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8039 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8040
8041 let cursor_row = cursor.to_point(&multibuffer).row;
8042
8043 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8044
8045 let mut inlay_ids = Vec::new();
8046 let invalidation_row_range;
8047 let move_invalidation_row_range = if cursor_row < edit_start_row {
8048 Some(cursor_row..edit_end_row)
8049 } else if cursor_row > edit_end_row {
8050 Some(edit_start_row..cursor_row)
8051 } else {
8052 None
8053 };
8054 let supports_jump = self
8055 .edit_prediction_provider
8056 .as_ref()
8057 .map(|provider| provider.provider.supports_jump_to_edit())
8058 .unwrap_or(true);
8059
8060 let is_move = supports_jump
8061 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8062 let completion = if is_move {
8063 invalidation_row_range =
8064 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8065 let target = first_edit_start;
8066 EditPrediction::MoveWithin { target, snapshot }
8067 } else {
8068 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8069 && !self.edit_predictions_hidden_for_vim_mode;
8070
8071 if show_completions_in_buffer {
8072 if let Some(provider) = &self.edit_prediction_provider {
8073 provider.provider.did_show(cx);
8074 }
8075 if edits
8076 .iter()
8077 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8078 {
8079 let mut inlays = Vec::new();
8080 for (range, new_text) in &edits {
8081 let inlay = Inlay::edit_prediction(
8082 post_inc(&mut self.next_inlay_id),
8083 range.start,
8084 new_text.as_ref(),
8085 );
8086 inlay_ids.push(inlay.id);
8087 inlays.push(inlay);
8088 }
8089
8090 self.splice_inlays(&[], inlays, cx);
8091 } else {
8092 let background_color = cx.theme().status().deleted_background;
8093 self.highlight_text::<EditPredictionHighlight>(
8094 edits.iter().map(|(range, _)| range.clone()).collect(),
8095 HighlightStyle {
8096 background_color: Some(background_color),
8097 ..Default::default()
8098 },
8099 cx,
8100 );
8101 }
8102 }
8103
8104 invalidation_row_range = edit_start_row..edit_end_row;
8105
8106 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8107 if provider.show_tab_accept_marker() {
8108 EditDisplayMode::TabAccept
8109 } else {
8110 EditDisplayMode::Inline
8111 }
8112 } else {
8113 EditDisplayMode::DiffPopover
8114 };
8115
8116 EditPrediction::Edit {
8117 edits,
8118 edit_preview,
8119 display_mode,
8120 snapshot,
8121 }
8122 };
8123
8124 let invalidation_range = multibuffer
8125 .anchor_before(Point::new(invalidation_row_range.start, 0))
8126 ..multibuffer.anchor_after(Point::new(
8127 invalidation_row_range.end,
8128 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8129 ));
8130
8131 self.stale_edit_prediction_in_menu = None;
8132 self.active_edit_prediction = Some(EditPredictionState {
8133 inlay_ids,
8134 completion,
8135 completion_id,
8136 invalidation_range: Some(invalidation_range),
8137 });
8138
8139 cx.notify();
8140
8141 Some(())
8142 }
8143
8144 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8145 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8146 }
8147
8148 fn clear_tasks(&mut self) {
8149 self.tasks.clear()
8150 }
8151
8152 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8153 if self.tasks.insert(key, value).is_some() {
8154 // This case should hopefully be rare, but just in case...
8155 log::error!(
8156 "multiple different run targets found on a single line, only the last target will be rendered"
8157 )
8158 }
8159 }
8160
8161 /// Get all display points of breakpoints that will be rendered within editor
8162 ///
8163 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8164 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8165 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8166 fn active_breakpoints(
8167 &self,
8168 range: Range<DisplayRow>,
8169 window: &mut Window,
8170 cx: &mut Context<Self>,
8171 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8172 let mut breakpoint_display_points = HashMap::default();
8173
8174 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8175 return breakpoint_display_points;
8176 };
8177
8178 let snapshot = self.snapshot(window, cx);
8179
8180 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8181 let Some(project) = self.project() else {
8182 return breakpoint_display_points;
8183 };
8184
8185 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8186 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8187
8188 for (buffer_snapshot, range, excerpt_id) in
8189 multi_buffer_snapshot.range_to_buffer_ranges(range)
8190 {
8191 let Some(buffer) = project
8192 .read(cx)
8193 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8194 else {
8195 continue;
8196 };
8197 let breakpoints = breakpoint_store.read(cx).breakpoints(
8198 &buffer,
8199 Some(
8200 buffer_snapshot.anchor_before(range.start)
8201 ..buffer_snapshot.anchor_after(range.end),
8202 ),
8203 buffer_snapshot,
8204 cx,
8205 );
8206 for (breakpoint, state) in breakpoints {
8207 let multi_buffer_anchor =
8208 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8209 let position = multi_buffer_anchor
8210 .to_point(&multi_buffer_snapshot)
8211 .to_display_point(&snapshot);
8212
8213 breakpoint_display_points.insert(
8214 position.row(),
8215 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8216 );
8217 }
8218 }
8219
8220 breakpoint_display_points
8221 }
8222
8223 fn breakpoint_context_menu(
8224 &self,
8225 anchor: Anchor,
8226 window: &mut Window,
8227 cx: &mut Context<Self>,
8228 ) -> Entity<ui::ContextMenu> {
8229 let weak_editor = cx.weak_entity();
8230 let focus_handle = self.focus_handle(cx);
8231
8232 let row = self
8233 .buffer
8234 .read(cx)
8235 .snapshot(cx)
8236 .summary_for_anchor::<Point>(&anchor)
8237 .row;
8238
8239 let breakpoint = self
8240 .breakpoint_at_row(row, window, cx)
8241 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8242
8243 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8244 "Edit Log Breakpoint"
8245 } else {
8246 "Set Log Breakpoint"
8247 };
8248
8249 let condition_breakpoint_msg = if breakpoint
8250 .as_ref()
8251 .is_some_and(|bp| bp.1.condition.is_some())
8252 {
8253 "Edit Condition Breakpoint"
8254 } else {
8255 "Set Condition Breakpoint"
8256 };
8257
8258 let hit_condition_breakpoint_msg = if breakpoint
8259 .as_ref()
8260 .is_some_and(|bp| bp.1.hit_condition.is_some())
8261 {
8262 "Edit Hit Condition Breakpoint"
8263 } else {
8264 "Set Hit Condition Breakpoint"
8265 };
8266
8267 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8268 "Unset Breakpoint"
8269 } else {
8270 "Set Breakpoint"
8271 };
8272
8273 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8274
8275 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8276 BreakpointState::Enabled => Some("Disable"),
8277 BreakpointState::Disabled => Some("Enable"),
8278 });
8279
8280 let (anchor, breakpoint) =
8281 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8282
8283 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8284 menu.on_blur_subscription(Subscription::new(|| {}))
8285 .context(focus_handle)
8286 .when(run_to_cursor, |this| {
8287 let weak_editor = weak_editor.clone();
8288 this.entry("Run to cursor", None, move |window, cx| {
8289 weak_editor
8290 .update(cx, |editor, cx| {
8291 editor.change_selections(
8292 SelectionEffects::no_scroll(),
8293 window,
8294 cx,
8295 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8296 );
8297 })
8298 .ok();
8299
8300 window.dispatch_action(Box::new(RunToCursor), cx);
8301 })
8302 .separator()
8303 })
8304 .when_some(toggle_state_msg, |this, msg| {
8305 this.entry(msg, None, {
8306 let weak_editor = weak_editor.clone();
8307 let breakpoint = breakpoint.clone();
8308 move |_window, cx| {
8309 weak_editor
8310 .update(cx, |this, cx| {
8311 this.edit_breakpoint_at_anchor(
8312 anchor,
8313 breakpoint.as_ref().clone(),
8314 BreakpointEditAction::InvertState,
8315 cx,
8316 );
8317 })
8318 .log_err();
8319 }
8320 })
8321 })
8322 .entry(set_breakpoint_msg, None, {
8323 let weak_editor = weak_editor.clone();
8324 let breakpoint = breakpoint.clone();
8325 move |_window, cx| {
8326 weak_editor
8327 .update(cx, |this, cx| {
8328 this.edit_breakpoint_at_anchor(
8329 anchor,
8330 breakpoint.as_ref().clone(),
8331 BreakpointEditAction::Toggle,
8332 cx,
8333 );
8334 })
8335 .log_err();
8336 }
8337 })
8338 .entry(log_breakpoint_msg, None, {
8339 let breakpoint = breakpoint.clone();
8340 let weak_editor = weak_editor.clone();
8341 move |window, cx| {
8342 weak_editor
8343 .update(cx, |this, cx| {
8344 this.add_edit_breakpoint_block(
8345 anchor,
8346 breakpoint.as_ref(),
8347 BreakpointPromptEditAction::Log,
8348 window,
8349 cx,
8350 );
8351 })
8352 .log_err();
8353 }
8354 })
8355 .entry(condition_breakpoint_msg, None, {
8356 let breakpoint = breakpoint.clone();
8357 let weak_editor = weak_editor.clone();
8358 move |window, cx| {
8359 weak_editor
8360 .update(cx, |this, cx| {
8361 this.add_edit_breakpoint_block(
8362 anchor,
8363 breakpoint.as_ref(),
8364 BreakpointPromptEditAction::Condition,
8365 window,
8366 cx,
8367 );
8368 })
8369 .log_err();
8370 }
8371 })
8372 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8373 weak_editor
8374 .update(cx, |this, cx| {
8375 this.add_edit_breakpoint_block(
8376 anchor,
8377 breakpoint.as_ref(),
8378 BreakpointPromptEditAction::HitCondition,
8379 window,
8380 cx,
8381 );
8382 })
8383 .log_err();
8384 })
8385 })
8386 }
8387
8388 fn render_breakpoint(
8389 &self,
8390 position: Anchor,
8391 row: DisplayRow,
8392 breakpoint: &Breakpoint,
8393 state: Option<BreakpointSessionState>,
8394 cx: &mut Context<Self>,
8395 ) -> IconButton {
8396 let is_rejected = state.is_some_and(|s| !s.verified);
8397 // Is it a breakpoint that shows up when hovering over gutter?
8398 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8399 (false, false),
8400 |PhantomBreakpointIndicator {
8401 is_active,
8402 display_row,
8403 collides_with_existing_breakpoint,
8404 }| {
8405 (
8406 is_active && display_row == row,
8407 collides_with_existing_breakpoint,
8408 )
8409 },
8410 );
8411
8412 let (color, icon) = {
8413 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8414 (false, false) => ui::IconName::DebugBreakpoint,
8415 (true, false) => ui::IconName::DebugLogBreakpoint,
8416 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8417 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8418 };
8419
8420 let color = if is_phantom {
8421 Color::Hint
8422 } else if is_rejected {
8423 Color::Disabled
8424 } else {
8425 Color::Debugger
8426 };
8427
8428 (color, icon)
8429 };
8430
8431 let breakpoint = Arc::from(breakpoint.clone());
8432
8433 let alt_as_text = gpui::Keystroke {
8434 modifiers: Modifiers::secondary_key(),
8435 ..Default::default()
8436 };
8437 let primary_action_text = if breakpoint.is_disabled() {
8438 "Enable breakpoint"
8439 } else if is_phantom && !collides_with_existing {
8440 "Set breakpoint"
8441 } else {
8442 "Unset breakpoint"
8443 };
8444 let focus_handle = self.focus_handle.clone();
8445
8446 let meta = if is_rejected {
8447 SharedString::from("No executable code is associated with this line.")
8448 } else if collides_with_existing && !breakpoint.is_disabled() {
8449 SharedString::from(format!(
8450 "{alt_as_text}-click to disable,\nright-click for more options."
8451 ))
8452 } else {
8453 SharedString::from("Right-click for more options.")
8454 };
8455 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8456 .icon_size(IconSize::XSmall)
8457 .size(ui::ButtonSize::None)
8458 .when(is_rejected, |this| {
8459 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8460 })
8461 .icon_color(color)
8462 .style(ButtonStyle::Transparent)
8463 .on_click(cx.listener({
8464 move |editor, event: &ClickEvent, window, cx| {
8465 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8466 BreakpointEditAction::InvertState
8467 } else {
8468 BreakpointEditAction::Toggle
8469 };
8470
8471 window.focus(&editor.focus_handle(cx));
8472 editor.edit_breakpoint_at_anchor(
8473 position,
8474 breakpoint.as_ref().clone(),
8475 edit_action,
8476 cx,
8477 );
8478 }
8479 }))
8480 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8481 editor.set_breakpoint_context_menu(
8482 row,
8483 Some(position),
8484 event.position(),
8485 window,
8486 cx,
8487 );
8488 }))
8489 .tooltip(move |_window, cx| {
8490 Tooltip::with_meta_in(
8491 primary_action_text,
8492 Some(&ToggleBreakpoint),
8493 meta.clone(),
8494 &focus_handle,
8495 cx,
8496 )
8497 })
8498 }
8499
8500 fn build_tasks_context(
8501 project: &Entity<Project>,
8502 buffer: &Entity<Buffer>,
8503 buffer_row: u32,
8504 tasks: &Arc<RunnableTasks>,
8505 cx: &mut Context<Self>,
8506 ) -> Task<Option<task::TaskContext>> {
8507 let position = Point::new(buffer_row, tasks.column);
8508 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8509 let location = Location {
8510 buffer: buffer.clone(),
8511 range: range_start..range_start,
8512 };
8513 // Fill in the environmental variables from the tree-sitter captures
8514 let mut captured_task_variables = TaskVariables::default();
8515 for (capture_name, value) in tasks.extra_variables.clone() {
8516 captured_task_variables.insert(
8517 task::VariableName::Custom(capture_name.into()),
8518 value.clone(),
8519 );
8520 }
8521 project.update(cx, |project, cx| {
8522 project.task_store().update(cx, |task_store, cx| {
8523 task_store.task_context_for_location(captured_task_variables, location, cx)
8524 })
8525 })
8526 }
8527
8528 pub fn spawn_nearest_task(
8529 &mut self,
8530 action: &SpawnNearestTask,
8531 window: &mut Window,
8532 cx: &mut Context<Self>,
8533 ) {
8534 let Some((workspace, _)) = self.workspace.clone() else {
8535 return;
8536 };
8537 let Some(project) = self.project.clone() else {
8538 return;
8539 };
8540
8541 // Try to find a closest, enclosing node using tree-sitter that has a task
8542 let Some((buffer, buffer_row, tasks)) = self
8543 .find_enclosing_node_task(cx)
8544 // Or find the task that's closest in row-distance.
8545 .or_else(|| self.find_closest_task(cx))
8546 else {
8547 return;
8548 };
8549
8550 let reveal_strategy = action.reveal;
8551 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8552 cx.spawn_in(window, async move |_, cx| {
8553 let context = task_context.await?;
8554 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8555
8556 let resolved = &mut resolved_task.resolved;
8557 resolved.reveal = reveal_strategy;
8558
8559 workspace
8560 .update_in(cx, |workspace, window, cx| {
8561 workspace.schedule_resolved_task(
8562 task_source_kind,
8563 resolved_task,
8564 false,
8565 window,
8566 cx,
8567 );
8568 })
8569 .ok()
8570 })
8571 .detach();
8572 }
8573
8574 fn find_closest_task(
8575 &mut self,
8576 cx: &mut Context<Self>,
8577 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8578 let cursor_row = self
8579 .selections
8580 .newest_adjusted(&self.display_snapshot(cx))
8581 .head()
8582 .row;
8583
8584 let ((buffer_id, row), tasks) = self
8585 .tasks
8586 .iter()
8587 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8588
8589 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8590 let tasks = Arc::new(tasks.to_owned());
8591 Some((buffer, *row, tasks))
8592 }
8593
8594 fn find_enclosing_node_task(
8595 &mut self,
8596 cx: &mut Context<Self>,
8597 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8598 let snapshot = self.buffer.read(cx).snapshot(cx);
8599 let offset = self
8600 .selections
8601 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8602 .head();
8603 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8604 let offset = excerpt.map_offset_to_buffer(offset);
8605 let buffer_id = excerpt.buffer().remote_id();
8606
8607 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8608 let mut cursor = layer.node().walk();
8609
8610 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8611 if cursor.node().end_byte() == offset.0 {
8612 cursor.goto_next_sibling();
8613 }
8614 }
8615
8616 // Ascend to the smallest ancestor that contains the range and has a task.
8617 loop {
8618 let node = cursor.node();
8619 let node_range = node.byte_range();
8620 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8621
8622 // Check if this node contains our offset
8623 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8624 // If it contains offset, check for task
8625 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8626 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8627 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8628 }
8629 }
8630
8631 if !cursor.goto_parent() {
8632 break;
8633 }
8634 }
8635 None
8636 }
8637
8638 fn render_run_indicator(
8639 &self,
8640 _style: &EditorStyle,
8641 is_active: bool,
8642 row: DisplayRow,
8643 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8644 cx: &mut Context<Self>,
8645 ) -> IconButton {
8646 let color = Color::Muted;
8647 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8648
8649 IconButton::new(
8650 ("run_indicator", row.0 as usize),
8651 ui::IconName::PlayOutlined,
8652 )
8653 .shape(ui::IconButtonShape::Square)
8654 .icon_size(IconSize::XSmall)
8655 .icon_color(color)
8656 .toggle_state(is_active)
8657 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8658 let quick_launch = match e {
8659 ClickEvent::Keyboard(_) => true,
8660 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8661 };
8662
8663 window.focus(&editor.focus_handle(cx));
8664 editor.toggle_code_actions(
8665 &ToggleCodeActions {
8666 deployed_from: Some(CodeActionSource::RunMenu(row)),
8667 quick_launch,
8668 },
8669 window,
8670 cx,
8671 );
8672 }))
8673 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8674 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8675 }))
8676 }
8677
8678 pub fn context_menu_visible(&self) -> bool {
8679 !self.edit_prediction_preview_is_active()
8680 && self
8681 .context_menu
8682 .borrow()
8683 .as_ref()
8684 .is_some_and(|menu| menu.visible())
8685 }
8686
8687 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8688 self.context_menu
8689 .borrow()
8690 .as_ref()
8691 .map(|menu| menu.origin())
8692 }
8693
8694 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8695 self.context_menu_options = Some(options);
8696 }
8697
8698 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8699 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8700
8701 fn render_edit_prediction_popover(
8702 &mut self,
8703 text_bounds: &Bounds<Pixels>,
8704 content_origin: gpui::Point<Pixels>,
8705 right_margin: Pixels,
8706 editor_snapshot: &EditorSnapshot,
8707 visible_row_range: Range<DisplayRow>,
8708 scroll_top: ScrollOffset,
8709 scroll_bottom: ScrollOffset,
8710 line_layouts: &[LineWithInvisibles],
8711 line_height: Pixels,
8712 scroll_position: gpui::Point<ScrollOffset>,
8713 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8714 newest_selection_head: Option<DisplayPoint>,
8715 editor_width: Pixels,
8716 style: &EditorStyle,
8717 window: &mut Window,
8718 cx: &mut App,
8719 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8720 if self.mode().is_minimap() {
8721 return None;
8722 }
8723 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8724
8725 if self.edit_prediction_visible_in_cursor_popover(true) {
8726 return None;
8727 }
8728
8729 match &active_edit_prediction.completion {
8730 EditPrediction::MoveWithin { target, .. } => {
8731 let target_display_point = target.to_display_point(editor_snapshot);
8732
8733 if self.edit_prediction_requires_modifier() {
8734 if !self.edit_prediction_preview_is_active() {
8735 return None;
8736 }
8737
8738 self.render_edit_prediction_modifier_jump_popover(
8739 text_bounds,
8740 content_origin,
8741 visible_row_range,
8742 line_layouts,
8743 line_height,
8744 scroll_pixel_position,
8745 newest_selection_head,
8746 target_display_point,
8747 window,
8748 cx,
8749 )
8750 } else {
8751 self.render_edit_prediction_eager_jump_popover(
8752 text_bounds,
8753 content_origin,
8754 editor_snapshot,
8755 visible_row_range,
8756 scroll_top,
8757 scroll_bottom,
8758 line_height,
8759 scroll_pixel_position,
8760 target_display_point,
8761 editor_width,
8762 window,
8763 cx,
8764 )
8765 }
8766 }
8767 EditPrediction::Edit {
8768 display_mode: EditDisplayMode::Inline,
8769 ..
8770 } => None,
8771 EditPrediction::Edit {
8772 display_mode: EditDisplayMode::TabAccept,
8773 edits,
8774 ..
8775 } => {
8776 let range = &edits.first()?.0;
8777 let target_display_point = range.end.to_display_point(editor_snapshot);
8778
8779 self.render_edit_prediction_end_of_line_popover(
8780 "Accept",
8781 editor_snapshot,
8782 visible_row_range,
8783 target_display_point,
8784 line_height,
8785 scroll_pixel_position,
8786 content_origin,
8787 editor_width,
8788 window,
8789 cx,
8790 )
8791 }
8792 EditPrediction::Edit {
8793 edits,
8794 edit_preview,
8795 display_mode: EditDisplayMode::DiffPopover,
8796 snapshot,
8797 } => self.render_edit_prediction_diff_popover(
8798 text_bounds,
8799 content_origin,
8800 right_margin,
8801 editor_snapshot,
8802 visible_row_range,
8803 line_layouts,
8804 line_height,
8805 scroll_position,
8806 scroll_pixel_position,
8807 newest_selection_head,
8808 editor_width,
8809 style,
8810 edits,
8811 edit_preview,
8812 snapshot,
8813 window,
8814 cx,
8815 ),
8816 EditPrediction::MoveOutside { snapshot, .. } => {
8817 let file_name = snapshot
8818 .file()
8819 .map(|file| file.file_name(cx))
8820 .unwrap_or("untitled");
8821 let mut element = self
8822 .render_edit_prediction_line_popover(
8823 format!("Jump to {file_name}"),
8824 Some(IconName::ZedPredict),
8825 window,
8826 cx,
8827 )
8828 .into_any();
8829
8830 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8831 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8832 let origin_y = text_bounds.size.height - size.height - px(30.);
8833 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8834 element.prepaint_at(origin, window, cx);
8835
8836 Some((element, origin))
8837 }
8838 }
8839 }
8840
8841 fn render_edit_prediction_modifier_jump_popover(
8842 &mut self,
8843 text_bounds: &Bounds<Pixels>,
8844 content_origin: gpui::Point<Pixels>,
8845 visible_row_range: Range<DisplayRow>,
8846 line_layouts: &[LineWithInvisibles],
8847 line_height: Pixels,
8848 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8849 newest_selection_head: Option<DisplayPoint>,
8850 target_display_point: DisplayPoint,
8851 window: &mut Window,
8852 cx: &mut App,
8853 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8854 let scrolled_content_origin =
8855 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8856
8857 const SCROLL_PADDING_Y: Pixels = px(12.);
8858
8859 if target_display_point.row() < visible_row_range.start {
8860 return self.render_edit_prediction_scroll_popover(
8861 |_| SCROLL_PADDING_Y,
8862 IconName::ArrowUp,
8863 visible_row_range,
8864 line_layouts,
8865 newest_selection_head,
8866 scrolled_content_origin,
8867 window,
8868 cx,
8869 );
8870 } else if target_display_point.row() >= visible_row_range.end {
8871 return self.render_edit_prediction_scroll_popover(
8872 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8873 IconName::ArrowDown,
8874 visible_row_range,
8875 line_layouts,
8876 newest_selection_head,
8877 scrolled_content_origin,
8878 window,
8879 cx,
8880 );
8881 }
8882
8883 const POLE_WIDTH: Pixels = px(2.);
8884
8885 let line_layout =
8886 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8887 let target_column = target_display_point.column() as usize;
8888
8889 let target_x = line_layout.x_for_index(target_column);
8890 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8891 - scroll_pixel_position.y;
8892
8893 let flag_on_right = target_x < text_bounds.size.width / 2.;
8894
8895 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8896 border_color.l += 0.001;
8897
8898 let mut element = v_flex()
8899 .items_end()
8900 .when(flag_on_right, |el| el.items_start())
8901 .child(if flag_on_right {
8902 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8903 .rounded_bl(px(0.))
8904 .rounded_tl(px(0.))
8905 .border_l_2()
8906 .border_color(border_color)
8907 } else {
8908 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8909 .rounded_br(px(0.))
8910 .rounded_tr(px(0.))
8911 .border_r_2()
8912 .border_color(border_color)
8913 })
8914 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8915 .into_any();
8916
8917 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8918
8919 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8920 - point(
8921 if flag_on_right {
8922 POLE_WIDTH
8923 } else {
8924 size.width - POLE_WIDTH
8925 },
8926 size.height - line_height,
8927 );
8928
8929 origin.x = origin.x.max(content_origin.x);
8930
8931 element.prepaint_at(origin, window, cx);
8932
8933 Some((element, origin))
8934 }
8935
8936 fn render_edit_prediction_scroll_popover(
8937 &mut self,
8938 to_y: impl Fn(Size<Pixels>) -> Pixels,
8939 scroll_icon: IconName,
8940 visible_row_range: Range<DisplayRow>,
8941 line_layouts: &[LineWithInvisibles],
8942 newest_selection_head: Option<DisplayPoint>,
8943 scrolled_content_origin: gpui::Point<Pixels>,
8944 window: &mut Window,
8945 cx: &mut App,
8946 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8947 let mut element = self
8948 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8949 .into_any();
8950
8951 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8952
8953 let cursor = newest_selection_head?;
8954 let cursor_row_layout =
8955 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8956 let cursor_column = cursor.column() as usize;
8957
8958 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8959
8960 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8961
8962 element.prepaint_at(origin, window, cx);
8963 Some((element, origin))
8964 }
8965
8966 fn render_edit_prediction_eager_jump_popover(
8967 &mut self,
8968 text_bounds: &Bounds<Pixels>,
8969 content_origin: gpui::Point<Pixels>,
8970 editor_snapshot: &EditorSnapshot,
8971 visible_row_range: Range<DisplayRow>,
8972 scroll_top: ScrollOffset,
8973 scroll_bottom: ScrollOffset,
8974 line_height: Pixels,
8975 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8976 target_display_point: DisplayPoint,
8977 editor_width: Pixels,
8978 window: &mut Window,
8979 cx: &mut App,
8980 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8981 if target_display_point.row().as_f64() < scroll_top {
8982 let mut element = self
8983 .render_edit_prediction_line_popover(
8984 "Jump to Edit",
8985 Some(IconName::ArrowUp),
8986 window,
8987 cx,
8988 )
8989 .into_any();
8990
8991 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8992 let offset = point(
8993 (text_bounds.size.width - size.width) / 2.,
8994 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8995 );
8996
8997 let origin = text_bounds.origin + offset;
8998 element.prepaint_at(origin, window, cx);
8999 Some((element, origin))
9000 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9001 let mut element = self
9002 .render_edit_prediction_line_popover(
9003 "Jump to Edit",
9004 Some(IconName::ArrowDown),
9005 window,
9006 cx,
9007 )
9008 .into_any();
9009
9010 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9011 let offset = point(
9012 (text_bounds.size.width - size.width) / 2.,
9013 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9014 );
9015
9016 let origin = text_bounds.origin + offset;
9017 element.prepaint_at(origin, window, cx);
9018 Some((element, origin))
9019 } else {
9020 self.render_edit_prediction_end_of_line_popover(
9021 "Jump to Edit",
9022 editor_snapshot,
9023 visible_row_range,
9024 target_display_point,
9025 line_height,
9026 scroll_pixel_position,
9027 content_origin,
9028 editor_width,
9029 window,
9030 cx,
9031 )
9032 }
9033 }
9034
9035 fn render_edit_prediction_end_of_line_popover(
9036 self: &mut Editor,
9037 label: &'static str,
9038 editor_snapshot: &EditorSnapshot,
9039 visible_row_range: Range<DisplayRow>,
9040 target_display_point: DisplayPoint,
9041 line_height: Pixels,
9042 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9043 content_origin: gpui::Point<Pixels>,
9044 editor_width: Pixels,
9045 window: &mut Window,
9046 cx: &mut App,
9047 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9048 let target_line_end = DisplayPoint::new(
9049 target_display_point.row(),
9050 editor_snapshot.line_len(target_display_point.row()),
9051 );
9052
9053 let mut element = self
9054 .render_edit_prediction_line_popover(label, None, window, cx)
9055 .into_any();
9056
9057 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9058
9059 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9060
9061 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9062 let mut origin = start_point
9063 + line_origin
9064 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9065 origin.x = origin.x.max(content_origin.x);
9066
9067 let max_x = content_origin.x + editor_width - size.width;
9068
9069 if origin.x > max_x {
9070 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9071
9072 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9073 origin.y += offset;
9074 IconName::ArrowUp
9075 } else {
9076 origin.y -= offset;
9077 IconName::ArrowDown
9078 };
9079
9080 element = self
9081 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9082 .into_any();
9083
9084 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9085
9086 origin.x = content_origin.x + editor_width - size.width - px(2.);
9087 }
9088
9089 element.prepaint_at(origin, window, cx);
9090 Some((element, origin))
9091 }
9092
9093 fn render_edit_prediction_diff_popover(
9094 self: &Editor,
9095 text_bounds: &Bounds<Pixels>,
9096 content_origin: gpui::Point<Pixels>,
9097 right_margin: Pixels,
9098 editor_snapshot: &EditorSnapshot,
9099 visible_row_range: Range<DisplayRow>,
9100 line_layouts: &[LineWithInvisibles],
9101 line_height: Pixels,
9102 scroll_position: gpui::Point<ScrollOffset>,
9103 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9104 newest_selection_head: Option<DisplayPoint>,
9105 editor_width: Pixels,
9106 style: &EditorStyle,
9107 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9108 edit_preview: &Option<language::EditPreview>,
9109 snapshot: &language::BufferSnapshot,
9110 window: &mut Window,
9111 cx: &mut App,
9112 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9113 let edit_start = edits
9114 .first()
9115 .unwrap()
9116 .0
9117 .start
9118 .to_display_point(editor_snapshot);
9119 let edit_end = edits
9120 .last()
9121 .unwrap()
9122 .0
9123 .end
9124 .to_display_point(editor_snapshot);
9125
9126 let is_visible = visible_row_range.contains(&edit_start.row())
9127 || visible_row_range.contains(&edit_end.row());
9128 if !is_visible {
9129 return None;
9130 }
9131
9132 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9133 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9134 } else {
9135 // Fallback for providers without edit_preview
9136 crate::edit_prediction_fallback_text(edits, cx)
9137 };
9138
9139 let styled_text = highlighted_edits.to_styled_text(&style.text);
9140 let line_count = highlighted_edits.text.lines().count();
9141
9142 const BORDER_WIDTH: Pixels = px(1.);
9143
9144 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9145 let has_keybind = keybind.is_some();
9146
9147 let mut element = h_flex()
9148 .items_start()
9149 .child(
9150 h_flex()
9151 .bg(cx.theme().colors().editor_background)
9152 .border(BORDER_WIDTH)
9153 .shadow_xs()
9154 .border_color(cx.theme().colors().border)
9155 .rounded_l_lg()
9156 .when(line_count > 1, |el| el.rounded_br_lg())
9157 .pr_1()
9158 .child(styled_text),
9159 )
9160 .child(
9161 h_flex()
9162 .h(line_height + BORDER_WIDTH * 2.)
9163 .px_1p5()
9164 .gap_1()
9165 // Workaround: For some reason, there's a gap if we don't do this
9166 .ml(-BORDER_WIDTH)
9167 .shadow(vec![gpui::BoxShadow {
9168 color: gpui::black().opacity(0.05),
9169 offset: point(px(1.), px(1.)),
9170 blur_radius: px(2.),
9171 spread_radius: px(0.),
9172 }])
9173 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9174 .border(BORDER_WIDTH)
9175 .border_color(cx.theme().colors().border)
9176 .rounded_r_lg()
9177 .id("edit_prediction_diff_popover_keybind")
9178 .when(!has_keybind, |el| {
9179 let status_colors = cx.theme().status();
9180
9181 el.bg(status_colors.error_background)
9182 .border_color(status_colors.error.opacity(0.6))
9183 .child(Icon::new(IconName::Info).color(Color::Error))
9184 .cursor_default()
9185 .hoverable_tooltip(move |_window, cx| {
9186 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9187 })
9188 })
9189 .children(keybind),
9190 )
9191 .into_any();
9192
9193 let longest_row =
9194 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9195 let longest_line_width = if visible_row_range.contains(&longest_row) {
9196 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9197 } else {
9198 layout_line(
9199 longest_row,
9200 editor_snapshot,
9201 style,
9202 editor_width,
9203 |_| false,
9204 window,
9205 cx,
9206 )
9207 .width
9208 };
9209
9210 let viewport_bounds =
9211 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9212 right: -right_margin,
9213 ..Default::default()
9214 });
9215
9216 let x_after_longest = Pixels::from(
9217 ScrollPixelOffset::from(
9218 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9219 ) - scroll_pixel_position.x,
9220 );
9221
9222 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9223
9224 // Fully visible if it can be displayed within the window (allow overlapping other
9225 // panes). However, this is only allowed if the popover starts within text_bounds.
9226 let can_position_to_the_right = x_after_longest < text_bounds.right()
9227 && x_after_longest + element_bounds.width < viewport_bounds.right();
9228
9229 let mut origin = if can_position_to_the_right {
9230 point(
9231 x_after_longest,
9232 text_bounds.origin.y
9233 + Pixels::from(
9234 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9235 - scroll_pixel_position.y,
9236 ),
9237 )
9238 } else {
9239 let cursor_row = newest_selection_head.map(|head| head.row());
9240 let above_edit = edit_start
9241 .row()
9242 .0
9243 .checked_sub(line_count as u32)
9244 .map(DisplayRow);
9245 let below_edit = Some(edit_end.row() + 1);
9246 let above_cursor =
9247 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9248 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9249
9250 // Place the edit popover adjacent to the edit if there is a location
9251 // available that is onscreen and does not obscure the cursor. Otherwise,
9252 // place it adjacent to the cursor.
9253 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9254 .into_iter()
9255 .flatten()
9256 .find(|&start_row| {
9257 let end_row = start_row + line_count as u32;
9258 visible_row_range.contains(&start_row)
9259 && visible_row_range.contains(&end_row)
9260 && cursor_row
9261 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9262 })?;
9263
9264 content_origin
9265 + point(
9266 Pixels::from(-scroll_pixel_position.x),
9267 Pixels::from(
9268 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9269 ),
9270 )
9271 };
9272
9273 origin.x -= BORDER_WIDTH;
9274
9275 window.defer_draw(element, origin, 1);
9276
9277 // Do not return an element, since it will already be drawn due to defer_draw.
9278 None
9279 }
9280
9281 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9282 px(30.)
9283 }
9284
9285 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9286 if self.read_only(cx) {
9287 cx.theme().players().read_only()
9288 } else {
9289 self.style.as_ref().unwrap().local_player
9290 }
9291 }
9292
9293 fn render_edit_prediction_accept_keybind(
9294 &self,
9295 window: &mut Window,
9296 cx: &mut App,
9297 ) -> Option<AnyElement> {
9298 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9299 let accept_keystroke = accept_binding.keystroke()?;
9300
9301 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9302
9303 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9304 Color::Accent
9305 } else {
9306 Color::Muted
9307 };
9308
9309 h_flex()
9310 .px_0p5()
9311 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9312 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9313 .text_size(TextSize::XSmall.rems(cx))
9314 .child(h_flex().children(ui::render_modifiers(
9315 accept_keystroke.modifiers(),
9316 PlatformStyle::platform(),
9317 Some(modifiers_color),
9318 Some(IconSize::XSmall.rems().into()),
9319 true,
9320 )))
9321 .when(is_platform_style_mac, |parent| {
9322 parent.child(accept_keystroke.key().to_string())
9323 })
9324 .when(!is_platform_style_mac, |parent| {
9325 parent.child(
9326 Key::new(
9327 util::capitalize(accept_keystroke.key()),
9328 Some(Color::Default),
9329 )
9330 .size(Some(IconSize::XSmall.rems().into())),
9331 )
9332 })
9333 .into_any()
9334 .into()
9335 }
9336
9337 fn render_edit_prediction_line_popover(
9338 &self,
9339 label: impl Into<SharedString>,
9340 icon: Option<IconName>,
9341 window: &mut Window,
9342 cx: &mut App,
9343 ) -> Stateful<Div> {
9344 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9345
9346 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9347 let has_keybind = keybind.is_some();
9348
9349 h_flex()
9350 .id("ep-line-popover")
9351 .py_0p5()
9352 .pl_1()
9353 .pr(padding_right)
9354 .gap_1()
9355 .rounded_md()
9356 .border_1()
9357 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9358 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9359 .shadow_xs()
9360 .when(!has_keybind, |el| {
9361 let status_colors = cx.theme().status();
9362
9363 el.bg(status_colors.error_background)
9364 .border_color(status_colors.error.opacity(0.6))
9365 .pl_2()
9366 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9367 .cursor_default()
9368 .hoverable_tooltip(move |_window, cx| {
9369 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9370 })
9371 })
9372 .children(keybind)
9373 .child(
9374 Label::new(label)
9375 .size(LabelSize::Small)
9376 .when(!has_keybind, |el| {
9377 el.color(cx.theme().status().error.into()).strikethrough()
9378 }),
9379 )
9380 .when(!has_keybind, |el| {
9381 el.child(
9382 h_flex().ml_1().child(
9383 Icon::new(IconName::Info)
9384 .size(IconSize::Small)
9385 .color(cx.theme().status().error.into()),
9386 ),
9387 )
9388 })
9389 .when_some(icon, |element, icon| {
9390 element.child(
9391 div()
9392 .mt(px(1.5))
9393 .child(Icon::new(icon).size(IconSize::Small)),
9394 )
9395 })
9396 }
9397
9398 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9399 let accent_color = cx.theme().colors().text_accent;
9400 let editor_bg_color = cx.theme().colors().editor_background;
9401 editor_bg_color.blend(accent_color.opacity(0.1))
9402 }
9403
9404 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9405 let accent_color = cx.theme().colors().text_accent;
9406 let editor_bg_color = cx.theme().colors().editor_background;
9407 editor_bg_color.blend(accent_color.opacity(0.6))
9408 }
9409 fn get_prediction_provider_icon_name(
9410 provider: &Option<RegisteredEditPredictionProvider>,
9411 ) -> IconName {
9412 match provider {
9413 Some(provider) => match provider.provider.name() {
9414 "copilot" => IconName::Copilot,
9415 "supermaven" => IconName::Supermaven,
9416 _ => IconName::ZedPredict,
9417 },
9418 None => IconName::ZedPredict,
9419 }
9420 }
9421
9422 fn render_edit_prediction_cursor_popover(
9423 &self,
9424 min_width: Pixels,
9425 max_width: Pixels,
9426 cursor_point: Point,
9427 style: &EditorStyle,
9428 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9429 _window: &Window,
9430 cx: &mut Context<Editor>,
9431 ) -> Option<AnyElement> {
9432 let provider = self.edit_prediction_provider.as_ref()?;
9433 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9434
9435 let is_refreshing = provider.provider.is_refreshing(cx);
9436
9437 fn pending_completion_container(icon: IconName) -> Div {
9438 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9439 }
9440
9441 let completion = match &self.active_edit_prediction {
9442 Some(prediction) => {
9443 if !self.has_visible_completions_menu() {
9444 const RADIUS: Pixels = px(6.);
9445 const BORDER_WIDTH: Pixels = px(1.);
9446
9447 return Some(
9448 h_flex()
9449 .elevation_2(cx)
9450 .border(BORDER_WIDTH)
9451 .border_color(cx.theme().colors().border)
9452 .when(accept_keystroke.is_none(), |el| {
9453 el.border_color(cx.theme().status().error)
9454 })
9455 .rounded(RADIUS)
9456 .rounded_tl(px(0.))
9457 .overflow_hidden()
9458 .child(div().px_1p5().child(match &prediction.completion {
9459 EditPrediction::MoveWithin { target, snapshot } => {
9460 use text::ToPoint as _;
9461 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9462 {
9463 Icon::new(IconName::ZedPredictDown)
9464 } else {
9465 Icon::new(IconName::ZedPredictUp)
9466 }
9467 }
9468 EditPrediction::MoveOutside { .. } => {
9469 // TODO [zeta2] custom icon for external jump?
9470 Icon::new(provider_icon)
9471 }
9472 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9473 }))
9474 .child(
9475 h_flex()
9476 .gap_1()
9477 .py_1()
9478 .px_2()
9479 .rounded_r(RADIUS - BORDER_WIDTH)
9480 .border_l_1()
9481 .border_color(cx.theme().colors().border)
9482 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9483 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9484 el.child(
9485 Label::new("Hold")
9486 .size(LabelSize::Small)
9487 .when(accept_keystroke.is_none(), |el| {
9488 el.strikethrough()
9489 })
9490 .line_height_style(LineHeightStyle::UiLabel),
9491 )
9492 })
9493 .id("edit_prediction_cursor_popover_keybind")
9494 .when(accept_keystroke.is_none(), |el| {
9495 let status_colors = cx.theme().status();
9496
9497 el.bg(status_colors.error_background)
9498 .border_color(status_colors.error.opacity(0.6))
9499 .child(Icon::new(IconName::Info).color(Color::Error))
9500 .cursor_default()
9501 .hoverable_tooltip(move |_window, cx| {
9502 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9503 .into()
9504 })
9505 })
9506 .when_some(
9507 accept_keystroke.as_ref(),
9508 |el, accept_keystroke| {
9509 el.child(h_flex().children(ui::render_modifiers(
9510 accept_keystroke.modifiers(),
9511 PlatformStyle::platform(),
9512 Some(Color::Default),
9513 Some(IconSize::XSmall.rems().into()),
9514 false,
9515 )))
9516 },
9517 ),
9518 )
9519 .into_any(),
9520 );
9521 }
9522
9523 self.render_edit_prediction_cursor_popover_preview(
9524 prediction,
9525 cursor_point,
9526 style,
9527 cx,
9528 )?
9529 }
9530
9531 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9532 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9533 stale_completion,
9534 cursor_point,
9535 style,
9536 cx,
9537 )?,
9538
9539 None => pending_completion_container(provider_icon)
9540 .child(Label::new("...").size(LabelSize::Small)),
9541 },
9542
9543 None => pending_completion_container(provider_icon)
9544 .child(Label::new("...").size(LabelSize::Small)),
9545 };
9546
9547 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9548 completion
9549 .with_animation(
9550 "loading-completion",
9551 Animation::new(Duration::from_secs(2))
9552 .repeat()
9553 .with_easing(pulsating_between(0.4, 0.8)),
9554 |label, delta| label.opacity(delta),
9555 )
9556 .into_any_element()
9557 } else {
9558 completion.into_any_element()
9559 };
9560
9561 let has_completion = self.active_edit_prediction.is_some();
9562
9563 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9564 Some(
9565 h_flex()
9566 .min_w(min_width)
9567 .max_w(max_width)
9568 .flex_1()
9569 .elevation_2(cx)
9570 .border_color(cx.theme().colors().border)
9571 .child(
9572 div()
9573 .flex_1()
9574 .py_1()
9575 .px_2()
9576 .overflow_hidden()
9577 .child(completion),
9578 )
9579 .when_some(accept_keystroke, |el, accept_keystroke| {
9580 if !accept_keystroke.modifiers().modified() {
9581 return el;
9582 }
9583
9584 el.child(
9585 h_flex()
9586 .h_full()
9587 .border_l_1()
9588 .rounded_r_lg()
9589 .border_color(cx.theme().colors().border)
9590 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9591 .gap_1()
9592 .py_1()
9593 .px_2()
9594 .child(
9595 h_flex()
9596 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9597 .when(is_platform_style_mac, |parent| parent.gap_1())
9598 .child(h_flex().children(ui::render_modifiers(
9599 accept_keystroke.modifiers(),
9600 PlatformStyle::platform(),
9601 Some(if !has_completion {
9602 Color::Muted
9603 } else {
9604 Color::Default
9605 }),
9606 None,
9607 false,
9608 ))),
9609 )
9610 .child(Label::new("Preview").into_any_element())
9611 .opacity(if has_completion { 1.0 } else { 0.4 }),
9612 )
9613 })
9614 .into_any(),
9615 )
9616 }
9617
9618 fn render_edit_prediction_cursor_popover_preview(
9619 &self,
9620 completion: &EditPredictionState,
9621 cursor_point: Point,
9622 style: &EditorStyle,
9623 cx: &mut Context<Editor>,
9624 ) -> Option<Div> {
9625 use text::ToPoint as _;
9626
9627 fn render_relative_row_jump(
9628 prefix: impl Into<String>,
9629 current_row: u32,
9630 target_row: u32,
9631 ) -> Div {
9632 let (row_diff, arrow) = if target_row < current_row {
9633 (current_row - target_row, IconName::ArrowUp)
9634 } else {
9635 (target_row - current_row, IconName::ArrowDown)
9636 };
9637
9638 h_flex()
9639 .child(
9640 Label::new(format!("{}{}", prefix.into(), row_diff))
9641 .color(Color::Muted)
9642 .size(LabelSize::Small),
9643 )
9644 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9645 }
9646
9647 let supports_jump = self
9648 .edit_prediction_provider
9649 .as_ref()
9650 .map(|provider| provider.provider.supports_jump_to_edit())
9651 .unwrap_or(true);
9652
9653 match &completion.completion {
9654 EditPrediction::MoveWithin {
9655 target, snapshot, ..
9656 } => {
9657 if !supports_jump {
9658 return None;
9659 }
9660
9661 Some(
9662 h_flex()
9663 .px_2()
9664 .gap_2()
9665 .flex_1()
9666 .child(
9667 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9668 Icon::new(IconName::ZedPredictDown)
9669 } else {
9670 Icon::new(IconName::ZedPredictUp)
9671 },
9672 )
9673 .child(Label::new("Jump to Edit")),
9674 )
9675 }
9676 EditPrediction::MoveOutside { snapshot, .. } => {
9677 let file_name = snapshot
9678 .file()
9679 .map(|file| file.file_name(cx))
9680 .unwrap_or("untitled");
9681 Some(
9682 h_flex()
9683 .px_2()
9684 .gap_2()
9685 .flex_1()
9686 .child(Icon::new(IconName::ZedPredict))
9687 .child(Label::new(format!("Jump to {file_name}"))),
9688 )
9689 }
9690 EditPrediction::Edit {
9691 edits,
9692 edit_preview,
9693 snapshot,
9694 display_mode: _,
9695 } => {
9696 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9697
9698 let (highlighted_edits, has_more_lines) =
9699 if let Some(edit_preview) = edit_preview.as_ref() {
9700 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9701 .first_line_preview()
9702 } else {
9703 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9704 };
9705
9706 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9707 .with_default_highlights(&style.text, highlighted_edits.highlights);
9708
9709 let preview = h_flex()
9710 .gap_1()
9711 .min_w_16()
9712 .child(styled_text)
9713 .when(has_more_lines, |parent| parent.child("…"));
9714
9715 let left = if supports_jump && first_edit_row != cursor_point.row {
9716 render_relative_row_jump("", cursor_point.row, first_edit_row)
9717 .into_any_element()
9718 } else {
9719 let icon_name =
9720 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9721 Icon::new(icon_name).into_any_element()
9722 };
9723
9724 Some(
9725 h_flex()
9726 .h_full()
9727 .flex_1()
9728 .gap_2()
9729 .pr_1()
9730 .overflow_x_hidden()
9731 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9732 .child(left)
9733 .child(preview),
9734 )
9735 }
9736 }
9737 }
9738
9739 pub fn render_context_menu(
9740 &self,
9741 style: &EditorStyle,
9742 max_height_in_lines: u32,
9743 window: &mut Window,
9744 cx: &mut Context<Editor>,
9745 ) -> Option<AnyElement> {
9746 let menu = self.context_menu.borrow();
9747 let menu = menu.as_ref()?;
9748 if !menu.visible() {
9749 return None;
9750 };
9751 Some(menu.render(style, max_height_in_lines, window, cx))
9752 }
9753
9754 fn render_context_menu_aside(
9755 &mut self,
9756 max_size: Size<Pixels>,
9757 window: &mut Window,
9758 cx: &mut Context<Editor>,
9759 ) -> Option<AnyElement> {
9760 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9761 if menu.visible() {
9762 menu.render_aside(max_size, window, cx)
9763 } else {
9764 None
9765 }
9766 })
9767 }
9768
9769 fn hide_context_menu(
9770 &mut self,
9771 window: &mut Window,
9772 cx: &mut Context<Self>,
9773 ) -> Option<CodeContextMenu> {
9774 cx.notify();
9775 self.completion_tasks.clear();
9776 let context_menu = self.context_menu.borrow_mut().take();
9777 self.stale_edit_prediction_in_menu.take();
9778 self.update_visible_edit_prediction(window, cx);
9779 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9780 && let Some(completion_provider) = &self.completion_provider
9781 {
9782 completion_provider.selection_changed(None, window, cx);
9783 }
9784 context_menu
9785 }
9786
9787 fn show_snippet_choices(
9788 &mut self,
9789 choices: &Vec<String>,
9790 selection: Range<Anchor>,
9791 cx: &mut Context<Self>,
9792 ) {
9793 let Some((_, buffer, _)) = self
9794 .buffer()
9795 .read(cx)
9796 .excerpt_containing(selection.start, cx)
9797 else {
9798 return;
9799 };
9800 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9801 else {
9802 return;
9803 };
9804 if buffer != end_buffer {
9805 log::error!("expected anchor range to have matching buffer IDs");
9806 return;
9807 }
9808
9809 let id = post_inc(&mut self.next_completion_id);
9810 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9811 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9812 CompletionsMenu::new_snippet_choices(
9813 id,
9814 true,
9815 choices,
9816 selection,
9817 buffer,
9818 snippet_sort_order,
9819 ),
9820 ));
9821 }
9822
9823 pub fn insert_snippet(
9824 &mut self,
9825 insertion_ranges: &[Range<MultiBufferOffset>],
9826 snippet: Snippet,
9827 window: &mut Window,
9828 cx: &mut Context<Self>,
9829 ) -> Result<()> {
9830 struct Tabstop<T> {
9831 is_end_tabstop: bool,
9832 ranges: Vec<Range<T>>,
9833 choices: Option<Vec<String>>,
9834 }
9835
9836 let tabstops = self.buffer.update(cx, |buffer, cx| {
9837 let snippet_text: Arc<str> = snippet.text.clone().into();
9838 let edits = insertion_ranges
9839 .iter()
9840 .cloned()
9841 .map(|range| (range, snippet_text.clone()));
9842 let autoindent_mode = AutoindentMode::Block {
9843 original_indent_columns: Vec::new(),
9844 };
9845 buffer.edit(edits, Some(autoindent_mode), cx);
9846
9847 let snapshot = &*buffer.read(cx);
9848 let snippet = &snippet;
9849 snippet
9850 .tabstops
9851 .iter()
9852 .map(|tabstop| {
9853 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9854 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9855 });
9856 let mut tabstop_ranges = tabstop
9857 .ranges
9858 .iter()
9859 .flat_map(|tabstop_range| {
9860 let mut delta = 0_isize;
9861 insertion_ranges.iter().map(move |insertion_range| {
9862 let insertion_start = insertion_range.start + delta;
9863 delta += snippet.text.len() as isize
9864 - (insertion_range.end - insertion_range.start) as isize;
9865
9866 let start =
9867 (insertion_start + tabstop_range.start).min(snapshot.len());
9868 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
9869 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9870 })
9871 })
9872 .collect::<Vec<_>>();
9873 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9874
9875 Tabstop {
9876 is_end_tabstop,
9877 ranges: tabstop_ranges,
9878 choices: tabstop.choices.clone(),
9879 }
9880 })
9881 .collect::<Vec<_>>()
9882 });
9883 if let Some(tabstop) = tabstops.first() {
9884 self.change_selections(Default::default(), window, cx, |s| {
9885 // Reverse order so that the first range is the newest created selection.
9886 // Completions will use it and autoscroll will prioritize it.
9887 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9888 });
9889
9890 if let Some(choices) = &tabstop.choices
9891 && let Some(selection) = tabstop.ranges.first()
9892 {
9893 self.show_snippet_choices(choices, selection.clone(), cx)
9894 }
9895
9896 // If we're already at the last tabstop and it's at the end of the snippet,
9897 // we're done, we don't need to keep the state around.
9898 if !tabstop.is_end_tabstop {
9899 let choices = tabstops
9900 .iter()
9901 .map(|tabstop| tabstop.choices.clone())
9902 .collect();
9903
9904 let ranges = tabstops
9905 .into_iter()
9906 .map(|tabstop| tabstop.ranges)
9907 .collect::<Vec<_>>();
9908
9909 self.snippet_stack.push(SnippetState {
9910 active_index: 0,
9911 ranges,
9912 choices,
9913 });
9914 }
9915
9916 // Check whether the just-entered snippet ends with an auto-closable bracket.
9917 if self.autoclose_regions.is_empty() {
9918 let snapshot = self.buffer.read(cx).snapshot(cx);
9919 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9920 let selection_head = selection.head();
9921 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9922 continue;
9923 };
9924
9925 let mut bracket_pair = None;
9926 let max_lookup_length = scope
9927 .brackets()
9928 .map(|(pair, _)| {
9929 pair.start
9930 .as_str()
9931 .chars()
9932 .count()
9933 .max(pair.end.as_str().chars().count())
9934 })
9935 .max();
9936 if let Some(max_lookup_length) = max_lookup_length {
9937 let next_text = snapshot
9938 .chars_at(selection_head)
9939 .take(max_lookup_length)
9940 .collect::<String>();
9941 let prev_text = snapshot
9942 .reversed_chars_at(selection_head)
9943 .take(max_lookup_length)
9944 .collect::<String>();
9945
9946 for (pair, enabled) in scope.brackets() {
9947 if enabled
9948 && pair.close
9949 && prev_text.starts_with(pair.start.as_str())
9950 && next_text.starts_with(pair.end.as_str())
9951 {
9952 bracket_pair = Some(pair.clone());
9953 break;
9954 }
9955 }
9956 }
9957
9958 if let Some(pair) = bracket_pair {
9959 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9960 let autoclose_enabled =
9961 self.use_autoclose && snapshot_settings.use_autoclose;
9962 if autoclose_enabled {
9963 let start = snapshot.anchor_after(selection_head);
9964 let end = snapshot.anchor_after(selection_head);
9965 self.autoclose_regions.push(AutocloseRegion {
9966 selection_id: selection.id,
9967 range: start..end,
9968 pair,
9969 });
9970 }
9971 }
9972 }
9973 }
9974 }
9975 Ok(())
9976 }
9977
9978 pub fn move_to_next_snippet_tabstop(
9979 &mut self,
9980 window: &mut Window,
9981 cx: &mut Context<Self>,
9982 ) -> bool {
9983 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9984 }
9985
9986 pub fn move_to_prev_snippet_tabstop(
9987 &mut self,
9988 window: &mut Window,
9989 cx: &mut Context<Self>,
9990 ) -> bool {
9991 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9992 }
9993
9994 pub fn move_to_snippet_tabstop(
9995 &mut self,
9996 bias: Bias,
9997 window: &mut Window,
9998 cx: &mut Context<Self>,
9999 ) -> bool {
10000 if let Some(mut snippet) = self.snippet_stack.pop() {
10001 match bias {
10002 Bias::Left => {
10003 if snippet.active_index > 0 {
10004 snippet.active_index -= 1;
10005 } else {
10006 self.snippet_stack.push(snippet);
10007 return false;
10008 }
10009 }
10010 Bias::Right => {
10011 if snippet.active_index + 1 < snippet.ranges.len() {
10012 snippet.active_index += 1;
10013 } else {
10014 self.snippet_stack.push(snippet);
10015 return false;
10016 }
10017 }
10018 }
10019 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10020 self.change_selections(Default::default(), window, cx, |s| {
10021 // Reverse order so that the first range is the newest created selection.
10022 // Completions will use it and autoscroll will prioritize it.
10023 s.select_ranges(current_ranges.iter().rev().cloned())
10024 });
10025
10026 if let Some(choices) = &snippet.choices[snippet.active_index]
10027 && let Some(selection) = current_ranges.first()
10028 {
10029 self.show_snippet_choices(choices, selection.clone(), cx);
10030 }
10031
10032 // If snippet state is not at the last tabstop, push it back on the stack
10033 if snippet.active_index + 1 < snippet.ranges.len() {
10034 self.snippet_stack.push(snippet);
10035 }
10036 return true;
10037 }
10038 }
10039
10040 false
10041 }
10042
10043 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10044 self.transact(window, cx, |this, window, cx| {
10045 this.select_all(&SelectAll, window, cx);
10046 this.insert("", window, cx);
10047 });
10048 }
10049
10050 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10051 if self.read_only(cx) {
10052 return;
10053 }
10054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10055 self.transact(window, cx, |this, window, cx| {
10056 this.select_autoclose_pair(window, cx);
10057
10058 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10059
10060 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10061 if !this.linked_edit_ranges.is_empty() {
10062 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10063 let snapshot = this.buffer.read(cx).snapshot(cx);
10064
10065 for selection in selections.iter() {
10066 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10067 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10068 if selection_start.buffer_id != selection_end.buffer_id {
10069 continue;
10070 }
10071 if let Some(ranges) =
10072 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10073 {
10074 for (buffer, entries) in ranges {
10075 linked_ranges.entry(buffer).or_default().extend(entries);
10076 }
10077 }
10078 }
10079 }
10080
10081 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10082 for selection in &mut selections {
10083 if selection.is_empty() {
10084 let old_head = selection.head();
10085 let mut new_head =
10086 movement::left(&display_map, old_head.to_display_point(&display_map))
10087 .to_point(&display_map);
10088 if let Some((buffer, line_buffer_range)) = display_map
10089 .buffer_snapshot()
10090 .buffer_line_for_row(MultiBufferRow(old_head.row))
10091 {
10092 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10093 let indent_len = match indent_size.kind {
10094 IndentKind::Space => {
10095 buffer.settings_at(line_buffer_range.start, cx).tab_size
10096 }
10097 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10098 };
10099 if old_head.column <= indent_size.len && old_head.column > 0 {
10100 let indent_len = indent_len.get();
10101 new_head = cmp::min(
10102 new_head,
10103 MultiBufferPoint::new(
10104 old_head.row,
10105 ((old_head.column - 1) / indent_len) * indent_len,
10106 ),
10107 );
10108 }
10109 }
10110
10111 selection.set_head(new_head, SelectionGoal::None);
10112 }
10113 }
10114
10115 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10116 this.insert("", window, cx);
10117 let empty_str: Arc<str> = Arc::from("");
10118 for (buffer, edits) in linked_ranges {
10119 let snapshot = buffer.read(cx).snapshot();
10120 use text::ToPoint as TP;
10121
10122 let edits = edits
10123 .into_iter()
10124 .map(|range| {
10125 let end_point = TP::to_point(&range.end, &snapshot);
10126 let mut start_point = TP::to_point(&range.start, &snapshot);
10127
10128 if end_point == start_point {
10129 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10130 .saturating_sub(1);
10131 start_point =
10132 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10133 };
10134
10135 (start_point..end_point, empty_str.clone())
10136 })
10137 .sorted_by_key(|(range, _)| range.start)
10138 .collect::<Vec<_>>();
10139 buffer.update(cx, |this, cx| {
10140 this.edit(edits, None, cx);
10141 })
10142 }
10143 this.refresh_edit_prediction(true, false, window, cx);
10144 refresh_linked_ranges(this, window, cx);
10145 });
10146 }
10147
10148 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10149 if self.read_only(cx) {
10150 return;
10151 }
10152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10153 self.transact(window, cx, |this, window, cx| {
10154 this.change_selections(Default::default(), window, cx, |s| {
10155 s.move_with(|map, selection| {
10156 if selection.is_empty() {
10157 let cursor = movement::right(map, selection.head());
10158 selection.end = cursor;
10159 selection.reversed = true;
10160 selection.goal = SelectionGoal::None;
10161 }
10162 })
10163 });
10164 this.insert("", window, cx);
10165 this.refresh_edit_prediction(true, false, window, cx);
10166 });
10167 }
10168
10169 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10170 if self.mode.is_single_line() {
10171 cx.propagate();
10172 return;
10173 }
10174
10175 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10176 if self.move_to_prev_snippet_tabstop(window, cx) {
10177 return;
10178 }
10179 self.outdent(&Outdent, window, cx);
10180 }
10181
10182 pub fn next_snippet_tabstop(
10183 &mut self,
10184 _: &NextSnippetTabstop,
10185 window: &mut Window,
10186 cx: &mut Context<Self>,
10187 ) {
10188 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10189 cx.propagate();
10190 return;
10191 }
10192
10193 if self.move_to_next_snippet_tabstop(window, cx) {
10194 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10195 return;
10196 }
10197 cx.propagate();
10198 }
10199
10200 pub fn previous_snippet_tabstop(
10201 &mut self,
10202 _: &PreviousSnippetTabstop,
10203 window: &mut Window,
10204 cx: &mut Context<Self>,
10205 ) {
10206 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10207 cx.propagate();
10208 return;
10209 }
10210
10211 if self.move_to_prev_snippet_tabstop(window, cx) {
10212 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10213 return;
10214 }
10215 cx.propagate();
10216 }
10217
10218 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10219 if self.mode.is_single_line() {
10220 cx.propagate();
10221 return;
10222 }
10223
10224 if self.move_to_next_snippet_tabstop(window, cx) {
10225 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10226 return;
10227 }
10228 if self.read_only(cx) {
10229 return;
10230 }
10231 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10232 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10233 let buffer = self.buffer.read(cx);
10234 let snapshot = buffer.snapshot(cx);
10235 let rows_iter = selections.iter().map(|s| s.head().row);
10236 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10237
10238 let has_some_cursor_in_whitespace = selections
10239 .iter()
10240 .filter(|selection| selection.is_empty())
10241 .any(|selection| {
10242 let cursor = selection.head();
10243 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10244 cursor.column < current_indent.len
10245 });
10246
10247 let mut edits = Vec::new();
10248 let mut prev_edited_row = 0;
10249 let mut row_delta = 0;
10250 for selection in &mut selections {
10251 if selection.start.row != prev_edited_row {
10252 row_delta = 0;
10253 }
10254 prev_edited_row = selection.end.row;
10255
10256 // If the selection is non-empty, then increase the indentation of the selected lines.
10257 if !selection.is_empty() {
10258 row_delta =
10259 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10260 continue;
10261 }
10262
10263 let cursor = selection.head();
10264 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10265 if let Some(suggested_indent) =
10266 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10267 {
10268 // Don't do anything if already at suggested indent
10269 // and there is any other cursor which is not
10270 if has_some_cursor_in_whitespace
10271 && cursor.column == current_indent.len
10272 && current_indent.len == suggested_indent.len
10273 {
10274 continue;
10275 }
10276
10277 // Adjust line and move cursor to suggested indent
10278 // if cursor is not at suggested indent
10279 if cursor.column < suggested_indent.len
10280 && cursor.column <= current_indent.len
10281 && current_indent.len <= suggested_indent.len
10282 {
10283 selection.start = Point::new(cursor.row, suggested_indent.len);
10284 selection.end = selection.start;
10285 if row_delta == 0 {
10286 edits.extend(Buffer::edit_for_indent_size_adjustment(
10287 cursor.row,
10288 current_indent,
10289 suggested_indent,
10290 ));
10291 row_delta = suggested_indent.len - current_indent.len;
10292 }
10293 continue;
10294 }
10295
10296 // If current indent is more than suggested indent
10297 // only move cursor to current indent and skip indent
10298 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10299 selection.start = Point::new(cursor.row, current_indent.len);
10300 selection.end = selection.start;
10301 continue;
10302 }
10303 }
10304
10305 // Otherwise, insert a hard or soft tab.
10306 let settings = buffer.language_settings_at(cursor, cx);
10307 let tab_size = if settings.hard_tabs {
10308 IndentSize::tab()
10309 } else {
10310 let tab_size = settings.tab_size.get();
10311 let indent_remainder = snapshot
10312 .text_for_range(Point::new(cursor.row, 0)..cursor)
10313 .flat_map(str::chars)
10314 .fold(row_delta % tab_size, |counter: u32, c| {
10315 if c == '\t' {
10316 0
10317 } else {
10318 (counter + 1) % tab_size
10319 }
10320 });
10321
10322 let chars_to_next_tab_stop = tab_size - indent_remainder;
10323 IndentSize::spaces(chars_to_next_tab_stop)
10324 };
10325 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10326 selection.end = selection.start;
10327 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10328 row_delta += tab_size.len;
10329 }
10330
10331 self.transact(window, cx, |this, window, cx| {
10332 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10333 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10334 this.refresh_edit_prediction(true, false, window, cx);
10335 });
10336 }
10337
10338 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10339 if self.read_only(cx) {
10340 return;
10341 }
10342 if self.mode.is_single_line() {
10343 cx.propagate();
10344 return;
10345 }
10346
10347 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10348 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10349 let mut prev_edited_row = 0;
10350 let mut row_delta = 0;
10351 let mut edits = Vec::new();
10352 let buffer = self.buffer.read(cx);
10353 let snapshot = buffer.snapshot(cx);
10354 for selection in &mut selections {
10355 if selection.start.row != prev_edited_row {
10356 row_delta = 0;
10357 }
10358 prev_edited_row = selection.end.row;
10359
10360 row_delta =
10361 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10362 }
10363
10364 self.transact(window, cx, |this, window, cx| {
10365 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10366 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10367 });
10368 }
10369
10370 fn indent_selection(
10371 buffer: &MultiBuffer,
10372 snapshot: &MultiBufferSnapshot,
10373 selection: &mut Selection<Point>,
10374 edits: &mut Vec<(Range<Point>, String)>,
10375 delta_for_start_row: u32,
10376 cx: &App,
10377 ) -> u32 {
10378 let settings = buffer.language_settings_at(selection.start, cx);
10379 let tab_size = settings.tab_size.get();
10380 let indent_kind = if settings.hard_tabs {
10381 IndentKind::Tab
10382 } else {
10383 IndentKind::Space
10384 };
10385 let mut start_row = selection.start.row;
10386 let mut end_row = selection.end.row + 1;
10387
10388 // If a selection ends at the beginning of a line, don't indent
10389 // that last line.
10390 if selection.end.column == 0 && selection.end.row > selection.start.row {
10391 end_row -= 1;
10392 }
10393
10394 // Avoid re-indenting a row that has already been indented by a
10395 // previous selection, but still update this selection's column
10396 // to reflect that indentation.
10397 if delta_for_start_row > 0 {
10398 start_row += 1;
10399 selection.start.column += delta_for_start_row;
10400 if selection.end.row == selection.start.row {
10401 selection.end.column += delta_for_start_row;
10402 }
10403 }
10404
10405 let mut delta_for_end_row = 0;
10406 let has_multiple_rows = start_row + 1 != end_row;
10407 for row in start_row..end_row {
10408 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10409 let indent_delta = match (current_indent.kind, indent_kind) {
10410 (IndentKind::Space, IndentKind::Space) => {
10411 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10412 IndentSize::spaces(columns_to_next_tab_stop)
10413 }
10414 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10415 (_, IndentKind::Tab) => IndentSize::tab(),
10416 };
10417
10418 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10419 0
10420 } else {
10421 selection.start.column
10422 };
10423 let row_start = Point::new(row, start);
10424 edits.push((
10425 row_start..row_start,
10426 indent_delta.chars().collect::<String>(),
10427 ));
10428
10429 // Update this selection's endpoints to reflect the indentation.
10430 if row == selection.start.row {
10431 selection.start.column += indent_delta.len;
10432 }
10433 if row == selection.end.row {
10434 selection.end.column += indent_delta.len;
10435 delta_for_end_row = indent_delta.len;
10436 }
10437 }
10438
10439 if selection.start.row == selection.end.row {
10440 delta_for_start_row + delta_for_end_row
10441 } else {
10442 delta_for_end_row
10443 }
10444 }
10445
10446 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10447 if self.read_only(cx) {
10448 return;
10449 }
10450 if self.mode.is_single_line() {
10451 cx.propagate();
10452 return;
10453 }
10454
10455 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10456 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10457 let selections = self.selections.all::<Point>(&display_map);
10458 let mut deletion_ranges = Vec::new();
10459 let mut last_outdent = None;
10460 {
10461 let buffer = self.buffer.read(cx);
10462 let snapshot = buffer.snapshot(cx);
10463 for selection in &selections {
10464 let settings = buffer.language_settings_at(selection.start, cx);
10465 let tab_size = settings.tab_size.get();
10466 let mut rows = selection.spanned_rows(false, &display_map);
10467
10468 // Avoid re-outdenting a row that has already been outdented by a
10469 // previous selection.
10470 if let Some(last_row) = last_outdent
10471 && last_row == rows.start
10472 {
10473 rows.start = rows.start.next_row();
10474 }
10475 let has_multiple_rows = rows.len() > 1;
10476 for row in rows.iter_rows() {
10477 let indent_size = snapshot.indent_size_for_line(row);
10478 if indent_size.len > 0 {
10479 let deletion_len = match indent_size.kind {
10480 IndentKind::Space => {
10481 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10482 if columns_to_prev_tab_stop == 0 {
10483 tab_size
10484 } else {
10485 columns_to_prev_tab_stop
10486 }
10487 }
10488 IndentKind::Tab => 1,
10489 };
10490 let start = if has_multiple_rows
10491 || deletion_len > selection.start.column
10492 || indent_size.len < selection.start.column
10493 {
10494 0
10495 } else {
10496 selection.start.column - deletion_len
10497 };
10498 deletion_ranges.push(
10499 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10500 );
10501 last_outdent = Some(row);
10502 }
10503 }
10504 }
10505 }
10506
10507 self.transact(window, cx, |this, window, cx| {
10508 this.buffer.update(cx, |buffer, cx| {
10509 let empty_str: Arc<str> = Arc::default();
10510 buffer.edit(
10511 deletion_ranges
10512 .into_iter()
10513 .map(|range| (range, empty_str.clone())),
10514 None,
10515 cx,
10516 );
10517 });
10518 let selections = this
10519 .selections
10520 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10521 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10522 });
10523 }
10524
10525 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10526 if self.read_only(cx) {
10527 return;
10528 }
10529 if self.mode.is_single_line() {
10530 cx.propagate();
10531 return;
10532 }
10533
10534 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10535 let selections = self
10536 .selections
10537 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10538 .into_iter()
10539 .map(|s| s.range());
10540
10541 self.transact(window, cx, |this, window, cx| {
10542 this.buffer.update(cx, |buffer, cx| {
10543 buffer.autoindent_ranges(selections, cx);
10544 });
10545 let selections = this
10546 .selections
10547 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10548 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10549 });
10550 }
10551
10552 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10553 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10554 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10555 let selections = self.selections.all::<Point>(&display_map);
10556
10557 let mut new_cursors = Vec::new();
10558 let mut edit_ranges = Vec::new();
10559 let mut selections = selections.iter().peekable();
10560 while let Some(selection) = selections.next() {
10561 let mut rows = selection.spanned_rows(false, &display_map);
10562
10563 // Accumulate contiguous regions of rows that we want to delete.
10564 while let Some(next_selection) = selections.peek() {
10565 let next_rows = next_selection.spanned_rows(false, &display_map);
10566 if next_rows.start <= rows.end {
10567 rows.end = next_rows.end;
10568 selections.next().unwrap();
10569 } else {
10570 break;
10571 }
10572 }
10573
10574 let buffer = display_map.buffer_snapshot();
10575 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10576 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10577 // If there's a line after the range, delete the \n from the end of the row range
10578 (
10579 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10580 rows.end,
10581 )
10582 } else {
10583 // If there isn't a line after the range, delete the \n from the line before the
10584 // start of the row range
10585 edit_start = edit_start.saturating_sub_usize(1);
10586 (buffer.len(), rows.start.previous_row())
10587 };
10588
10589 let text_layout_details = self.text_layout_details(window);
10590 let x = display_map.x_for_display_point(
10591 selection.head().to_display_point(&display_map),
10592 &text_layout_details,
10593 );
10594 let row = Point::new(target_row.0, 0)
10595 .to_display_point(&display_map)
10596 .row();
10597 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10598
10599 new_cursors.push((
10600 selection.id,
10601 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10602 SelectionGoal::None,
10603 ));
10604 edit_ranges.push(edit_start..edit_end);
10605 }
10606
10607 self.transact(window, cx, |this, window, cx| {
10608 let buffer = this.buffer.update(cx, |buffer, cx| {
10609 let empty_str: Arc<str> = Arc::default();
10610 buffer.edit(
10611 edit_ranges
10612 .into_iter()
10613 .map(|range| (range, empty_str.clone())),
10614 None,
10615 cx,
10616 );
10617 buffer.snapshot(cx)
10618 });
10619 let new_selections = new_cursors
10620 .into_iter()
10621 .map(|(id, cursor, goal)| {
10622 let cursor = cursor.to_point(&buffer);
10623 Selection {
10624 id,
10625 start: cursor,
10626 end: cursor,
10627 reversed: false,
10628 goal,
10629 }
10630 })
10631 .collect();
10632
10633 this.change_selections(Default::default(), window, cx, |s| {
10634 s.select(new_selections);
10635 });
10636 });
10637 }
10638
10639 pub fn join_lines_impl(
10640 &mut self,
10641 insert_whitespace: bool,
10642 window: &mut Window,
10643 cx: &mut Context<Self>,
10644 ) {
10645 if self.read_only(cx) {
10646 return;
10647 }
10648 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10649 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10650 let start = MultiBufferRow(selection.start.row);
10651 // Treat single line selections as if they include the next line. Otherwise this action
10652 // would do nothing for single line selections individual cursors.
10653 let end = if selection.start.row == selection.end.row {
10654 MultiBufferRow(selection.start.row + 1)
10655 } else {
10656 MultiBufferRow(selection.end.row)
10657 };
10658
10659 if let Some(last_row_range) = row_ranges.last_mut()
10660 && start <= last_row_range.end
10661 {
10662 last_row_range.end = end;
10663 continue;
10664 }
10665 row_ranges.push(start..end);
10666 }
10667
10668 let snapshot = self.buffer.read(cx).snapshot(cx);
10669 let mut cursor_positions = Vec::new();
10670 for row_range in &row_ranges {
10671 let anchor = snapshot.anchor_before(Point::new(
10672 row_range.end.previous_row().0,
10673 snapshot.line_len(row_range.end.previous_row()),
10674 ));
10675 cursor_positions.push(anchor..anchor);
10676 }
10677
10678 self.transact(window, cx, |this, window, cx| {
10679 for row_range in row_ranges.into_iter().rev() {
10680 for row in row_range.iter_rows().rev() {
10681 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10682 let next_line_row = row.next_row();
10683 let indent = snapshot.indent_size_for_line(next_line_row);
10684 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10685
10686 let replace =
10687 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10688 " "
10689 } else {
10690 ""
10691 };
10692
10693 this.buffer.update(cx, |buffer, cx| {
10694 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10695 });
10696 }
10697 }
10698
10699 this.change_selections(Default::default(), window, cx, |s| {
10700 s.select_anchor_ranges(cursor_positions)
10701 });
10702 });
10703 }
10704
10705 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10706 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10707 self.join_lines_impl(true, window, cx);
10708 }
10709
10710 pub fn sort_lines_case_sensitive(
10711 &mut self,
10712 _: &SortLinesCaseSensitive,
10713 window: &mut Window,
10714 cx: &mut Context<Self>,
10715 ) {
10716 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10717 }
10718
10719 pub fn sort_lines_by_length(
10720 &mut self,
10721 _: &SortLinesByLength,
10722 window: &mut Window,
10723 cx: &mut Context<Self>,
10724 ) {
10725 self.manipulate_immutable_lines(window, cx, |lines| {
10726 lines.sort_by_key(|&line| line.chars().count())
10727 })
10728 }
10729
10730 pub fn sort_lines_case_insensitive(
10731 &mut self,
10732 _: &SortLinesCaseInsensitive,
10733 window: &mut Window,
10734 cx: &mut Context<Self>,
10735 ) {
10736 self.manipulate_immutable_lines(window, cx, |lines| {
10737 lines.sort_by_key(|line| line.to_lowercase())
10738 })
10739 }
10740
10741 pub fn unique_lines_case_insensitive(
10742 &mut self,
10743 _: &UniqueLinesCaseInsensitive,
10744 window: &mut Window,
10745 cx: &mut Context<Self>,
10746 ) {
10747 self.manipulate_immutable_lines(window, cx, |lines| {
10748 let mut seen = HashSet::default();
10749 lines.retain(|line| seen.insert(line.to_lowercase()));
10750 })
10751 }
10752
10753 pub fn unique_lines_case_sensitive(
10754 &mut self,
10755 _: &UniqueLinesCaseSensitive,
10756 window: &mut Window,
10757 cx: &mut Context<Self>,
10758 ) {
10759 self.manipulate_immutable_lines(window, cx, |lines| {
10760 let mut seen = HashSet::default();
10761 lines.retain(|line| seen.insert(*line));
10762 })
10763 }
10764
10765 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10766 let snapshot = self.buffer.read(cx).snapshot(cx);
10767 for selection in self.selections.disjoint_anchors_arc().iter() {
10768 if snapshot
10769 .language_at(selection.start)
10770 .and_then(|lang| lang.config().wrap_characters.as_ref())
10771 .is_some()
10772 {
10773 return true;
10774 }
10775 }
10776 false
10777 }
10778
10779 fn wrap_selections_in_tag(
10780 &mut self,
10781 _: &WrapSelectionsInTag,
10782 window: &mut Window,
10783 cx: &mut Context<Self>,
10784 ) {
10785 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10786
10787 let snapshot = self.buffer.read(cx).snapshot(cx);
10788
10789 let mut edits = Vec::new();
10790 let mut boundaries = Vec::new();
10791
10792 for selection in self
10793 .selections
10794 .all_adjusted(&self.display_snapshot(cx))
10795 .iter()
10796 {
10797 let Some(wrap_config) = snapshot
10798 .language_at(selection.start)
10799 .and_then(|lang| lang.config().wrap_characters.clone())
10800 else {
10801 continue;
10802 };
10803
10804 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10805 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10806
10807 let start_before = snapshot.anchor_before(selection.start);
10808 let end_after = snapshot.anchor_after(selection.end);
10809
10810 edits.push((start_before..start_before, open_tag));
10811 edits.push((end_after..end_after, close_tag));
10812
10813 boundaries.push((
10814 start_before,
10815 end_after,
10816 wrap_config.start_prefix.len(),
10817 wrap_config.end_suffix.len(),
10818 ));
10819 }
10820
10821 if edits.is_empty() {
10822 return;
10823 }
10824
10825 self.transact(window, cx, |this, window, cx| {
10826 let buffer = this.buffer.update(cx, |buffer, cx| {
10827 buffer.edit(edits, None, cx);
10828 buffer.snapshot(cx)
10829 });
10830
10831 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10832 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10833 boundaries.into_iter()
10834 {
10835 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10836 let close_offset = end_after
10837 .to_offset(&buffer)
10838 .saturating_sub_usize(end_suffix_len);
10839 new_selections.push(open_offset..open_offset);
10840 new_selections.push(close_offset..close_offset);
10841 }
10842
10843 this.change_selections(Default::default(), window, cx, |s| {
10844 s.select_ranges(new_selections);
10845 });
10846
10847 this.request_autoscroll(Autoscroll::fit(), cx);
10848 });
10849 }
10850
10851 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10852 let Some(project) = self.project.clone() else {
10853 return;
10854 };
10855 self.reload(project, window, cx)
10856 .detach_and_notify_err(window, cx);
10857 }
10858
10859 pub fn restore_file(
10860 &mut self,
10861 _: &::git::RestoreFile,
10862 window: &mut Window,
10863 cx: &mut Context<Self>,
10864 ) {
10865 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10866 let mut buffer_ids = HashSet::default();
10867 let snapshot = self.buffer().read(cx).snapshot(cx);
10868 for selection in self
10869 .selections
10870 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10871 {
10872 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10873 }
10874
10875 let buffer = self.buffer().read(cx);
10876 let ranges = buffer_ids
10877 .into_iter()
10878 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10879 .collect::<Vec<_>>();
10880
10881 self.restore_hunks_in_ranges(ranges, window, cx);
10882 }
10883
10884 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10885 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10886 let selections = self
10887 .selections
10888 .all(&self.display_snapshot(cx))
10889 .into_iter()
10890 .map(|s| s.range())
10891 .collect();
10892 self.restore_hunks_in_ranges(selections, window, cx);
10893 }
10894
10895 pub fn restore_hunks_in_ranges(
10896 &mut self,
10897 ranges: Vec<Range<Point>>,
10898 window: &mut Window,
10899 cx: &mut Context<Editor>,
10900 ) {
10901 let mut revert_changes = HashMap::default();
10902 let chunk_by = self
10903 .snapshot(window, cx)
10904 .hunks_for_ranges(ranges)
10905 .into_iter()
10906 .chunk_by(|hunk| hunk.buffer_id);
10907 for (buffer_id, hunks) in &chunk_by {
10908 let hunks = hunks.collect::<Vec<_>>();
10909 for hunk in &hunks {
10910 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10911 }
10912 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10913 }
10914 drop(chunk_by);
10915 if !revert_changes.is_empty() {
10916 self.transact(window, cx, |editor, window, cx| {
10917 editor.restore(revert_changes, window, cx);
10918 });
10919 }
10920 }
10921
10922 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10923 if let Some(status) = self
10924 .addons
10925 .iter()
10926 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10927 {
10928 return Some(status);
10929 }
10930 self.project
10931 .as_ref()?
10932 .read(cx)
10933 .status_for_buffer_id(buffer_id, cx)
10934 }
10935
10936 pub fn open_active_item_in_terminal(
10937 &mut self,
10938 _: &OpenInTerminal,
10939 window: &mut Window,
10940 cx: &mut Context<Self>,
10941 ) {
10942 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10943 let project_path = buffer.read(cx).project_path(cx)?;
10944 let project = self.project()?.read(cx);
10945 let entry = project.entry_for_path(&project_path, cx)?;
10946 let parent = match &entry.canonical_path {
10947 Some(canonical_path) => canonical_path.to_path_buf(),
10948 None => project.absolute_path(&project_path, cx)?,
10949 }
10950 .parent()?
10951 .to_path_buf();
10952 Some(parent)
10953 }) {
10954 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10955 }
10956 }
10957
10958 fn set_breakpoint_context_menu(
10959 &mut self,
10960 display_row: DisplayRow,
10961 position: Option<Anchor>,
10962 clicked_point: gpui::Point<Pixels>,
10963 window: &mut Window,
10964 cx: &mut Context<Self>,
10965 ) {
10966 let source = self
10967 .buffer
10968 .read(cx)
10969 .snapshot(cx)
10970 .anchor_before(Point::new(display_row.0, 0u32));
10971
10972 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10973
10974 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10975 self,
10976 source,
10977 clicked_point,
10978 context_menu,
10979 window,
10980 cx,
10981 );
10982 }
10983
10984 fn add_edit_breakpoint_block(
10985 &mut self,
10986 anchor: Anchor,
10987 breakpoint: &Breakpoint,
10988 edit_action: BreakpointPromptEditAction,
10989 window: &mut Window,
10990 cx: &mut Context<Self>,
10991 ) {
10992 let weak_editor = cx.weak_entity();
10993 let bp_prompt = cx.new(|cx| {
10994 BreakpointPromptEditor::new(
10995 weak_editor,
10996 anchor,
10997 breakpoint.clone(),
10998 edit_action,
10999 window,
11000 cx,
11001 )
11002 });
11003
11004 let height = bp_prompt.update(cx, |this, cx| {
11005 this.prompt
11006 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11007 });
11008 let cloned_prompt = bp_prompt.clone();
11009 let blocks = vec![BlockProperties {
11010 style: BlockStyle::Sticky,
11011 placement: BlockPlacement::Above(anchor),
11012 height: Some(height),
11013 render: Arc::new(move |cx| {
11014 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11015 cloned_prompt.clone().into_any_element()
11016 }),
11017 priority: 0,
11018 }];
11019
11020 let focus_handle = bp_prompt.focus_handle(cx);
11021 window.focus(&focus_handle);
11022
11023 let block_ids = self.insert_blocks(blocks, None, cx);
11024 bp_prompt.update(cx, |prompt, _| {
11025 prompt.add_block_ids(block_ids);
11026 });
11027 }
11028
11029 pub(crate) fn breakpoint_at_row(
11030 &self,
11031 row: u32,
11032 window: &mut Window,
11033 cx: &mut Context<Self>,
11034 ) -> Option<(Anchor, Breakpoint)> {
11035 let snapshot = self.snapshot(window, cx);
11036 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11037
11038 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11039 }
11040
11041 pub(crate) fn breakpoint_at_anchor(
11042 &self,
11043 breakpoint_position: Anchor,
11044 snapshot: &EditorSnapshot,
11045 cx: &mut Context<Self>,
11046 ) -> Option<(Anchor, Breakpoint)> {
11047 let buffer = self
11048 .buffer
11049 .read(cx)
11050 .buffer_for_anchor(breakpoint_position, cx)?;
11051
11052 let enclosing_excerpt = breakpoint_position.excerpt_id;
11053 let buffer_snapshot = buffer.read(cx).snapshot();
11054
11055 let row = buffer_snapshot
11056 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11057 .row;
11058
11059 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11060 let anchor_end = snapshot
11061 .buffer_snapshot()
11062 .anchor_after(Point::new(row, line_len));
11063
11064 self.breakpoint_store
11065 .as_ref()?
11066 .read_with(cx, |breakpoint_store, cx| {
11067 breakpoint_store
11068 .breakpoints(
11069 &buffer,
11070 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11071 &buffer_snapshot,
11072 cx,
11073 )
11074 .next()
11075 .and_then(|(bp, _)| {
11076 let breakpoint_row = buffer_snapshot
11077 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11078 .row;
11079
11080 if breakpoint_row == row {
11081 snapshot
11082 .buffer_snapshot()
11083 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11084 .map(|position| (position, bp.bp.clone()))
11085 } else {
11086 None
11087 }
11088 })
11089 })
11090 }
11091
11092 pub fn edit_log_breakpoint(
11093 &mut self,
11094 _: &EditLogBreakpoint,
11095 window: &mut Window,
11096 cx: &mut Context<Self>,
11097 ) {
11098 if self.breakpoint_store.is_none() {
11099 return;
11100 }
11101
11102 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11103 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11104 message: None,
11105 state: BreakpointState::Enabled,
11106 condition: None,
11107 hit_condition: None,
11108 });
11109
11110 self.add_edit_breakpoint_block(
11111 anchor,
11112 &breakpoint,
11113 BreakpointPromptEditAction::Log,
11114 window,
11115 cx,
11116 );
11117 }
11118 }
11119
11120 fn breakpoints_at_cursors(
11121 &self,
11122 window: &mut Window,
11123 cx: &mut Context<Self>,
11124 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11125 let snapshot = self.snapshot(window, cx);
11126 let cursors = self
11127 .selections
11128 .disjoint_anchors_arc()
11129 .iter()
11130 .map(|selection| {
11131 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11132
11133 let breakpoint_position = self
11134 .breakpoint_at_row(cursor_position.row, window, cx)
11135 .map(|bp| bp.0)
11136 .unwrap_or_else(|| {
11137 snapshot
11138 .display_snapshot
11139 .buffer_snapshot()
11140 .anchor_after(Point::new(cursor_position.row, 0))
11141 });
11142
11143 let breakpoint = self
11144 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11145 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11146
11147 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11148 })
11149 // 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.
11150 .collect::<HashMap<Anchor, _>>();
11151
11152 cursors.into_iter().collect()
11153 }
11154
11155 pub fn enable_breakpoint(
11156 &mut self,
11157 _: &crate::actions::EnableBreakpoint,
11158 window: &mut Window,
11159 cx: &mut Context<Self>,
11160 ) {
11161 if self.breakpoint_store.is_none() {
11162 return;
11163 }
11164
11165 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11166 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11167 continue;
11168 };
11169 self.edit_breakpoint_at_anchor(
11170 anchor,
11171 breakpoint,
11172 BreakpointEditAction::InvertState,
11173 cx,
11174 );
11175 }
11176 }
11177
11178 pub fn disable_breakpoint(
11179 &mut self,
11180 _: &crate::actions::DisableBreakpoint,
11181 window: &mut Window,
11182 cx: &mut Context<Self>,
11183 ) {
11184 if self.breakpoint_store.is_none() {
11185 return;
11186 }
11187
11188 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11189 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11190 continue;
11191 };
11192 self.edit_breakpoint_at_anchor(
11193 anchor,
11194 breakpoint,
11195 BreakpointEditAction::InvertState,
11196 cx,
11197 );
11198 }
11199 }
11200
11201 pub fn toggle_breakpoint(
11202 &mut self,
11203 _: &crate::actions::ToggleBreakpoint,
11204 window: &mut Window,
11205 cx: &mut Context<Self>,
11206 ) {
11207 if self.breakpoint_store.is_none() {
11208 return;
11209 }
11210
11211 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11212 if let Some(breakpoint) = breakpoint {
11213 self.edit_breakpoint_at_anchor(
11214 anchor,
11215 breakpoint,
11216 BreakpointEditAction::Toggle,
11217 cx,
11218 );
11219 } else {
11220 self.edit_breakpoint_at_anchor(
11221 anchor,
11222 Breakpoint::new_standard(),
11223 BreakpointEditAction::Toggle,
11224 cx,
11225 );
11226 }
11227 }
11228 }
11229
11230 pub fn edit_breakpoint_at_anchor(
11231 &mut self,
11232 breakpoint_position: Anchor,
11233 breakpoint: Breakpoint,
11234 edit_action: BreakpointEditAction,
11235 cx: &mut Context<Self>,
11236 ) {
11237 let Some(breakpoint_store) = &self.breakpoint_store else {
11238 return;
11239 };
11240
11241 let Some(buffer) = self
11242 .buffer
11243 .read(cx)
11244 .buffer_for_anchor(breakpoint_position, cx)
11245 else {
11246 return;
11247 };
11248
11249 breakpoint_store.update(cx, |breakpoint_store, cx| {
11250 breakpoint_store.toggle_breakpoint(
11251 buffer,
11252 BreakpointWithPosition {
11253 position: breakpoint_position.text_anchor,
11254 bp: breakpoint,
11255 },
11256 edit_action,
11257 cx,
11258 );
11259 });
11260
11261 cx.notify();
11262 }
11263
11264 #[cfg(any(test, feature = "test-support"))]
11265 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11266 self.breakpoint_store.clone()
11267 }
11268
11269 pub fn prepare_restore_change(
11270 &self,
11271 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11272 hunk: &MultiBufferDiffHunk,
11273 cx: &mut App,
11274 ) -> Option<()> {
11275 if hunk.is_created_file() {
11276 return None;
11277 }
11278 let buffer = self.buffer.read(cx);
11279 let diff = buffer.diff_for(hunk.buffer_id)?;
11280 let buffer = buffer.buffer(hunk.buffer_id)?;
11281 let buffer = buffer.read(cx);
11282 let original_text = diff
11283 .read(cx)
11284 .base_text()
11285 .as_rope()
11286 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11287 let buffer_snapshot = buffer.snapshot();
11288 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11289 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11290 probe
11291 .0
11292 .start
11293 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11294 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11295 }) {
11296 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11297 Some(())
11298 } else {
11299 None
11300 }
11301 }
11302
11303 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11304 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11305 }
11306
11307 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11308 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11309 }
11310
11311 fn manipulate_lines<M>(
11312 &mut self,
11313 window: &mut Window,
11314 cx: &mut Context<Self>,
11315 mut manipulate: M,
11316 ) where
11317 M: FnMut(&str) -> LineManipulationResult,
11318 {
11319 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11320
11321 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11322 let buffer = self.buffer.read(cx).snapshot(cx);
11323
11324 let mut edits = Vec::new();
11325
11326 let selections = self.selections.all::<Point>(&display_map);
11327 let mut selections = selections.iter().peekable();
11328 let mut contiguous_row_selections = Vec::new();
11329 let mut new_selections = Vec::new();
11330 let mut added_lines = 0;
11331 let mut removed_lines = 0;
11332
11333 while let Some(selection) = selections.next() {
11334 let (start_row, end_row) = consume_contiguous_rows(
11335 &mut contiguous_row_selections,
11336 selection,
11337 &display_map,
11338 &mut selections,
11339 );
11340
11341 let start_point = Point::new(start_row.0, 0);
11342 let end_point = Point::new(
11343 end_row.previous_row().0,
11344 buffer.line_len(end_row.previous_row()),
11345 );
11346 let text = buffer
11347 .text_for_range(start_point..end_point)
11348 .collect::<String>();
11349
11350 let LineManipulationResult {
11351 new_text,
11352 line_count_before,
11353 line_count_after,
11354 } = manipulate(&text);
11355
11356 edits.push((start_point..end_point, new_text));
11357
11358 // Selections must change based on added and removed line count
11359 let start_row =
11360 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11361 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11362 new_selections.push(Selection {
11363 id: selection.id,
11364 start: start_row,
11365 end: end_row,
11366 goal: SelectionGoal::None,
11367 reversed: selection.reversed,
11368 });
11369
11370 if line_count_after > line_count_before {
11371 added_lines += line_count_after - line_count_before;
11372 } else if line_count_before > line_count_after {
11373 removed_lines += line_count_before - line_count_after;
11374 }
11375 }
11376
11377 self.transact(window, cx, |this, window, cx| {
11378 let buffer = this.buffer.update(cx, |buffer, cx| {
11379 buffer.edit(edits, None, cx);
11380 buffer.snapshot(cx)
11381 });
11382
11383 // Recalculate offsets on newly edited buffer
11384 let new_selections = new_selections
11385 .iter()
11386 .map(|s| {
11387 let start_point = Point::new(s.start.0, 0);
11388 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11389 Selection {
11390 id: s.id,
11391 start: buffer.point_to_offset(start_point),
11392 end: buffer.point_to_offset(end_point),
11393 goal: s.goal,
11394 reversed: s.reversed,
11395 }
11396 })
11397 .collect();
11398
11399 this.change_selections(Default::default(), window, cx, |s| {
11400 s.select(new_selections);
11401 });
11402
11403 this.request_autoscroll(Autoscroll::fit(), cx);
11404 });
11405 }
11406
11407 fn manipulate_immutable_lines<Fn>(
11408 &mut self,
11409 window: &mut Window,
11410 cx: &mut Context<Self>,
11411 mut callback: Fn,
11412 ) where
11413 Fn: FnMut(&mut Vec<&str>),
11414 {
11415 self.manipulate_lines(window, cx, |text| {
11416 let mut lines: Vec<&str> = text.split('\n').collect();
11417 let line_count_before = lines.len();
11418
11419 callback(&mut lines);
11420
11421 LineManipulationResult {
11422 new_text: lines.join("\n"),
11423 line_count_before,
11424 line_count_after: lines.len(),
11425 }
11426 });
11427 }
11428
11429 fn manipulate_mutable_lines<Fn>(
11430 &mut self,
11431 window: &mut Window,
11432 cx: &mut Context<Self>,
11433 mut callback: Fn,
11434 ) where
11435 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11436 {
11437 self.manipulate_lines(window, cx, |text| {
11438 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11439 let line_count_before = lines.len();
11440
11441 callback(&mut lines);
11442
11443 LineManipulationResult {
11444 new_text: lines.join("\n"),
11445 line_count_before,
11446 line_count_after: lines.len(),
11447 }
11448 });
11449 }
11450
11451 pub fn convert_indentation_to_spaces(
11452 &mut self,
11453 _: &ConvertIndentationToSpaces,
11454 window: &mut Window,
11455 cx: &mut Context<Self>,
11456 ) {
11457 let settings = self.buffer.read(cx).language_settings(cx);
11458 let tab_size = settings.tab_size.get() as usize;
11459
11460 self.manipulate_mutable_lines(window, cx, |lines| {
11461 // Allocates a reasonably sized scratch buffer once for the whole loop
11462 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11463 // Avoids recomputing spaces that could be inserted many times
11464 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11465 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11466 .collect();
11467
11468 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11469 let mut chars = line.as_ref().chars();
11470 let mut col = 0;
11471 let mut changed = false;
11472
11473 for ch in chars.by_ref() {
11474 match ch {
11475 ' ' => {
11476 reindented_line.push(' ');
11477 col += 1;
11478 }
11479 '\t' => {
11480 // \t are converted to spaces depending on the current column
11481 let spaces_len = tab_size - (col % tab_size);
11482 reindented_line.extend(&space_cache[spaces_len - 1]);
11483 col += spaces_len;
11484 changed = true;
11485 }
11486 _ => {
11487 // If we dont append before break, the character is consumed
11488 reindented_line.push(ch);
11489 break;
11490 }
11491 }
11492 }
11493
11494 if !changed {
11495 reindented_line.clear();
11496 continue;
11497 }
11498 // Append the rest of the line and replace old reference with new one
11499 reindented_line.extend(chars);
11500 *line = Cow::Owned(reindented_line.clone());
11501 reindented_line.clear();
11502 }
11503 });
11504 }
11505
11506 pub fn convert_indentation_to_tabs(
11507 &mut self,
11508 _: &ConvertIndentationToTabs,
11509 window: &mut Window,
11510 cx: &mut Context<Self>,
11511 ) {
11512 let settings = self.buffer.read(cx).language_settings(cx);
11513 let tab_size = settings.tab_size.get() as usize;
11514
11515 self.manipulate_mutable_lines(window, cx, |lines| {
11516 // Allocates a reasonably sized buffer once for the whole loop
11517 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11518 // Avoids recomputing spaces that could be inserted many times
11519 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11520 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11521 .collect();
11522
11523 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11524 let mut chars = line.chars();
11525 let mut spaces_count = 0;
11526 let mut first_non_indent_char = None;
11527 let mut changed = false;
11528
11529 for ch in chars.by_ref() {
11530 match ch {
11531 ' ' => {
11532 // Keep track of spaces. Append \t when we reach tab_size
11533 spaces_count += 1;
11534 changed = true;
11535 if spaces_count == tab_size {
11536 reindented_line.push('\t');
11537 spaces_count = 0;
11538 }
11539 }
11540 '\t' => {
11541 reindented_line.push('\t');
11542 spaces_count = 0;
11543 }
11544 _ => {
11545 // Dont append it yet, we might have remaining spaces
11546 first_non_indent_char = Some(ch);
11547 break;
11548 }
11549 }
11550 }
11551
11552 if !changed {
11553 reindented_line.clear();
11554 continue;
11555 }
11556 // Remaining spaces that didn't make a full tab stop
11557 if spaces_count > 0 {
11558 reindented_line.extend(&space_cache[spaces_count - 1]);
11559 }
11560 // If we consume an extra character that was not indentation, add it back
11561 if let Some(extra_char) = first_non_indent_char {
11562 reindented_line.push(extra_char);
11563 }
11564 // Append the rest of the line and replace old reference with new one
11565 reindented_line.extend(chars);
11566 *line = Cow::Owned(reindented_line.clone());
11567 reindented_line.clear();
11568 }
11569 });
11570 }
11571
11572 pub fn convert_to_upper_case(
11573 &mut self,
11574 _: &ConvertToUpperCase,
11575 window: &mut Window,
11576 cx: &mut Context<Self>,
11577 ) {
11578 self.manipulate_text(window, cx, |text| text.to_uppercase())
11579 }
11580
11581 pub fn convert_to_lower_case(
11582 &mut self,
11583 _: &ConvertToLowerCase,
11584 window: &mut Window,
11585 cx: &mut Context<Self>,
11586 ) {
11587 self.manipulate_text(window, cx, |text| text.to_lowercase())
11588 }
11589
11590 pub fn convert_to_title_case(
11591 &mut self,
11592 _: &ConvertToTitleCase,
11593 window: &mut Window,
11594 cx: &mut Context<Self>,
11595 ) {
11596 self.manipulate_text(window, cx, |text| {
11597 text.split('\n')
11598 .map(|line| line.to_case(Case::Title))
11599 .join("\n")
11600 })
11601 }
11602
11603 pub fn convert_to_snake_case(
11604 &mut self,
11605 _: &ConvertToSnakeCase,
11606 window: &mut Window,
11607 cx: &mut Context<Self>,
11608 ) {
11609 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11610 }
11611
11612 pub fn convert_to_kebab_case(
11613 &mut self,
11614 _: &ConvertToKebabCase,
11615 window: &mut Window,
11616 cx: &mut Context<Self>,
11617 ) {
11618 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11619 }
11620
11621 pub fn convert_to_upper_camel_case(
11622 &mut self,
11623 _: &ConvertToUpperCamelCase,
11624 window: &mut Window,
11625 cx: &mut Context<Self>,
11626 ) {
11627 self.manipulate_text(window, cx, |text| {
11628 text.split('\n')
11629 .map(|line| line.to_case(Case::UpperCamel))
11630 .join("\n")
11631 })
11632 }
11633
11634 pub fn convert_to_lower_camel_case(
11635 &mut self,
11636 _: &ConvertToLowerCamelCase,
11637 window: &mut Window,
11638 cx: &mut Context<Self>,
11639 ) {
11640 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11641 }
11642
11643 pub fn convert_to_opposite_case(
11644 &mut self,
11645 _: &ConvertToOppositeCase,
11646 window: &mut Window,
11647 cx: &mut Context<Self>,
11648 ) {
11649 self.manipulate_text(window, cx, |text| {
11650 text.chars()
11651 .fold(String::with_capacity(text.len()), |mut t, c| {
11652 if c.is_uppercase() {
11653 t.extend(c.to_lowercase());
11654 } else {
11655 t.extend(c.to_uppercase());
11656 }
11657 t
11658 })
11659 })
11660 }
11661
11662 pub fn convert_to_sentence_case(
11663 &mut self,
11664 _: &ConvertToSentenceCase,
11665 window: &mut Window,
11666 cx: &mut Context<Self>,
11667 ) {
11668 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11669 }
11670
11671 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11672 self.manipulate_text(window, cx, |text| {
11673 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11674 if has_upper_case_characters {
11675 text.to_lowercase()
11676 } else {
11677 text.to_uppercase()
11678 }
11679 })
11680 }
11681
11682 pub fn convert_to_rot13(
11683 &mut self,
11684 _: &ConvertToRot13,
11685 window: &mut Window,
11686 cx: &mut Context<Self>,
11687 ) {
11688 self.manipulate_text(window, cx, |text| {
11689 text.chars()
11690 .map(|c| match c {
11691 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11692 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11693 _ => c,
11694 })
11695 .collect()
11696 })
11697 }
11698
11699 pub fn convert_to_rot47(
11700 &mut self,
11701 _: &ConvertToRot47,
11702 window: &mut Window,
11703 cx: &mut Context<Self>,
11704 ) {
11705 self.manipulate_text(window, cx, |text| {
11706 text.chars()
11707 .map(|c| {
11708 let code_point = c as u32;
11709 if code_point >= 33 && code_point <= 126 {
11710 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11711 }
11712 c
11713 })
11714 .collect()
11715 })
11716 }
11717
11718 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11719 where
11720 Fn: FnMut(&str) -> String,
11721 {
11722 let buffer = self.buffer.read(cx).snapshot(cx);
11723
11724 let mut new_selections = Vec::new();
11725 let mut edits = Vec::new();
11726 let mut selection_adjustment = 0isize;
11727
11728 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11729 let selection_is_empty = selection.is_empty();
11730
11731 let (start, end) = if selection_is_empty {
11732 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11733 (word_range.start, word_range.end)
11734 } else {
11735 (
11736 buffer.point_to_offset(selection.start),
11737 buffer.point_to_offset(selection.end),
11738 )
11739 };
11740
11741 let text = buffer.text_for_range(start..end).collect::<String>();
11742 let old_length = text.len() as isize;
11743 let text = callback(&text);
11744
11745 new_selections.push(Selection {
11746 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11747 end: MultiBufferOffset(
11748 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11749 ),
11750 goal: SelectionGoal::None,
11751 id: selection.id,
11752 reversed: selection.reversed,
11753 });
11754
11755 selection_adjustment += old_length - text.len() as isize;
11756
11757 edits.push((start..end, text));
11758 }
11759
11760 self.transact(window, cx, |this, window, cx| {
11761 this.buffer.update(cx, |buffer, cx| {
11762 buffer.edit(edits, None, cx);
11763 });
11764
11765 this.change_selections(Default::default(), window, cx, |s| {
11766 s.select(new_selections);
11767 });
11768
11769 this.request_autoscroll(Autoscroll::fit(), cx);
11770 });
11771 }
11772
11773 pub fn move_selection_on_drop(
11774 &mut self,
11775 selection: &Selection<Anchor>,
11776 target: DisplayPoint,
11777 is_cut: bool,
11778 window: &mut Window,
11779 cx: &mut Context<Self>,
11780 ) {
11781 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11782 let buffer = display_map.buffer_snapshot();
11783 let mut edits = Vec::new();
11784 let insert_point = display_map
11785 .clip_point(target, Bias::Left)
11786 .to_point(&display_map);
11787 let text = buffer
11788 .text_for_range(selection.start..selection.end)
11789 .collect::<String>();
11790 if is_cut {
11791 edits.push(((selection.start..selection.end), String::new()));
11792 }
11793 let insert_anchor = buffer.anchor_before(insert_point);
11794 edits.push(((insert_anchor..insert_anchor), text));
11795 let last_edit_start = insert_anchor.bias_left(buffer);
11796 let last_edit_end = insert_anchor.bias_right(buffer);
11797 self.transact(window, cx, |this, window, cx| {
11798 this.buffer.update(cx, |buffer, cx| {
11799 buffer.edit(edits, None, cx);
11800 });
11801 this.change_selections(Default::default(), window, cx, |s| {
11802 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11803 });
11804 });
11805 }
11806
11807 pub fn clear_selection_drag_state(&mut self) {
11808 self.selection_drag_state = SelectionDragState::None;
11809 }
11810
11811 pub fn duplicate(
11812 &mut self,
11813 upwards: bool,
11814 whole_lines: bool,
11815 window: &mut Window,
11816 cx: &mut Context<Self>,
11817 ) {
11818 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11819
11820 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11821 let buffer = display_map.buffer_snapshot();
11822 let selections = self.selections.all::<Point>(&display_map);
11823
11824 let mut edits = Vec::new();
11825 let mut selections_iter = selections.iter().peekable();
11826 while let Some(selection) = selections_iter.next() {
11827 let mut rows = selection.spanned_rows(false, &display_map);
11828 // duplicate line-wise
11829 if whole_lines || selection.start == selection.end {
11830 // Avoid duplicating the same lines twice.
11831 while let Some(next_selection) = selections_iter.peek() {
11832 let next_rows = next_selection.spanned_rows(false, &display_map);
11833 if next_rows.start < rows.end {
11834 rows.end = next_rows.end;
11835 selections_iter.next().unwrap();
11836 } else {
11837 break;
11838 }
11839 }
11840
11841 // Copy the text from the selected row region and splice it either at the start
11842 // or end of the region.
11843 let start = Point::new(rows.start.0, 0);
11844 let end = Point::new(
11845 rows.end.previous_row().0,
11846 buffer.line_len(rows.end.previous_row()),
11847 );
11848
11849 let mut text = buffer.text_for_range(start..end).collect::<String>();
11850
11851 let insert_location = if upwards {
11852 // When duplicating upward, we need to insert before the current line.
11853 // If we're on the last line and it doesn't end with a newline,
11854 // we need to add a newline before the duplicated content.
11855 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11856 && buffer.max_point().column > 0
11857 && !text.ends_with('\n');
11858
11859 if needs_leading_newline {
11860 text.insert(0, '\n');
11861 end
11862 } else {
11863 text.push('\n');
11864 Point::new(rows.start.0, 0)
11865 }
11866 } else {
11867 text.push('\n');
11868 start
11869 };
11870 edits.push((insert_location..insert_location, text));
11871 } else {
11872 // duplicate character-wise
11873 let start = selection.start;
11874 let end = selection.end;
11875 let text = buffer.text_for_range(start..end).collect::<String>();
11876 edits.push((selection.end..selection.end, text));
11877 }
11878 }
11879
11880 self.transact(window, cx, |this, window, cx| {
11881 this.buffer.update(cx, |buffer, cx| {
11882 buffer.edit(edits, None, cx);
11883 });
11884
11885 // When duplicating upward with whole lines, move the cursor to the duplicated line
11886 if upwards && whole_lines {
11887 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11888
11889 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11890 let mut new_ranges = Vec::new();
11891 let selections = s.all::<Point>(&display_map);
11892 let mut selections_iter = selections.iter().peekable();
11893
11894 while let Some(first_selection) = selections_iter.next() {
11895 // Group contiguous selections together to find the total row span
11896 let mut group_selections = vec![first_selection];
11897 let mut rows = first_selection.spanned_rows(false, &display_map);
11898
11899 while let Some(next_selection) = selections_iter.peek() {
11900 let next_rows = next_selection.spanned_rows(false, &display_map);
11901 if next_rows.start < rows.end {
11902 rows.end = next_rows.end;
11903 group_selections.push(selections_iter.next().unwrap());
11904 } else {
11905 break;
11906 }
11907 }
11908
11909 let row_count = rows.end.0 - rows.start.0;
11910
11911 // Move all selections in this group up by the total number of duplicated rows
11912 for selection in group_selections {
11913 let new_start = Point::new(
11914 selection.start.row.saturating_sub(row_count),
11915 selection.start.column,
11916 );
11917
11918 let new_end = Point::new(
11919 selection.end.row.saturating_sub(row_count),
11920 selection.end.column,
11921 );
11922
11923 new_ranges.push(new_start..new_end);
11924 }
11925 }
11926
11927 s.select_ranges(new_ranges);
11928 });
11929 }
11930
11931 this.request_autoscroll(Autoscroll::fit(), cx);
11932 });
11933 }
11934
11935 pub fn duplicate_line_up(
11936 &mut self,
11937 _: &DuplicateLineUp,
11938 window: &mut Window,
11939 cx: &mut Context<Self>,
11940 ) {
11941 self.duplicate(true, true, window, cx);
11942 }
11943
11944 pub fn duplicate_line_down(
11945 &mut self,
11946 _: &DuplicateLineDown,
11947 window: &mut Window,
11948 cx: &mut Context<Self>,
11949 ) {
11950 self.duplicate(false, true, window, cx);
11951 }
11952
11953 pub fn duplicate_selection(
11954 &mut self,
11955 _: &DuplicateSelection,
11956 window: &mut Window,
11957 cx: &mut Context<Self>,
11958 ) {
11959 self.duplicate(false, false, window, cx);
11960 }
11961
11962 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11964 if self.mode.is_single_line() {
11965 cx.propagate();
11966 return;
11967 }
11968
11969 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11970 let buffer = self.buffer.read(cx).snapshot(cx);
11971
11972 let mut edits = Vec::new();
11973 let mut unfold_ranges = Vec::new();
11974 let mut refold_creases = Vec::new();
11975
11976 let selections = self.selections.all::<Point>(&display_map);
11977 let mut selections = selections.iter().peekable();
11978 let mut contiguous_row_selections = Vec::new();
11979 let mut new_selections = Vec::new();
11980
11981 while let Some(selection) = selections.next() {
11982 // Find all the selections that span a contiguous row range
11983 let (start_row, end_row) = consume_contiguous_rows(
11984 &mut contiguous_row_selections,
11985 selection,
11986 &display_map,
11987 &mut selections,
11988 );
11989
11990 // Move the text spanned by the row range to be before the line preceding the row range
11991 if start_row.0 > 0 {
11992 let range_to_move = Point::new(
11993 start_row.previous_row().0,
11994 buffer.line_len(start_row.previous_row()),
11995 )
11996 ..Point::new(
11997 end_row.previous_row().0,
11998 buffer.line_len(end_row.previous_row()),
11999 );
12000 let insertion_point = display_map
12001 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12002 .0;
12003
12004 // Don't move lines across excerpts
12005 if buffer
12006 .excerpt_containing(insertion_point..range_to_move.end)
12007 .is_some()
12008 {
12009 let text = buffer
12010 .text_for_range(range_to_move.clone())
12011 .flat_map(|s| s.chars())
12012 .skip(1)
12013 .chain(['\n'])
12014 .collect::<String>();
12015
12016 edits.push((
12017 buffer.anchor_after(range_to_move.start)
12018 ..buffer.anchor_before(range_to_move.end),
12019 String::new(),
12020 ));
12021 let insertion_anchor = buffer.anchor_after(insertion_point);
12022 edits.push((insertion_anchor..insertion_anchor, text));
12023
12024 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12025
12026 // Move selections up
12027 new_selections.extend(contiguous_row_selections.drain(..).map(
12028 |mut selection| {
12029 selection.start.row -= row_delta;
12030 selection.end.row -= row_delta;
12031 selection
12032 },
12033 ));
12034
12035 // Move folds up
12036 unfold_ranges.push(range_to_move.clone());
12037 for fold in display_map.folds_in_range(
12038 buffer.anchor_before(range_to_move.start)
12039 ..buffer.anchor_after(range_to_move.end),
12040 ) {
12041 let mut start = fold.range.start.to_point(&buffer);
12042 let mut end = fold.range.end.to_point(&buffer);
12043 start.row -= row_delta;
12044 end.row -= row_delta;
12045 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12046 }
12047 }
12048 }
12049
12050 // If we didn't move line(s), preserve the existing selections
12051 new_selections.append(&mut contiguous_row_selections);
12052 }
12053
12054 self.transact(window, cx, |this, window, cx| {
12055 this.unfold_ranges(&unfold_ranges, true, true, cx);
12056 this.buffer.update(cx, |buffer, cx| {
12057 for (range, text) in edits {
12058 buffer.edit([(range, text)], None, cx);
12059 }
12060 });
12061 this.fold_creases(refold_creases, true, window, cx);
12062 this.change_selections(Default::default(), window, cx, |s| {
12063 s.select(new_selections);
12064 })
12065 });
12066 }
12067
12068 pub fn move_line_down(
12069 &mut self,
12070 _: &MoveLineDown,
12071 window: &mut Window,
12072 cx: &mut Context<Self>,
12073 ) {
12074 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12075 if self.mode.is_single_line() {
12076 cx.propagate();
12077 return;
12078 }
12079
12080 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12081 let buffer = self.buffer.read(cx).snapshot(cx);
12082
12083 let mut edits = Vec::new();
12084 let mut unfold_ranges = Vec::new();
12085 let mut refold_creases = Vec::new();
12086
12087 let selections = self.selections.all::<Point>(&display_map);
12088 let mut selections = selections.iter().peekable();
12089 let mut contiguous_row_selections = Vec::new();
12090 let mut new_selections = Vec::new();
12091
12092 while let Some(selection) = selections.next() {
12093 // Find all the selections that span a contiguous row range
12094 let (start_row, end_row) = consume_contiguous_rows(
12095 &mut contiguous_row_selections,
12096 selection,
12097 &display_map,
12098 &mut selections,
12099 );
12100
12101 // Move the text spanned by the row range to be after the last line of the row range
12102 if end_row.0 <= buffer.max_point().row {
12103 let range_to_move =
12104 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12105 let insertion_point = display_map
12106 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12107 .0;
12108
12109 // Don't move lines across excerpt boundaries
12110 if buffer
12111 .excerpt_containing(range_to_move.start..insertion_point)
12112 .is_some()
12113 {
12114 let mut text = String::from("\n");
12115 text.extend(buffer.text_for_range(range_to_move.clone()));
12116 text.pop(); // Drop trailing newline
12117 edits.push((
12118 buffer.anchor_after(range_to_move.start)
12119 ..buffer.anchor_before(range_to_move.end),
12120 String::new(),
12121 ));
12122 let insertion_anchor = buffer.anchor_after(insertion_point);
12123 edits.push((insertion_anchor..insertion_anchor, text));
12124
12125 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12126
12127 // Move selections down
12128 new_selections.extend(contiguous_row_selections.drain(..).map(
12129 |mut selection| {
12130 selection.start.row += row_delta;
12131 selection.end.row += row_delta;
12132 selection
12133 },
12134 ));
12135
12136 // Move folds down
12137 unfold_ranges.push(range_to_move.clone());
12138 for fold in display_map.folds_in_range(
12139 buffer.anchor_before(range_to_move.start)
12140 ..buffer.anchor_after(range_to_move.end),
12141 ) {
12142 let mut start = fold.range.start.to_point(&buffer);
12143 let mut end = fold.range.end.to_point(&buffer);
12144 start.row += row_delta;
12145 end.row += row_delta;
12146 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12147 }
12148 }
12149 }
12150
12151 // If we didn't move line(s), preserve the existing selections
12152 new_selections.append(&mut contiguous_row_selections);
12153 }
12154
12155 self.transact(window, cx, |this, window, cx| {
12156 this.unfold_ranges(&unfold_ranges, true, true, cx);
12157 this.buffer.update(cx, |buffer, cx| {
12158 for (range, text) in edits {
12159 buffer.edit([(range, text)], None, cx);
12160 }
12161 });
12162 this.fold_creases(refold_creases, true, window, cx);
12163 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12164 });
12165 }
12166
12167 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12168 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12169 let text_layout_details = &self.text_layout_details(window);
12170 self.transact(window, cx, |this, window, cx| {
12171 let edits = this.change_selections(Default::default(), window, cx, |s| {
12172 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12173 s.move_with(|display_map, selection| {
12174 if !selection.is_empty() {
12175 return;
12176 }
12177
12178 let mut head = selection.head();
12179 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12180 if head.column() == display_map.line_len(head.row()) {
12181 transpose_offset = display_map
12182 .buffer_snapshot()
12183 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12184 }
12185
12186 if transpose_offset == MultiBufferOffset(0) {
12187 return;
12188 }
12189
12190 *head.column_mut() += 1;
12191 head = display_map.clip_point(head, Bias::Right);
12192 let goal = SelectionGoal::HorizontalPosition(
12193 display_map
12194 .x_for_display_point(head, text_layout_details)
12195 .into(),
12196 );
12197 selection.collapse_to(head, goal);
12198
12199 let transpose_start = display_map
12200 .buffer_snapshot()
12201 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12202 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12203 let transpose_end = display_map
12204 .buffer_snapshot()
12205 .clip_offset(transpose_offset + 1usize, Bias::Right);
12206 if let Some(ch) = display_map
12207 .buffer_snapshot()
12208 .chars_at(transpose_start)
12209 .next()
12210 {
12211 edits.push((transpose_start..transpose_offset, String::new()));
12212 edits.push((transpose_end..transpose_end, ch.to_string()));
12213 }
12214 }
12215 });
12216 edits
12217 });
12218 this.buffer
12219 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12220 let selections = this
12221 .selections
12222 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12223 this.change_selections(Default::default(), window, cx, |s| {
12224 s.select(selections);
12225 });
12226 });
12227 }
12228
12229 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12230 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12231 if self.mode.is_single_line() {
12232 cx.propagate();
12233 return;
12234 }
12235
12236 self.rewrap_impl(RewrapOptions::default(), cx)
12237 }
12238
12239 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12240 let buffer = self.buffer.read(cx).snapshot(cx);
12241 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12242
12243 #[derive(Clone, Debug, PartialEq)]
12244 enum CommentFormat {
12245 /// single line comment, with prefix for line
12246 Line(String),
12247 /// single line within a block comment, with prefix for line
12248 BlockLine(String),
12249 /// a single line of a block comment that includes the initial delimiter
12250 BlockCommentWithStart(BlockCommentConfig),
12251 /// a single line of a block comment that includes the ending delimiter
12252 BlockCommentWithEnd(BlockCommentConfig),
12253 }
12254
12255 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12256 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12257 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12258 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12259 .peekable();
12260
12261 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12262 row
12263 } else {
12264 return Vec::new();
12265 };
12266
12267 let language_settings = buffer.language_settings_at(selection.head(), cx);
12268 let language_scope = buffer.language_scope_at(selection.head());
12269
12270 let indent_and_prefix_for_row =
12271 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12272 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12273 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12274 &language_scope
12275 {
12276 let indent_end = Point::new(row, indent.len);
12277 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12278 let line_text_after_indent = buffer
12279 .text_for_range(indent_end..line_end)
12280 .collect::<String>();
12281
12282 let is_within_comment_override = buffer
12283 .language_scope_at(indent_end)
12284 .is_some_and(|scope| scope.override_name() == Some("comment"));
12285 let comment_delimiters = if is_within_comment_override {
12286 // we are within a comment syntax node, but we don't
12287 // yet know what kind of comment: block, doc or line
12288 match (
12289 language_scope.documentation_comment(),
12290 language_scope.block_comment(),
12291 ) {
12292 (Some(config), _) | (_, Some(config))
12293 if buffer.contains_str_at(indent_end, &config.start) =>
12294 {
12295 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12296 }
12297 (Some(config), _) | (_, Some(config))
12298 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12299 {
12300 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12301 }
12302 (Some(config), _) | (_, Some(config))
12303 if buffer.contains_str_at(indent_end, &config.prefix) =>
12304 {
12305 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12306 }
12307 (_, _) => language_scope
12308 .line_comment_prefixes()
12309 .iter()
12310 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12311 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12312 }
12313 } else {
12314 // we not in an overridden comment node, but we may
12315 // be within a non-overridden line comment node
12316 language_scope
12317 .line_comment_prefixes()
12318 .iter()
12319 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12320 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12321 };
12322
12323 let rewrap_prefix = language_scope
12324 .rewrap_prefixes()
12325 .iter()
12326 .find_map(|prefix_regex| {
12327 prefix_regex.find(&line_text_after_indent).map(|mat| {
12328 if mat.start() == 0 {
12329 Some(mat.as_str().to_string())
12330 } else {
12331 None
12332 }
12333 })
12334 })
12335 .flatten();
12336 (comment_delimiters, rewrap_prefix)
12337 } else {
12338 (None, None)
12339 };
12340 (indent, comment_prefix, rewrap_prefix)
12341 };
12342
12343 let mut ranges = Vec::new();
12344 let from_empty_selection = selection.is_empty();
12345
12346 let mut current_range_start = first_row;
12347 let mut prev_row = first_row;
12348 let (
12349 mut current_range_indent,
12350 mut current_range_comment_delimiters,
12351 mut current_range_rewrap_prefix,
12352 ) = indent_and_prefix_for_row(first_row);
12353
12354 for row in non_blank_rows_iter.skip(1) {
12355 let has_paragraph_break = row > prev_row + 1;
12356
12357 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12358 indent_and_prefix_for_row(row);
12359
12360 let has_indent_change = row_indent != current_range_indent;
12361 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12362
12363 let has_boundary_change = has_comment_change
12364 || row_rewrap_prefix.is_some()
12365 || (has_indent_change && current_range_comment_delimiters.is_some());
12366
12367 if has_paragraph_break || has_boundary_change {
12368 ranges.push((
12369 language_settings.clone(),
12370 Point::new(current_range_start, 0)
12371 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12372 current_range_indent,
12373 current_range_comment_delimiters.clone(),
12374 current_range_rewrap_prefix.clone(),
12375 from_empty_selection,
12376 ));
12377 current_range_start = row;
12378 current_range_indent = row_indent;
12379 current_range_comment_delimiters = row_comment_delimiters;
12380 current_range_rewrap_prefix = row_rewrap_prefix;
12381 }
12382 prev_row = row;
12383 }
12384
12385 ranges.push((
12386 language_settings.clone(),
12387 Point::new(current_range_start, 0)
12388 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12389 current_range_indent,
12390 current_range_comment_delimiters,
12391 current_range_rewrap_prefix,
12392 from_empty_selection,
12393 ));
12394
12395 ranges
12396 });
12397
12398 let mut edits = Vec::new();
12399 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12400
12401 for (
12402 language_settings,
12403 wrap_range,
12404 mut indent_size,
12405 comment_prefix,
12406 rewrap_prefix,
12407 from_empty_selection,
12408 ) in wrap_ranges
12409 {
12410 let mut start_row = wrap_range.start.row;
12411 let mut end_row = wrap_range.end.row;
12412
12413 // Skip selections that overlap with a range that has already been rewrapped.
12414 let selection_range = start_row..end_row;
12415 if rewrapped_row_ranges
12416 .iter()
12417 .any(|range| range.overlaps(&selection_range))
12418 {
12419 continue;
12420 }
12421
12422 let tab_size = language_settings.tab_size;
12423
12424 let (line_prefix, inside_comment) = match &comment_prefix {
12425 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12426 (Some(prefix.as_str()), true)
12427 }
12428 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12429 (Some(prefix.as_ref()), true)
12430 }
12431 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12432 start: _,
12433 end: _,
12434 prefix,
12435 tab_size,
12436 })) => {
12437 indent_size.len += tab_size;
12438 (Some(prefix.as_ref()), true)
12439 }
12440 None => (None, false),
12441 };
12442 let indent_prefix = indent_size.chars().collect::<String>();
12443 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12444
12445 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12446 RewrapBehavior::InComments => inside_comment,
12447 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12448 RewrapBehavior::Anywhere => true,
12449 };
12450
12451 let should_rewrap = options.override_language_settings
12452 || allow_rewrap_based_on_language
12453 || self.hard_wrap.is_some();
12454 if !should_rewrap {
12455 continue;
12456 }
12457
12458 if from_empty_selection {
12459 'expand_upwards: while start_row > 0 {
12460 let prev_row = start_row - 1;
12461 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12462 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12463 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12464 {
12465 start_row = prev_row;
12466 } else {
12467 break 'expand_upwards;
12468 }
12469 }
12470
12471 'expand_downwards: while end_row < buffer.max_point().row {
12472 let next_row = end_row + 1;
12473 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12474 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12475 && !buffer.is_line_blank(MultiBufferRow(next_row))
12476 {
12477 end_row = next_row;
12478 } else {
12479 break 'expand_downwards;
12480 }
12481 }
12482 }
12483
12484 let start = Point::new(start_row, 0);
12485 let start_offset = ToOffset::to_offset(&start, &buffer);
12486 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12487 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12488 let mut first_line_delimiter = None;
12489 let mut last_line_delimiter = None;
12490 let Some(lines_without_prefixes) = selection_text
12491 .lines()
12492 .enumerate()
12493 .map(|(ix, line)| {
12494 let line_trimmed = line.trim_start();
12495 if rewrap_prefix.is_some() && ix > 0 {
12496 Ok(line_trimmed)
12497 } else if let Some(
12498 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12499 start,
12500 prefix,
12501 end,
12502 tab_size,
12503 })
12504 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12505 start,
12506 prefix,
12507 end,
12508 tab_size,
12509 }),
12510 ) = &comment_prefix
12511 {
12512 let line_trimmed = line_trimmed
12513 .strip_prefix(start.as_ref())
12514 .map(|s| {
12515 let mut indent_size = indent_size;
12516 indent_size.len -= tab_size;
12517 let indent_prefix: String = indent_size.chars().collect();
12518 first_line_delimiter = Some((indent_prefix, start));
12519 s.trim_start()
12520 })
12521 .unwrap_or(line_trimmed);
12522 let line_trimmed = line_trimmed
12523 .strip_suffix(end.as_ref())
12524 .map(|s| {
12525 last_line_delimiter = Some(end);
12526 s.trim_end()
12527 })
12528 .unwrap_or(line_trimmed);
12529 let line_trimmed = line_trimmed
12530 .strip_prefix(prefix.as_ref())
12531 .unwrap_or(line_trimmed);
12532 Ok(line_trimmed)
12533 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12534 line_trimmed.strip_prefix(prefix).with_context(|| {
12535 format!("line did not start with prefix {prefix:?}: {line:?}")
12536 })
12537 } else {
12538 line_trimmed
12539 .strip_prefix(&line_prefix.trim_start())
12540 .with_context(|| {
12541 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12542 })
12543 }
12544 })
12545 .collect::<Result<Vec<_>, _>>()
12546 .log_err()
12547 else {
12548 continue;
12549 };
12550
12551 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12552 buffer
12553 .language_settings_at(Point::new(start_row, 0), cx)
12554 .preferred_line_length as usize
12555 });
12556
12557 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12558 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12559 } else {
12560 line_prefix.clone()
12561 };
12562
12563 let wrapped_text = {
12564 let mut wrapped_text = wrap_with_prefix(
12565 line_prefix,
12566 subsequent_lines_prefix,
12567 lines_without_prefixes.join("\n"),
12568 wrap_column,
12569 tab_size,
12570 options.preserve_existing_whitespace,
12571 );
12572
12573 if let Some((indent, delimiter)) = first_line_delimiter {
12574 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12575 }
12576 if let Some(last_line) = last_line_delimiter {
12577 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12578 }
12579
12580 wrapped_text
12581 };
12582
12583 // TODO: should always use char-based diff while still supporting cursor behavior that
12584 // matches vim.
12585 let mut diff_options = DiffOptions::default();
12586 if options.override_language_settings {
12587 diff_options.max_word_diff_len = 0;
12588 diff_options.max_word_diff_line_count = 0;
12589 } else {
12590 diff_options.max_word_diff_len = usize::MAX;
12591 diff_options.max_word_diff_line_count = usize::MAX;
12592 }
12593
12594 for (old_range, new_text) in
12595 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12596 {
12597 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12598 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12599 edits.push((edit_start..edit_end, new_text));
12600 }
12601
12602 rewrapped_row_ranges.push(start_row..=end_row);
12603 }
12604
12605 self.buffer
12606 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12607 }
12608
12609 pub fn cut_common(
12610 &mut self,
12611 cut_no_selection_line: bool,
12612 window: &mut Window,
12613 cx: &mut Context<Self>,
12614 ) -> ClipboardItem {
12615 let mut text = String::new();
12616 let buffer = self.buffer.read(cx).snapshot(cx);
12617 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12618 let mut clipboard_selections = Vec::with_capacity(selections.len());
12619 {
12620 let max_point = buffer.max_point();
12621 let mut is_first = true;
12622 let mut prev_selection_was_entire_line = false;
12623 for selection in &mut selections {
12624 let is_entire_line =
12625 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12626 if is_entire_line {
12627 selection.start = Point::new(selection.start.row, 0);
12628 if !selection.is_empty() && selection.end.column == 0 {
12629 selection.end = cmp::min(max_point, selection.end);
12630 } else {
12631 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12632 }
12633 selection.goal = SelectionGoal::None;
12634 }
12635 if is_first {
12636 is_first = false;
12637 } else if !prev_selection_was_entire_line {
12638 text += "\n";
12639 }
12640 prev_selection_was_entire_line = is_entire_line;
12641 let mut len = 0;
12642 for chunk in buffer.text_for_range(selection.start..selection.end) {
12643 text.push_str(chunk);
12644 len += chunk.len();
12645 }
12646 clipboard_selections.push(ClipboardSelection {
12647 len,
12648 is_entire_line,
12649 first_line_indent: buffer
12650 .indent_size_for_line(MultiBufferRow(selection.start.row))
12651 .len,
12652 });
12653 }
12654 }
12655
12656 self.transact(window, cx, |this, window, cx| {
12657 this.change_selections(Default::default(), window, cx, |s| {
12658 s.select(selections);
12659 });
12660 this.insert("", window, cx);
12661 });
12662 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12663 }
12664
12665 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12666 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12667 let item = self.cut_common(true, window, cx);
12668 cx.write_to_clipboard(item);
12669 }
12670
12671 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12673 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12674 s.move_with(|snapshot, sel| {
12675 if sel.is_empty() {
12676 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12677 }
12678 if sel.is_empty() {
12679 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12680 }
12681 });
12682 });
12683 let item = self.cut_common(false, window, cx);
12684 cx.set_global(KillRing(item))
12685 }
12686
12687 pub fn kill_ring_yank(
12688 &mut self,
12689 _: &KillRingYank,
12690 window: &mut Window,
12691 cx: &mut Context<Self>,
12692 ) {
12693 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12694 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12695 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12696 (kill_ring.text().to_string(), kill_ring.metadata_json())
12697 } else {
12698 return;
12699 }
12700 } else {
12701 return;
12702 };
12703 self.do_paste(&text, metadata, false, window, cx);
12704 }
12705
12706 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12707 self.do_copy(true, cx);
12708 }
12709
12710 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12711 self.do_copy(false, cx);
12712 }
12713
12714 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12715 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12716 let buffer = self.buffer.read(cx).read(cx);
12717 let mut text = String::new();
12718
12719 let mut clipboard_selections = Vec::with_capacity(selections.len());
12720 {
12721 let max_point = buffer.max_point();
12722 let mut is_first = true;
12723 let mut prev_selection_was_entire_line = false;
12724 for selection in &selections {
12725 let mut start = selection.start;
12726 let mut end = selection.end;
12727 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12728 let mut add_trailing_newline = false;
12729 if is_entire_line {
12730 start = Point::new(start.row, 0);
12731 let next_line_start = Point::new(end.row + 1, 0);
12732 if next_line_start <= max_point {
12733 end = next_line_start;
12734 } else {
12735 // We're on the last line without a trailing newline.
12736 // Copy to the end of the line and add a newline afterwards.
12737 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12738 add_trailing_newline = true;
12739 }
12740 }
12741
12742 let mut trimmed_selections = Vec::new();
12743 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12744 let row = MultiBufferRow(start.row);
12745 let first_indent = buffer.indent_size_for_line(row);
12746 if first_indent.len == 0 || start.column > first_indent.len {
12747 trimmed_selections.push(start..end);
12748 } else {
12749 trimmed_selections.push(
12750 Point::new(row.0, first_indent.len)
12751 ..Point::new(row.0, buffer.line_len(row)),
12752 );
12753 for row in start.row + 1..=end.row {
12754 let mut line_len = buffer.line_len(MultiBufferRow(row));
12755 if row == end.row {
12756 line_len = end.column;
12757 }
12758 if line_len == 0 {
12759 trimmed_selections
12760 .push(Point::new(row, 0)..Point::new(row, line_len));
12761 continue;
12762 }
12763 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12764 if row_indent_size.len >= first_indent.len {
12765 trimmed_selections.push(
12766 Point::new(row, first_indent.len)..Point::new(row, line_len),
12767 );
12768 } else {
12769 trimmed_selections.clear();
12770 trimmed_selections.push(start..end);
12771 break;
12772 }
12773 }
12774 }
12775 } else {
12776 trimmed_selections.push(start..end);
12777 }
12778
12779 for trimmed_range in trimmed_selections {
12780 if is_first {
12781 is_first = false;
12782 } else if !prev_selection_was_entire_line {
12783 text += "\n";
12784 }
12785 prev_selection_was_entire_line = is_entire_line;
12786 let mut len = 0;
12787 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12788 text.push_str(chunk);
12789 len += chunk.len();
12790 }
12791 if add_trailing_newline {
12792 text.push('\n');
12793 len += 1;
12794 }
12795 clipboard_selections.push(ClipboardSelection {
12796 len,
12797 is_entire_line,
12798 first_line_indent: buffer
12799 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12800 .len,
12801 });
12802 }
12803 }
12804 }
12805
12806 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12807 text,
12808 clipboard_selections,
12809 ));
12810 }
12811
12812 pub fn do_paste(
12813 &mut self,
12814 text: &String,
12815 clipboard_selections: Option<Vec<ClipboardSelection>>,
12816 handle_entire_lines: bool,
12817 window: &mut Window,
12818 cx: &mut Context<Self>,
12819 ) {
12820 if self.read_only(cx) {
12821 return;
12822 }
12823
12824 let clipboard_text = Cow::Borrowed(text.as_str());
12825
12826 self.transact(window, cx, |this, window, cx| {
12827 let had_active_edit_prediction = this.has_active_edit_prediction();
12828 let display_map = this.display_snapshot(cx);
12829 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
12830 let cursor_offset = this
12831 .selections
12832 .last::<MultiBufferOffset>(&display_map)
12833 .head();
12834
12835 if let Some(mut clipboard_selections) = clipboard_selections {
12836 let all_selections_were_entire_line =
12837 clipboard_selections.iter().all(|s| s.is_entire_line);
12838 let first_selection_indent_column =
12839 clipboard_selections.first().map(|s| s.first_line_indent);
12840 if clipboard_selections.len() != old_selections.len() {
12841 clipboard_selections.drain(..);
12842 }
12843 let mut auto_indent_on_paste = true;
12844
12845 this.buffer.update(cx, |buffer, cx| {
12846 let snapshot = buffer.read(cx);
12847 auto_indent_on_paste = snapshot
12848 .language_settings_at(cursor_offset, cx)
12849 .auto_indent_on_paste;
12850
12851 let mut start_offset = 0;
12852 let mut edits = Vec::new();
12853 let mut original_indent_columns = Vec::new();
12854 for (ix, selection) in old_selections.iter().enumerate() {
12855 let to_insert;
12856 let entire_line;
12857 let original_indent_column;
12858 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12859 let end_offset = start_offset + clipboard_selection.len;
12860 to_insert = &clipboard_text[start_offset..end_offset];
12861 entire_line = clipboard_selection.is_entire_line;
12862 start_offset = if entire_line {
12863 end_offset
12864 } else {
12865 end_offset + 1
12866 };
12867 original_indent_column = Some(clipboard_selection.first_line_indent);
12868 } else {
12869 to_insert = &*clipboard_text;
12870 entire_line = all_selections_were_entire_line;
12871 original_indent_column = first_selection_indent_column
12872 }
12873
12874 let (range, to_insert) =
12875 if selection.is_empty() && handle_entire_lines && entire_line {
12876 // If the corresponding selection was empty when this slice of the
12877 // clipboard text was written, then the entire line containing the
12878 // selection was copied. If this selection is also currently empty,
12879 // then paste the line before the current line of the buffer.
12880 let column = selection.start.to_point(&snapshot).column as usize;
12881 let line_start = selection.start - column;
12882 (line_start..line_start, Cow::Borrowed(to_insert))
12883 } else {
12884 let language = snapshot.language_at(selection.head());
12885 let range = selection.range();
12886 if let Some(language) = language
12887 && language.name() == "Markdown".into()
12888 {
12889 edit_for_markdown_paste(
12890 &snapshot,
12891 range,
12892 to_insert,
12893 url::Url::parse(to_insert).ok(),
12894 )
12895 } else {
12896 (range, Cow::Borrowed(to_insert))
12897 }
12898 };
12899
12900 edits.push((range, to_insert));
12901 original_indent_columns.push(original_indent_column);
12902 }
12903 drop(snapshot);
12904
12905 buffer.edit(
12906 edits,
12907 if auto_indent_on_paste {
12908 Some(AutoindentMode::Block {
12909 original_indent_columns,
12910 })
12911 } else {
12912 None
12913 },
12914 cx,
12915 );
12916 });
12917
12918 let selections = this
12919 .selections
12920 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12921 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12922 } else {
12923 let url = url::Url::parse(&clipboard_text).ok();
12924
12925 let auto_indent_mode = if !clipboard_text.is_empty() {
12926 Some(AutoindentMode::Block {
12927 original_indent_columns: Vec::new(),
12928 })
12929 } else {
12930 None
12931 };
12932
12933 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12934 let snapshot = buffer.snapshot(cx);
12935
12936 let anchors = old_selections
12937 .iter()
12938 .map(|s| {
12939 let anchor = snapshot.anchor_after(s.head());
12940 s.map(|_| anchor)
12941 })
12942 .collect::<Vec<_>>();
12943
12944 let mut edits = Vec::new();
12945
12946 for selection in old_selections.iter() {
12947 let language = snapshot.language_at(selection.head());
12948 let range = selection.range();
12949
12950 let (edit_range, edit_text) = if let Some(language) = language
12951 && language.name() == "Markdown".into()
12952 {
12953 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12954 } else {
12955 (range, clipboard_text.clone())
12956 };
12957
12958 edits.push((edit_range, edit_text));
12959 }
12960
12961 drop(snapshot);
12962 buffer.edit(edits, auto_indent_mode, cx);
12963
12964 anchors
12965 });
12966
12967 this.change_selections(Default::default(), window, cx, |s| {
12968 s.select_anchors(selection_anchors);
12969 });
12970 }
12971
12972 // 🤔 | .. | show_in_menu |
12973 // | .. | true true
12974 // | had_edit_prediction | false true
12975
12976 let trigger_in_words =
12977 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12978
12979 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12980 });
12981 }
12982
12983 pub fn diff_clipboard_with_selection(
12984 &mut self,
12985 _: &DiffClipboardWithSelection,
12986 window: &mut Window,
12987 cx: &mut Context<Self>,
12988 ) {
12989 let selections = self
12990 .selections
12991 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
12992
12993 if selections.is_empty() {
12994 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12995 return;
12996 };
12997
12998 let clipboard_text = match cx.read_from_clipboard() {
12999 Some(item) => match item.entries().first() {
13000 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13001 _ => None,
13002 },
13003 None => None,
13004 };
13005
13006 let Some(clipboard_text) = clipboard_text else {
13007 log::warn!("Clipboard doesn't contain text.");
13008 return;
13009 };
13010
13011 window.dispatch_action(
13012 Box::new(DiffClipboardWithSelectionData {
13013 clipboard_text,
13014 editor: cx.entity(),
13015 }),
13016 cx,
13017 );
13018 }
13019
13020 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13021 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13022 if let Some(item) = cx.read_from_clipboard() {
13023 let entries = item.entries();
13024
13025 match entries.first() {
13026 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13027 // of all the pasted entries.
13028 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13029 .do_paste(
13030 clipboard_string.text(),
13031 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13032 true,
13033 window,
13034 cx,
13035 ),
13036 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13037 }
13038 }
13039 }
13040
13041 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13042 if self.read_only(cx) {
13043 return;
13044 }
13045
13046 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13047
13048 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13049 if let Some((selections, _)) =
13050 self.selection_history.transaction(transaction_id).cloned()
13051 {
13052 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13053 s.select_anchors(selections.to_vec());
13054 });
13055 } else {
13056 log::error!(
13057 "No entry in selection_history found for undo. \
13058 This may correspond to a bug where undo does not update the selection. \
13059 If this is occurring, please add details to \
13060 https://github.com/zed-industries/zed/issues/22692"
13061 );
13062 }
13063 self.request_autoscroll(Autoscroll::fit(), cx);
13064 self.unmark_text(window, cx);
13065 self.refresh_edit_prediction(true, false, window, cx);
13066 cx.emit(EditorEvent::Edited { transaction_id });
13067 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13068 }
13069 }
13070
13071 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13072 if self.read_only(cx) {
13073 return;
13074 }
13075
13076 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13077
13078 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13079 if let Some((_, Some(selections))) =
13080 self.selection_history.transaction(transaction_id).cloned()
13081 {
13082 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13083 s.select_anchors(selections.to_vec());
13084 });
13085 } else {
13086 log::error!(
13087 "No entry in selection_history found for redo. \
13088 This may correspond to a bug where undo does not update the selection. \
13089 If this is occurring, please add details to \
13090 https://github.com/zed-industries/zed/issues/22692"
13091 );
13092 }
13093 self.request_autoscroll(Autoscroll::fit(), cx);
13094 self.unmark_text(window, cx);
13095 self.refresh_edit_prediction(true, false, window, cx);
13096 cx.emit(EditorEvent::Edited { transaction_id });
13097 }
13098 }
13099
13100 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13101 self.buffer
13102 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13103 }
13104
13105 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13106 self.buffer
13107 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13108 }
13109
13110 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13112 self.change_selections(Default::default(), window, cx, |s| {
13113 s.move_with(|map, selection| {
13114 let cursor = if selection.is_empty() {
13115 movement::left(map, selection.start)
13116 } else {
13117 selection.start
13118 };
13119 selection.collapse_to(cursor, SelectionGoal::None);
13120 });
13121 })
13122 }
13123
13124 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13126 self.change_selections(Default::default(), window, cx, |s| {
13127 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13128 })
13129 }
13130
13131 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13132 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13133 self.change_selections(Default::default(), window, cx, |s| {
13134 s.move_with(|map, selection| {
13135 let cursor = if selection.is_empty() {
13136 movement::right(map, selection.end)
13137 } else {
13138 selection.end
13139 };
13140 selection.collapse_to(cursor, SelectionGoal::None)
13141 });
13142 })
13143 }
13144
13145 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13146 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13147 self.change_selections(Default::default(), window, cx, |s| {
13148 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13149 });
13150 }
13151
13152 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13153 if self.take_rename(true, window, cx).is_some() {
13154 return;
13155 }
13156
13157 if self.mode.is_single_line() {
13158 cx.propagate();
13159 return;
13160 }
13161
13162 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13163
13164 let text_layout_details = &self.text_layout_details(window);
13165 let selection_count = self.selections.count();
13166 let first_selection = self.selections.first_anchor();
13167
13168 self.change_selections(Default::default(), window, cx, |s| {
13169 s.move_with(|map, selection| {
13170 if !selection.is_empty() {
13171 selection.goal = SelectionGoal::None;
13172 }
13173 let (cursor, goal) = movement::up(
13174 map,
13175 selection.start,
13176 selection.goal,
13177 false,
13178 text_layout_details,
13179 );
13180 selection.collapse_to(cursor, goal);
13181 });
13182 });
13183
13184 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13185 {
13186 cx.propagate();
13187 }
13188 }
13189
13190 pub fn move_up_by_lines(
13191 &mut self,
13192 action: &MoveUpByLines,
13193 window: &mut Window,
13194 cx: &mut Context<Self>,
13195 ) {
13196 if self.take_rename(true, window, cx).is_some() {
13197 return;
13198 }
13199
13200 if self.mode.is_single_line() {
13201 cx.propagate();
13202 return;
13203 }
13204
13205 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13206
13207 let text_layout_details = &self.text_layout_details(window);
13208
13209 self.change_selections(Default::default(), window, cx, |s| {
13210 s.move_with(|map, selection| {
13211 if !selection.is_empty() {
13212 selection.goal = SelectionGoal::None;
13213 }
13214 let (cursor, goal) = movement::up_by_rows(
13215 map,
13216 selection.start,
13217 action.lines,
13218 selection.goal,
13219 false,
13220 text_layout_details,
13221 );
13222 selection.collapse_to(cursor, goal);
13223 });
13224 })
13225 }
13226
13227 pub fn move_down_by_lines(
13228 &mut self,
13229 action: &MoveDownByLines,
13230 window: &mut Window,
13231 cx: &mut Context<Self>,
13232 ) {
13233 if self.take_rename(true, window, cx).is_some() {
13234 return;
13235 }
13236
13237 if self.mode.is_single_line() {
13238 cx.propagate();
13239 return;
13240 }
13241
13242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13243
13244 let text_layout_details = &self.text_layout_details(window);
13245
13246 self.change_selections(Default::default(), window, cx, |s| {
13247 s.move_with(|map, selection| {
13248 if !selection.is_empty() {
13249 selection.goal = SelectionGoal::None;
13250 }
13251 let (cursor, goal) = movement::down_by_rows(
13252 map,
13253 selection.start,
13254 action.lines,
13255 selection.goal,
13256 false,
13257 text_layout_details,
13258 );
13259 selection.collapse_to(cursor, goal);
13260 });
13261 })
13262 }
13263
13264 pub fn select_down_by_lines(
13265 &mut self,
13266 action: &SelectDownByLines,
13267 window: &mut Window,
13268 cx: &mut Context<Self>,
13269 ) {
13270 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13271 let text_layout_details = &self.text_layout_details(window);
13272 self.change_selections(Default::default(), window, cx, |s| {
13273 s.move_heads_with(|map, head, goal| {
13274 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13275 })
13276 })
13277 }
13278
13279 pub fn select_up_by_lines(
13280 &mut self,
13281 action: &SelectUpByLines,
13282 window: &mut Window,
13283 cx: &mut Context<Self>,
13284 ) {
13285 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13286 let text_layout_details = &self.text_layout_details(window);
13287 self.change_selections(Default::default(), window, cx, |s| {
13288 s.move_heads_with(|map, head, goal| {
13289 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13290 })
13291 })
13292 }
13293
13294 pub fn select_page_up(
13295 &mut self,
13296 _: &SelectPageUp,
13297 window: &mut Window,
13298 cx: &mut Context<Self>,
13299 ) {
13300 let Some(row_count) = self.visible_row_count() else {
13301 return;
13302 };
13303
13304 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13305
13306 let text_layout_details = &self.text_layout_details(window);
13307
13308 self.change_selections(Default::default(), window, cx, |s| {
13309 s.move_heads_with(|map, head, goal| {
13310 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13311 })
13312 })
13313 }
13314
13315 pub fn move_page_up(
13316 &mut self,
13317 action: &MovePageUp,
13318 window: &mut Window,
13319 cx: &mut Context<Self>,
13320 ) {
13321 if self.take_rename(true, window, cx).is_some() {
13322 return;
13323 }
13324
13325 if self
13326 .context_menu
13327 .borrow_mut()
13328 .as_mut()
13329 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13330 .unwrap_or(false)
13331 {
13332 return;
13333 }
13334
13335 if matches!(self.mode, EditorMode::SingleLine) {
13336 cx.propagate();
13337 return;
13338 }
13339
13340 let Some(row_count) = self.visible_row_count() else {
13341 return;
13342 };
13343
13344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13345
13346 let effects = if action.center_cursor {
13347 SelectionEffects::scroll(Autoscroll::center())
13348 } else {
13349 SelectionEffects::default()
13350 };
13351
13352 let text_layout_details = &self.text_layout_details(window);
13353
13354 self.change_selections(effects, window, cx, |s| {
13355 s.move_with(|map, selection| {
13356 if !selection.is_empty() {
13357 selection.goal = SelectionGoal::None;
13358 }
13359 let (cursor, goal) = movement::up_by_rows(
13360 map,
13361 selection.end,
13362 row_count,
13363 selection.goal,
13364 false,
13365 text_layout_details,
13366 );
13367 selection.collapse_to(cursor, goal);
13368 });
13369 });
13370 }
13371
13372 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13373 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13374 let text_layout_details = &self.text_layout_details(window);
13375 self.change_selections(Default::default(), window, cx, |s| {
13376 s.move_heads_with(|map, head, goal| {
13377 movement::up(map, head, goal, false, text_layout_details)
13378 })
13379 })
13380 }
13381
13382 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13383 self.take_rename(true, window, cx);
13384
13385 if self.mode.is_single_line() {
13386 cx.propagate();
13387 return;
13388 }
13389
13390 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13391
13392 let text_layout_details = &self.text_layout_details(window);
13393 let selection_count = self.selections.count();
13394 let first_selection = self.selections.first_anchor();
13395
13396 self.change_selections(Default::default(), window, cx, |s| {
13397 s.move_with(|map, selection| {
13398 if !selection.is_empty() {
13399 selection.goal = SelectionGoal::None;
13400 }
13401 let (cursor, goal) = movement::down(
13402 map,
13403 selection.end,
13404 selection.goal,
13405 false,
13406 text_layout_details,
13407 );
13408 selection.collapse_to(cursor, goal);
13409 });
13410 });
13411
13412 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13413 {
13414 cx.propagate();
13415 }
13416 }
13417
13418 pub fn select_page_down(
13419 &mut self,
13420 _: &SelectPageDown,
13421 window: &mut Window,
13422 cx: &mut Context<Self>,
13423 ) {
13424 let Some(row_count) = self.visible_row_count() else {
13425 return;
13426 };
13427
13428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13429
13430 let text_layout_details = &self.text_layout_details(window);
13431
13432 self.change_selections(Default::default(), window, cx, |s| {
13433 s.move_heads_with(|map, head, goal| {
13434 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13435 })
13436 })
13437 }
13438
13439 pub fn move_page_down(
13440 &mut self,
13441 action: &MovePageDown,
13442 window: &mut Window,
13443 cx: &mut Context<Self>,
13444 ) {
13445 if self.take_rename(true, window, cx).is_some() {
13446 return;
13447 }
13448
13449 if self
13450 .context_menu
13451 .borrow_mut()
13452 .as_mut()
13453 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13454 .unwrap_or(false)
13455 {
13456 return;
13457 }
13458
13459 if matches!(self.mode, EditorMode::SingleLine) {
13460 cx.propagate();
13461 return;
13462 }
13463
13464 let Some(row_count) = self.visible_row_count() else {
13465 return;
13466 };
13467
13468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13469
13470 let effects = if action.center_cursor {
13471 SelectionEffects::scroll(Autoscroll::center())
13472 } else {
13473 SelectionEffects::default()
13474 };
13475
13476 let text_layout_details = &self.text_layout_details(window);
13477 self.change_selections(effects, window, cx, |s| {
13478 s.move_with(|map, selection| {
13479 if !selection.is_empty() {
13480 selection.goal = SelectionGoal::None;
13481 }
13482 let (cursor, goal) = movement::down_by_rows(
13483 map,
13484 selection.end,
13485 row_count,
13486 selection.goal,
13487 false,
13488 text_layout_details,
13489 );
13490 selection.collapse_to(cursor, goal);
13491 });
13492 });
13493 }
13494
13495 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13496 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13497 let text_layout_details = &self.text_layout_details(window);
13498 self.change_selections(Default::default(), window, cx, |s| {
13499 s.move_heads_with(|map, head, goal| {
13500 movement::down(map, head, goal, false, text_layout_details)
13501 })
13502 });
13503 }
13504
13505 pub fn context_menu_first(
13506 &mut self,
13507 _: &ContextMenuFirst,
13508 window: &mut Window,
13509 cx: &mut Context<Self>,
13510 ) {
13511 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13512 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13513 }
13514 }
13515
13516 pub fn context_menu_prev(
13517 &mut self,
13518 _: &ContextMenuPrevious,
13519 window: &mut Window,
13520 cx: &mut Context<Self>,
13521 ) {
13522 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13523 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13524 }
13525 }
13526
13527 pub fn context_menu_next(
13528 &mut self,
13529 _: &ContextMenuNext,
13530 window: &mut Window,
13531 cx: &mut Context<Self>,
13532 ) {
13533 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13534 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13535 }
13536 }
13537
13538 pub fn context_menu_last(
13539 &mut self,
13540 _: &ContextMenuLast,
13541 window: &mut Window,
13542 cx: &mut Context<Self>,
13543 ) {
13544 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13545 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13546 }
13547 }
13548
13549 pub fn signature_help_prev(
13550 &mut self,
13551 _: &SignatureHelpPrevious,
13552 _: &mut Window,
13553 cx: &mut Context<Self>,
13554 ) {
13555 if let Some(popover) = self.signature_help_state.popover_mut() {
13556 if popover.current_signature == 0 {
13557 popover.current_signature = popover.signatures.len() - 1;
13558 } else {
13559 popover.current_signature -= 1;
13560 }
13561 cx.notify();
13562 }
13563 }
13564
13565 pub fn signature_help_next(
13566 &mut self,
13567 _: &SignatureHelpNext,
13568 _: &mut Window,
13569 cx: &mut Context<Self>,
13570 ) {
13571 if let Some(popover) = self.signature_help_state.popover_mut() {
13572 if popover.current_signature + 1 == popover.signatures.len() {
13573 popover.current_signature = 0;
13574 } else {
13575 popover.current_signature += 1;
13576 }
13577 cx.notify();
13578 }
13579 }
13580
13581 pub fn move_to_previous_word_start(
13582 &mut self,
13583 _: &MoveToPreviousWordStart,
13584 window: &mut Window,
13585 cx: &mut Context<Self>,
13586 ) {
13587 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13588 self.change_selections(Default::default(), window, cx, |s| {
13589 s.move_cursors_with(|map, head, _| {
13590 (
13591 movement::previous_word_start(map, head),
13592 SelectionGoal::None,
13593 )
13594 });
13595 })
13596 }
13597
13598 pub fn move_to_previous_subword_start(
13599 &mut self,
13600 _: &MoveToPreviousSubwordStart,
13601 window: &mut Window,
13602 cx: &mut Context<Self>,
13603 ) {
13604 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13605 self.change_selections(Default::default(), window, cx, |s| {
13606 s.move_cursors_with(|map, head, _| {
13607 (
13608 movement::previous_subword_start(map, head),
13609 SelectionGoal::None,
13610 )
13611 });
13612 })
13613 }
13614
13615 pub fn select_to_previous_word_start(
13616 &mut self,
13617 _: &SelectToPreviousWordStart,
13618 window: &mut Window,
13619 cx: &mut Context<Self>,
13620 ) {
13621 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13622 self.change_selections(Default::default(), window, cx, |s| {
13623 s.move_heads_with(|map, head, _| {
13624 (
13625 movement::previous_word_start(map, head),
13626 SelectionGoal::None,
13627 )
13628 });
13629 })
13630 }
13631
13632 pub fn select_to_previous_subword_start(
13633 &mut self,
13634 _: &SelectToPreviousSubwordStart,
13635 window: &mut Window,
13636 cx: &mut Context<Self>,
13637 ) {
13638 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13639 self.change_selections(Default::default(), window, cx, |s| {
13640 s.move_heads_with(|map, head, _| {
13641 (
13642 movement::previous_subword_start(map, head),
13643 SelectionGoal::None,
13644 )
13645 });
13646 })
13647 }
13648
13649 pub fn delete_to_previous_word_start(
13650 &mut self,
13651 action: &DeleteToPreviousWordStart,
13652 window: &mut Window,
13653 cx: &mut Context<Self>,
13654 ) {
13655 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13656 self.transact(window, cx, |this, window, cx| {
13657 this.select_autoclose_pair(window, cx);
13658 this.change_selections(Default::default(), window, cx, |s| {
13659 s.move_with(|map, selection| {
13660 if selection.is_empty() {
13661 let mut cursor = if action.ignore_newlines {
13662 movement::previous_word_start(map, selection.head())
13663 } else {
13664 movement::previous_word_start_or_newline(map, selection.head())
13665 };
13666 cursor = movement::adjust_greedy_deletion(
13667 map,
13668 selection.head(),
13669 cursor,
13670 action.ignore_brackets,
13671 );
13672 selection.set_head(cursor, SelectionGoal::None);
13673 }
13674 });
13675 });
13676 this.insert("", window, cx);
13677 });
13678 }
13679
13680 pub fn delete_to_previous_subword_start(
13681 &mut self,
13682 _: &DeleteToPreviousSubwordStart,
13683 window: &mut Window,
13684 cx: &mut Context<Self>,
13685 ) {
13686 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13687 self.transact(window, cx, |this, window, cx| {
13688 this.select_autoclose_pair(window, cx);
13689 this.change_selections(Default::default(), window, cx, |s| {
13690 s.move_with(|map, selection| {
13691 if selection.is_empty() {
13692 let mut cursor = movement::previous_subword_start(map, selection.head());
13693 cursor =
13694 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13695 selection.set_head(cursor, SelectionGoal::None);
13696 }
13697 });
13698 });
13699 this.insert("", window, cx);
13700 });
13701 }
13702
13703 pub fn move_to_next_word_end(
13704 &mut self,
13705 _: &MoveToNextWordEnd,
13706 window: &mut Window,
13707 cx: &mut Context<Self>,
13708 ) {
13709 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13710 self.change_selections(Default::default(), window, cx, |s| {
13711 s.move_cursors_with(|map, head, _| {
13712 (movement::next_word_end(map, head), SelectionGoal::None)
13713 });
13714 })
13715 }
13716
13717 pub fn move_to_next_subword_end(
13718 &mut self,
13719 _: &MoveToNextSubwordEnd,
13720 window: &mut Window,
13721 cx: &mut Context<Self>,
13722 ) {
13723 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13724 self.change_selections(Default::default(), window, cx, |s| {
13725 s.move_cursors_with(|map, head, _| {
13726 (movement::next_subword_end(map, head), SelectionGoal::None)
13727 });
13728 })
13729 }
13730
13731 pub fn select_to_next_word_end(
13732 &mut self,
13733 _: &SelectToNextWordEnd,
13734 window: &mut Window,
13735 cx: &mut Context<Self>,
13736 ) {
13737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13738 self.change_selections(Default::default(), window, cx, |s| {
13739 s.move_heads_with(|map, head, _| {
13740 (movement::next_word_end(map, head), SelectionGoal::None)
13741 });
13742 })
13743 }
13744
13745 pub fn select_to_next_subword_end(
13746 &mut self,
13747 _: &SelectToNextSubwordEnd,
13748 window: &mut Window,
13749 cx: &mut Context<Self>,
13750 ) {
13751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13752 self.change_selections(Default::default(), window, cx, |s| {
13753 s.move_heads_with(|map, head, _| {
13754 (movement::next_subword_end(map, head), SelectionGoal::None)
13755 });
13756 })
13757 }
13758
13759 pub fn delete_to_next_word_end(
13760 &mut self,
13761 action: &DeleteToNextWordEnd,
13762 window: &mut Window,
13763 cx: &mut Context<Self>,
13764 ) {
13765 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13766 self.transact(window, cx, |this, window, cx| {
13767 this.change_selections(Default::default(), window, cx, |s| {
13768 s.move_with(|map, selection| {
13769 if selection.is_empty() {
13770 let mut cursor = if action.ignore_newlines {
13771 movement::next_word_end(map, selection.head())
13772 } else {
13773 movement::next_word_end_or_newline(map, selection.head())
13774 };
13775 cursor = movement::adjust_greedy_deletion(
13776 map,
13777 selection.head(),
13778 cursor,
13779 action.ignore_brackets,
13780 );
13781 selection.set_head(cursor, SelectionGoal::None);
13782 }
13783 });
13784 });
13785 this.insert("", window, cx);
13786 });
13787 }
13788
13789 pub fn delete_to_next_subword_end(
13790 &mut self,
13791 _: &DeleteToNextSubwordEnd,
13792 window: &mut Window,
13793 cx: &mut Context<Self>,
13794 ) {
13795 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13796 self.transact(window, cx, |this, window, cx| {
13797 this.change_selections(Default::default(), window, cx, |s| {
13798 s.move_with(|map, selection| {
13799 if selection.is_empty() {
13800 let mut cursor = movement::next_subword_end(map, selection.head());
13801 cursor =
13802 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13803 selection.set_head(cursor, SelectionGoal::None);
13804 }
13805 });
13806 });
13807 this.insert("", window, cx);
13808 });
13809 }
13810
13811 pub fn move_to_beginning_of_line(
13812 &mut self,
13813 action: &MoveToBeginningOfLine,
13814 window: &mut Window,
13815 cx: &mut Context<Self>,
13816 ) {
13817 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13818 self.change_selections(Default::default(), window, cx, |s| {
13819 s.move_cursors_with(|map, head, _| {
13820 (
13821 movement::indented_line_beginning(
13822 map,
13823 head,
13824 action.stop_at_soft_wraps,
13825 action.stop_at_indent,
13826 ),
13827 SelectionGoal::None,
13828 )
13829 });
13830 })
13831 }
13832
13833 pub fn select_to_beginning_of_line(
13834 &mut self,
13835 action: &SelectToBeginningOfLine,
13836 window: &mut Window,
13837 cx: &mut Context<Self>,
13838 ) {
13839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13840 self.change_selections(Default::default(), window, cx, |s| {
13841 s.move_heads_with(|map, head, _| {
13842 (
13843 movement::indented_line_beginning(
13844 map,
13845 head,
13846 action.stop_at_soft_wraps,
13847 action.stop_at_indent,
13848 ),
13849 SelectionGoal::None,
13850 )
13851 });
13852 });
13853 }
13854
13855 pub fn delete_to_beginning_of_line(
13856 &mut self,
13857 action: &DeleteToBeginningOfLine,
13858 window: &mut Window,
13859 cx: &mut Context<Self>,
13860 ) {
13861 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13862 self.transact(window, cx, |this, window, cx| {
13863 this.change_selections(Default::default(), window, cx, |s| {
13864 s.move_with(|_, selection| {
13865 selection.reversed = true;
13866 });
13867 });
13868
13869 this.select_to_beginning_of_line(
13870 &SelectToBeginningOfLine {
13871 stop_at_soft_wraps: false,
13872 stop_at_indent: action.stop_at_indent,
13873 },
13874 window,
13875 cx,
13876 );
13877 this.backspace(&Backspace, window, cx);
13878 });
13879 }
13880
13881 pub fn move_to_end_of_line(
13882 &mut self,
13883 action: &MoveToEndOfLine,
13884 window: &mut Window,
13885 cx: &mut Context<Self>,
13886 ) {
13887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13888 self.change_selections(Default::default(), window, cx, |s| {
13889 s.move_cursors_with(|map, head, _| {
13890 (
13891 movement::line_end(map, head, action.stop_at_soft_wraps),
13892 SelectionGoal::None,
13893 )
13894 });
13895 })
13896 }
13897
13898 pub fn select_to_end_of_line(
13899 &mut self,
13900 action: &SelectToEndOfLine,
13901 window: &mut Window,
13902 cx: &mut Context<Self>,
13903 ) {
13904 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13905 self.change_selections(Default::default(), window, cx, |s| {
13906 s.move_heads_with(|map, head, _| {
13907 (
13908 movement::line_end(map, head, action.stop_at_soft_wraps),
13909 SelectionGoal::None,
13910 )
13911 });
13912 })
13913 }
13914
13915 pub fn delete_to_end_of_line(
13916 &mut self,
13917 _: &DeleteToEndOfLine,
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.select_to_end_of_line(
13924 &SelectToEndOfLine {
13925 stop_at_soft_wraps: false,
13926 },
13927 window,
13928 cx,
13929 );
13930 this.delete(&Delete, window, cx);
13931 });
13932 }
13933
13934 pub fn cut_to_end_of_line(
13935 &mut self,
13936 action: &CutToEndOfLine,
13937 window: &mut Window,
13938 cx: &mut Context<Self>,
13939 ) {
13940 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13941 self.transact(window, cx, |this, window, cx| {
13942 this.select_to_end_of_line(
13943 &SelectToEndOfLine {
13944 stop_at_soft_wraps: false,
13945 },
13946 window,
13947 cx,
13948 );
13949 if !action.stop_at_newlines {
13950 this.change_selections(Default::default(), window, cx, |s| {
13951 s.move_with(|_, sel| {
13952 if sel.is_empty() {
13953 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13954 }
13955 });
13956 });
13957 }
13958 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13959 let item = this.cut_common(false, window, cx);
13960 cx.write_to_clipboard(item);
13961 });
13962 }
13963
13964 pub fn move_to_start_of_paragraph(
13965 &mut self,
13966 _: &MoveToStartOfParagraph,
13967 window: &mut Window,
13968 cx: &mut Context<Self>,
13969 ) {
13970 if matches!(self.mode, EditorMode::SingleLine) {
13971 cx.propagate();
13972 return;
13973 }
13974 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13975 self.change_selections(Default::default(), window, cx, |s| {
13976 s.move_with(|map, selection| {
13977 selection.collapse_to(
13978 movement::start_of_paragraph(map, selection.head(), 1),
13979 SelectionGoal::None,
13980 )
13981 });
13982 })
13983 }
13984
13985 pub fn move_to_end_of_paragraph(
13986 &mut self,
13987 _: &MoveToEndOfParagraph,
13988 window: &mut Window,
13989 cx: &mut Context<Self>,
13990 ) {
13991 if matches!(self.mode, EditorMode::SingleLine) {
13992 cx.propagate();
13993 return;
13994 }
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13996 self.change_selections(Default::default(), window, cx, |s| {
13997 s.move_with(|map, selection| {
13998 selection.collapse_to(
13999 movement::end_of_paragraph(map, selection.head(), 1),
14000 SelectionGoal::None,
14001 )
14002 });
14003 })
14004 }
14005
14006 pub fn select_to_start_of_paragraph(
14007 &mut self,
14008 _: &SelectToStartOfParagraph,
14009 window: &mut Window,
14010 cx: &mut Context<Self>,
14011 ) {
14012 if matches!(self.mode, EditorMode::SingleLine) {
14013 cx.propagate();
14014 return;
14015 }
14016 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14017 self.change_selections(Default::default(), window, cx, |s| {
14018 s.move_heads_with(|map, head, _| {
14019 (
14020 movement::start_of_paragraph(map, head, 1),
14021 SelectionGoal::None,
14022 )
14023 });
14024 })
14025 }
14026
14027 pub fn select_to_end_of_paragraph(
14028 &mut self,
14029 _: &SelectToEndOfParagraph,
14030 window: &mut Window,
14031 cx: &mut Context<Self>,
14032 ) {
14033 if matches!(self.mode, EditorMode::SingleLine) {
14034 cx.propagate();
14035 return;
14036 }
14037 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14038 self.change_selections(Default::default(), window, cx, |s| {
14039 s.move_heads_with(|map, head, _| {
14040 (
14041 movement::end_of_paragraph(map, head, 1),
14042 SelectionGoal::None,
14043 )
14044 });
14045 })
14046 }
14047
14048 pub fn move_to_start_of_excerpt(
14049 &mut self,
14050 _: &MoveToStartOfExcerpt,
14051 window: &mut Window,
14052 cx: &mut Context<Self>,
14053 ) {
14054 if matches!(self.mode, EditorMode::SingleLine) {
14055 cx.propagate();
14056 return;
14057 }
14058 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14059 self.change_selections(Default::default(), window, cx, |s| {
14060 s.move_with(|map, selection| {
14061 selection.collapse_to(
14062 movement::start_of_excerpt(
14063 map,
14064 selection.head(),
14065 workspace::searchable::Direction::Prev,
14066 ),
14067 SelectionGoal::None,
14068 )
14069 });
14070 })
14071 }
14072
14073 pub fn move_to_start_of_next_excerpt(
14074 &mut self,
14075 _: &MoveToStartOfNextExcerpt,
14076 window: &mut Window,
14077 cx: &mut Context<Self>,
14078 ) {
14079 if matches!(self.mode, EditorMode::SingleLine) {
14080 cx.propagate();
14081 return;
14082 }
14083
14084 self.change_selections(Default::default(), window, cx, |s| {
14085 s.move_with(|map, selection| {
14086 selection.collapse_to(
14087 movement::start_of_excerpt(
14088 map,
14089 selection.head(),
14090 workspace::searchable::Direction::Next,
14091 ),
14092 SelectionGoal::None,
14093 )
14094 });
14095 })
14096 }
14097
14098 pub fn move_to_end_of_excerpt(
14099 &mut self,
14100 _: &MoveToEndOfExcerpt,
14101 window: &mut Window,
14102 cx: &mut Context<Self>,
14103 ) {
14104 if matches!(self.mode, EditorMode::SingleLine) {
14105 cx.propagate();
14106 return;
14107 }
14108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14109 self.change_selections(Default::default(), window, cx, |s| {
14110 s.move_with(|map, selection| {
14111 selection.collapse_to(
14112 movement::end_of_excerpt(
14113 map,
14114 selection.head(),
14115 workspace::searchable::Direction::Next,
14116 ),
14117 SelectionGoal::None,
14118 )
14119 });
14120 })
14121 }
14122
14123 pub fn move_to_end_of_previous_excerpt(
14124 &mut self,
14125 _: &MoveToEndOfPreviousExcerpt,
14126 window: &mut Window,
14127 cx: &mut Context<Self>,
14128 ) {
14129 if matches!(self.mode, EditorMode::SingleLine) {
14130 cx.propagate();
14131 return;
14132 }
14133 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14134 self.change_selections(Default::default(), window, cx, |s| {
14135 s.move_with(|map, selection| {
14136 selection.collapse_to(
14137 movement::end_of_excerpt(
14138 map,
14139 selection.head(),
14140 workspace::searchable::Direction::Prev,
14141 ),
14142 SelectionGoal::None,
14143 )
14144 });
14145 })
14146 }
14147
14148 pub fn select_to_start_of_excerpt(
14149 &mut self,
14150 _: &SelectToStartOfExcerpt,
14151 window: &mut Window,
14152 cx: &mut Context<Self>,
14153 ) {
14154 if matches!(self.mode, EditorMode::SingleLine) {
14155 cx.propagate();
14156 return;
14157 }
14158 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14159 self.change_selections(Default::default(), window, cx, |s| {
14160 s.move_heads_with(|map, head, _| {
14161 (
14162 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14163 SelectionGoal::None,
14164 )
14165 });
14166 })
14167 }
14168
14169 pub fn select_to_start_of_next_excerpt(
14170 &mut self,
14171 _: &SelectToStartOfNextExcerpt,
14172 window: &mut Window,
14173 cx: &mut Context<Self>,
14174 ) {
14175 if matches!(self.mode, EditorMode::SingleLine) {
14176 cx.propagate();
14177 return;
14178 }
14179 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14180 self.change_selections(Default::default(), window, cx, |s| {
14181 s.move_heads_with(|map, head, _| {
14182 (
14183 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14184 SelectionGoal::None,
14185 )
14186 });
14187 })
14188 }
14189
14190 pub fn select_to_end_of_excerpt(
14191 &mut self,
14192 _: &SelectToEndOfExcerpt,
14193 window: &mut Window,
14194 cx: &mut Context<Self>,
14195 ) {
14196 if matches!(self.mode, EditorMode::SingleLine) {
14197 cx.propagate();
14198 return;
14199 }
14200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14201 self.change_selections(Default::default(), window, cx, |s| {
14202 s.move_heads_with(|map, head, _| {
14203 (
14204 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14205 SelectionGoal::None,
14206 )
14207 });
14208 })
14209 }
14210
14211 pub fn select_to_end_of_previous_excerpt(
14212 &mut self,
14213 _: &SelectToEndOfPreviousExcerpt,
14214 window: &mut Window,
14215 cx: &mut Context<Self>,
14216 ) {
14217 if matches!(self.mode, EditorMode::SingleLine) {
14218 cx.propagate();
14219 return;
14220 }
14221 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14222 self.change_selections(Default::default(), window, cx, |s| {
14223 s.move_heads_with(|map, head, _| {
14224 (
14225 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14226 SelectionGoal::None,
14227 )
14228 });
14229 })
14230 }
14231
14232 pub fn move_to_beginning(
14233 &mut self,
14234 _: &MoveToBeginning,
14235 window: &mut Window,
14236 cx: &mut Context<Self>,
14237 ) {
14238 if matches!(self.mode, EditorMode::SingleLine) {
14239 cx.propagate();
14240 return;
14241 }
14242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14243 self.change_selections(Default::default(), window, cx, |s| {
14244 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14245 });
14246 }
14247
14248 pub fn select_to_beginning(
14249 &mut self,
14250 _: &SelectToBeginning,
14251 window: &mut Window,
14252 cx: &mut Context<Self>,
14253 ) {
14254 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14255 selection.set_head(Point::zero(), SelectionGoal::None);
14256 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14257 self.change_selections(Default::default(), window, cx, |s| {
14258 s.select(vec![selection]);
14259 });
14260 }
14261
14262 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14263 if matches!(self.mode, EditorMode::SingleLine) {
14264 cx.propagate();
14265 return;
14266 }
14267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14268 let cursor = self.buffer.read(cx).read(cx).len();
14269 self.change_selections(Default::default(), window, cx, |s| {
14270 s.select_ranges(vec![cursor..cursor])
14271 });
14272 }
14273
14274 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14275 self.nav_history = nav_history;
14276 }
14277
14278 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14279 self.nav_history.as_ref()
14280 }
14281
14282 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14283 self.push_to_nav_history(
14284 self.selections.newest_anchor().head(),
14285 None,
14286 false,
14287 true,
14288 cx,
14289 );
14290 }
14291
14292 fn push_to_nav_history(
14293 &mut self,
14294 cursor_anchor: Anchor,
14295 new_position: Option<Point>,
14296 is_deactivate: bool,
14297 always: bool,
14298 cx: &mut Context<Self>,
14299 ) {
14300 if let Some(nav_history) = self.nav_history.as_mut() {
14301 let buffer = self.buffer.read(cx).read(cx);
14302 let cursor_position = cursor_anchor.to_point(&buffer);
14303 let scroll_state = self.scroll_manager.anchor();
14304 let scroll_top_row = scroll_state.top_row(&buffer);
14305 drop(buffer);
14306
14307 if let Some(new_position) = new_position {
14308 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14309 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14310 return;
14311 }
14312 }
14313
14314 nav_history.push(
14315 Some(NavigationData {
14316 cursor_anchor,
14317 cursor_position,
14318 scroll_anchor: scroll_state,
14319 scroll_top_row,
14320 }),
14321 cx,
14322 );
14323 cx.emit(EditorEvent::PushedToNavHistory {
14324 anchor: cursor_anchor,
14325 is_deactivate,
14326 })
14327 }
14328 }
14329
14330 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14331 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14332 let buffer = self.buffer.read(cx).snapshot(cx);
14333 let mut selection = self
14334 .selections
14335 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14336 selection.set_head(buffer.len(), SelectionGoal::None);
14337 self.change_selections(Default::default(), window, cx, |s| {
14338 s.select(vec![selection]);
14339 });
14340 }
14341
14342 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14343 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14344 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14345 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14346 });
14347 }
14348
14349 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14350 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14351 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14352 let mut selections = self.selections.all::<Point>(&display_map);
14353 let max_point = display_map.buffer_snapshot().max_point();
14354 for selection in &mut selections {
14355 let rows = selection.spanned_rows(true, &display_map);
14356 selection.start = Point::new(rows.start.0, 0);
14357 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14358 selection.reversed = false;
14359 }
14360 self.change_selections(Default::default(), window, cx, |s| {
14361 s.select(selections);
14362 });
14363 }
14364
14365 pub fn split_selection_into_lines(
14366 &mut self,
14367 action: &SplitSelectionIntoLines,
14368 window: &mut Window,
14369 cx: &mut Context<Self>,
14370 ) {
14371 let selections = self
14372 .selections
14373 .all::<Point>(&self.display_snapshot(cx))
14374 .into_iter()
14375 .map(|selection| selection.start..selection.end)
14376 .collect::<Vec<_>>();
14377 self.unfold_ranges(&selections, true, true, cx);
14378
14379 let mut new_selection_ranges = Vec::new();
14380 {
14381 let buffer = self.buffer.read(cx).read(cx);
14382 for selection in selections {
14383 for row in selection.start.row..selection.end.row {
14384 let line_start = Point::new(row, 0);
14385 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14386
14387 if action.keep_selections {
14388 // Keep the selection range for each line
14389 let selection_start = if row == selection.start.row {
14390 selection.start
14391 } else {
14392 line_start
14393 };
14394 new_selection_ranges.push(selection_start..line_end);
14395 } else {
14396 // Collapse to cursor at end of line
14397 new_selection_ranges.push(line_end..line_end);
14398 }
14399 }
14400
14401 let is_multiline_selection = selection.start.row != selection.end.row;
14402 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14403 // so this action feels more ergonomic when paired with other selection operations
14404 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14405 if !should_skip_last {
14406 if action.keep_selections {
14407 if is_multiline_selection {
14408 let line_start = Point::new(selection.end.row, 0);
14409 new_selection_ranges.push(line_start..selection.end);
14410 } else {
14411 new_selection_ranges.push(selection.start..selection.end);
14412 }
14413 } else {
14414 new_selection_ranges.push(selection.end..selection.end);
14415 }
14416 }
14417 }
14418 }
14419 self.change_selections(Default::default(), window, cx, |s| {
14420 s.select_ranges(new_selection_ranges);
14421 });
14422 }
14423
14424 pub fn add_selection_above(
14425 &mut self,
14426 action: &AddSelectionAbove,
14427 window: &mut Window,
14428 cx: &mut Context<Self>,
14429 ) {
14430 self.add_selection(true, action.skip_soft_wrap, window, cx);
14431 }
14432
14433 pub fn add_selection_below(
14434 &mut self,
14435 action: &AddSelectionBelow,
14436 window: &mut Window,
14437 cx: &mut Context<Self>,
14438 ) {
14439 self.add_selection(false, action.skip_soft_wrap, window, cx);
14440 }
14441
14442 fn add_selection(
14443 &mut self,
14444 above: bool,
14445 skip_soft_wrap: bool,
14446 window: &mut Window,
14447 cx: &mut Context<Self>,
14448 ) {
14449 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14450
14451 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14452 let all_selections = self.selections.all::<Point>(&display_map);
14453 let text_layout_details = self.text_layout_details(window);
14454
14455 let (mut columnar_selections, new_selections_to_columnarize) = {
14456 if let Some(state) = self.add_selections_state.as_ref() {
14457 let columnar_selection_ids: HashSet<_> = state
14458 .groups
14459 .iter()
14460 .flat_map(|group| group.stack.iter())
14461 .copied()
14462 .collect();
14463
14464 all_selections
14465 .into_iter()
14466 .partition(|s| columnar_selection_ids.contains(&s.id))
14467 } else {
14468 (Vec::new(), all_selections)
14469 }
14470 };
14471
14472 let mut state = self
14473 .add_selections_state
14474 .take()
14475 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14476
14477 for selection in new_selections_to_columnarize {
14478 let range = selection.display_range(&display_map).sorted();
14479 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14480 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14481 let positions = start_x.min(end_x)..start_x.max(end_x);
14482 let mut stack = Vec::new();
14483 for row in range.start.row().0..=range.end.row().0 {
14484 if let Some(selection) = self.selections.build_columnar_selection(
14485 &display_map,
14486 DisplayRow(row),
14487 &positions,
14488 selection.reversed,
14489 &text_layout_details,
14490 ) {
14491 stack.push(selection.id);
14492 columnar_selections.push(selection);
14493 }
14494 }
14495 if !stack.is_empty() {
14496 if above {
14497 stack.reverse();
14498 }
14499 state.groups.push(AddSelectionsGroup { above, stack });
14500 }
14501 }
14502
14503 let mut final_selections = Vec::new();
14504 let end_row = if above {
14505 DisplayRow(0)
14506 } else {
14507 display_map.max_point().row()
14508 };
14509
14510 let mut last_added_item_per_group = HashMap::default();
14511 for group in state.groups.iter_mut() {
14512 if let Some(last_id) = group.stack.last() {
14513 last_added_item_per_group.insert(*last_id, group);
14514 }
14515 }
14516
14517 for selection in columnar_selections {
14518 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14519 if above == group.above {
14520 let range = selection.display_range(&display_map).sorted();
14521 debug_assert_eq!(range.start.row(), range.end.row());
14522 let mut row = range.start.row();
14523 let positions =
14524 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14525 Pixels::from(start)..Pixels::from(end)
14526 } else {
14527 let start_x =
14528 display_map.x_for_display_point(range.start, &text_layout_details);
14529 let end_x =
14530 display_map.x_for_display_point(range.end, &text_layout_details);
14531 start_x.min(end_x)..start_x.max(end_x)
14532 };
14533
14534 let mut maybe_new_selection = None;
14535 let direction = if above { -1 } else { 1 };
14536
14537 while row != end_row {
14538 if skip_soft_wrap {
14539 row = display_map
14540 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14541 .row();
14542 } else if above {
14543 row.0 -= 1;
14544 } else {
14545 row.0 += 1;
14546 }
14547
14548 if let Some(new_selection) = self.selections.build_columnar_selection(
14549 &display_map,
14550 row,
14551 &positions,
14552 selection.reversed,
14553 &text_layout_details,
14554 ) {
14555 maybe_new_selection = Some(new_selection);
14556 break;
14557 }
14558 }
14559
14560 if let Some(new_selection) = maybe_new_selection {
14561 group.stack.push(new_selection.id);
14562 if above {
14563 final_selections.push(new_selection);
14564 final_selections.push(selection);
14565 } else {
14566 final_selections.push(selection);
14567 final_selections.push(new_selection);
14568 }
14569 } else {
14570 final_selections.push(selection);
14571 }
14572 } else {
14573 group.stack.pop();
14574 }
14575 } else {
14576 final_selections.push(selection);
14577 }
14578 }
14579
14580 self.change_selections(Default::default(), window, cx, |s| {
14581 s.select(final_selections);
14582 });
14583
14584 let final_selection_ids: HashSet<_> = self
14585 .selections
14586 .all::<Point>(&display_map)
14587 .iter()
14588 .map(|s| s.id)
14589 .collect();
14590 state.groups.retain_mut(|group| {
14591 // selections might get merged above so we remove invalid items from stacks
14592 group.stack.retain(|id| final_selection_ids.contains(id));
14593
14594 // single selection in stack can be treated as initial state
14595 group.stack.len() > 1
14596 });
14597
14598 if !state.groups.is_empty() {
14599 self.add_selections_state = Some(state);
14600 }
14601 }
14602
14603 fn select_match_ranges(
14604 &mut self,
14605 range: Range<MultiBufferOffset>,
14606 reversed: bool,
14607 replace_newest: bool,
14608 auto_scroll: Option<Autoscroll>,
14609 window: &mut Window,
14610 cx: &mut Context<Editor>,
14611 ) {
14612 self.unfold_ranges(
14613 std::slice::from_ref(&range),
14614 false,
14615 auto_scroll.is_some(),
14616 cx,
14617 );
14618 let effects = if let Some(scroll) = auto_scroll {
14619 SelectionEffects::scroll(scroll)
14620 } else {
14621 SelectionEffects::no_scroll()
14622 };
14623 self.change_selections(effects, window, cx, |s| {
14624 if replace_newest {
14625 s.delete(s.newest_anchor().id);
14626 }
14627 if reversed {
14628 s.insert_range(range.end..range.start);
14629 } else {
14630 s.insert_range(range);
14631 }
14632 });
14633 }
14634
14635 pub fn select_next_match_internal(
14636 &mut self,
14637 display_map: &DisplaySnapshot,
14638 replace_newest: bool,
14639 autoscroll: Option<Autoscroll>,
14640 window: &mut Window,
14641 cx: &mut Context<Self>,
14642 ) -> Result<()> {
14643 let buffer = display_map.buffer_snapshot();
14644 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14645 if let Some(mut select_next_state) = self.select_next_state.take() {
14646 let query = &select_next_state.query;
14647 if !select_next_state.done {
14648 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14649 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14650 let mut next_selected_range = None;
14651
14652 let bytes_after_last_selection =
14653 buffer.bytes_in_range(last_selection.end..buffer.len());
14654 let bytes_before_first_selection =
14655 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14656 let query_matches = query
14657 .stream_find_iter(bytes_after_last_selection)
14658 .map(|result| (last_selection.end, result))
14659 .chain(
14660 query
14661 .stream_find_iter(bytes_before_first_selection)
14662 .map(|result| (MultiBufferOffset(0), result)),
14663 );
14664
14665 for (start_offset, query_match) in query_matches {
14666 let query_match = query_match.unwrap(); // can only fail due to I/O
14667 let offset_range =
14668 start_offset + query_match.start()..start_offset + query_match.end();
14669
14670 if !select_next_state.wordwise
14671 || (!buffer.is_inside_word(offset_range.start, None)
14672 && !buffer.is_inside_word(offset_range.end, None))
14673 {
14674 let idx = selections
14675 .partition_point(|selection| selection.end <= offset_range.start);
14676 let overlaps = selections
14677 .get(idx)
14678 .map_or(false, |selection| selection.start < offset_range.end);
14679
14680 if !overlaps {
14681 next_selected_range = Some(offset_range);
14682 break;
14683 }
14684 }
14685 }
14686
14687 if let Some(next_selected_range) = next_selected_range {
14688 self.select_match_ranges(
14689 next_selected_range,
14690 last_selection.reversed,
14691 replace_newest,
14692 autoscroll,
14693 window,
14694 cx,
14695 );
14696 } else {
14697 select_next_state.done = true;
14698 }
14699 }
14700
14701 self.select_next_state = Some(select_next_state);
14702 } else {
14703 let mut only_carets = true;
14704 let mut same_text_selected = true;
14705 let mut selected_text = None;
14706
14707 let mut selections_iter = selections.iter().peekable();
14708 while let Some(selection) = selections_iter.next() {
14709 if selection.start != selection.end {
14710 only_carets = false;
14711 }
14712
14713 if same_text_selected {
14714 if selected_text.is_none() {
14715 selected_text =
14716 Some(buffer.text_for_range(selection.range()).collect::<String>());
14717 }
14718
14719 if let Some(next_selection) = selections_iter.peek() {
14720 if next_selection.len() == selection.len() {
14721 let next_selected_text = buffer
14722 .text_for_range(next_selection.range())
14723 .collect::<String>();
14724 if Some(next_selected_text) != selected_text {
14725 same_text_selected = false;
14726 selected_text = None;
14727 }
14728 } else {
14729 same_text_selected = false;
14730 selected_text = None;
14731 }
14732 }
14733 }
14734 }
14735
14736 if only_carets {
14737 for selection in &mut selections {
14738 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14739 selection.start = word_range.start;
14740 selection.end = word_range.end;
14741 selection.goal = SelectionGoal::None;
14742 selection.reversed = false;
14743 self.select_match_ranges(
14744 selection.start..selection.end,
14745 selection.reversed,
14746 replace_newest,
14747 autoscroll,
14748 window,
14749 cx,
14750 );
14751 }
14752
14753 if selections.len() == 1 {
14754 let selection = selections
14755 .last()
14756 .expect("ensured that there's only one selection");
14757 let query = buffer
14758 .text_for_range(selection.start..selection.end)
14759 .collect::<String>();
14760 let is_empty = query.is_empty();
14761 let select_state = SelectNextState {
14762 query: self.build_query(&[query], cx)?,
14763 wordwise: true,
14764 done: is_empty,
14765 };
14766 self.select_next_state = Some(select_state);
14767 } else {
14768 self.select_next_state = None;
14769 }
14770 } else if let Some(selected_text) = selected_text {
14771 self.select_next_state = Some(SelectNextState {
14772 query: self.build_query(&[selected_text], cx)?,
14773 wordwise: false,
14774 done: false,
14775 });
14776 self.select_next_match_internal(
14777 display_map,
14778 replace_newest,
14779 autoscroll,
14780 window,
14781 cx,
14782 )?;
14783 }
14784 }
14785 Ok(())
14786 }
14787
14788 pub fn select_all_matches(
14789 &mut self,
14790 _action: &SelectAllMatches,
14791 window: &mut Window,
14792 cx: &mut Context<Self>,
14793 ) -> Result<()> {
14794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14795
14796 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14797
14798 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14799 let Some(select_next_state) = self.select_next_state.as_mut() else {
14800 return Ok(());
14801 };
14802 if select_next_state.done {
14803 return Ok(());
14804 }
14805
14806 let mut new_selections = Vec::new();
14807
14808 let reversed = self
14809 .selections
14810 .oldest::<MultiBufferOffset>(&display_map)
14811 .reversed;
14812 let buffer = display_map.buffer_snapshot();
14813 let query_matches = select_next_state
14814 .query
14815 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
14816
14817 for query_match in query_matches.into_iter() {
14818 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14819 let offset_range = if reversed {
14820 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
14821 } else {
14822 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
14823 };
14824
14825 if !select_next_state.wordwise
14826 || (!buffer.is_inside_word(offset_range.start, None)
14827 && !buffer.is_inside_word(offset_range.end, None))
14828 {
14829 new_selections.push(offset_range.start..offset_range.end);
14830 }
14831 }
14832
14833 select_next_state.done = true;
14834
14835 if new_selections.is_empty() {
14836 log::error!("bug: new_selections is empty in select_all_matches");
14837 return Ok(());
14838 }
14839
14840 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14841 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14842 selections.select_ranges(new_selections)
14843 });
14844
14845 Ok(())
14846 }
14847
14848 pub fn select_next(
14849 &mut self,
14850 action: &SelectNext,
14851 window: &mut Window,
14852 cx: &mut Context<Self>,
14853 ) -> Result<()> {
14854 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14855 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14856 self.select_next_match_internal(
14857 &display_map,
14858 action.replace_newest,
14859 Some(Autoscroll::newest()),
14860 window,
14861 cx,
14862 )?;
14863 Ok(())
14864 }
14865
14866 pub fn select_previous(
14867 &mut self,
14868 action: &SelectPrevious,
14869 window: &mut Window,
14870 cx: &mut Context<Self>,
14871 ) -> Result<()> {
14872 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14873 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14874 let buffer = display_map.buffer_snapshot();
14875 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14876 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14877 let query = &select_prev_state.query;
14878 if !select_prev_state.done {
14879 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14880 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14881 let mut next_selected_range = None;
14882 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14883 let bytes_before_last_selection =
14884 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
14885 let bytes_after_first_selection =
14886 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14887 let query_matches = query
14888 .stream_find_iter(bytes_before_last_selection)
14889 .map(|result| (last_selection.start, result))
14890 .chain(
14891 query
14892 .stream_find_iter(bytes_after_first_selection)
14893 .map(|result| (buffer.len(), result)),
14894 );
14895 for (end_offset, query_match) in query_matches {
14896 let query_match = query_match.unwrap(); // can only fail due to I/O
14897 let offset_range =
14898 end_offset - query_match.end()..end_offset - query_match.start();
14899
14900 if !select_prev_state.wordwise
14901 || (!buffer.is_inside_word(offset_range.start, None)
14902 && !buffer.is_inside_word(offset_range.end, None))
14903 {
14904 next_selected_range = Some(offset_range);
14905 break;
14906 }
14907 }
14908
14909 if let Some(next_selected_range) = next_selected_range {
14910 self.select_match_ranges(
14911 next_selected_range,
14912 last_selection.reversed,
14913 action.replace_newest,
14914 Some(Autoscroll::newest()),
14915 window,
14916 cx,
14917 );
14918 } else {
14919 select_prev_state.done = true;
14920 }
14921 }
14922
14923 self.select_prev_state = Some(select_prev_state);
14924 } else {
14925 let mut only_carets = true;
14926 let mut same_text_selected = true;
14927 let mut selected_text = None;
14928
14929 let mut selections_iter = selections.iter().peekable();
14930 while let Some(selection) = selections_iter.next() {
14931 if selection.start != selection.end {
14932 only_carets = false;
14933 }
14934
14935 if same_text_selected {
14936 if selected_text.is_none() {
14937 selected_text =
14938 Some(buffer.text_for_range(selection.range()).collect::<String>());
14939 }
14940
14941 if let Some(next_selection) = selections_iter.peek() {
14942 if next_selection.len() == selection.len() {
14943 let next_selected_text = buffer
14944 .text_for_range(next_selection.range())
14945 .collect::<String>();
14946 if Some(next_selected_text) != selected_text {
14947 same_text_selected = false;
14948 selected_text = None;
14949 }
14950 } else {
14951 same_text_selected = false;
14952 selected_text = None;
14953 }
14954 }
14955 }
14956 }
14957
14958 if only_carets {
14959 for selection in &mut selections {
14960 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14961 selection.start = word_range.start;
14962 selection.end = word_range.end;
14963 selection.goal = SelectionGoal::None;
14964 selection.reversed = false;
14965 self.select_match_ranges(
14966 selection.start..selection.end,
14967 selection.reversed,
14968 action.replace_newest,
14969 Some(Autoscroll::newest()),
14970 window,
14971 cx,
14972 );
14973 }
14974 if selections.len() == 1 {
14975 let selection = selections
14976 .last()
14977 .expect("ensured that there's only one selection");
14978 let query = buffer
14979 .text_for_range(selection.start..selection.end)
14980 .collect::<String>();
14981 let is_empty = query.is_empty();
14982 let select_state = SelectNextState {
14983 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
14984 wordwise: true,
14985 done: is_empty,
14986 };
14987 self.select_prev_state = Some(select_state);
14988 } else {
14989 self.select_prev_state = None;
14990 }
14991 } else if let Some(selected_text) = selected_text {
14992 self.select_prev_state = Some(SelectNextState {
14993 query: self
14994 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
14995 wordwise: false,
14996 done: false,
14997 });
14998 self.select_previous(action, window, cx)?;
14999 }
15000 }
15001 Ok(())
15002 }
15003
15004 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15005 /// setting the case sensitivity based on the global
15006 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15007 /// editor's settings.
15008 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15009 where
15010 I: IntoIterator<Item = P>,
15011 P: AsRef<[u8]>,
15012 {
15013 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15014 || EditorSettings::get_global(cx).search.case_sensitive,
15015 |value| value,
15016 );
15017
15018 let mut builder = AhoCorasickBuilder::new();
15019 builder.ascii_case_insensitive(!case_sensitive);
15020 builder.build(patterns)
15021 }
15022
15023 pub fn find_next_match(
15024 &mut self,
15025 _: &FindNextMatch,
15026 window: &mut Window,
15027 cx: &mut Context<Self>,
15028 ) -> Result<()> {
15029 let selections = self.selections.disjoint_anchors_arc();
15030 match selections.first() {
15031 Some(first) if selections.len() >= 2 => {
15032 self.change_selections(Default::default(), window, cx, |s| {
15033 s.select_ranges([first.range()]);
15034 });
15035 }
15036 _ => self.select_next(
15037 &SelectNext {
15038 replace_newest: true,
15039 },
15040 window,
15041 cx,
15042 )?,
15043 }
15044 Ok(())
15045 }
15046
15047 pub fn find_previous_match(
15048 &mut self,
15049 _: &FindPreviousMatch,
15050 window: &mut Window,
15051 cx: &mut Context<Self>,
15052 ) -> Result<()> {
15053 let selections = self.selections.disjoint_anchors_arc();
15054 match selections.last() {
15055 Some(last) if selections.len() >= 2 => {
15056 self.change_selections(Default::default(), window, cx, |s| {
15057 s.select_ranges([last.range()]);
15058 });
15059 }
15060 _ => self.select_previous(
15061 &SelectPrevious {
15062 replace_newest: true,
15063 },
15064 window,
15065 cx,
15066 )?,
15067 }
15068 Ok(())
15069 }
15070
15071 pub fn toggle_comments(
15072 &mut self,
15073 action: &ToggleComments,
15074 window: &mut Window,
15075 cx: &mut Context<Self>,
15076 ) {
15077 if self.read_only(cx) {
15078 return;
15079 }
15080 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15081 let text_layout_details = &self.text_layout_details(window);
15082 self.transact(window, cx, |this, window, cx| {
15083 let mut selections = this
15084 .selections
15085 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15086 let mut edits = Vec::new();
15087 let mut selection_edit_ranges = Vec::new();
15088 let mut last_toggled_row = None;
15089 let snapshot = this.buffer.read(cx).read(cx);
15090 let empty_str: Arc<str> = Arc::default();
15091 let mut suffixes_inserted = Vec::new();
15092 let ignore_indent = action.ignore_indent;
15093
15094 fn comment_prefix_range(
15095 snapshot: &MultiBufferSnapshot,
15096 row: MultiBufferRow,
15097 comment_prefix: &str,
15098 comment_prefix_whitespace: &str,
15099 ignore_indent: bool,
15100 ) -> Range<Point> {
15101 let indent_size = if ignore_indent {
15102 0
15103 } else {
15104 snapshot.indent_size_for_line(row).len
15105 };
15106
15107 let start = Point::new(row.0, indent_size);
15108
15109 let mut line_bytes = snapshot
15110 .bytes_in_range(start..snapshot.max_point())
15111 .flatten()
15112 .copied();
15113
15114 // If this line currently begins with the line comment prefix, then record
15115 // the range containing the prefix.
15116 if line_bytes
15117 .by_ref()
15118 .take(comment_prefix.len())
15119 .eq(comment_prefix.bytes())
15120 {
15121 // Include any whitespace that matches the comment prefix.
15122 let matching_whitespace_len = line_bytes
15123 .zip(comment_prefix_whitespace.bytes())
15124 .take_while(|(a, b)| a == b)
15125 .count() as u32;
15126 let end = Point::new(
15127 start.row,
15128 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15129 );
15130 start..end
15131 } else {
15132 start..start
15133 }
15134 }
15135
15136 fn comment_suffix_range(
15137 snapshot: &MultiBufferSnapshot,
15138 row: MultiBufferRow,
15139 comment_suffix: &str,
15140 comment_suffix_has_leading_space: bool,
15141 ) -> Range<Point> {
15142 let end = Point::new(row.0, snapshot.line_len(row));
15143 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15144
15145 let mut line_end_bytes = snapshot
15146 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15147 .flatten()
15148 .copied();
15149
15150 let leading_space_len = if suffix_start_column > 0
15151 && line_end_bytes.next() == Some(b' ')
15152 && comment_suffix_has_leading_space
15153 {
15154 1
15155 } else {
15156 0
15157 };
15158
15159 // If this line currently begins with the line comment prefix, then record
15160 // the range containing the prefix.
15161 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15162 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15163 start..end
15164 } else {
15165 end..end
15166 }
15167 }
15168
15169 // TODO: Handle selections that cross excerpts
15170 for selection in &mut selections {
15171 let start_column = snapshot
15172 .indent_size_for_line(MultiBufferRow(selection.start.row))
15173 .len;
15174 let language = if let Some(language) =
15175 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15176 {
15177 language
15178 } else {
15179 continue;
15180 };
15181
15182 selection_edit_ranges.clear();
15183
15184 // If multiple selections contain a given row, avoid processing that
15185 // row more than once.
15186 let mut start_row = MultiBufferRow(selection.start.row);
15187 if last_toggled_row == Some(start_row) {
15188 start_row = start_row.next_row();
15189 }
15190 let end_row =
15191 if selection.end.row > selection.start.row && selection.end.column == 0 {
15192 MultiBufferRow(selection.end.row - 1)
15193 } else {
15194 MultiBufferRow(selection.end.row)
15195 };
15196 last_toggled_row = Some(end_row);
15197
15198 if start_row > end_row {
15199 continue;
15200 }
15201
15202 // If the language has line comments, toggle those.
15203 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15204
15205 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15206 if ignore_indent {
15207 full_comment_prefixes = full_comment_prefixes
15208 .into_iter()
15209 .map(|s| Arc::from(s.trim_end()))
15210 .collect();
15211 }
15212
15213 if !full_comment_prefixes.is_empty() {
15214 let first_prefix = full_comment_prefixes
15215 .first()
15216 .expect("prefixes is non-empty");
15217 let prefix_trimmed_lengths = full_comment_prefixes
15218 .iter()
15219 .map(|p| p.trim_end_matches(' ').len())
15220 .collect::<SmallVec<[usize; 4]>>();
15221
15222 let mut all_selection_lines_are_comments = true;
15223
15224 for row in start_row.0..=end_row.0 {
15225 let row = MultiBufferRow(row);
15226 if start_row < end_row && snapshot.is_line_blank(row) {
15227 continue;
15228 }
15229
15230 let prefix_range = full_comment_prefixes
15231 .iter()
15232 .zip(prefix_trimmed_lengths.iter().copied())
15233 .map(|(prefix, trimmed_prefix_len)| {
15234 comment_prefix_range(
15235 snapshot.deref(),
15236 row,
15237 &prefix[..trimmed_prefix_len],
15238 &prefix[trimmed_prefix_len..],
15239 ignore_indent,
15240 )
15241 })
15242 .max_by_key(|range| range.end.column - range.start.column)
15243 .expect("prefixes is non-empty");
15244
15245 if prefix_range.is_empty() {
15246 all_selection_lines_are_comments = false;
15247 }
15248
15249 selection_edit_ranges.push(prefix_range);
15250 }
15251
15252 if all_selection_lines_are_comments {
15253 edits.extend(
15254 selection_edit_ranges
15255 .iter()
15256 .cloned()
15257 .map(|range| (range, empty_str.clone())),
15258 );
15259 } else {
15260 let min_column = selection_edit_ranges
15261 .iter()
15262 .map(|range| range.start.column)
15263 .min()
15264 .unwrap_or(0);
15265 edits.extend(selection_edit_ranges.iter().map(|range| {
15266 let position = Point::new(range.start.row, min_column);
15267 (position..position, first_prefix.clone())
15268 }));
15269 }
15270 } else if let Some(BlockCommentConfig {
15271 start: full_comment_prefix,
15272 end: comment_suffix,
15273 ..
15274 }) = language.block_comment()
15275 {
15276 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15277 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15278 let prefix_range = comment_prefix_range(
15279 snapshot.deref(),
15280 start_row,
15281 comment_prefix,
15282 comment_prefix_whitespace,
15283 ignore_indent,
15284 );
15285 let suffix_range = comment_suffix_range(
15286 snapshot.deref(),
15287 end_row,
15288 comment_suffix.trim_start_matches(' '),
15289 comment_suffix.starts_with(' '),
15290 );
15291
15292 if prefix_range.is_empty() || suffix_range.is_empty() {
15293 edits.push((
15294 prefix_range.start..prefix_range.start,
15295 full_comment_prefix.clone(),
15296 ));
15297 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15298 suffixes_inserted.push((end_row, comment_suffix.len()));
15299 } else {
15300 edits.push((prefix_range, empty_str.clone()));
15301 edits.push((suffix_range, empty_str.clone()));
15302 }
15303 } else {
15304 continue;
15305 }
15306 }
15307
15308 drop(snapshot);
15309 this.buffer.update(cx, |buffer, cx| {
15310 buffer.edit(edits, None, cx);
15311 });
15312
15313 // Adjust selections so that they end before any comment suffixes that
15314 // were inserted.
15315 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15316 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15317 let snapshot = this.buffer.read(cx).read(cx);
15318 for selection in &mut selections {
15319 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15320 match row.cmp(&MultiBufferRow(selection.end.row)) {
15321 Ordering::Less => {
15322 suffixes_inserted.next();
15323 continue;
15324 }
15325 Ordering::Greater => break,
15326 Ordering::Equal => {
15327 if selection.end.column == snapshot.line_len(row) {
15328 if selection.is_empty() {
15329 selection.start.column -= suffix_len as u32;
15330 }
15331 selection.end.column -= suffix_len as u32;
15332 }
15333 break;
15334 }
15335 }
15336 }
15337 }
15338
15339 drop(snapshot);
15340 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15341
15342 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15343 let selections_on_single_row = selections.windows(2).all(|selections| {
15344 selections[0].start.row == selections[1].start.row
15345 && selections[0].end.row == selections[1].end.row
15346 && selections[0].start.row == selections[0].end.row
15347 });
15348 let selections_selecting = selections
15349 .iter()
15350 .any(|selection| selection.start != selection.end);
15351 let advance_downwards = action.advance_downwards
15352 && selections_on_single_row
15353 && !selections_selecting
15354 && !matches!(this.mode, EditorMode::SingleLine);
15355
15356 if advance_downwards {
15357 let snapshot = this.buffer.read(cx).snapshot(cx);
15358
15359 this.change_selections(Default::default(), window, cx, |s| {
15360 s.move_cursors_with(|display_snapshot, display_point, _| {
15361 let mut point = display_point.to_point(display_snapshot);
15362 point.row += 1;
15363 point = snapshot.clip_point(point, Bias::Left);
15364 let display_point = point.to_display_point(display_snapshot);
15365 let goal = SelectionGoal::HorizontalPosition(
15366 display_snapshot
15367 .x_for_display_point(display_point, text_layout_details)
15368 .into(),
15369 );
15370 (display_point, goal)
15371 })
15372 });
15373 }
15374 });
15375 }
15376
15377 pub fn select_enclosing_symbol(
15378 &mut self,
15379 _: &SelectEnclosingSymbol,
15380 window: &mut Window,
15381 cx: &mut Context<Self>,
15382 ) {
15383 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15384
15385 let buffer = self.buffer.read(cx).snapshot(cx);
15386 let old_selections = self
15387 .selections
15388 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15389 .into_boxed_slice();
15390
15391 fn update_selection(
15392 selection: &Selection<MultiBufferOffset>,
15393 buffer_snap: &MultiBufferSnapshot,
15394 ) -> Option<Selection<MultiBufferOffset>> {
15395 let cursor = selection.head();
15396 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15397 for symbol in symbols.iter().rev() {
15398 let start = symbol.range.start.to_offset(buffer_snap);
15399 let end = symbol.range.end.to_offset(buffer_snap);
15400 let new_range = start..end;
15401 if start < selection.start || end > selection.end {
15402 return Some(Selection {
15403 id: selection.id,
15404 start: new_range.start,
15405 end: new_range.end,
15406 goal: SelectionGoal::None,
15407 reversed: selection.reversed,
15408 });
15409 }
15410 }
15411 None
15412 }
15413
15414 let mut selected_larger_symbol = false;
15415 let new_selections = old_selections
15416 .iter()
15417 .map(|selection| match update_selection(selection, &buffer) {
15418 Some(new_selection) => {
15419 if new_selection.range() != selection.range() {
15420 selected_larger_symbol = true;
15421 }
15422 new_selection
15423 }
15424 None => selection.clone(),
15425 })
15426 .collect::<Vec<_>>();
15427
15428 if selected_larger_symbol {
15429 self.change_selections(Default::default(), window, cx, |s| {
15430 s.select(new_selections);
15431 });
15432 }
15433 }
15434
15435 pub fn select_larger_syntax_node(
15436 &mut self,
15437 _: &SelectLargerSyntaxNode,
15438 window: &mut Window,
15439 cx: &mut Context<Self>,
15440 ) {
15441 let Some(visible_row_count) = self.visible_row_count() else {
15442 return;
15443 };
15444 let old_selections: Box<[_]> = self
15445 .selections
15446 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15447 .into();
15448 if old_selections.is_empty() {
15449 return;
15450 }
15451
15452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15453
15454 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15455 let buffer = self.buffer.read(cx).snapshot(cx);
15456
15457 let mut selected_larger_node = false;
15458 let mut new_selections = old_selections
15459 .iter()
15460 .map(|selection| {
15461 let old_range = selection.start..selection.end;
15462
15463 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15464 // manually select word at selection
15465 if ["string_content", "inline"].contains(&node.kind()) {
15466 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15467 // ignore if word is already selected
15468 if !word_range.is_empty() && old_range != word_range {
15469 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15470 // only select word if start and end point belongs to same word
15471 if word_range == last_word_range {
15472 selected_larger_node = true;
15473 return Selection {
15474 id: selection.id,
15475 start: word_range.start,
15476 end: word_range.end,
15477 goal: SelectionGoal::None,
15478 reversed: selection.reversed,
15479 };
15480 }
15481 }
15482 }
15483 }
15484
15485 let mut new_range = old_range.clone();
15486 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15487 new_range = range;
15488 if !node.is_named() {
15489 continue;
15490 }
15491 if !display_map.intersects_fold(new_range.start)
15492 && !display_map.intersects_fold(new_range.end)
15493 {
15494 break;
15495 }
15496 }
15497
15498 selected_larger_node |= new_range != old_range;
15499 Selection {
15500 id: selection.id,
15501 start: new_range.start,
15502 end: new_range.end,
15503 goal: SelectionGoal::None,
15504 reversed: selection.reversed,
15505 }
15506 })
15507 .collect::<Vec<_>>();
15508
15509 if !selected_larger_node {
15510 return; // don't put this call in the history
15511 }
15512
15513 // scroll based on transformation done to the last selection created by the user
15514 let (last_old, last_new) = old_selections
15515 .last()
15516 .zip(new_selections.last().cloned())
15517 .expect("old_selections isn't empty");
15518
15519 // revert selection
15520 let is_selection_reversed = {
15521 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15522 new_selections.last_mut().expect("checked above").reversed =
15523 should_newest_selection_be_reversed;
15524 should_newest_selection_be_reversed
15525 };
15526
15527 if selected_larger_node {
15528 self.select_syntax_node_history.disable_clearing = true;
15529 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15530 s.select(new_selections.clone());
15531 });
15532 self.select_syntax_node_history.disable_clearing = false;
15533 }
15534
15535 let start_row = last_new.start.to_display_point(&display_map).row().0;
15536 let end_row = last_new.end.to_display_point(&display_map).row().0;
15537 let selection_height = end_row - start_row + 1;
15538 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15539
15540 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15541 let scroll_behavior = if fits_on_the_screen {
15542 self.request_autoscroll(Autoscroll::fit(), cx);
15543 SelectSyntaxNodeScrollBehavior::FitSelection
15544 } else if is_selection_reversed {
15545 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15546 SelectSyntaxNodeScrollBehavior::CursorTop
15547 } else {
15548 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15549 SelectSyntaxNodeScrollBehavior::CursorBottom
15550 };
15551
15552 self.select_syntax_node_history.push((
15553 old_selections,
15554 scroll_behavior,
15555 is_selection_reversed,
15556 ));
15557 }
15558
15559 pub fn select_smaller_syntax_node(
15560 &mut self,
15561 _: &SelectSmallerSyntaxNode,
15562 window: &mut Window,
15563 cx: &mut Context<Self>,
15564 ) {
15565 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15566
15567 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15568 self.select_syntax_node_history.pop()
15569 {
15570 if let Some(selection) = selections.last_mut() {
15571 selection.reversed = is_selection_reversed;
15572 }
15573
15574 self.select_syntax_node_history.disable_clearing = true;
15575 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15576 s.select(selections.to_vec());
15577 });
15578 self.select_syntax_node_history.disable_clearing = false;
15579
15580 match scroll_behavior {
15581 SelectSyntaxNodeScrollBehavior::CursorTop => {
15582 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15583 }
15584 SelectSyntaxNodeScrollBehavior::FitSelection => {
15585 self.request_autoscroll(Autoscroll::fit(), cx);
15586 }
15587 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15588 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15589 }
15590 }
15591 }
15592 }
15593
15594 pub fn unwrap_syntax_node(
15595 &mut self,
15596 _: &UnwrapSyntaxNode,
15597 window: &mut Window,
15598 cx: &mut Context<Self>,
15599 ) {
15600 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15601
15602 let buffer = self.buffer.read(cx).snapshot(cx);
15603 let selections = self
15604 .selections
15605 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15606 .into_iter()
15607 // subtracting the offset requires sorting
15608 .sorted_by_key(|i| i.start);
15609
15610 let full_edits = selections
15611 .into_iter()
15612 .filter_map(|selection| {
15613 let child = if selection.is_empty()
15614 && let Some((_, ancestor_range)) =
15615 buffer.syntax_ancestor(selection.start..selection.end)
15616 {
15617 ancestor_range
15618 } else {
15619 selection.range()
15620 };
15621
15622 let mut parent = child.clone();
15623 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15624 parent = ancestor_range;
15625 if parent.start < child.start || parent.end > child.end {
15626 break;
15627 }
15628 }
15629
15630 if parent == child {
15631 return None;
15632 }
15633 let text = buffer.text_for_range(child).collect::<String>();
15634 Some((selection.id, parent, text))
15635 })
15636 .collect::<Vec<_>>();
15637 if full_edits.is_empty() {
15638 return;
15639 }
15640
15641 self.transact(window, cx, |this, window, cx| {
15642 this.buffer.update(cx, |buffer, cx| {
15643 buffer.edit(
15644 full_edits
15645 .iter()
15646 .map(|(_, p, t)| (p.clone(), t.clone()))
15647 .collect::<Vec<_>>(),
15648 None,
15649 cx,
15650 );
15651 });
15652 this.change_selections(Default::default(), window, cx, |s| {
15653 let mut offset = 0;
15654 let mut selections = vec![];
15655 for (id, parent, text) in full_edits {
15656 let start = parent.start - offset;
15657 offset += (parent.end - parent.start) - text.len();
15658 selections.push(Selection {
15659 id,
15660 start,
15661 end: start + text.len(),
15662 reversed: false,
15663 goal: Default::default(),
15664 });
15665 }
15666 s.select(selections);
15667 });
15668 });
15669 }
15670
15671 pub fn select_next_syntax_node(
15672 &mut self,
15673 _: &SelectNextSyntaxNode,
15674 window: &mut Window,
15675 cx: &mut Context<Self>,
15676 ) {
15677 let old_selections: Box<[_]> = self
15678 .selections
15679 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15680 .into();
15681 if old_selections.is_empty() {
15682 return;
15683 }
15684
15685 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15686
15687 let buffer = self.buffer.read(cx).snapshot(cx);
15688 let mut selected_sibling = false;
15689
15690 let new_selections = old_selections
15691 .iter()
15692 .map(|selection| {
15693 let old_range = selection.start..selection.end;
15694
15695 let old_range =
15696 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15697 let excerpt = buffer.excerpt_containing(old_range.clone());
15698
15699 if let Some(mut excerpt) = excerpt
15700 && let Some(node) = excerpt
15701 .buffer()
15702 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15703 {
15704 let new_range = excerpt.map_range_from_buffer(
15705 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15706 );
15707 selected_sibling = true;
15708 Selection {
15709 id: selection.id,
15710 start: new_range.start,
15711 end: new_range.end,
15712 goal: SelectionGoal::None,
15713 reversed: selection.reversed,
15714 }
15715 } else {
15716 selection.clone()
15717 }
15718 })
15719 .collect::<Vec<_>>();
15720
15721 if selected_sibling {
15722 self.change_selections(
15723 SelectionEffects::scroll(Autoscroll::fit()),
15724 window,
15725 cx,
15726 |s| {
15727 s.select(new_selections);
15728 },
15729 );
15730 }
15731 }
15732
15733 pub fn select_prev_syntax_node(
15734 &mut self,
15735 _: &SelectPreviousSyntaxNode,
15736 window: &mut Window,
15737 cx: &mut Context<Self>,
15738 ) {
15739 let old_selections: Box<[_]> = self
15740 .selections
15741 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15742 .into();
15743 if old_selections.is_empty() {
15744 return;
15745 }
15746
15747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15748
15749 let buffer = self.buffer.read(cx).snapshot(cx);
15750 let mut selected_sibling = false;
15751
15752 let new_selections = old_selections
15753 .iter()
15754 .map(|selection| {
15755 let old_range = selection.start..selection.end;
15756 let old_range =
15757 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15758 let excerpt = buffer.excerpt_containing(old_range.clone());
15759
15760 if let Some(mut excerpt) = excerpt
15761 && let Some(node) = excerpt
15762 .buffer()
15763 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
15764 {
15765 let new_range = excerpt.map_range_from_buffer(
15766 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15767 );
15768 selected_sibling = true;
15769 Selection {
15770 id: selection.id,
15771 start: new_range.start,
15772 end: new_range.end,
15773 goal: SelectionGoal::None,
15774 reversed: selection.reversed,
15775 }
15776 } else {
15777 selection.clone()
15778 }
15779 })
15780 .collect::<Vec<_>>();
15781
15782 if selected_sibling {
15783 self.change_selections(
15784 SelectionEffects::scroll(Autoscroll::fit()),
15785 window,
15786 cx,
15787 |s| {
15788 s.select(new_selections);
15789 },
15790 );
15791 }
15792 }
15793
15794 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15795 if !EditorSettings::get_global(cx).gutter.runnables {
15796 self.clear_tasks();
15797 return Task::ready(());
15798 }
15799 let project = self.project().map(Entity::downgrade);
15800 let task_sources = self.lsp_task_sources(cx);
15801 let multi_buffer = self.buffer.downgrade();
15802 cx.spawn_in(window, async move |editor, cx| {
15803 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15804 let Some(project) = project.and_then(|p| p.upgrade()) else {
15805 return;
15806 };
15807 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15808 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15809 }) else {
15810 return;
15811 };
15812
15813 let hide_runnables = project
15814 .update(cx, |project, _| project.is_via_collab())
15815 .unwrap_or(true);
15816 if hide_runnables {
15817 return;
15818 }
15819 let new_rows =
15820 cx.background_spawn({
15821 let snapshot = display_snapshot.clone();
15822 async move {
15823 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15824 }
15825 })
15826 .await;
15827 let Ok(lsp_tasks) =
15828 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15829 else {
15830 return;
15831 };
15832 let lsp_tasks = lsp_tasks.await;
15833
15834 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15835 lsp_tasks
15836 .into_iter()
15837 .flat_map(|(kind, tasks)| {
15838 tasks.into_iter().filter_map(move |(location, task)| {
15839 Some((kind.clone(), location?, task))
15840 })
15841 })
15842 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15843 let buffer = location.target.buffer;
15844 let buffer_snapshot = buffer.read(cx).snapshot();
15845 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15846 |(excerpt_id, snapshot, _)| {
15847 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15848 display_snapshot
15849 .buffer_snapshot()
15850 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15851 } else {
15852 None
15853 }
15854 },
15855 );
15856 if let Some(offset) = offset {
15857 let task_buffer_range =
15858 location.target.range.to_point(&buffer_snapshot);
15859 let context_buffer_range =
15860 task_buffer_range.to_offset(&buffer_snapshot);
15861 let context_range = BufferOffset(context_buffer_range.start)
15862 ..BufferOffset(context_buffer_range.end);
15863
15864 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15865 .or_insert_with(|| RunnableTasks {
15866 templates: Vec::new(),
15867 offset,
15868 column: task_buffer_range.start.column,
15869 extra_variables: HashMap::default(),
15870 context_range,
15871 })
15872 .templates
15873 .push((kind, task.original_task().clone()));
15874 }
15875
15876 acc
15877 })
15878 }) else {
15879 return;
15880 };
15881
15882 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15883 buffer.language_settings(cx).tasks.prefer_lsp
15884 }) else {
15885 return;
15886 };
15887
15888 let rows = Self::runnable_rows(
15889 project,
15890 display_snapshot,
15891 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15892 new_rows,
15893 cx.clone(),
15894 )
15895 .await;
15896 editor
15897 .update(cx, |editor, _| {
15898 editor.clear_tasks();
15899 for (key, mut value) in rows {
15900 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15901 value.templates.extend(lsp_tasks.templates);
15902 }
15903
15904 editor.insert_tasks(key, value);
15905 }
15906 for (key, value) in lsp_tasks_by_rows {
15907 editor.insert_tasks(key, value);
15908 }
15909 })
15910 .ok();
15911 })
15912 }
15913 fn fetch_runnable_ranges(
15914 snapshot: &DisplaySnapshot,
15915 range: Range<Anchor>,
15916 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
15917 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15918 }
15919
15920 fn runnable_rows(
15921 project: Entity<Project>,
15922 snapshot: DisplaySnapshot,
15923 prefer_lsp: bool,
15924 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
15925 cx: AsyncWindowContext,
15926 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15927 cx.spawn(async move |cx| {
15928 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15929 for (run_range, mut runnable) in runnable_ranges {
15930 let Some(tasks) = cx
15931 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15932 .ok()
15933 else {
15934 continue;
15935 };
15936 let mut tasks = tasks.await;
15937
15938 if prefer_lsp {
15939 tasks.retain(|(task_kind, _)| {
15940 !matches!(task_kind, TaskSourceKind::Language { .. })
15941 });
15942 }
15943 if tasks.is_empty() {
15944 continue;
15945 }
15946
15947 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
15948 let Some(row) = snapshot
15949 .buffer_snapshot()
15950 .buffer_line_for_row(MultiBufferRow(point.row))
15951 .map(|(_, range)| range.start.row)
15952 else {
15953 continue;
15954 };
15955
15956 let context_range =
15957 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15958 runnable_rows.push((
15959 (runnable.buffer_id, row),
15960 RunnableTasks {
15961 templates: tasks,
15962 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
15963 context_range,
15964 column: point.column,
15965 extra_variables: runnable.extra_captures,
15966 },
15967 ));
15968 }
15969 runnable_rows
15970 })
15971 }
15972
15973 fn templates_with_tags(
15974 project: &Entity<Project>,
15975 runnable: &mut Runnable,
15976 cx: &mut App,
15977 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15978 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15979 let (worktree_id, file) = project
15980 .buffer_for_id(runnable.buffer, cx)
15981 .and_then(|buffer| buffer.read(cx).file())
15982 .map(|file| (file.worktree_id(cx), file.clone()))
15983 .unzip();
15984
15985 (
15986 project.task_store().read(cx).task_inventory().cloned(),
15987 worktree_id,
15988 file,
15989 )
15990 });
15991
15992 let tags = mem::take(&mut runnable.tags);
15993 let language = runnable.language.clone();
15994 cx.spawn(async move |cx| {
15995 let mut templates_with_tags = Vec::new();
15996 if let Some(inventory) = inventory {
15997 for RunnableTag(tag) in tags {
15998 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15999 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16000 }) else {
16001 return templates_with_tags;
16002 };
16003 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16004 move |(_, template)| {
16005 template.tags.iter().any(|source_tag| source_tag == &tag)
16006 },
16007 ));
16008 }
16009 }
16010 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16011
16012 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16013 // Strongest source wins; if we have worktree tag binding, prefer that to
16014 // global and language bindings;
16015 // if we have a global binding, prefer that to language binding.
16016 let first_mismatch = templates_with_tags
16017 .iter()
16018 .position(|(tag_source, _)| tag_source != leading_tag_source);
16019 if let Some(index) = first_mismatch {
16020 templates_with_tags.truncate(index);
16021 }
16022 }
16023
16024 templates_with_tags
16025 })
16026 }
16027
16028 pub fn move_to_enclosing_bracket(
16029 &mut self,
16030 _: &MoveToEnclosingBracket,
16031 window: &mut Window,
16032 cx: &mut Context<Self>,
16033 ) {
16034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16035 self.change_selections(Default::default(), window, cx, |s| {
16036 s.move_offsets_with(|snapshot, selection| {
16037 let Some(enclosing_bracket_ranges) =
16038 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16039 else {
16040 return;
16041 };
16042
16043 let mut best_length = usize::MAX;
16044 let mut best_inside = false;
16045 let mut best_in_bracket_range = false;
16046 let mut best_destination = None;
16047 for (open, close) in enclosing_bracket_ranges {
16048 let close = close.to_inclusive();
16049 let length = *close.end() - open.start;
16050 let inside = selection.start >= open.end && selection.end <= *close.start();
16051 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16052 || close.contains(&selection.head());
16053
16054 // If best is next to a bracket and current isn't, skip
16055 if !in_bracket_range && best_in_bracket_range {
16056 continue;
16057 }
16058
16059 // Prefer smaller lengths unless best is inside and current isn't
16060 if length > best_length && (best_inside || !inside) {
16061 continue;
16062 }
16063
16064 best_length = length;
16065 best_inside = inside;
16066 best_in_bracket_range = in_bracket_range;
16067 best_destination = Some(
16068 if close.contains(&selection.start) && close.contains(&selection.end) {
16069 if inside { open.end } else { open.start }
16070 } else if inside {
16071 *close.start()
16072 } else {
16073 *close.end()
16074 },
16075 );
16076 }
16077
16078 if let Some(destination) = best_destination {
16079 selection.collapse_to(destination, SelectionGoal::None);
16080 }
16081 })
16082 });
16083 }
16084
16085 pub fn undo_selection(
16086 &mut self,
16087 _: &UndoSelection,
16088 window: &mut Window,
16089 cx: &mut Context<Self>,
16090 ) {
16091 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16092 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16093 self.selection_history.mode = SelectionHistoryMode::Undoing;
16094 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16095 this.end_selection(window, cx);
16096 this.change_selections(
16097 SelectionEffects::scroll(Autoscroll::newest()),
16098 window,
16099 cx,
16100 |s| s.select_anchors(entry.selections.to_vec()),
16101 );
16102 });
16103 self.selection_history.mode = SelectionHistoryMode::Normal;
16104
16105 self.select_next_state = entry.select_next_state;
16106 self.select_prev_state = entry.select_prev_state;
16107 self.add_selections_state = entry.add_selections_state;
16108 }
16109 }
16110
16111 pub fn redo_selection(
16112 &mut self,
16113 _: &RedoSelection,
16114 window: &mut Window,
16115 cx: &mut Context<Self>,
16116 ) {
16117 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16118 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16119 self.selection_history.mode = SelectionHistoryMode::Redoing;
16120 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16121 this.end_selection(window, cx);
16122 this.change_selections(
16123 SelectionEffects::scroll(Autoscroll::newest()),
16124 window,
16125 cx,
16126 |s| s.select_anchors(entry.selections.to_vec()),
16127 );
16128 });
16129 self.selection_history.mode = SelectionHistoryMode::Normal;
16130
16131 self.select_next_state = entry.select_next_state;
16132 self.select_prev_state = entry.select_prev_state;
16133 self.add_selections_state = entry.add_selections_state;
16134 }
16135 }
16136
16137 pub fn expand_excerpts(
16138 &mut self,
16139 action: &ExpandExcerpts,
16140 _: &mut Window,
16141 cx: &mut Context<Self>,
16142 ) {
16143 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16144 }
16145
16146 pub fn expand_excerpts_down(
16147 &mut self,
16148 action: &ExpandExcerptsDown,
16149 _: &mut Window,
16150 cx: &mut Context<Self>,
16151 ) {
16152 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16153 }
16154
16155 pub fn expand_excerpts_up(
16156 &mut self,
16157 action: &ExpandExcerptsUp,
16158 _: &mut Window,
16159 cx: &mut Context<Self>,
16160 ) {
16161 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16162 }
16163
16164 pub fn expand_excerpts_for_direction(
16165 &mut self,
16166 lines: u32,
16167 direction: ExpandExcerptDirection,
16168
16169 cx: &mut Context<Self>,
16170 ) {
16171 let selections = self.selections.disjoint_anchors_arc();
16172
16173 let lines = if lines == 0 {
16174 EditorSettings::get_global(cx).expand_excerpt_lines
16175 } else {
16176 lines
16177 };
16178
16179 self.buffer.update(cx, |buffer, cx| {
16180 let snapshot = buffer.snapshot(cx);
16181 let mut excerpt_ids = selections
16182 .iter()
16183 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16184 .collect::<Vec<_>>();
16185 excerpt_ids.sort();
16186 excerpt_ids.dedup();
16187 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16188 })
16189 }
16190
16191 pub fn expand_excerpt(
16192 &mut self,
16193 excerpt: ExcerptId,
16194 direction: ExpandExcerptDirection,
16195 window: &mut Window,
16196 cx: &mut Context<Self>,
16197 ) {
16198 let current_scroll_position = self.scroll_position(cx);
16199 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16200 let mut scroll = None;
16201
16202 if direction == ExpandExcerptDirection::Down {
16203 let multi_buffer = self.buffer.read(cx);
16204 let snapshot = multi_buffer.snapshot(cx);
16205 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16206 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16207 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16208 {
16209 let buffer_snapshot = buffer.read(cx).snapshot();
16210 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16211 let last_row = buffer_snapshot.max_point().row;
16212 let lines_below = last_row.saturating_sub(excerpt_end_row);
16213 if lines_below >= lines_to_expand {
16214 scroll = Some(
16215 current_scroll_position
16216 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16217 );
16218 }
16219 }
16220 }
16221 if direction == ExpandExcerptDirection::Up
16222 && self
16223 .buffer
16224 .read(cx)
16225 .snapshot(cx)
16226 .excerpt_before(excerpt)
16227 .is_none()
16228 {
16229 scroll = Some(current_scroll_position);
16230 }
16231
16232 self.buffer.update(cx, |buffer, cx| {
16233 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16234 });
16235
16236 if let Some(new_scroll_position) = scroll {
16237 self.set_scroll_position(new_scroll_position, window, cx);
16238 }
16239 }
16240
16241 pub fn go_to_singleton_buffer_point(
16242 &mut self,
16243 point: Point,
16244 window: &mut Window,
16245 cx: &mut Context<Self>,
16246 ) {
16247 self.go_to_singleton_buffer_range(point..point, window, cx);
16248 }
16249
16250 pub fn go_to_singleton_buffer_range(
16251 &mut self,
16252 range: Range<Point>,
16253 window: &mut Window,
16254 cx: &mut Context<Self>,
16255 ) {
16256 let multibuffer = self.buffer().read(cx);
16257 let Some(buffer) = multibuffer.as_singleton() else {
16258 return;
16259 };
16260 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16261 return;
16262 };
16263 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16264 return;
16265 };
16266 self.change_selections(
16267 SelectionEffects::default().nav_history(true),
16268 window,
16269 cx,
16270 |s| s.select_anchor_ranges([start..end]),
16271 );
16272 }
16273
16274 pub fn go_to_diagnostic(
16275 &mut self,
16276 action: &GoToDiagnostic,
16277 window: &mut Window,
16278 cx: &mut Context<Self>,
16279 ) {
16280 if !self.diagnostics_enabled() {
16281 return;
16282 }
16283 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16284 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16285 }
16286
16287 pub fn go_to_prev_diagnostic(
16288 &mut self,
16289 action: &GoToPreviousDiagnostic,
16290 window: &mut Window,
16291 cx: &mut Context<Self>,
16292 ) {
16293 if !self.diagnostics_enabled() {
16294 return;
16295 }
16296 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16297 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16298 }
16299
16300 pub fn go_to_diagnostic_impl(
16301 &mut self,
16302 direction: Direction,
16303 severity: GoToDiagnosticSeverityFilter,
16304 window: &mut Window,
16305 cx: &mut Context<Self>,
16306 ) {
16307 let buffer = self.buffer.read(cx).snapshot(cx);
16308 let selection = self
16309 .selections
16310 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16311
16312 let mut active_group_id = None;
16313 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16314 && active_group.active_range.start.to_offset(&buffer) == selection.start
16315 {
16316 active_group_id = Some(active_group.group_id);
16317 }
16318
16319 fn filtered<'a>(
16320 severity: GoToDiagnosticSeverityFilter,
16321 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16322 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16323 diagnostics
16324 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16325 .filter(|entry| entry.range.start != entry.range.end)
16326 .filter(|entry| !entry.diagnostic.is_unnecessary)
16327 }
16328
16329 let before = filtered(
16330 severity,
16331 buffer
16332 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16333 .filter(|entry| entry.range.start <= selection.start),
16334 );
16335 let after = filtered(
16336 severity,
16337 buffer
16338 .diagnostics_in_range(selection.start..buffer.len())
16339 .filter(|entry| entry.range.start >= selection.start),
16340 );
16341
16342 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16343 if direction == Direction::Prev {
16344 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16345 {
16346 for diagnostic in prev_diagnostics.into_iter().rev() {
16347 if diagnostic.range.start != selection.start
16348 || active_group_id
16349 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16350 {
16351 found = Some(diagnostic);
16352 break 'outer;
16353 }
16354 }
16355 }
16356 } else {
16357 for diagnostic in after.chain(before) {
16358 if diagnostic.range.start != selection.start
16359 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16360 {
16361 found = Some(diagnostic);
16362 break;
16363 }
16364 }
16365 }
16366 let Some(next_diagnostic) = found else {
16367 return;
16368 };
16369
16370 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16371 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16372 return;
16373 };
16374 let snapshot = self.snapshot(window, cx);
16375 if snapshot.intersects_fold(next_diagnostic.range.start) {
16376 self.unfold_ranges(
16377 std::slice::from_ref(&next_diagnostic.range),
16378 true,
16379 false,
16380 cx,
16381 );
16382 }
16383 self.change_selections(Default::default(), window, cx, |s| {
16384 s.select_ranges(vec![
16385 next_diagnostic.range.start..next_diagnostic.range.start,
16386 ])
16387 });
16388 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16389 self.refresh_edit_prediction(false, true, window, cx);
16390 }
16391
16392 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16394 let snapshot = self.snapshot(window, cx);
16395 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16396 self.go_to_hunk_before_or_after_position(
16397 &snapshot,
16398 selection.head(),
16399 Direction::Next,
16400 window,
16401 cx,
16402 );
16403 }
16404
16405 pub fn go_to_hunk_before_or_after_position(
16406 &mut self,
16407 snapshot: &EditorSnapshot,
16408 position: Point,
16409 direction: Direction,
16410 window: &mut Window,
16411 cx: &mut Context<Editor>,
16412 ) {
16413 let row = if direction == Direction::Next {
16414 self.hunk_after_position(snapshot, position)
16415 .map(|hunk| hunk.row_range.start)
16416 } else {
16417 self.hunk_before_position(snapshot, position)
16418 };
16419
16420 if let Some(row) = row {
16421 let destination = Point::new(row.0, 0);
16422 let autoscroll = Autoscroll::center();
16423
16424 self.unfold_ranges(&[destination..destination], false, false, cx);
16425 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16426 s.select_ranges([destination..destination]);
16427 });
16428 }
16429 }
16430
16431 fn hunk_after_position(
16432 &mut self,
16433 snapshot: &EditorSnapshot,
16434 position: Point,
16435 ) -> Option<MultiBufferDiffHunk> {
16436 snapshot
16437 .buffer_snapshot()
16438 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16439 .find(|hunk| hunk.row_range.start.0 > position.row)
16440 .or_else(|| {
16441 snapshot
16442 .buffer_snapshot()
16443 .diff_hunks_in_range(Point::zero()..position)
16444 .find(|hunk| hunk.row_range.end.0 < position.row)
16445 })
16446 }
16447
16448 fn go_to_prev_hunk(
16449 &mut self,
16450 _: &GoToPreviousHunk,
16451 window: &mut Window,
16452 cx: &mut Context<Self>,
16453 ) {
16454 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16455 let snapshot = self.snapshot(window, cx);
16456 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16457 self.go_to_hunk_before_or_after_position(
16458 &snapshot,
16459 selection.head(),
16460 Direction::Prev,
16461 window,
16462 cx,
16463 );
16464 }
16465
16466 fn hunk_before_position(
16467 &mut self,
16468 snapshot: &EditorSnapshot,
16469 position: Point,
16470 ) -> Option<MultiBufferRow> {
16471 snapshot
16472 .buffer_snapshot()
16473 .diff_hunk_before(position)
16474 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16475 }
16476
16477 fn go_to_next_change(
16478 &mut self,
16479 _: &GoToNextChange,
16480 window: &mut Window,
16481 cx: &mut Context<Self>,
16482 ) {
16483 if let Some(selections) = self
16484 .change_list
16485 .next_change(1, Direction::Next)
16486 .map(|s| s.to_vec())
16487 {
16488 self.change_selections(Default::default(), window, cx, |s| {
16489 let map = s.display_snapshot();
16490 s.select_display_ranges(selections.iter().map(|a| {
16491 let point = a.to_display_point(&map);
16492 point..point
16493 }))
16494 })
16495 }
16496 }
16497
16498 fn go_to_previous_change(
16499 &mut self,
16500 _: &GoToPreviousChange,
16501 window: &mut Window,
16502 cx: &mut Context<Self>,
16503 ) {
16504 if let Some(selections) = self
16505 .change_list
16506 .next_change(1, Direction::Prev)
16507 .map(|s| s.to_vec())
16508 {
16509 self.change_selections(Default::default(), window, cx, |s| {
16510 let map = s.display_snapshot();
16511 s.select_display_ranges(selections.iter().map(|a| {
16512 let point = a.to_display_point(&map);
16513 point..point
16514 }))
16515 })
16516 }
16517 }
16518
16519 pub fn go_to_next_document_highlight(
16520 &mut self,
16521 _: &GoToNextDocumentHighlight,
16522 window: &mut Window,
16523 cx: &mut Context<Self>,
16524 ) {
16525 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16526 }
16527
16528 pub fn go_to_prev_document_highlight(
16529 &mut self,
16530 _: &GoToPreviousDocumentHighlight,
16531 window: &mut Window,
16532 cx: &mut Context<Self>,
16533 ) {
16534 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16535 }
16536
16537 pub fn go_to_document_highlight_before_or_after_position(
16538 &mut self,
16539 direction: Direction,
16540 window: &mut Window,
16541 cx: &mut Context<Editor>,
16542 ) {
16543 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16544 let snapshot = self.snapshot(window, cx);
16545 let buffer = &snapshot.buffer_snapshot();
16546 let position = self
16547 .selections
16548 .newest::<Point>(&snapshot.display_snapshot)
16549 .head();
16550 let anchor_position = buffer.anchor_after(position);
16551
16552 // Get all document highlights (both read and write)
16553 let mut all_highlights = Vec::new();
16554
16555 if let Some((_, read_highlights)) = self
16556 .background_highlights
16557 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16558 {
16559 all_highlights.extend(read_highlights.iter());
16560 }
16561
16562 if let Some((_, write_highlights)) = self
16563 .background_highlights
16564 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16565 {
16566 all_highlights.extend(write_highlights.iter());
16567 }
16568
16569 if all_highlights.is_empty() {
16570 return;
16571 }
16572
16573 // Sort highlights by position
16574 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16575
16576 let target_highlight = match direction {
16577 Direction::Next => {
16578 // Find the first highlight after the current position
16579 all_highlights
16580 .iter()
16581 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16582 }
16583 Direction::Prev => {
16584 // Find the last highlight before the current position
16585 all_highlights
16586 .iter()
16587 .rev()
16588 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16589 }
16590 };
16591
16592 if let Some(highlight) = target_highlight {
16593 let destination = highlight.start.to_point(buffer);
16594 let autoscroll = Autoscroll::center();
16595
16596 self.unfold_ranges(&[destination..destination], false, false, cx);
16597 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16598 s.select_ranges([destination..destination]);
16599 });
16600 }
16601 }
16602
16603 fn go_to_line<T: 'static>(
16604 &mut self,
16605 position: Anchor,
16606 highlight_color: Option<Hsla>,
16607 window: &mut Window,
16608 cx: &mut Context<Self>,
16609 ) {
16610 let snapshot = self.snapshot(window, cx).display_snapshot;
16611 let position = position.to_point(&snapshot.buffer_snapshot());
16612 let start = snapshot
16613 .buffer_snapshot()
16614 .clip_point(Point::new(position.row, 0), Bias::Left);
16615 let end = start + Point::new(1, 0);
16616 let start = snapshot.buffer_snapshot().anchor_before(start);
16617 let end = snapshot.buffer_snapshot().anchor_before(end);
16618
16619 self.highlight_rows::<T>(
16620 start..end,
16621 highlight_color
16622 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16623 Default::default(),
16624 cx,
16625 );
16626
16627 if self.buffer.read(cx).is_singleton() {
16628 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16629 }
16630 }
16631
16632 pub fn go_to_definition(
16633 &mut self,
16634 _: &GoToDefinition,
16635 window: &mut Window,
16636 cx: &mut Context<Self>,
16637 ) -> Task<Result<Navigated>> {
16638 let definition =
16639 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16640 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16641 cx.spawn_in(window, async move |editor, cx| {
16642 if definition.await? == Navigated::Yes {
16643 return Ok(Navigated::Yes);
16644 }
16645 match fallback_strategy {
16646 GoToDefinitionFallback::None => Ok(Navigated::No),
16647 GoToDefinitionFallback::FindAllReferences => {
16648 match editor.update_in(cx, |editor, window, cx| {
16649 editor.find_all_references(&FindAllReferences, window, cx)
16650 })? {
16651 Some(references) => references.await,
16652 None => Ok(Navigated::No),
16653 }
16654 }
16655 }
16656 })
16657 }
16658
16659 pub fn go_to_declaration(
16660 &mut self,
16661 _: &GoToDeclaration,
16662 window: &mut Window,
16663 cx: &mut Context<Self>,
16664 ) -> Task<Result<Navigated>> {
16665 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16666 }
16667
16668 pub fn go_to_declaration_split(
16669 &mut self,
16670 _: &GoToDeclaration,
16671 window: &mut Window,
16672 cx: &mut Context<Self>,
16673 ) -> Task<Result<Navigated>> {
16674 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16675 }
16676
16677 pub fn go_to_implementation(
16678 &mut self,
16679 _: &GoToImplementation,
16680 window: &mut Window,
16681 cx: &mut Context<Self>,
16682 ) -> Task<Result<Navigated>> {
16683 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16684 }
16685
16686 pub fn go_to_implementation_split(
16687 &mut self,
16688 _: &GoToImplementationSplit,
16689 window: &mut Window,
16690 cx: &mut Context<Self>,
16691 ) -> Task<Result<Navigated>> {
16692 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16693 }
16694
16695 pub fn go_to_type_definition(
16696 &mut self,
16697 _: &GoToTypeDefinition,
16698 window: &mut Window,
16699 cx: &mut Context<Self>,
16700 ) -> Task<Result<Navigated>> {
16701 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16702 }
16703
16704 pub fn go_to_definition_split(
16705 &mut self,
16706 _: &GoToDefinitionSplit,
16707 window: &mut Window,
16708 cx: &mut Context<Self>,
16709 ) -> Task<Result<Navigated>> {
16710 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16711 }
16712
16713 pub fn go_to_type_definition_split(
16714 &mut self,
16715 _: &GoToTypeDefinitionSplit,
16716 window: &mut Window,
16717 cx: &mut Context<Self>,
16718 ) -> Task<Result<Navigated>> {
16719 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16720 }
16721
16722 fn go_to_definition_of_kind(
16723 &mut self,
16724 kind: GotoDefinitionKind,
16725 split: bool,
16726 window: &mut Window,
16727 cx: &mut Context<Self>,
16728 ) -> Task<Result<Navigated>> {
16729 let Some(provider) = self.semantics_provider.clone() else {
16730 return Task::ready(Ok(Navigated::No));
16731 };
16732 let head = self
16733 .selections
16734 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16735 .head();
16736 let buffer = self.buffer.read(cx);
16737 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16738 return Task::ready(Ok(Navigated::No));
16739 };
16740 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16741 return Task::ready(Ok(Navigated::No));
16742 };
16743
16744 cx.spawn_in(window, async move |editor, cx| {
16745 let Some(definitions) = definitions.await? else {
16746 return Ok(Navigated::No);
16747 };
16748 let navigated = editor
16749 .update_in(cx, |editor, window, cx| {
16750 editor.navigate_to_hover_links(
16751 Some(kind),
16752 definitions
16753 .into_iter()
16754 .filter(|location| {
16755 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16756 })
16757 .map(HoverLink::Text)
16758 .collect::<Vec<_>>(),
16759 split,
16760 window,
16761 cx,
16762 )
16763 })?
16764 .await?;
16765 anyhow::Ok(navigated)
16766 })
16767 }
16768
16769 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16770 let selection = self.selections.newest_anchor();
16771 let head = selection.head();
16772 let tail = selection.tail();
16773
16774 let Some((buffer, start_position)) =
16775 self.buffer.read(cx).text_anchor_for_position(head, cx)
16776 else {
16777 return;
16778 };
16779
16780 let end_position = if head != tail {
16781 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16782 return;
16783 };
16784 Some(pos)
16785 } else {
16786 None
16787 };
16788
16789 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16790 let url = if let Some(end_pos) = end_position {
16791 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16792 } else {
16793 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16794 };
16795
16796 if let Some(url) = url {
16797 cx.update(|window, cx| {
16798 if parse_zed_link(&url, cx).is_some() {
16799 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16800 } else {
16801 cx.open_url(&url);
16802 }
16803 })?;
16804 }
16805
16806 anyhow::Ok(())
16807 });
16808
16809 url_finder.detach();
16810 }
16811
16812 pub fn open_selected_filename(
16813 &mut self,
16814 _: &OpenSelectedFilename,
16815 window: &mut Window,
16816 cx: &mut Context<Self>,
16817 ) {
16818 let Some(workspace) = self.workspace() else {
16819 return;
16820 };
16821
16822 let position = self.selections.newest_anchor().head();
16823
16824 let Some((buffer, buffer_position)) =
16825 self.buffer.read(cx).text_anchor_for_position(position, cx)
16826 else {
16827 return;
16828 };
16829
16830 let project = self.project.clone();
16831
16832 cx.spawn_in(window, async move |_, cx| {
16833 let result = find_file(&buffer, project, buffer_position, cx).await;
16834
16835 if let Some((_, path)) = result {
16836 workspace
16837 .update_in(cx, |workspace, window, cx| {
16838 workspace.open_resolved_path(path, window, cx)
16839 })?
16840 .await?;
16841 }
16842 anyhow::Ok(())
16843 })
16844 .detach();
16845 }
16846
16847 pub(crate) fn navigate_to_hover_links(
16848 &mut self,
16849 kind: Option<GotoDefinitionKind>,
16850 definitions: Vec<HoverLink>,
16851 split: bool,
16852 window: &mut Window,
16853 cx: &mut Context<Editor>,
16854 ) -> Task<Result<Navigated>> {
16855 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16856 let mut first_url_or_file = None;
16857 let definitions: Vec<_> = definitions
16858 .into_iter()
16859 .filter_map(|def| match def {
16860 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16861 HoverLink::InlayHint(lsp_location, server_id) => {
16862 let computation =
16863 self.compute_target_location(lsp_location, server_id, window, cx);
16864 Some(cx.background_spawn(computation))
16865 }
16866 HoverLink::Url(url) => {
16867 first_url_or_file = Some(Either::Left(url));
16868 None
16869 }
16870 HoverLink::File(path) => {
16871 first_url_or_file = Some(Either::Right(path));
16872 None
16873 }
16874 })
16875 .collect();
16876
16877 let workspace = self.workspace();
16878
16879 cx.spawn_in(window, async move |editor, cx| {
16880 let locations: Vec<Location> = future::join_all(definitions)
16881 .await
16882 .into_iter()
16883 .filter_map(|location| location.transpose())
16884 .collect::<Result<_>>()
16885 .context("location tasks")?;
16886 let mut locations = cx.update(|_, cx| {
16887 locations
16888 .into_iter()
16889 .map(|location| {
16890 let buffer = location.buffer.read(cx);
16891 (location.buffer, location.range.to_point(buffer))
16892 })
16893 .into_group_map()
16894 })?;
16895 let mut num_locations = 0;
16896 for ranges in locations.values_mut() {
16897 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16898 ranges.dedup();
16899 num_locations += ranges.len();
16900 }
16901
16902 if num_locations > 1 {
16903 let Some(workspace) = workspace else {
16904 return Ok(Navigated::No);
16905 };
16906
16907 let tab_kind = match kind {
16908 Some(GotoDefinitionKind::Implementation) => "Implementations",
16909 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16910 Some(GotoDefinitionKind::Declaration) => "Declarations",
16911 Some(GotoDefinitionKind::Type) => "Types",
16912 };
16913 let title = editor
16914 .update_in(cx, |_, _, cx| {
16915 let target = locations
16916 .iter()
16917 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16918 .map(|(buffer, location)| {
16919 buffer
16920 .read(cx)
16921 .text_for_range(location.clone())
16922 .collect::<String>()
16923 })
16924 .filter(|text| !text.contains('\n'))
16925 .unique()
16926 .take(3)
16927 .join(", ");
16928 if target.is_empty() {
16929 tab_kind.to_owned()
16930 } else {
16931 format!("{tab_kind} for {target}")
16932 }
16933 })
16934 .context("buffer title")?;
16935
16936 let opened = workspace
16937 .update_in(cx, |workspace, window, cx| {
16938 Self::open_locations_in_multibuffer(
16939 workspace,
16940 locations,
16941 title,
16942 split,
16943 MultibufferSelectionMode::First,
16944 window,
16945 cx,
16946 )
16947 })
16948 .is_ok();
16949
16950 anyhow::Ok(Navigated::from_bool(opened))
16951 } else if num_locations == 0 {
16952 // If there is one url or file, open it directly
16953 match first_url_or_file {
16954 Some(Either::Left(url)) => {
16955 cx.update(|_, cx| cx.open_url(&url))?;
16956 Ok(Navigated::Yes)
16957 }
16958 Some(Either::Right(path)) => {
16959 let Some(workspace) = workspace else {
16960 return Ok(Navigated::No);
16961 };
16962
16963 workspace
16964 .update_in(cx, |workspace, window, cx| {
16965 workspace.open_resolved_path(path, window, cx)
16966 })?
16967 .await?;
16968 Ok(Navigated::Yes)
16969 }
16970 None => Ok(Navigated::No),
16971 }
16972 } else {
16973 let Some(workspace) = workspace else {
16974 return Ok(Navigated::No);
16975 };
16976
16977 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16978 let target_range = target_ranges.first().unwrap().clone();
16979
16980 editor.update_in(cx, |editor, window, cx| {
16981 let range = target_range.to_point(target_buffer.read(cx));
16982 let range = editor.range_for_match(&range);
16983 let range = collapse_multiline_range(range);
16984
16985 if !split
16986 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16987 {
16988 editor.go_to_singleton_buffer_range(range, window, cx);
16989 } else {
16990 let pane = workspace.read(cx).active_pane().clone();
16991 window.defer(cx, move |window, cx| {
16992 let target_editor: Entity<Self> =
16993 workspace.update(cx, |workspace, cx| {
16994 let pane = if split {
16995 workspace.adjacent_pane(window, cx)
16996 } else {
16997 workspace.active_pane().clone()
16998 };
16999
17000 workspace.open_project_item(
17001 pane,
17002 target_buffer.clone(),
17003 true,
17004 true,
17005 window,
17006 cx,
17007 )
17008 });
17009 target_editor.update(cx, |target_editor, cx| {
17010 // When selecting a definition in a different buffer, disable the nav history
17011 // to avoid creating a history entry at the previous cursor location.
17012 pane.update(cx, |pane, _| pane.disable_history());
17013 target_editor.go_to_singleton_buffer_range(range, window, cx);
17014 pane.update(cx, |pane, _| pane.enable_history());
17015 });
17016 });
17017 }
17018 Navigated::Yes
17019 })
17020 }
17021 })
17022 }
17023
17024 fn compute_target_location(
17025 &self,
17026 lsp_location: lsp::Location,
17027 server_id: LanguageServerId,
17028 window: &mut Window,
17029 cx: &mut Context<Self>,
17030 ) -> Task<anyhow::Result<Option<Location>>> {
17031 let Some(project) = self.project.clone() else {
17032 return Task::ready(Ok(None));
17033 };
17034
17035 cx.spawn_in(window, async move |editor, cx| {
17036 let location_task = editor.update(cx, |_, cx| {
17037 project.update(cx, |project, cx| {
17038 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17039 })
17040 })?;
17041 let location = Some({
17042 let target_buffer_handle = location_task.await.context("open local buffer")?;
17043 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17044 let target_start = target_buffer
17045 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17046 let target_end = target_buffer
17047 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17048 target_buffer.anchor_after(target_start)
17049 ..target_buffer.anchor_before(target_end)
17050 })?;
17051 Location {
17052 buffer: target_buffer_handle,
17053 range,
17054 }
17055 });
17056 Ok(location)
17057 })
17058 }
17059
17060 fn go_to_next_reference(
17061 &mut self,
17062 _: &GoToNextReference,
17063 window: &mut Window,
17064 cx: &mut Context<Self>,
17065 ) {
17066 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17067 if let Some(task) = task {
17068 task.detach();
17069 };
17070 }
17071
17072 fn go_to_prev_reference(
17073 &mut self,
17074 _: &GoToPreviousReference,
17075 window: &mut Window,
17076 cx: &mut Context<Self>,
17077 ) {
17078 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17079 if let Some(task) = task {
17080 task.detach();
17081 };
17082 }
17083
17084 pub fn go_to_reference_before_or_after_position(
17085 &mut self,
17086 direction: Direction,
17087 count: usize,
17088 window: &mut Window,
17089 cx: &mut Context<Self>,
17090 ) -> Option<Task<Result<()>>> {
17091 let selection = self.selections.newest_anchor();
17092 let head = selection.head();
17093
17094 let multi_buffer = self.buffer.read(cx);
17095
17096 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17097 let workspace = self.workspace()?;
17098 let project = workspace.read(cx).project().clone();
17099 let references =
17100 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17101 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17102 let Some(locations) = references.await? else {
17103 return Ok(());
17104 };
17105
17106 if locations.is_empty() {
17107 // totally normal - the cursor may be on something which is not
17108 // a symbol (e.g. a keyword)
17109 log::info!("no references found under cursor");
17110 return Ok(());
17111 }
17112
17113 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17114
17115 let (locations, current_location_index) =
17116 multi_buffer.update(cx, |multi_buffer, cx| {
17117 let mut locations = locations
17118 .into_iter()
17119 .filter_map(|loc| {
17120 let start = multi_buffer.buffer_anchor_to_anchor(
17121 &loc.buffer,
17122 loc.range.start,
17123 cx,
17124 )?;
17125 let end = multi_buffer.buffer_anchor_to_anchor(
17126 &loc.buffer,
17127 loc.range.end,
17128 cx,
17129 )?;
17130 Some(start..end)
17131 })
17132 .collect::<Vec<_>>();
17133
17134 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17135 // There is an O(n) implementation, but given this list will be
17136 // small (usually <100 items), the extra O(log(n)) factor isn't
17137 // worth the (surprisingly large amount of) extra complexity.
17138 locations
17139 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17140
17141 let head_offset = head.to_offset(&multi_buffer_snapshot);
17142
17143 let current_location_index = locations.iter().position(|loc| {
17144 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17145 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17146 });
17147
17148 (locations, current_location_index)
17149 })?;
17150
17151 let Some(current_location_index) = current_location_index else {
17152 // This indicates something has gone wrong, because we already
17153 // handle the "no references" case above
17154 log::error!(
17155 "failed to find current reference under cursor. Total references: {}",
17156 locations.len()
17157 );
17158 return Ok(());
17159 };
17160
17161 let destination_location_index = match direction {
17162 Direction::Next => (current_location_index + count) % locations.len(),
17163 Direction::Prev => {
17164 (current_location_index + locations.len() - count % locations.len())
17165 % locations.len()
17166 }
17167 };
17168
17169 // TODO(cameron): is this needed?
17170 // the thinking is to avoid "jumping to the current location" (avoid
17171 // polluting "jumplist" in vim terms)
17172 if current_location_index == destination_location_index {
17173 return Ok(());
17174 }
17175
17176 let Range { start, end } = locations[destination_location_index];
17177
17178 editor.update_in(cx, |editor, window, cx| {
17179 let effects = SelectionEffects::default();
17180
17181 editor.unfold_ranges(&[start..end], false, false, cx);
17182 editor.change_selections(effects, window, cx, |s| {
17183 s.select_ranges([start..start]);
17184 });
17185 })?;
17186
17187 Ok(())
17188 }))
17189 }
17190
17191 pub fn find_all_references(
17192 &mut self,
17193 _: &FindAllReferences,
17194 window: &mut Window,
17195 cx: &mut Context<Self>,
17196 ) -> Option<Task<Result<Navigated>>> {
17197 let selection = self
17198 .selections
17199 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17200 let multi_buffer = self.buffer.read(cx);
17201 let head = selection.head();
17202
17203 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17204 let head_anchor = multi_buffer_snapshot.anchor_at(
17205 head,
17206 if head < selection.tail() {
17207 Bias::Right
17208 } else {
17209 Bias::Left
17210 },
17211 );
17212
17213 match self
17214 .find_all_references_task_sources
17215 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17216 {
17217 Ok(_) => {
17218 log::info!(
17219 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17220 );
17221 return None;
17222 }
17223 Err(i) => {
17224 self.find_all_references_task_sources.insert(i, head_anchor);
17225 }
17226 }
17227
17228 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17229 let workspace = self.workspace()?;
17230 let project = workspace.read(cx).project().clone();
17231 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17232 Some(cx.spawn_in(window, async move |editor, cx| {
17233 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17234 if let Ok(i) = editor
17235 .find_all_references_task_sources
17236 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17237 {
17238 editor.find_all_references_task_sources.remove(i);
17239 }
17240 });
17241
17242 let Some(locations) = references.await? else {
17243 return anyhow::Ok(Navigated::No);
17244 };
17245 let mut locations = cx.update(|_, cx| {
17246 locations
17247 .into_iter()
17248 .map(|location| {
17249 let buffer = location.buffer.read(cx);
17250 (location.buffer, location.range.to_point(buffer))
17251 })
17252 .into_group_map()
17253 })?;
17254 if locations.is_empty() {
17255 return anyhow::Ok(Navigated::No);
17256 }
17257 for ranges in locations.values_mut() {
17258 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17259 ranges.dedup();
17260 }
17261
17262 workspace.update_in(cx, |workspace, window, cx| {
17263 let target = locations
17264 .iter()
17265 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17266 .map(|(buffer, location)| {
17267 buffer
17268 .read(cx)
17269 .text_for_range(location.clone())
17270 .collect::<String>()
17271 })
17272 .filter(|text| !text.contains('\n'))
17273 .unique()
17274 .take(3)
17275 .join(", ");
17276 let title = if target.is_empty() {
17277 "References".to_owned()
17278 } else {
17279 format!("References to {target}")
17280 };
17281 Self::open_locations_in_multibuffer(
17282 workspace,
17283 locations,
17284 title,
17285 false,
17286 MultibufferSelectionMode::First,
17287 window,
17288 cx,
17289 );
17290 Navigated::Yes
17291 })
17292 }))
17293 }
17294
17295 /// Opens a multibuffer with the given project locations in it
17296 pub fn open_locations_in_multibuffer(
17297 workspace: &mut Workspace,
17298 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17299 title: String,
17300 split: bool,
17301 multibuffer_selection_mode: MultibufferSelectionMode,
17302 window: &mut Window,
17303 cx: &mut Context<Workspace>,
17304 ) {
17305 if locations.is_empty() {
17306 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17307 return;
17308 }
17309
17310 let capability = workspace.project().read(cx).capability();
17311 let mut ranges = <Vec<Range<Anchor>>>::new();
17312
17313 // a key to find existing multibuffer editors with the same set of locations
17314 // to prevent us from opening more and more multibuffer tabs for searches and the like
17315 let mut key = (title.clone(), vec![]);
17316 let excerpt_buffer = cx.new(|cx| {
17317 let key = &mut key.1;
17318 let mut multibuffer = MultiBuffer::new(capability);
17319 for (buffer, mut ranges_for_buffer) in locations {
17320 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17321 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17322 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17323 PathKey::for_buffer(&buffer, cx),
17324 buffer.clone(),
17325 ranges_for_buffer,
17326 multibuffer_context_lines(cx),
17327 cx,
17328 );
17329 ranges.extend(new_ranges)
17330 }
17331
17332 multibuffer.with_title(title)
17333 });
17334 let existing = workspace.active_pane().update(cx, |pane, cx| {
17335 pane.items()
17336 .filter_map(|item| item.downcast::<Editor>())
17337 .find(|editor| {
17338 editor
17339 .read(cx)
17340 .lookup_key
17341 .as_ref()
17342 .and_then(|it| {
17343 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17344 })
17345 .is_some_and(|it| *it == key)
17346 })
17347 });
17348 let editor = existing.unwrap_or_else(|| {
17349 cx.new(|cx| {
17350 let mut editor = Editor::for_multibuffer(
17351 excerpt_buffer,
17352 Some(workspace.project().clone()),
17353 window,
17354 cx,
17355 );
17356 editor.lookup_key = Some(Box::new(key));
17357 editor
17358 })
17359 });
17360 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17361 MultibufferSelectionMode::First => {
17362 if let Some(first_range) = ranges.first() {
17363 editor.change_selections(
17364 SelectionEffects::no_scroll(),
17365 window,
17366 cx,
17367 |selections| {
17368 selections.clear_disjoint();
17369 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17370 },
17371 );
17372 }
17373 editor.highlight_background::<Self>(
17374 &ranges,
17375 |theme| theme.colors().editor_highlighted_line_background,
17376 cx,
17377 );
17378 }
17379 MultibufferSelectionMode::All => {
17380 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17381 selections.clear_disjoint();
17382 selections.select_anchor_ranges(ranges);
17383 });
17384 }
17385 });
17386
17387 let item = Box::new(editor);
17388 let item_id = item.item_id();
17389
17390 if split {
17391 let pane = workspace.adjacent_pane(window, cx);
17392 workspace.add_item(pane, item, None, true, true, window, cx);
17393 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17394 let (preview_item_id, preview_item_idx) =
17395 workspace.active_pane().read_with(cx, |pane, _| {
17396 (pane.preview_item_id(), pane.preview_item_idx())
17397 });
17398
17399 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17400
17401 if let Some(preview_item_id) = preview_item_id {
17402 workspace.active_pane().update(cx, |pane, cx| {
17403 pane.remove_item(preview_item_id, false, false, window, cx);
17404 });
17405 }
17406 } else {
17407 workspace.add_item_to_active_pane(item, None, true, window, cx);
17408 }
17409 workspace.active_pane().update(cx, |pane, cx| {
17410 pane.set_preview_item_id(Some(item_id), cx);
17411 });
17412 }
17413
17414 pub fn rename(
17415 &mut self,
17416 _: &Rename,
17417 window: &mut Window,
17418 cx: &mut Context<Self>,
17419 ) -> Option<Task<Result<()>>> {
17420 use language::ToOffset as _;
17421
17422 let provider = self.semantics_provider.clone()?;
17423 let selection = self.selections.newest_anchor().clone();
17424 let (cursor_buffer, cursor_buffer_position) = self
17425 .buffer
17426 .read(cx)
17427 .text_anchor_for_position(selection.head(), cx)?;
17428 let (tail_buffer, cursor_buffer_position_end) = self
17429 .buffer
17430 .read(cx)
17431 .text_anchor_for_position(selection.tail(), cx)?;
17432 if tail_buffer != cursor_buffer {
17433 return None;
17434 }
17435
17436 let snapshot = cursor_buffer.read(cx).snapshot();
17437 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17438 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17439 let prepare_rename = provider
17440 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17441 .unwrap_or_else(|| Task::ready(Ok(None)));
17442 drop(snapshot);
17443
17444 Some(cx.spawn_in(window, async move |this, cx| {
17445 let rename_range = if let Some(range) = prepare_rename.await? {
17446 Some(range)
17447 } else {
17448 this.update(cx, |this, cx| {
17449 let buffer = this.buffer.read(cx).snapshot(cx);
17450 let mut buffer_highlights = this
17451 .document_highlights_for_position(selection.head(), &buffer)
17452 .filter(|highlight| {
17453 highlight.start.excerpt_id == selection.head().excerpt_id
17454 && highlight.end.excerpt_id == selection.head().excerpt_id
17455 });
17456 buffer_highlights
17457 .next()
17458 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17459 })?
17460 };
17461 if let Some(rename_range) = rename_range {
17462 this.update_in(cx, |this, window, cx| {
17463 let snapshot = cursor_buffer.read(cx).snapshot();
17464 let rename_buffer_range = rename_range.to_offset(&snapshot);
17465 let cursor_offset_in_rename_range =
17466 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17467 let cursor_offset_in_rename_range_end =
17468 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17469
17470 this.take_rename(false, window, cx);
17471 let buffer = this.buffer.read(cx).read(cx);
17472 let cursor_offset = selection.head().to_offset(&buffer);
17473 let rename_start =
17474 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17475 let rename_end = rename_start + rename_buffer_range.len();
17476 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17477 let mut old_highlight_id = None;
17478 let old_name: Arc<str> = buffer
17479 .chunks(rename_start..rename_end, true)
17480 .map(|chunk| {
17481 if old_highlight_id.is_none() {
17482 old_highlight_id = chunk.syntax_highlight_id;
17483 }
17484 chunk.text
17485 })
17486 .collect::<String>()
17487 .into();
17488
17489 drop(buffer);
17490
17491 // Position the selection in the rename editor so that it matches the current selection.
17492 this.show_local_selections = false;
17493 let rename_editor = cx.new(|cx| {
17494 let mut editor = Editor::single_line(window, cx);
17495 editor.buffer.update(cx, |buffer, cx| {
17496 buffer.edit(
17497 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17498 None,
17499 cx,
17500 )
17501 });
17502 let cursor_offset_in_rename_range =
17503 MultiBufferOffset(cursor_offset_in_rename_range);
17504 let cursor_offset_in_rename_range_end =
17505 MultiBufferOffset(cursor_offset_in_rename_range_end);
17506 let rename_selection_range = match cursor_offset_in_rename_range
17507 .cmp(&cursor_offset_in_rename_range_end)
17508 {
17509 Ordering::Equal => {
17510 editor.select_all(&SelectAll, window, cx);
17511 return editor;
17512 }
17513 Ordering::Less => {
17514 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17515 }
17516 Ordering::Greater => {
17517 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17518 }
17519 };
17520 if rename_selection_range.end.0 > old_name.len() {
17521 editor.select_all(&SelectAll, window, cx);
17522 } else {
17523 editor.change_selections(Default::default(), window, cx, |s| {
17524 s.select_ranges([rename_selection_range]);
17525 });
17526 }
17527 editor
17528 });
17529 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17530 if e == &EditorEvent::Focused {
17531 cx.emit(EditorEvent::FocusedIn)
17532 }
17533 })
17534 .detach();
17535
17536 let write_highlights =
17537 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17538 let read_highlights =
17539 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17540 let ranges = write_highlights
17541 .iter()
17542 .flat_map(|(_, ranges)| ranges.iter())
17543 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17544 .cloned()
17545 .collect();
17546
17547 this.highlight_text::<Rename>(
17548 ranges,
17549 HighlightStyle {
17550 fade_out: Some(0.6),
17551 ..Default::default()
17552 },
17553 cx,
17554 );
17555 let rename_focus_handle = rename_editor.focus_handle(cx);
17556 window.focus(&rename_focus_handle);
17557 let block_id = this.insert_blocks(
17558 [BlockProperties {
17559 style: BlockStyle::Flex,
17560 placement: BlockPlacement::Below(range.start),
17561 height: Some(1),
17562 render: Arc::new({
17563 let rename_editor = rename_editor.clone();
17564 move |cx: &mut BlockContext| {
17565 let mut text_style = cx.editor_style.text.clone();
17566 if let Some(highlight_style) = old_highlight_id
17567 .and_then(|h| h.style(&cx.editor_style.syntax))
17568 {
17569 text_style = text_style.highlight(highlight_style);
17570 }
17571 div()
17572 .block_mouse_except_scroll()
17573 .pl(cx.anchor_x)
17574 .child(EditorElement::new(
17575 &rename_editor,
17576 EditorStyle {
17577 background: cx.theme().system().transparent,
17578 local_player: cx.editor_style.local_player,
17579 text: text_style,
17580 scrollbar_width: cx.editor_style.scrollbar_width,
17581 syntax: cx.editor_style.syntax.clone(),
17582 status: cx.editor_style.status.clone(),
17583 inlay_hints_style: HighlightStyle {
17584 font_weight: Some(FontWeight::BOLD),
17585 ..make_inlay_hints_style(cx.app)
17586 },
17587 edit_prediction_styles: make_suggestion_styles(
17588 cx.app,
17589 ),
17590 ..EditorStyle::default()
17591 },
17592 ))
17593 .into_any_element()
17594 }
17595 }),
17596 priority: 0,
17597 }],
17598 Some(Autoscroll::fit()),
17599 cx,
17600 )[0];
17601 this.pending_rename = Some(RenameState {
17602 range,
17603 old_name,
17604 editor: rename_editor,
17605 block_id,
17606 });
17607 })?;
17608 }
17609
17610 Ok(())
17611 }))
17612 }
17613
17614 pub fn confirm_rename(
17615 &mut self,
17616 _: &ConfirmRename,
17617 window: &mut Window,
17618 cx: &mut Context<Self>,
17619 ) -> Option<Task<Result<()>>> {
17620 let rename = self.take_rename(false, window, cx)?;
17621 let workspace = self.workspace()?.downgrade();
17622 let (buffer, start) = self
17623 .buffer
17624 .read(cx)
17625 .text_anchor_for_position(rename.range.start, cx)?;
17626 let (end_buffer, _) = self
17627 .buffer
17628 .read(cx)
17629 .text_anchor_for_position(rename.range.end, cx)?;
17630 if buffer != end_buffer {
17631 return None;
17632 }
17633
17634 let old_name = rename.old_name;
17635 let new_name = rename.editor.read(cx).text(cx);
17636
17637 let rename = self.semantics_provider.as_ref()?.perform_rename(
17638 &buffer,
17639 start,
17640 new_name.clone(),
17641 cx,
17642 )?;
17643
17644 Some(cx.spawn_in(window, async move |editor, cx| {
17645 let project_transaction = rename.await?;
17646 Self::open_project_transaction(
17647 &editor,
17648 workspace,
17649 project_transaction,
17650 format!("Rename: {} → {}", old_name, new_name),
17651 cx,
17652 )
17653 .await?;
17654
17655 editor.update(cx, |editor, cx| {
17656 editor.refresh_document_highlights(cx);
17657 })?;
17658 Ok(())
17659 }))
17660 }
17661
17662 fn take_rename(
17663 &mut self,
17664 moving_cursor: bool,
17665 window: &mut Window,
17666 cx: &mut Context<Self>,
17667 ) -> Option<RenameState> {
17668 let rename = self.pending_rename.take()?;
17669 if rename.editor.focus_handle(cx).is_focused(window) {
17670 window.focus(&self.focus_handle);
17671 }
17672
17673 self.remove_blocks(
17674 [rename.block_id].into_iter().collect(),
17675 Some(Autoscroll::fit()),
17676 cx,
17677 );
17678 self.clear_highlights::<Rename>(cx);
17679 self.show_local_selections = true;
17680
17681 if moving_cursor {
17682 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17683 editor
17684 .selections
17685 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
17686 .head()
17687 });
17688
17689 // Update the selection to match the position of the selection inside
17690 // the rename editor.
17691 let snapshot = self.buffer.read(cx).read(cx);
17692 let rename_range = rename.range.to_offset(&snapshot);
17693 let cursor_in_editor = snapshot
17694 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17695 .min(rename_range.end);
17696 drop(snapshot);
17697
17698 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17699 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17700 });
17701 } else {
17702 self.refresh_document_highlights(cx);
17703 }
17704
17705 Some(rename)
17706 }
17707
17708 pub fn pending_rename(&self) -> Option<&RenameState> {
17709 self.pending_rename.as_ref()
17710 }
17711
17712 fn format(
17713 &mut self,
17714 _: &Format,
17715 window: &mut Window,
17716 cx: &mut Context<Self>,
17717 ) -> Option<Task<Result<()>>> {
17718 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17719
17720 let project = match &self.project {
17721 Some(project) => project.clone(),
17722 None => return None,
17723 };
17724
17725 Some(self.perform_format(
17726 project,
17727 FormatTrigger::Manual,
17728 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17729 window,
17730 cx,
17731 ))
17732 }
17733
17734 fn format_selections(
17735 &mut self,
17736 _: &FormatSelections,
17737 window: &mut Window,
17738 cx: &mut Context<Self>,
17739 ) -> Option<Task<Result<()>>> {
17740 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17741
17742 let project = match &self.project {
17743 Some(project) => project.clone(),
17744 None => return None,
17745 };
17746
17747 let ranges = self
17748 .selections
17749 .all_adjusted(&self.display_snapshot(cx))
17750 .into_iter()
17751 .map(|selection| selection.range())
17752 .collect_vec();
17753
17754 Some(self.perform_format(
17755 project,
17756 FormatTrigger::Manual,
17757 FormatTarget::Ranges(ranges),
17758 window,
17759 cx,
17760 ))
17761 }
17762
17763 fn perform_format(
17764 &mut self,
17765 project: Entity<Project>,
17766 trigger: FormatTrigger,
17767 target: FormatTarget,
17768 window: &mut Window,
17769 cx: &mut Context<Self>,
17770 ) -> Task<Result<()>> {
17771 let buffer = self.buffer.clone();
17772 let (buffers, target) = match target {
17773 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17774 FormatTarget::Ranges(selection_ranges) => {
17775 let multi_buffer = buffer.read(cx);
17776 let snapshot = multi_buffer.read(cx);
17777 let mut buffers = HashSet::default();
17778 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17779 BTreeMap::new();
17780 for selection_range in selection_ranges {
17781 for (buffer, buffer_range, _) in
17782 snapshot.range_to_buffer_ranges(selection_range)
17783 {
17784 let buffer_id = buffer.remote_id();
17785 let start = buffer.anchor_before(buffer_range.start);
17786 let end = buffer.anchor_after(buffer_range.end);
17787 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17788 buffer_id_to_ranges
17789 .entry(buffer_id)
17790 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17791 .or_insert_with(|| vec![start..end]);
17792 }
17793 }
17794 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17795 }
17796 };
17797
17798 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17799 let selections_prev = transaction_id_prev
17800 .and_then(|transaction_id_prev| {
17801 // default to selections as they were after the last edit, if we have them,
17802 // instead of how they are now.
17803 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17804 // will take you back to where you made the last edit, instead of staying where you scrolled
17805 self.selection_history
17806 .transaction(transaction_id_prev)
17807 .map(|t| t.0.clone())
17808 })
17809 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17810
17811 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17812 let format = project.update(cx, |project, cx| {
17813 project.format(buffers, target, true, trigger, cx)
17814 });
17815
17816 cx.spawn_in(window, async move |editor, cx| {
17817 let transaction = futures::select_biased! {
17818 transaction = format.log_err().fuse() => transaction,
17819 () = timeout => {
17820 log::warn!("timed out waiting for formatting");
17821 None
17822 }
17823 };
17824
17825 buffer
17826 .update(cx, |buffer, cx| {
17827 if let Some(transaction) = transaction
17828 && !buffer.is_singleton()
17829 {
17830 buffer.push_transaction(&transaction.0, cx);
17831 }
17832 cx.notify();
17833 })
17834 .ok();
17835
17836 if let Some(transaction_id_now) =
17837 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17838 {
17839 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17840 if has_new_transaction {
17841 _ = editor.update(cx, |editor, _| {
17842 editor
17843 .selection_history
17844 .insert_transaction(transaction_id_now, selections_prev);
17845 });
17846 }
17847 }
17848
17849 Ok(())
17850 })
17851 }
17852
17853 fn organize_imports(
17854 &mut self,
17855 _: &OrganizeImports,
17856 window: &mut Window,
17857 cx: &mut Context<Self>,
17858 ) -> Option<Task<Result<()>>> {
17859 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17860 let project = match &self.project {
17861 Some(project) => project.clone(),
17862 None => return None,
17863 };
17864 Some(self.perform_code_action_kind(
17865 project,
17866 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17867 window,
17868 cx,
17869 ))
17870 }
17871
17872 fn perform_code_action_kind(
17873 &mut self,
17874 project: Entity<Project>,
17875 kind: CodeActionKind,
17876 window: &mut Window,
17877 cx: &mut Context<Self>,
17878 ) -> Task<Result<()>> {
17879 let buffer = self.buffer.clone();
17880 let buffers = buffer.read(cx).all_buffers();
17881 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17882 let apply_action = project.update(cx, |project, cx| {
17883 project.apply_code_action_kind(buffers, kind, true, cx)
17884 });
17885 cx.spawn_in(window, async move |_, cx| {
17886 let transaction = futures::select_biased! {
17887 () = timeout => {
17888 log::warn!("timed out waiting for executing code action");
17889 None
17890 }
17891 transaction = apply_action.log_err().fuse() => transaction,
17892 };
17893 buffer
17894 .update(cx, |buffer, cx| {
17895 // check if we need this
17896 if let Some(transaction) = transaction
17897 && !buffer.is_singleton()
17898 {
17899 buffer.push_transaction(&transaction.0, cx);
17900 }
17901 cx.notify();
17902 })
17903 .ok();
17904 Ok(())
17905 })
17906 }
17907
17908 pub fn restart_language_server(
17909 &mut self,
17910 _: &RestartLanguageServer,
17911 _: &mut Window,
17912 cx: &mut Context<Self>,
17913 ) {
17914 if let Some(project) = self.project.clone() {
17915 self.buffer.update(cx, |multi_buffer, cx| {
17916 project.update(cx, |project, cx| {
17917 project.restart_language_servers_for_buffers(
17918 multi_buffer.all_buffers().into_iter().collect(),
17919 HashSet::default(),
17920 cx,
17921 );
17922 });
17923 })
17924 }
17925 }
17926
17927 pub fn stop_language_server(
17928 &mut self,
17929 _: &StopLanguageServer,
17930 _: &mut Window,
17931 cx: &mut Context<Self>,
17932 ) {
17933 if let Some(project) = self.project.clone() {
17934 self.buffer.update(cx, |multi_buffer, cx| {
17935 project.update(cx, |project, cx| {
17936 project.stop_language_servers_for_buffers(
17937 multi_buffer.all_buffers().into_iter().collect(),
17938 HashSet::default(),
17939 cx,
17940 );
17941 });
17942 });
17943 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17944 }
17945 }
17946
17947 fn cancel_language_server_work(
17948 workspace: &mut Workspace,
17949 _: &actions::CancelLanguageServerWork,
17950 _: &mut Window,
17951 cx: &mut Context<Workspace>,
17952 ) {
17953 let project = workspace.project();
17954 let buffers = workspace
17955 .active_item(cx)
17956 .and_then(|item| item.act_as::<Editor>(cx))
17957 .map_or(HashSet::default(), |editor| {
17958 editor.read(cx).buffer.read(cx).all_buffers()
17959 });
17960 project.update(cx, |project, cx| {
17961 project.cancel_language_server_work_for_buffers(buffers, cx);
17962 });
17963 }
17964
17965 fn show_character_palette(
17966 &mut self,
17967 _: &ShowCharacterPalette,
17968 window: &mut Window,
17969 _: &mut Context<Self>,
17970 ) {
17971 window.show_character_palette();
17972 }
17973
17974 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17975 if !self.diagnostics_enabled() {
17976 return;
17977 }
17978
17979 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17980 let buffer = self.buffer.read(cx).snapshot(cx);
17981 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17982 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17983 let is_valid = buffer
17984 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
17985 .any(|entry| {
17986 entry.diagnostic.is_primary
17987 && !entry.range.is_empty()
17988 && entry.range.start == primary_range_start
17989 && entry.diagnostic.message == active_diagnostics.active_message
17990 });
17991
17992 if !is_valid {
17993 self.dismiss_diagnostics(cx);
17994 }
17995 }
17996 }
17997
17998 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17999 match &self.active_diagnostics {
18000 ActiveDiagnostic::Group(group) => Some(group),
18001 _ => None,
18002 }
18003 }
18004
18005 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18006 if !self.diagnostics_enabled() {
18007 return;
18008 }
18009 self.dismiss_diagnostics(cx);
18010 self.active_diagnostics = ActiveDiagnostic::All;
18011 }
18012
18013 fn activate_diagnostics(
18014 &mut self,
18015 buffer_id: BufferId,
18016 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18017 window: &mut Window,
18018 cx: &mut Context<Self>,
18019 ) {
18020 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18021 return;
18022 }
18023 self.dismiss_diagnostics(cx);
18024 let snapshot = self.snapshot(window, cx);
18025 let buffer = self.buffer.read(cx).snapshot(cx);
18026 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18027 return;
18028 };
18029
18030 let diagnostic_group = buffer
18031 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18032 .collect::<Vec<_>>();
18033
18034 let language_registry = self
18035 .project()
18036 .map(|project| project.read(cx).languages().clone());
18037
18038 let blocks = renderer.render_group(
18039 diagnostic_group,
18040 buffer_id,
18041 snapshot,
18042 cx.weak_entity(),
18043 language_registry,
18044 cx,
18045 );
18046
18047 let blocks = self.display_map.update(cx, |display_map, cx| {
18048 display_map.insert_blocks(blocks, cx).into_iter().collect()
18049 });
18050 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18051 active_range: buffer.anchor_before(diagnostic.range.start)
18052 ..buffer.anchor_after(diagnostic.range.end),
18053 active_message: diagnostic.diagnostic.message.clone(),
18054 group_id: diagnostic.diagnostic.group_id,
18055 blocks,
18056 });
18057 cx.notify();
18058 }
18059
18060 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18061 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18062 return;
18063 };
18064
18065 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18066 if let ActiveDiagnostic::Group(group) = prev {
18067 self.display_map.update(cx, |display_map, cx| {
18068 display_map.remove_blocks(group.blocks, cx);
18069 });
18070 cx.notify();
18071 }
18072 }
18073
18074 /// Disable inline diagnostics rendering for this editor.
18075 pub fn disable_inline_diagnostics(&mut self) {
18076 self.inline_diagnostics_enabled = false;
18077 self.inline_diagnostics_update = Task::ready(());
18078 self.inline_diagnostics.clear();
18079 }
18080
18081 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18082 self.diagnostics_enabled = false;
18083 self.dismiss_diagnostics(cx);
18084 self.inline_diagnostics_update = Task::ready(());
18085 self.inline_diagnostics.clear();
18086 }
18087
18088 pub fn disable_word_completions(&mut self) {
18089 self.word_completions_enabled = false;
18090 }
18091
18092 pub fn diagnostics_enabled(&self) -> bool {
18093 self.diagnostics_enabled && self.mode.is_full()
18094 }
18095
18096 pub fn inline_diagnostics_enabled(&self) -> bool {
18097 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18098 }
18099
18100 pub fn show_inline_diagnostics(&self) -> bool {
18101 self.show_inline_diagnostics
18102 }
18103
18104 pub fn toggle_inline_diagnostics(
18105 &mut self,
18106 _: &ToggleInlineDiagnostics,
18107 window: &mut Window,
18108 cx: &mut Context<Editor>,
18109 ) {
18110 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18111 self.refresh_inline_diagnostics(false, window, cx);
18112 }
18113
18114 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18115 self.diagnostics_max_severity = severity;
18116 self.display_map.update(cx, |display_map, _| {
18117 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18118 });
18119 }
18120
18121 pub fn toggle_diagnostics(
18122 &mut self,
18123 _: &ToggleDiagnostics,
18124 window: &mut Window,
18125 cx: &mut Context<Editor>,
18126 ) {
18127 if !self.diagnostics_enabled() {
18128 return;
18129 }
18130
18131 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18132 EditorSettings::get_global(cx)
18133 .diagnostics_max_severity
18134 .filter(|severity| severity != &DiagnosticSeverity::Off)
18135 .unwrap_or(DiagnosticSeverity::Hint)
18136 } else {
18137 DiagnosticSeverity::Off
18138 };
18139 self.set_max_diagnostics_severity(new_severity, cx);
18140 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18141 self.active_diagnostics = ActiveDiagnostic::None;
18142 self.inline_diagnostics_update = Task::ready(());
18143 self.inline_diagnostics.clear();
18144 } else {
18145 self.refresh_inline_diagnostics(false, window, cx);
18146 }
18147
18148 cx.notify();
18149 }
18150
18151 pub fn toggle_minimap(
18152 &mut self,
18153 _: &ToggleMinimap,
18154 window: &mut Window,
18155 cx: &mut Context<Editor>,
18156 ) {
18157 if self.supports_minimap(cx) {
18158 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18159 }
18160 }
18161
18162 fn refresh_inline_diagnostics(
18163 &mut self,
18164 debounce: bool,
18165 window: &mut Window,
18166 cx: &mut Context<Self>,
18167 ) {
18168 let max_severity = ProjectSettings::get_global(cx)
18169 .diagnostics
18170 .inline
18171 .max_severity
18172 .unwrap_or(self.diagnostics_max_severity);
18173
18174 if !self.inline_diagnostics_enabled()
18175 || !self.diagnostics_enabled()
18176 || !self.show_inline_diagnostics
18177 || max_severity == DiagnosticSeverity::Off
18178 {
18179 self.inline_diagnostics_update = Task::ready(());
18180 self.inline_diagnostics.clear();
18181 return;
18182 }
18183
18184 let debounce_ms = ProjectSettings::get_global(cx)
18185 .diagnostics
18186 .inline
18187 .update_debounce_ms;
18188 let debounce = if debounce && debounce_ms > 0 {
18189 Some(Duration::from_millis(debounce_ms))
18190 } else {
18191 None
18192 };
18193 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18194 if let Some(debounce) = debounce {
18195 cx.background_executor().timer(debounce).await;
18196 }
18197 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18198 editor
18199 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18200 .ok()
18201 }) else {
18202 return;
18203 };
18204
18205 let new_inline_diagnostics = cx
18206 .background_spawn(async move {
18207 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18208 for diagnostic_entry in
18209 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18210 {
18211 let message = diagnostic_entry
18212 .diagnostic
18213 .message
18214 .split_once('\n')
18215 .map(|(line, _)| line)
18216 .map(SharedString::new)
18217 .unwrap_or_else(|| {
18218 SharedString::new(&*diagnostic_entry.diagnostic.message)
18219 });
18220 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18221 let (Ok(i) | Err(i)) = inline_diagnostics
18222 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18223 inline_diagnostics.insert(
18224 i,
18225 (
18226 start_anchor,
18227 InlineDiagnostic {
18228 message,
18229 group_id: diagnostic_entry.diagnostic.group_id,
18230 start: diagnostic_entry.range.start.to_point(&snapshot),
18231 is_primary: diagnostic_entry.diagnostic.is_primary,
18232 severity: diagnostic_entry.diagnostic.severity,
18233 },
18234 ),
18235 );
18236 }
18237 inline_diagnostics
18238 })
18239 .await;
18240
18241 editor
18242 .update(cx, |editor, cx| {
18243 editor.inline_diagnostics = new_inline_diagnostics;
18244 cx.notify();
18245 })
18246 .ok();
18247 });
18248 }
18249
18250 fn pull_diagnostics(
18251 &mut self,
18252 buffer_id: Option<BufferId>,
18253 window: &Window,
18254 cx: &mut Context<Self>,
18255 ) -> Option<()> {
18256 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18257 return None;
18258 }
18259 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18260 .diagnostics
18261 .lsp_pull_diagnostics;
18262 if !pull_diagnostics_settings.enabled {
18263 return None;
18264 }
18265 let project = self.project()?.downgrade();
18266 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18267 let mut buffers = self.buffer.read(cx).all_buffers();
18268 buffers.retain(|buffer| {
18269 let buffer_id_to_retain = buffer.read(cx).remote_id();
18270 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18271 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18272 });
18273 if buffers.is_empty() {
18274 self.pull_diagnostics_task = Task::ready(());
18275 return None;
18276 }
18277
18278 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18279 cx.background_executor().timer(debounce).await;
18280
18281 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18282 buffers
18283 .into_iter()
18284 .filter_map(|buffer| {
18285 project
18286 .update(cx, |project, cx| {
18287 project.lsp_store().update(cx, |lsp_store, cx| {
18288 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18289 })
18290 })
18291 .ok()
18292 })
18293 .collect::<FuturesUnordered<_>>()
18294 }) else {
18295 return;
18296 };
18297
18298 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18299 match pull_task {
18300 Ok(()) => {
18301 if editor
18302 .update_in(cx, |editor, window, cx| {
18303 editor.update_diagnostics_state(window, cx);
18304 })
18305 .is_err()
18306 {
18307 return;
18308 }
18309 }
18310 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18311 }
18312 }
18313 });
18314
18315 Some(())
18316 }
18317
18318 pub fn set_selections_from_remote(
18319 &mut self,
18320 selections: Vec<Selection<Anchor>>,
18321 pending_selection: Option<Selection<Anchor>>,
18322 window: &mut Window,
18323 cx: &mut Context<Self>,
18324 ) {
18325 let old_cursor_position = self.selections.newest_anchor().head();
18326 self.selections
18327 .change_with(&self.display_snapshot(cx), |s| {
18328 s.select_anchors(selections);
18329 if let Some(pending_selection) = pending_selection {
18330 s.set_pending(pending_selection, SelectMode::Character);
18331 } else {
18332 s.clear_pending();
18333 }
18334 });
18335 self.selections_did_change(
18336 false,
18337 &old_cursor_position,
18338 SelectionEffects::default(),
18339 window,
18340 cx,
18341 );
18342 }
18343
18344 pub fn transact(
18345 &mut self,
18346 window: &mut Window,
18347 cx: &mut Context<Self>,
18348 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18349 ) -> Option<TransactionId> {
18350 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18351 this.start_transaction_at(Instant::now(), window, cx);
18352 update(this, window, cx);
18353 this.end_transaction_at(Instant::now(), cx)
18354 })
18355 }
18356
18357 pub fn start_transaction_at(
18358 &mut self,
18359 now: Instant,
18360 window: &mut Window,
18361 cx: &mut Context<Self>,
18362 ) -> Option<TransactionId> {
18363 self.end_selection(window, cx);
18364 if let Some(tx_id) = self
18365 .buffer
18366 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18367 {
18368 self.selection_history
18369 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18370 cx.emit(EditorEvent::TransactionBegun {
18371 transaction_id: tx_id,
18372 });
18373 Some(tx_id)
18374 } else {
18375 None
18376 }
18377 }
18378
18379 pub fn end_transaction_at(
18380 &mut self,
18381 now: Instant,
18382 cx: &mut Context<Self>,
18383 ) -> Option<TransactionId> {
18384 if let Some(transaction_id) = self
18385 .buffer
18386 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18387 {
18388 if let Some((_, end_selections)) =
18389 self.selection_history.transaction_mut(transaction_id)
18390 {
18391 *end_selections = Some(self.selections.disjoint_anchors_arc());
18392 } else {
18393 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18394 }
18395
18396 cx.emit(EditorEvent::Edited { transaction_id });
18397 Some(transaction_id)
18398 } else {
18399 None
18400 }
18401 }
18402
18403 pub fn modify_transaction_selection_history(
18404 &mut self,
18405 transaction_id: TransactionId,
18406 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18407 ) -> bool {
18408 self.selection_history
18409 .transaction_mut(transaction_id)
18410 .map(modify)
18411 .is_some()
18412 }
18413
18414 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18415 if self.selection_mark_mode {
18416 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18417 s.move_with(|_, sel| {
18418 sel.collapse_to(sel.head(), SelectionGoal::None);
18419 });
18420 })
18421 }
18422 self.selection_mark_mode = true;
18423 cx.notify();
18424 }
18425
18426 pub fn swap_selection_ends(
18427 &mut self,
18428 _: &actions::SwapSelectionEnds,
18429 window: &mut Window,
18430 cx: &mut Context<Self>,
18431 ) {
18432 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18433 s.move_with(|_, sel| {
18434 if sel.start != sel.end {
18435 sel.reversed = !sel.reversed
18436 }
18437 });
18438 });
18439 self.request_autoscroll(Autoscroll::newest(), cx);
18440 cx.notify();
18441 }
18442
18443 pub fn toggle_focus(
18444 workspace: &mut Workspace,
18445 _: &actions::ToggleFocus,
18446 window: &mut Window,
18447 cx: &mut Context<Workspace>,
18448 ) {
18449 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18450 return;
18451 };
18452 workspace.activate_item(&item, true, true, window, cx);
18453 }
18454
18455 pub fn toggle_fold(
18456 &mut self,
18457 _: &actions::ToggleFold,
18458 window: &mut Window,
18459 cx: &mut Context<Self>,
18460 ) {
18461 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18462 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18463 let selection = self.selections.newest::<Point>(&display_map);
18464
18465 let range = if selection.is_empty() {
18466 let point = selection.head().to_display_point(&display_map);
18467 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18468 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18469 .to_point(&display_map);
18470 start..end
18471 } else {
18472 selection.range()
18473 };
18474 if display_map.folds_in_range(range).next().is_some() {
18475 self.unfold_lines(&Default::default(), window, cx)
18476 } else {
18477 self.fold(&Default::default(), window, cx)
18478 }
18479 } else {
18480 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18481 let buffer_ids: HashSet<_> = self
18482 .selections
18483 .disjoint_anchor_ranges()
18484 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18485 .collect();
18486
18487 let should_unfold = buffer_ids
18488 .iter()
18489 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18490
18491 for buffer_id in buffer_ids {
18492 if should_unfold {
18493 self.unfold_buffer(buffer_id, cx);
18494 } else {
18495 self.fold_buffer(buffer_id, cx);
18496 }
18497 }
18498 }
18499 }
18500
18501 pub fn toggle_fold_recursive(
18502 &mut self,
18503 _: &actions::ToggleFoldRecursive,
18504 window: &mut Window,
18505 cx: &mut Context<Self>,
18506 ) {
18507 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18508
18509 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18510 let range = if selection.is_empty() {
18511 let point = selection.head().to_display_point(&display_map);
18512 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18513 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18514 .to_point(&display_map);
18515 start..end
18516 } else {
18517 selection.range()
18518 };
18519 if display_map.folds_in_range(range).next().is_some() {
18520 self.unfold_recursive(&Default::default(), window, cx)
18521 } else {
18522 self.fold_recursive(&Default::default(), window, cx)
18523 }
18524 }
18525
18526 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18527 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18528 let mut to_fold = Vec::new();
18529 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18530 let selections = self.selections.all_adjusted(&display_map);
18531
18532 for selection in selections {
18533 let range = selection.range().sorted();
18534 let buffer_start_row = range.start.row;
18535
18536 if range.start.row != range.end.row {
18537 let mut found = false;
18538 let mut row = range.start.row;
18539 while row <= range.end.row {
18540 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18541 {
18542 found = true;
18543 row = crease.range().end.row + 1;
18544 to_fold.push(crease);
18545 } else {
18546 row += 1
18547 }
18548 }
18549 if found {
18550 continue;
18551 }
18552 }
18553
18554 for row in (0..=range.start.row).rev() {
18555 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18556 && crease.range().end.row >= buffer_start_row
18557 {
18558 to_fold.push(crease);
18559 if row <= range.start.row {
18560 break;
18561 }
18562 }
18563 }
18564 }
18565
18566 self.fold_creases(to_fold, true, window, cx);
18567 } else {
18568 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18569 let buffer_ids = self
18570 .selections
18571 .disjoint_anchor_ranges()
18572 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18573 .collect::<HashSet<_>>();
18574 for buffer_id in buffer_ids {
18575 self.fold_buffer(buffer_id, cx);
18576 }
18577 }
18578 }
18579
18580 pub fn toggle_fold_all(
18581 &mut self,
18582 _: &actions::ToggleFoldAll,
18583 window: &mut Window,
18584 cx: &mut Context<Self>,
18585 ) {
18586 if self.buffer.read(cx).is_singleton() {
18587 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18588 let has_folds = display_map
18589 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18590 .next()
18591 .is_some();
18592
18593 if has_folds {
18594 self.unfold_all(&actions::UnfoldAll, window, cx);
18595 } else {
18596 self.fold_all(&actions::FoldAll, window, cx);
18597 }
18598 } else {
18599 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18600 let should_unfold = buffer_ids
18601 .iter()
18602 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18603
18604 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18605 editor
18606 .update_in(cx, |editor, _, cx| {
18607 for buffer_id in buffer_ids {
18608 if should_unfold {
18609 editor.unfold_buffer(buffer_id, cx);
18610 } else {
18611 editor.fold_buffer(buffer_id, cx);
18612 }
18613 }
18614 })
18615 .ok();
18616 });
18617 }
18618 }
18619
18620 fn fold_at_level(
18621 &mut self,
18622 fold_at: &FoldAtLevel,
18623 window: &mut Window,
18624 cx: &mut Context<Self>,
18625 ) {
18626 if !self.buffer.read(cx).is_singleton() {
18627 return;
18628 }
18629
18630 let fold_at_level = fold_at.0;
18631 let snapshot = self.buffer.read(cx).snapshot(cx);
18632 let mut to_fold = Vec::new();
18633 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18634
18635 let row_ranges_to_keep: Vec<Range<u32>> = self
18636 .selections
18637 .all::<Point>(&self.display_snapshot(cx))
18638 .into_iter()
18639 .map(|sel| sel.start.row..sel.end.row)
18640 .collect();
18641
18642 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18643 while start_row < end_row {
18644 match self
18645 .snapshot(window, cx)
18646 .crease_for_buffer_row(MultiBufferRow(start_row))
18647 {
18648 Some(crease) => {
18649 let nested_start_row = crease.range().start.row + 1;
18650 let nested_end_row = crease.range().end.row;
18651
18652 if current_level < fold_at_level {
18653 stack.push((nested_start_row, nested_end_row, current_level + 1));
18654 } else if current_level == fold_at_level {
18655 // Fold iff there is no selection completely contained within the fold region
18656 if !row_ranges_to_keep.iter().any(|selection| {
18657 selection.end >= nested_start_row
18658 && selection.start <= nested_end_row
18659 }) {
18660 to_fold.push(crease);
18661 }
18662 }
18663
18664 start_row = nested_end_row + 1;
18665 }
18666 None => start_row += 1,
18667 }
18668 }
18669 }
18670
18671 self.fold_creases(to_fold, true, window, cx);
18672 }
18673
18674 pub fn fold_at_level_1(
18675 &mut self,
18676 _: &actions::FoldAtLevel1,
18677 window: &mut Window,
18678 cx: &mut Context<Self>,
18679 ) {
18680 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18681 }
18682
18683 pub fn fold_at_level_2(
18684 &mut self,
18685 _: &actions::FoldAtLevel2,
18686 window: &mut Window,
18687 cx: &mut Context<Self>,
18688 ) {
18689 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18690 }
18691
18692 pub fn fold_at_level_3(
18693 &mut self,
18694 _: &actions::FoldAtLevel3,
18695 window: &mut Window,
18696 cx: &mut Context<Self>,
18697 ) {
18698 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18699 }
18700
18701 pub fn fold_at_level_4(
18702 &mut self,
18703 _: &actions::FoldAtLevel4,
18704 window: &mut Window,
18705 cx: &mut Context<Self>,
18706 ) {
18707 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18708 }
18709
18710 pub fn fold_at_level_5(
18711 &mut self,
18712 _: &actions::FoldAtLevel5,
18713 window: &mut Window,
18714 cx: &mut Context<Self>,
18715 ) {
18716 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18717 }
18718
18719 pub fn fold_at_level_6(
18720 &mut self,
18721 _: &actions::FoldAtLevel6,
18722 window: &mut Window,
18723 cx: &mut Context<Self>,
18724 ) {
18725 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18726 }
18727
18728 pub fn fold_at_level_7(
18729 &mut self,
18730 _: &actions::FoldAtLevel7,
18731 window: &mut Window,
18732 cx: &mut Context<Self>,
18733 ) {
18734 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18735 }
18736
18737 pub fn fold_at_level_8(
18738 &mut self,
18739 _: &actions::FoldAtLevel8,
18740 window: &mut Window,
18741 cx: &mut Context<Self>,
18742 ) {
18743 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18744 }
18745
18746 pub fn fold_at_level_9(
18747 &mut self,
18748 _: &actions::FoldAtLevel9,
18749 window: &mut Window,
18750 cx: &mut Context<Self>,
18751 ) {
18752 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18753 }
18754
18755 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18756 if self.buffer.read(cx).is_singleton() {
18757 let mut fold_ranges = Vec::new();
18758 let snapshot = self.buffer.read(cx).snapshot(cx);
18759
18760 for row in 0..snapshot.max_row().0 {
18761 if let Some(foldable_range) = self
18762 .snapshot(window, cx)
18763 .crease_for_buffer_row(MultiBufferRow(row))
18764 {
18765 fold_ranges.push(foldable_range);
18766 }
18767 }
18768
18769 self.fold_creases(fold_ranges, true, window, cx);
18770 } else {
18771 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18772 editor
18773 .update_in(cx, |editor, _, cx| {
18774 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18775 editor.fold_buffer(buffer_id, cx);
18776 }
18777 })
18778 .ok();
18779 });
18780 }
18781 }
18782
18783 pub fn fold_function_bodies(
18784 &mut self,
18785 _: &actions::FoldFunctionBodies,
18786 window: &mut Window,
18787 cx: &mut Context<Self>,
18788 ) {
18789 let snapshot = self.buffer.read(cx).snapshot(cx);
18790
18791 let ranges = snapshot
18792 .text_object_ranges(
18793 MultiBufferOffset(0)..snapshot.len(),
18794 TreeSitterOptions::default(),
18795 )
18796 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18797 .collect::<Vec<_>>();
18798
18799 let creases = ranges
18800 .into_iter()
18801 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18802 .collect();
18803
18804 self.fold_creases(creases, true, window, cx);
18805 }
18806
18807 pub fn fold_recursive(
18808 &mut self,
18809 _: &actions::FoldRecursive,
18810 window: &mut Window,
18811 cx: &mut Context<Self>,
18812 ) {
18813 let mut to_fold = Vec::new();
18814 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18815 let selections = self.selections.all_adjusted(&display_map);
18816
18817 for selection in selections {
18818 let range = selection.range().sorted();
18819 let buffer_start_row = range.start.row;
18820
18821 if range.start.row != range.end.row {
18822 let mut found = false;
18823 for row in range.start.row..=range.end.row {
18824 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18825 found = true;
18826 to_fold.push(crease);
18827 }
18828 }
18829 if found {
18830 continue;
18831 }
18832 }
18833
18834 for row in (0..=range.start.row).rev() {
18835 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18836 if crease.range().end.row >= buffer_start_row {
18837 to_fold.push(crease);
18838 } else {
18839 break;
18840 }
18841 }
18842 }
18843 }
18844
18845 self.fold_creases(to_fold, true, window, cx);
18846 }
18847
18848 pub fn fold_at(
18849 &mut self,
18850 buffer_row: MultiBufferRow,
18851 window: &mut Window,
18852 cx: &mut Context<Self>,
18853 ) {
18854 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18855
18856 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18857 let autoscroll = self
18858 .selections
18859 .all::<Point>(&display_map)
18860 .iter()
18861 .any(|selection| crease.range().overlaps(&selection.range()));
18862
18863 self.fold_creases(vec![crease], autoscroll, window, cx);
18864 }
18865 }
18866
18867 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18868 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18869 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18870 let buffer = display_map.buffer_snapshot();
18871 let selections = self.selections.all::<Point>(&display_map);
18872 let ranges = selections
18873 .iter()
18874 .map(|s| {
18875 let range = s.display_range(&display_map).sorted();
18876 let mut start = range.start.to_point(&display_map);
18877 let mut end = range.end.to_point(&display_map);
18878 start.column = 0;
18879 end.column = buffer.line_len(MultiBufferRow(end.row));
18880 start..end
18881 })
18882 .collect::<Vec<_>>();
18883
18884 self.unfold_ranges(&ranges, true, true, cx);
18885 } else {
18886 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18887 let buffer_ids = self
18888 .selections
18889 .disjoint_anchor_ranges()
18890 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18891 .collect::<HashSet<_>>();
18892 for buffer_id in buffer_ids {
18893 self.unfold_buffer(buffer_id, cx);
18894 }
18895 }
18896 }
18897
18898 pub fn unfold_recursive(
18899 &mut self,
18900 _: &UnfoldRecursive,
18901 _window: &mut Window,
18902 cx: &mut Context<Self>,
18903 ) {
18904 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18905 let selections = self.selections.all::<Point>(&display_map);
18906 let ranges = selections
18907 .iter()
18908 .map(|s| {
18909 let mut range = s.display_range(&display_map).sorted();
18910 *range.start.column_mut() = 0;
18911 *range.end.column_mut() = display_map.line_len(range.end.row());
18912 let start = range.start.to_point(&display_map);
18913 let end = range.end.to_point(&display_map);
18914 start..end
18915 })
18916 .collect::<Vec<_>>();
18917
18918 self.unfold_ranges(&ranges, true, true, cx);
18919 }
18920
18921 pub fn unfold_at(
18922 &mut self,
18923 buffer_row: MultiBufferRow,
18924 _window: &mut Window,
18925 cx: &mut Context<Self>,
18926 ) {
18927 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18928
18929 let intersection_range = Point::new(buffer_row.0, 0)
18930 ..Point::new(
18931 buffer_row.0,
18932 display_map.buffer_snapshot().line_len(buffer_row),
18933 );
18934
18935 let autoscroll = self
18936 .selections
18937 .all::<Point>(&display_map)
18938 .iter()
18939 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18940
18941 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18942 }
18943
18944 pub fn unfold_all(
18945 &mut self,
18946 _: &actions::UnfoldAll,
18947 _window: &mut Window,
18948 cx: &mut Context<Self>,
18949 ) {
18950 if self.buffer.read(cx).is_singleton() {
18951 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18952 self.unfold_ranges(
18953 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
18954 true,
18955 true,
18956 cx,
18957 );
18958 } else {
18959 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18960 editor
18961 .update(cx, |editor, cx| {
18962 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18963 editor.unfold_buffer(buffer_id, cx);
18964 }
18965 })
18966 .ok();
18967 });
18968 }
18969 }
18970
18971 pub fn fold_selected_ranges(
18972 &mut self,
18973 _: &FoldSelectedRanges,
18974 window: &mut Window,
18975 cx: &mut Context<Self>,
18976 ) {
18977 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18978 let selections = self.selections.all_adjusted(&display_map);
18979 let ranges = selections
18980 .into_iter()
18981 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18982 .collect::<Vec<_>>();
18983 self.fold_creases(ranges, true, window, cx);
18984 }
18985
18986 pub fn fold_ranges<T: ToOffset + Clone>(
18987 &mut self,
18988 ranges: Vec<Range<T>>,
18989 auto_scroll: bool,
18990 window: &mut Window,
18991 cx: &mut Context<Self>,
18992 ) {
18993 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18994 let ranges = ranges
18995 .into_iter()
18996 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18997 .collect::<Vec<_>>();
18998 self.fold_creases(ranges, auto_scroll, window, cx);
18999 }
19000
19001 pub fn fold_creases<T: ToOffset + Clone>(
19002 &mut self,
19003 creases: Vec<Crease<T>>,
19004 auto_scroll: bool,
19005 _window: &mut Window,
19006 cx: &mut Context<Self>,
19007 ) {
19008 if creases.is_empty() {
19009 return;
19010 }
19011
19012 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19013
19014 if auto_scroll {
19015 self.request_autoscroll(Autoscroll::fit(), cx);
19016 }
19017
19018 cx.notify();
19019
19020 self.scrollbar_marker_state.dirty = true;
19021 self.folds_did_change(cx);
19022 }
19023
19024 /// Removes any folds whose ranges intersect any of the given ranges.
19025 pub fn unfold_ranges<T: ToOffset + Clone>(
19026 &mut self,
19027 ranges: &[Range<T>],
19028 inclusive: bool,
19029 auto_scroll: bool,
19030 cx: &mut Context<Self>,
19031 ) {
19032 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19033 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19034 });
19035 self.folds_did_change(cx);
19036 }
19037
19038 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19039 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19040 return;
19041 }
19042
19043 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19044 self.display_map.update(cx, |display_map, cx| {
19045 display_map.fold_buffers([buffer_id], cx)
19046 });
19047
19048 let snapshot = self.display_snapshot(cx);
19049 self.selections.change_with(&snapshot, |selections| {
19050 selections.remove_selections_from_buffer(buffer_id);
19051 });
19052
19053 cx.emit(EditorEvent::BufferFoldToggled {
19054 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19055 folded: true,
19056 });
19057 cx.notify();
19058 }
19059
19060 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19061 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19062 return;
19063 }
19064 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19065 self.display_map.update(cx, |display_map, cx| {
19066 display_map.unfold_buffers([buffer_id], cx);
19067 });
19068 cx.emit(EditorEvent::BufferFoldToggled {
19069 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19070 folded: false,
19071 });
19072 cx.notify();
19073 }
19074
19075 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19076 self.display_map.read(cx).is_buffer_folded(buffer)
19077 }
19078
19079 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19080 self.display_map.read(cx).folded_buffers()
19081 }
19082
19083 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19084 self.display_map.update(cx, |display_map, cx| {
19085 display_map.disable_header_for_buffer(buffer_id, cx);
19086 });
19087 cx.notify();
19088 }
19089
19090 /// Removes any folds with the given ranges.
19091 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19092 &mut self,
19093 ranges: &[Range<T>],
19094 type_id: TypeId,
19095 auto_scroll: bool,
19096 cx: &mut Context<Self>,
19097 ) {
19098 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19099 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19100 });
19101 self.folds_did_change(cx);
19102 }
19103
19104 fn remove_folds_with<T: ToOffset + Clone>(
19105 &mut self,
19106 ranges: &[Range<T>],
19107 auto_scroll: bool,
19108 cx: &mut Context<Self>,
19109 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19110 ) {
19111 if ranges.is_empty() {
19112 return;
19113 }
19114
19115 let mut buffers_affected = HashSet::default();
19116 let multi_buffer = self.buffer().read(cx);
19117 for range in ranges {
19118 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19119 buffers_affected.insert(buffer.read(cx).remote_id());
19120 };
19121 }
19122
19123 self.display_map.update(cx, update);
19124
19125 if auto_scroll {
19126 self.request_autoscroll(Autoscroll::fit(), cx);
19127 }
19128
19129 cx.notify();
19130 self.scrollbar_marker_state.dirty = true;
19131 self.active_indent_guides_state.dirty = true;
19132 }
19133
19134 pub fn update_renderer_widths(
19135 &mut self,
19136 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19137 cx: &mut Context<Self>,
19138 ) -> bool {
19139 self.display_map
19140 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19141 }
19142
19143 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19144 self.display_map.read(cx).fold_placeholder.clone()
19145 }
19146
19147 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19148 self.buffer.update(cx, |buffer, cx| {
19149 buffer.set_all_diff_hunks_expanded(cx);
19150 });
19151 }
19152
19153 pub fn expand_all_diff_hunks(
19154 &mut self,
19155 _: &ExpandAllDiffHunks,
19156 _window: &mut Window,
19157 cx: &mut Context<Self>,
19158 ) {
19159 self.buffer.update(cx, |buffer, cx| {
19160 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19161 });
19162 }
19163
19164 pub fn collapse_all_diff_hunks(
19165 &mut self,
19166 _: &CollapseAllDiffHunks,
19167 _window: &mut Window,
19168 cx: &mut Context<Self>,
19169 ) {
19170 self.buffer.update(cx, |buffer, cx| {
19171 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19172 });
19173 }
19174
19175 pub fn toggle_selected_diff_hunks(
19176 &mut self,
19177 _: &ToggleSelectedDiffHunks,
19178 _window: &mut Window,
19179 cx: &mut Context<Self>,
19180 ) {
19181 let ranges: Vec<_> = self
19182 .selections
19183 .disjoint_anchors()
19184 .iter()
19185 .map(|s| s.range())
19186 .collect();
19187 self.toggle_diff_hunks_in_ranges(ranges, cx);
19188 }
19189
19190 pub fn diff_hunks_in_ranges<'a>(
19191 &'a self,
19192 ranges: &'a [Range<Anchor>],
19193 buffer: &'a MultiBufferSnapshot,
19194 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19195 ranges.iter().flat_map(move |range| {
19196 let end_excerpt_id = range.end.excerpt_id;
19197 let range = range.to_point(buffer);
19198 let mut peek_end = range.end;
19199 if range.end.row < buffer.max_row().0 {
19200 peek_end = Point::new(range.end.row + 1, 0);
19201 }
19202 buffer
19203 .diff_hunks_in_range(range.start..peek_end)
19204 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19205 })
19206 }
19207
19208 pub fn has_stageable_diff_hunks_in_ranges(
19209 &self,
19210 ranges: &[Range<Anchor>],
19211 snapshot: &MultiBufferSnapshot,
19212 ) -> bool {
19213 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19214 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19215 }
19216
19217 pub fn toggle_staged_selected_diff_hunks(
19218 &mut self,
19219 _: &::git::ToggleStaged,
19220 _: &mut Window,
19221 cx: &mut Context<Self>,
19222 ) {
19223 let snapshot = self.buffer.read(cx).snapshot(cx);
19224 let ranges: Vec<_> = self
19225 .selections
19226 .disjoint_anchors()
19227 .iter()
19228 .map(|s| s.range())
19229 .collect();
19230 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19231 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19232 }
19233
19234 pub fn set_render_diff_hunk_controls(
19235 &mut self,
19236 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19237 cx: &mut Context<Self>,
19238 ) {
19239 self.render_diff_hunk_controls = render_diff_hunk_controls;
19240 cx.notify();
19241 }
19242
19243 pub fn stage_and_next(
19244 &mut self,
19245 _: &::git::StageAndNext,
19246 window: &mut Window,
19247 cx: &mut Context<Self>,
19248 ) {
19249 self.do_stage_or_unstage_and_next(true, window, cx);
19250 }
19251
19252 pub fn unstage_and_next(
19253 &mut self,
19254 _: &::git::UnstageAndNext,
19255 window: &mut Window,
19256 cx: &mut Context<Self>,
19257 ) {
19258 self.do_stage_or_unstage_and_next(false, window, cx);
19259 }
19260
19261 pub fn stage_or_unstage_diff_hunks(
19262 &mut self,
19263 stage: bool,
19264 ranges: Vec<Range<Anchor>>,
19265 cx: &mut Context<Self>,
19266 ) {
19267 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19268 cx.spawn(async move |this, cx| {
19269 task.await?;
19270 this.update(cx, |this, cx| {
19271 let snapshot = this.buffer.read(cx).snapshot(cx);
19272 let chunk_by = this
19273 .diff_hunks_in_ranges(&ranges, &snapshot)
19274 .chunk_by(|hunk| hunk.buffer_id);
19275 for (buffer_id, hunks) in &chunk_by {
19276 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19277 }
19278 })
19279 })
19280 .detach_and_log_err(cx);
19281 }
19282
19283 fn save_buffers_for_ranges_if_needed(
19284 &mut self,
19285 ranges: &[Range<Anchor>],
19286 cx: &mut Context<Editor>,
19287 ) -> Task<Result<()>> {
19288 let multibuffer = self.buffer.read(cx);
19289 let snapshot = multibuffer.read(cx);
19290 let buffer_ids: HashSet<_> = ranges
19291 .iter()
19292 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19293 .collect();
19294 drop(snapshot);
19295
19296 let mut buffers = HashSet::default();
19297 for buffer_id in buffer_ids {
19298 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19299 let buffer = buffer_entity.read(cx);
19300 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19301 {
19302 buffers.insert(buffer_entity);
19303 }
19304 }
19305 }
19306
19307 if let Some(project) = &self.project {
19308 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19309 } else {
19310 Task::ready(Ok(()))
19311 }
19312 }
19313
19314 fn do_stage_or_unstage_and_next(
19315 &mut self,
19316 stage: bool,
19317 window: &mut Window,
19318 cx: &mut Context<Self>,
19319 ) {
19320 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19321
19322 if ranges.iter().any(|range| range.start != range.end) {
19323 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19324 return;
19325 }
19326
19327 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19328 let snapshot = self.snapshot(window, cx);
19329 let position = self
19330 .selections
19331 .newest::<Point>(&snapshot.display_snapshot)
19332 .head();
19333 let mut row = snapshot
19334 .buffer_snapshot()
19335 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19336 .find(|hunk| hunk.row_range.start.0 > position.row)
19337 .map(|hunk| hunk.row_range.start);
19338
19339 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19340 // Outside of the project diff editor, wrap around to the beginning.
19341 if !all_diff_hunks_expanded {
19342 row = row.or_else(|| {
19343 snapshot
19344 .buffer_snapshot()
19345 .diff_hunks_in_range(Point::zero()..position)
19346 .find(|hunk| hunk.row_range.end.0 < position.row)
19347 .map(|hunk| hunk.row_range.start)
19348 });
19349 }
19350
19351 if let Some(row) = row {
19352 let destination = Point::new(row.0, 0);
19353 let autoscroll = Autoscroll::center();
19354
19355 self.unfold_ranges(&[destination..destination], false, false, cx);
19356 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19357 s.select_ranges([destination..destination]);
19358 });
19359 }
19360 }
19361
19362 fn do_stage_or_unstage(
19363 &self,
19364 stage: bool,
19365 buffer_id: BufferId,
19366 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19367 cx: &mut App,
19368 ) -> Option<()> {
19369 let project = self.project()?;
19370 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19371 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19372 let buffer_snapshot = buffer.read(cx).snapshot();
19373 let file_exists = buffer_snapshot
19374 .file()
19375 .is_some_and(|file| file.disk_state().exists());
19376 diff.update(cx, |diff, cx| {
19377 diff.stage_or_unstage_hunks(
19378 stage,
19379 &hunks
19380 .map(|hunk| buffer_diff::DiffHunk {
19381 buffer_range: hunk.buffer_range,
19382 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19383 ..hunk.diff_base_byte_range.end.0,
19384 secondary_status: hunk.secondary_status,
19385 range: Point::zero()..Point::zero(), // unused
19386 })
19387 .collect::<Vec<_>>(),
19388 &buffer_snapshot,
19389 file_exists,
19390 cx,
19391 )
19392 });
19393 None
19394 }
19395
19396 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19397 let ranges: Vec<_> = self
19398 .selections
19399 .disjoint_anchors()
19400 .iter()
19401 .map(|s| s.range())
19402 .collect();
19403 self.buffer
19404 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19405 }
19406
19407 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19408 self.buffer.update(cx, |buffer, cx| {
19409 let ranges = vec![Anchor::min()..Anchor::max()];
19410 if !buffer.all_diff_hunks_expanded()
19411 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19412 {
19413 buffer.collapse_diff_hunks(ranges, cx);
19414 true
19415 } else {
19416 false
19417 }
19418 })
19419 }
19420
19421 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19422 if self.buffer.read(cx).all_diff_hunks_expanded() {
19423 return true;
19424 }
19425 let ranges = vec![Anchor::min()..Anchor::max()];
19426 self.buffer
19427 .read(cx)
19428 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19429 }
19430
19431 fn toggle_diff_hunks_in_ranges(
19432 &mut self,
19433 ranges: Vec<Range<Anchor>>,
19434 cx: &mut Context<Editor>,
19435 ) {
19436 self.buffer.update(cx, |buffer, cx| {
19437 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19438 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19439 })
19440 }
19441
19442 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19443 self.buffer.update(cx, |buffer, cx| {
19444 let snapshot = buffer.snapshot(cx);
19445 let excerpt_id = range.end.excerpt_id;
19446 let point_range = range.to_point(&snapshot);
19447 let expand = !buffer.single_hunk_is_expanded(range, cx);
19448 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19449 })
19450 }
19451
19452 pub(crate) fn apply_all_diff_hunks(
19453 &mut self,
19454 _: &ApplyAllDiffHunks,
19455 window: &mut Window,
19456 cx: &mut Context<Self>,
19457 ) {
19458 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19459
19460 let buffers = self.buffer.read(cx).all_buffers();
19461 for branch_buffer in buffers {
19462 branch_buffer.update(cx, |branch_buffer, cx| {
19463 branch_buffer.merge_into_base(Vec::new(), cx);
19464 });
19465 }
19466
19467 if let Some(project) = self.project.clone() {
19468 self.save(
19469 SaveOptions {
19470 format: true,
19471 autosave: false,
19472 },
19473 project,
19474 window,
19475 cx,
19476 )
19477 .detach_and_log_err(cx);
19478 }
19479 }
19480
19481 pub(crate) fn apply_selected_diff_hunks(
19482 &mut self,
19483 _: &ApplyDiffHunk,
19484 window: &mut Window,
19485 cx: &mut Context<Self>,
19486 ) {
19487 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19488 let snapshot = self.snapshot(window, cx);
19489 let hunks = snapshot.hunks_for_ranges(
19490 self.selections
19491 .all(&snapshot.display_snapshot)
19492 .into_iter()
19493 .map(|selection| selection.range()),
19494 );
19495 let mut ranges_by_buffer = HashMap::default();
19496 self.transact(window, cx, |editor, _window, cx| {
19497 for hunk in hunks {
19498 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19499 ranges_by_buffer
19500 .entry(buffer.clone())
19501 .or_insert_with(Vec::new)
19502 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19503 }
19504 }
19505
19506 for (buffer, ranges) in ranges_by_buffer {
19507 buffer.update(cx, |buffer, cx| {
19508 buffer.merge_into_base(ranges, cx);
19509 });
19510 }
19511 });
19512
19513 if let Some(project) = self.project.clone() {
19514 self.save(
19515 SaveOptions {
19516 format: true,
19517 autosave: false,
19518 },
19519 project,
19520 window,
19521 cx,
19522 )
19523 .detach_and_log_err(cx);
19524 }
19525 }
19526
19527 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19528 if hovered != self.gutter_hovered {
19529 self.gutter_hovered = hovered;
19530 cx.notify();
19531 }
19532 }
19533
19534 pub fn insert_blocks(
19535 &mut self,
19536 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19537 autoscroll: Option<Autoscroll>,
19538 cx: &mut Context<Self>,
19539 ) -> Vec<CustomBlockId> {
19540 let blocks = self
19541 .display_map
19542 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19543 if let Some(autoscroll) = autoscroll {
19544 self.request_autoscroll(autoscroll, cx);
19545 }
19546 cx.notify();
19547 blocks
19548 }
19549
19550 pub fn resize_blocks(
19551 &mut self,
19552 heights: HashMap<CustomBlockId, u32>,
19553 autoscroll: Option<Autoscroll>,
19554 cx: &mut Context<Self>,
19555 ) {
19556 self.display_map
19557 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19558 if let Some(autoscroll) = autoscroll {
19559 self.request_autoscroll(autoscroll, cx);
19560 }
19561 cx.notify();
19562 }
19563
19564 pub fn replace_blocks(
19565 &mut self,
19566 renderers: HashMap<CustomBlockId, RenderBlock>,
19567 autoscroll: Option<Autoscroll>,
19568 cx: &mut Context<Self>,
19569 ) {
19570 self.display_map
19571 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19572 if let Some(autoscroll) = autoscroll {
19573 self.request_autoscroll(autoscroll, cx);
19574 }
19575 cx.notify();
19576 }
19577
19578 pub fn remove_blocks(
19579 &mut self,
19580 block_ids: HashSet<CustomBlockId>,
19581 autoscroll: Option<Autoscroll>,
19582 cx: &mut Context<Self>,
19583 ) {
19584 self.display_map.update(cx, |display_map, cx| {
19585 display_map.remove_blocks(block_ids, cx)
19586 });
19587 if let Some(autoscroll) = autoscroll {
19588 self.request_autoscroll(autoscroll, cx);
19589 }
19590 cx.notify();
19591 }
19592
19593 pub fn row_for_block(
19594 &self,
19595 block_id: CustomBlockId,
19596 cx: &mut Context<Self>,
19597 ) -> Option<DisplayRow> {
19598 self.display_map
19599 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19600 }
19601
19602 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19603 self.focused_block = Some(focused_block);
19604 }
19605
19606 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19607 self.focused_block.take()
19608 }
19609
19610 pub fn insert_creases(
19611 &mut self,
19612 creases: impl IntoIterator<Item = Crease<Anchor>>,
19613 cx: &mut Context<Self>,
19614 ) -> Vec<CreaseId> {
19615 self.display_map
19616 .update(cx, |map, cx| map.insert_creases(creases, cx))
19617 }
19618
19619 pub fn remove_creases(
19620 &mut self,
19621 ids: impl IntoIterator<Item = CreaseId>,
19622 cx: &mut Context<Self>,
19623 ) -> Vec<(CreaseId, Range<Anchor>)> {
19624 self.display_map
19625 .update(cx, |map, cx| map.remove_creases(ids, cx))
19626 }
19627
19628 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19629 self.display_map
19630 .update(cx, |map, cx| map.snapshot(cx))
19631 .longest_row()
19632 }
19633
19634 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19635 self.display_map
19636 .update(cx, |map, cx| map.snapshot(cx))
19637 .max_point()
19638 }
19639
19640 pub fn text(&self, cx: &App) -> String {
19641 self.buffer.read(cx).read(cx).text()
19642 }
19643
19644 pub fn is_empty(&self, cx: &App) -> bool {
19645 self.buffer.read(cx).read(cx).is_empty()
19646 }
19647
19648 pub fn text_option(&self, cx: &App) -> Option<String> {
19649 let text = self.text(cx);
19650 let text = text.trim();
19651
19652 if text.is_empty() {
19653 return None;
19654 }
19655
19656 Some(text.to_string())
19657 }
19658
19659 pub fn set_text(
19660 &mut self,
19661 text: impl Into<Arc<str>>,
19662 window: &mut Window,
19663 cx: &mut Context<Self>,
19664 ) {
19665 self.transact(window, cx, |this, _, cx| {
19666 this.buffer
19667 .read(cx)
19668 .as_singleton()
19669 .expect("you can only call set_text on editors for singleton buffers")
19670 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19671 });
19672 }
19673
19674 pub fn display_text(&self, cx: &mut App) -> String {
19675 self.display_map
19676 .update(cx, |map, cx| map.snapshot(cx))
19677 .text()
19678 }
19679
19680 fn create_minimap(
19681 &self,
19682 minimap_settings: MinimapSettings,
19683 window: &mut Window,
19684 cx: &mut Context<Self>,
19685 ) -> Option<Entity<Self>> {
19686 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19687 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19688 }
19689
19690 fn initialize_new_minimap(
19691 &self,
19692 minimap_settings: MinimapSettings,
19693 window: &mut Window,
19694 cx: &mut Context<Self>,
19695 ) -> Entity<Self> {
19696 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19697
19698 let mut minimap = Editor::new_internal(
19699 EditorMode::Minimap {
19700 parent: cx.weak_entity(),
19701 },
19702 self.buffer.clone(),
19703 None,
19704 Some(self.display_map.clone()),
19705 window,
19706 cx,
19707 );
19708 minimap.scroll_manager.clone_state(&self.scroll_manager);
19709 minimap.set_text_style_refinement(TextStyleRefinement {
19710 font_size: Some(MINIMAP_FONT_SIZE),
19711 font_weight: Some(MINIMAP_FONT_WEIGHT),
19712 ..Default::default()
19713 });
19714 minimap.update_minimap_configuration(minimap_settings, cx);
19715 cx.new(|_| minimap)
19716 }
19717
19718 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19719 let current_line_highlight = minimap_settings
19720 .current_line_highlight
19721 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19722 self.set_current_line_highlight(Some(current_line_highlight));
19723 }
19724
19725 pub fn minimap(&self) -> Option<&Entity<Self>> {
19726 self.minimap
19727 .as_ref()
19728 .filter(|_| self.minimap_visibility.visible())
19729 }
19730
19731 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19732 let mut wrap_guides = smallvec![];
19733
19734 if self.show_wrap_guides == Some(false) {
19735 return wrap_guides;
19736 }
19737
19738 let settings = self.buffer.read(cx).language_settings(cx);
19739 if settings.show_wrap_guides {
19740 match self.soft_wrap_mode(cx) {
19741 SoftWrap::Column(soft_wrap) => {
19742 wrap_guides.push((soft_wrap as usize, true));
19743 }
19744 SoftWrap::Bounded(soft_wrap) => {
19745 wrap_guides.push((soft_wrap as usize, true));
19746 }
19747 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19748 }
19749 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19750 }
19751
19752 wrap_guides
19753 }
19754
19755 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19756 let settings = self.buffer.read(cx).language_settings(cx);
19757 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19758 match mode {
19759 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19760 SoftWrap::None
19761 }
19762 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19763 language_settings::SoftWrap::PreferredLineLength => {
19764 SoftWrap::Column(settings.preferred_line_length)
19765 }
19766 language_settings::SoftWrap::Bounded => {
19767 SoftWrap::Bounded(settings.preferred_line_length)
19768 }
19769 }
19770 }
19771
19772 pub fn set_soft_wrap_mode(
19773 &mut self,
19774 mode: language_settings::SoftWrap,
19775
19776 cx: &mut Context<Self>,
19777 ) {
19778 self.soft_wrap_mode_override = Some(mode);
19779 cx.notify();
19780 }
19781
19782 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19783 self.hard_wrap = hard_wrap;
19784 cx.notify();
19785 }
19786
19787 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19788 self.text_style_refinement = Some(style);
19789 }
19790
19791 /// called by the Element so we know what style we were most recently rendered with.
19792 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19793 // We intentionally do not inform the display map about the minimap style
19794 // so that wrapping is not recalculated and stays consistent for the editor
19795 // and its linked minimap.
19796 if !self.mode.is_minimap() {
19797 let font = style.text.font();
19798 let font_size = style.text.font_size.to_pixels(window.rem_size());
19799 let display_map = self
19800 .placeholder_display_map
19801 .as_ref()
19802 .filter(|_| self.is_empty(cx))
19803 .unwrap_or(&self.display_map);
19804
19805 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19806 }
19807 self.style = Some(style);
19808 }
19809
19810 pub fn style(&self) -> Option<&EditorStyle> {
19811 self.style.as_ref()
19812 }
19813
19814 // Called by the element. This method is not designed to be called outside of the editor
19815 // element's layout code because it does not notify when rewrapping is computed synchronously.
19816 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19817 if self.is_empty(cx) {
19818 self.placeholder_display_map
19819 .as_ref()
19820 .map_or(false, |display_map| {
19821 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19822 })
19823 } else {
19824 self.display_map
19825 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19826 }
19827 }
19828
19829 pub fn set_soft_wrap(&mut self) {
19830 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19831 }
19832
19833 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19834 if self.soft_wrap_mode_override.is_some() {
19835 self.soft_wrap_mode_override.take();
19836 } else {
19837 let soft_wrap = match self.soft_wrap_mode(cx) {
19838 SoftWrap::GitDiff => return,
19839 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19840 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19841 language_settings::SoftWrap::None
19842 }
19843 };
19844 self.soft_wrap_mode_override = Some(soft_wrap);
19845 }
19846 cx.notify();
19847 }
19848
19849 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19850 let Some(workspace) = self.workspace() else {
19851 return;
19852 };
19853 let fs = workspace.read(cx).app_state().fs.clone();
19854 let current_show = TabBarSettings::get_global(cx).show;
19855 update_settings_file(fs, cx, move |setting, _| {
19856 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19857 });
19858 }
19859
19860 pub fn toggle_indent_guides(
19861 &mut self,
19862 _: &ToggleIndentGuides,
19863 _: &mut Window,
19864 cx: &mut Context<Self>,
19865 ) {
19866 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19867 self.buffer
19868 .read(cx)
19869 .language_settings(cx)
19870 .indent_guides
19871 .enabled
19872 });
19873 self.show_indent_guides = Some(!currently_enabled);
19874 cx.notify();
19875 }
19876
19877 fn should_show_indent_guides(&self) -> Option<bool> {
19878 self.show_indent_guides
19879 }
19880
19881 pub fn toggle_line_numbers(
19882 &mut self,
19883 _: &ToggleLineNumbers,
19884 _: &mut Window,
19885 cx: &mut Context<Self>,
19886 ) {
19887 let mut editor_settings = EditorSettings::get_global(cx).clone();
19888 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19889 EditorSettings::override_global(editor_settings, cx);
19890 }
19891
19892 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19893 if let Some(show_line_numbers) = self.show_line_numbers {
19894 return show_line_numbers;
19895 }
19896 EditorSettings::get_global(cx).gutter.line_numbers
19897 }
19898
19899 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19900 match (
19901 self.use_relative_line_numbers,
19902 EditorSettings::get_global(cx).relative_line_numbers,
19903 ) {
19904 (None, setting) => setting,
19905 (Some(false), _) => RelativeLineNumbers::Disabled,
19906 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19907 (Some(true), _) => RelativeLineNumbers::Enabled,
19908 }
19909 }
19910
19911 pub fn toggle_relative_line_numbers(
19912 &mut self,
19913 _: &ToggleRelativeLineNumbers,
19914 _: &mut Window,
19915 cx: &mut Context<Self>,
19916 ) {
19917 let is_relative = self.relative_line_numbers(cx);
19918 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19919 }
19920
19921 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19922 self.use_relative_line_numbers = is_relative;
19923 cx.notify();
19924 }
19925
19926 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19927 self.show_gutter = show_gutter;
19928 cx.notify();
19929 }
19930
19931 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19932 self.show_scrollbars = ScrollbarAxes {
19933 horizontal: show,
19934 vertical: show,
19935 };
19936 cx.notify();
19937 }
19938
19939 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19940 self.show_scrollbars.vertical = show;
19941 cx.notify();
19942 }
19943
19944 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19945 self.show_scrollbars.horizontal = show;
19946 cx.notify();
19947 }
19948
19949 pub fn set_minimap_visibility(
19950 &mut self,
19951 minimap_visibility: MinimapVisibility,
19952 window: &mut Window,
19953 cx: &mut Context<Self>,
19954 ) {
19955 if self.minimap_visibility != minimap_visibility {
19956 if minimap_visibility.visible() && self.minimap.is_none() {
19957 let minimap_settings = EditorSettings::get_global(cx).minimap;
19958 self.minimap =
19959 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19960 }
19961 self.minimap_visibility = minimap_visibility;
19962 cx.notify();
19963 }
19964 }
19965
19966 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19967 self.set_show_scrollbars(false, cx);
19968 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19969 }
19970
19971 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19972 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19973 }
19974
19975 /// Normally the text in full mode and auto height editors is padded on the
19976 /// left side by roughly half a character width for improved hit testing.
19977 ///
19978 /// Use this method to disable this for cases where this is not wanted (e.g.
19979 /// if you want to align the editor text with some other text above or below)
19980 /// or if you want to add this padding to single-line editors.
19981 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19982 self.offset_content = offset_content;
19983 cx.notify();
19984 }
19985
19986 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19987 self.show_line_numbers = Some(show_line_numbers);
19988 cx.notify();
19989 }
19990
19991 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19992 self.disable_expand_excerpt_buttons = true;
19993 cx.notify();
19994 }
19995
19996 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19997 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19998 cx.notify();
19999 }
20000
20001 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20002 self.show_code_actions = Some(show_code_actions);
20003 cx.notify();
20004 }
20005
20006 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20007 self.show_runnables = Some(show_runnables);
20008 cx.notify();
20009 }
20010
20011 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20012 self.show_breakpoints = Some(show_breakpoints);
20013 cx.notify();
20014 }
20015
20016 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20017 if self.display_map.read(cx).masked != masked {
20018 self.display_map.update(cx, |map, _| map.masked = masked);
20019 }
20020 cx.notify()
20021 }
20022
20023 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20024 self.show_wrap_guides = Some(show_wrap_guides);
20025 cx.notify();
20026 }
20027
20028 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20029 self.show_indent_guides = Some(show_indent_guides);
20030 cx.notify();
20031 }
20032
20033 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20034 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20035 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20036 && let Some(dir) = file.abs_path(cx).parent()
20037 {
20038 return Some(dir.to_owned());
20039 }
20040 }
20041
20042 None
20043 }
20044
20045 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20046 self.active_excerpt(cx)?
20047 .1
20048 .read(cx)
20049 .file()
20050 .and_then(|f| f.as_local())
20051 }
20052
20053 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20054 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20055 let buffer = buffer.read(cx);
20056 if let Some(project_path) = buffer.project_path(cx) {
20057 let project = self.project()?.read(cx);
20058 project.absolute_path(&project_path, cx)
20059 } else {
20060 buffer
20061 .file()
20062 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20063 }
20064 })
20065 }
20066
20067 pub fn reveal_in_finder(
20068 &mut self,
20069 _: &RevealInFileManager,
20070 _window: &mut Window,
20071 cx: &mut Context<Self>,
20072 ) {
20073 if let Some(target) = self.target_file(cx) {
20074 cx.reveal_path(&target.abs_path(cx));
20075 }
20076 }
20077
20078 pub fn copy_path(
20079 &mut self,
20080 _: &zed_actions::workspace::CopyPath,
20081 _window: &mut Window,
20082 cx: &mut Context<Self>,
20083 ) {
20084 if let Some(path) = self.target_file_abs_path(cx)
20085 && let Some(path) = path.to_str()
20086 {
20087 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20088 } else {
20089 cx.propagate();
20090 }
20091 }
20092
20093 pub fn copy_relative_path(
20094 &mut self,
20095 _: &zed_actions::workspace::CopyRelativePath,
20096 _window: &mut Window,
20097 cx: &mut Context<Self>,
20098 ) {
20099 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20100 let project = self.project()?.read(cx);
20101 let path = buffer.read(cx).file()?.path();
20102 let path = path.display(project.path_style(cx));
20103 Some(path)
20104 }) {
20105 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20106 } else {
20107 cx.propagate();
20108 }
20109 }
20110
20111 /// Returns the project path for the editor's buffer, if any buffer is
20112 /// opened in the editor.
20113 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20114 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20115 buffer.read(cx).project_path(cx)
20116 } else {
20117 None
20118 }
20119 }
20120
20121 // Returns true if the editor handled a go-to-line request
20122 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20123 maybe!({
20124 let breakpoint_store = self.breakpoint_store.as_ref()?;
20125
20126 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20127 else {
20128 self.clear_row_highlights::<ActiveDebugLine>();
20129 return None;
20130 };
20131
20132 let position = active_stack_frame.position;
20133 let buffer_id = position.buffer_id?;
20134 let snapshot = self
20135 .project
20136 .as_ref()?
20137 .read(cx)
20138 .buffer_for_id(buffer_id, cx)?
20139 .read(cx)
20140 .snapshot();
20141
20142 let mut handled = false;
20143 for (id, ExcerptRange { context, .. }) in
20144 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20145 {
20146 if context.start.cmp(&position, &snapshot).is_ge()
20147 || context.end.cmp(&position, &snapshot).is_lt()
20148 {
20149 continue;
20150 }
20151 let snapshot = self.buffer.read(cx).snapshot(cx);
20152 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20153
20154 handled = true;
20155 self.clear_row_highlights::<ActiveDebugLine>();
20156
20157 self.go_to_line::<ActiveDebugLine>(
20158 multibuffer_anchor,
20159 Some(cx.theme().colors().editor_debugger_active_line_background),
20160 window,
20161 cx,
20162 );
20163
20164 cx.notify();
20165 }
20166
20167 handled.then_some(())
20168 })
20169 .is_some()
20170 }
20171
20172 pub fn copy_file_name_without_extension(
20173 &mut self,
20174 _: &CopyFileNameWithoutExtension,
20175 _: &mut Window,
20176 cx: &mut Context<Self>,
20177 ) {
20178 if let Some(file) = self.target_file(cx)
20179 && let Some(file_stem) = file.path().file_stem()
20180 {
20181 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20182 }
20183 }
20184
20185 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20186 if let Some(file) = self.target_file(cx)
20187 && let Some(name) = file.path().file_name()
20188 {
20189 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
20190 }
20191 }
20192
20193 pub fn toggle_git_blame(
20194 &mut self,
20195 _: &::git::Blame,
20196 window: &mut Window,
20197 cx: &mut Context<Self>,
20198 ) {
20199 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20200
20201 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20202 self.start_git_blame(true, window, cx);
20203 }
20204
20205 cx.notify();
20206 }
20207
20208 pub fn toggle_git_blame_inline(
20209 &mut self,
20210 _: &ToggleGitBlameInline,
20211 window: &mut Window,
20212 cx: &mut Context<Self>,
20213 ) {
20214 self.toggle_git_blame_inline_internal(true, window, cx);
20215 cx.notify();
20216 }
20217
20218 pub fn open_git_blame_commit(
20219 &mut self,
20220 _: &OpenGitBlameCommit,
20221 window: &mut Window,
20222 cx: &mut Context<Self>,
20223 ) {
20224 self.open_git_blame_commit_internal(window, cx);
20225 }
20226
20227 fn open_git_blame_commit_internal(
20228 &mut self,
20229 window: &mut Window,
20230 cx: &mut Context<Self>,
20231 ) -> Option<()> {
20232 let blame = self.blame.as_ref()?;
20233 let snapshot = self.snapshot(window, cx);
20234 let cursor = self
20235 .selections
20236 .newest::<Point>(&snapshot.display_snapshot)
20237 .head();
20238 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20239 let (_, blame_entry) = blame
20240 .update(cx, |blame, cx| {
20241 blame
20242 .blame_for_rows(
20243 &[RowInfo {
20244 buffer_id: Some(buffer.remote_id()),
20245 buffer_row: Some(point.row),
20246 ..Default::default()
20247 }],
20248 cx,
20249 )
20250 .next()
20251 })
20252 .flatten()?;
20253 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20254 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20255 let workspace = self.workspace()?.downgrade();
20256 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20257 None
20258 }
20259
20260 pub fn git_blame_inline_enabled(&self) -> bool {
20261 self.git_blame_inline_enabled
20262 }
20263
20264 pub fn toggle_selection_menu(
20265 &mut self,
20266 _: &ToggleSelectionMenu,
20267 _: &mut Window,
20268 cx: &mut Context<Self>,
20269 ) {
20270 self.show_selection_menu = self
20271 .show_selection_menu
20272 .map(|show_selections_menu| !show_selections_menu)
20273 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20274
20275 cx.notify();
20276 }
20277
20278 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20279 self.show_selection_menu
20280 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20281 }
20282
20283 fn start_git_blame(
20284 &mut self,
20285 user_triggered: bool,
20286 window: &mut Window,
20287 cx: &mut Context<Self>,
20288 ) {
20289 if let Some(project) = self.project() {
20290 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20291 && buffer.read(cx).file().is_none()
20292 {
20293 return;
20294 }
20295
20296 let focused = self.focus_handle(cx).contains_focused(window, cx);
20297
20298 let project = project.clone();
20299 let blame = cx
20300 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20301 self.blame_subscription =
20302 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20303 self.blame = Some(blame);
20304 }
20305 }
20306
20307 fn toggle_git_blame_inline_internal(
20308 &mut self,
20309 user_triggered: bool,
20310 window: &mut Window,
20311 cx: &mut Context<Self>,
20312 ) {
20313 if self.git_blame_inline_enabled {
20314 self.git_blame_inline_enabled = false;
20315 self.show_git_blame_inline = false;
20316 self.show_git_blame_inline_delay_task.take();
20317 } else {
20318 self.git_blame_inline_enabled = true;
20319 self.start_git_blame_inline(user_triggered, window, cx);
20320 }
20321
20322 cx.notify();
20323 }
20324
20325 fn start_git_blame_inline(
20326 &mut self,
20327 user_triggered: bool,
20328 window: &mut Window,
20329 cx: &mut Context<Self>,
20330 ) {
20331 self.start_git_blame(user_triggered, window, cx);
20332
20333 if ProjectSettings::get_global(cx)
20334 .git
20335 .inline_blame_delay()
20336 .is_some()
20337 {
20338 self.start_inline_blame_timer(window, cx);
20339 } else {
20340 self.show_git_blame_inline = true
20341 }
20342 }
20343
20344 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20345 self.blame.as_ref()
20346 }
20347
20348 pub fn show_git_blame_gutter(&self) -> bool {
20349 self.show_git_blame_gutter
20350 }
20351
20352 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20353 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20354 }
20355
20356 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20357 self.show_git_blame_inline
20358 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20359 && !self.newest_selection_head_on_empty_line(cx)
20360 && self.has_blame_entries(cx)
20361 }
20362
20363 fn has_blame_entries(&self, cx: &App) -> bool {
20364 self.blame()
20365 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20366 }
20367
20368 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20369 let cursor_anchor = self.selections.newest_anchor().head();
20370
20371 let snapshot = self.buffer.read(cx).snapshot(cx);
20372 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20373
20374 snapshot.line_len(buffer_row) == 0
20375 }
20376
20377 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20378 let buffer_and_selection = maybe!({
20379 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20380 let selection_range = selection.range();
20381
20382 let multi_buffer = self.buffer().read(cx);
20383 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20384 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20385
20386 let (buffer, range, _) = if selection.reversed {
20387 buffer_ranges.first()
20388 } else {
20389 buffer_ranges.last()
20390 }?;
20391
20392 let selection = text::ToPoint::to_point(&range.start, buffer).row
20393 ..text::ToPoint::to_point(&range.end, buffer).row;
20394 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20395 });
20396
20397 let Some((buffer, selection)) = buffer_and_selection else {
20398 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20399 };
20400
20401 let Some(project) = self.project() else {
20402 return Task::ready(Err(anyhow!("editor does not have project")));
20403 };
20404
20405 project.update(cx, |project, cx| {
20406 project.get_permalink_to_line(&buffer, selection, cx)
20407 })
20408 }
20409
20410 pub fn copy_permalink_to_line(
20411 &mut self,
20412 _: &CopyPermalinkToLine,
20413 window: &mut Window,
20414 cx: &mut Context<Self>,
20415 ) {
20416 let permalink_task = self.get_permalink_to_line(cx);
20417 let workspace = self.workspace();
20418
20419 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20420 Ok(permalink) => {
20421 cx.update(|_, cx| {
20422 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20423 })
20424 .ok();
20425 }
20426 Err(err) => {
20427 let message = format!("Failed to copy permalink: {err}");
20428
20429 anyhow::Result::<()>::Err(err).log_err();
20430
20431 if let Some(workspace) = workspace {
20432 workspace
20433 .update_in(cx, |workspace, _, cx| {
20434 struct CopyPermalinkToLine;
20435
20436 workspace.show_toast(
20437 Toast::new(
20438 NotificationId::unique::<CopyPermalinkToLine>(),
20439 message,
20440 ),
20441 cx,
20442 )
20443 })
20444 .ok();
20445 }
20446 }
20447 })
20448 .detach();
20449 }
20450
20451 pub fn copy_file_location(
20452 &mut self,
20453 _: &CopyFileLocation,
20454 _: &mut Window,
20455 cx: &mut Context<Self>,
20456 ) {
20457 let selection = self
20458 .selections
20459 .newest::<Point>(&self.display_snapshot(cx))
20460 .start
20461 .row
20462 + 1;
20463 if let Some(file) = self.target_file(cx) {
20464 let path = file.path().display(file.path_style(cx));
20465 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20466 }
20467 }
20468
20469 pub fn open_permalink_to_line(
20470 &mut self,
20471 _: &OpenPermalinkToLine,
20472 window: &mut Window,
20473 cx: &mut Context<Self>,
20474 ) {
20475 let permalink_task = self.get_permalink_to_line(cx);
20476 let workspace = self.workspace();
20477
20478 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20479 Ok(permalink) => {
20480 cx.update(|_, cx| {
20481 cx.open_url(permalink.as_ref());
20482 })
20483 .ok();
20484 }
20485 Err(err) => {
20486 let message = format!("Failed to open permalink: {err}");
20487
20488 anyhow::Result::<()>::Err(err).log_err();
20489
20490 if let Some(workspace) = workspace {
20491 workspace
20492 .update(cx, |workspace, cx| {
20493 struct OpenPermalinkToLine;
20494
20495 workspace.show_toast(
20496 Toast::new(
20497 NotificationId::unique::<OpenPermalinkToLine>(),
20498 message,
20499 ),
20500 cx,
20501 )
20502 })
20503 .ok();
20504 }
20505 }
20506 })
20507 .detach();
20508 }
20509
20510 pub fn insert_uuid_v4(
20511 &mut self,
20512 _: &InsertUuidV4,
20513 window: &mut Window,
20514 cx: &mut Context<Self>,
20515 ) {
20516 self.insert_uuid(UuidVersion::V4, window, cx);
20517 }
20518
20519 pub fn insert_uuid_v7(
20520 &mut self,
20521 _: &InsertUuidV7,
20522 window: &mut Window,
20523 cx: &mut Context<Self>,
20524 ) {
20525 self.insert_uuid(UuidVersion::V7, window, cx);
20526 }
20527
20528 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20529 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20530 self.transact(window, cx, |this, window, cx| {
20531 let edits = this
20532 .selections
20533 .all::<Point>(&this.display_snapshot(cx))
20534 .into_iter()
20535 .map(|selection| {
20536 let uuid = match version {
20537 UuidVersion::V4 => uuid::Uuid::new_v4(),
20538 UuidVersion::V7 => uuid::Uuid::now_v7(),
20539 };
20540
20541 (selection.range(), uuid.to_string())
20542 });
20543 this.edit(edits, cx);
20544 this.refresh_edit_prediction(true, false, window, cx);
20545 });
20546 }
20547
20548 pub fn open_selections_in_multibuffer(
20549 &mut self,
20550 _: &OpenSelectionsInMultibuffer,
20551 window: &mut Window,
20552 cx: &mut Context<Self>,
20553 ) {
20554 let multibuffer = self.buffer.read(cx);
20555
20556 let Some(buffer) = multibuffer.as_singleton() else {
20557 return;
20558 };
20559
20560 let Some(workspace) = self.workspace() else {
20561 return;
20562 };
20563
20564 let title = multibuffer.title(cx).to_string();
20565
20566 let locations = self
20567 .selections
20568 .all_anchors(&self.display_snapshot(cx))
20569 .iter()
20570 .map(|selection| {
20571 (
20572 buffer.clone(),
20573 (selection.start.text_anchor..selection.end.text_anchor)
20574 .to_point(buffer.read(cx)),
20575 )
20576 })
20577 .into_group_map();
20578
20579 cx.spawn_in(window, async move |_, cx| {
20580 workspace.update_in(cx, |workspace, window, cx| {
20581 Self::open_locations_in_multibuffer(
20582 workspace,
20583 locations,
20584 format!("Selections for '{title}'"),
20585 false,
20586 MultibufferSelectionMode::All,
20587 window,
20588 cx,
20589 );
20590 })
20591 })
20592 .detach();
20593 }
20594
20595 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20596 /// last highlight added will be used.
20597 ///
20598 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20599 pub fn highlight_rows<T: 'static>(
20600 &mut self,
20601 range: Range<Anchor>,
20602 color: Hsla,
20603 options: RowHighlightOptions,
20604 cx: &mut Context<Self>,
20605 ) {
20606 let snapshot = self.buffer().read(cx).snapshot(cx);
20607 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20608 let ix = row_highlights.binary_search_by(|highlight| {
20609 Ordering::Equal
20610 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20611 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20612 });
20613
20614 if let Err(mut ix) = ix {
20615 let index = post_inc(&mut self.highlight_order);
20616
20617 // If this range intersects with the preceding highlight, then merge it with
20618 // the preceding highlight. Otherwise insert a new highlight.
20619 let mut merged = false;
20620 if ix > 0 {
20621 let prev_highlight = &mut row_highlights[ix - 1];
20622 if prev_highlight
20623 .range
20624 .end
20625 .cmp(&range.start, &snapshot)
20626 .is_ge()
20627 {
20628 ix -= 1;
20629 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20630 prev_highlight.range.end = range.end;
20631 }
20632 merged = true;
20633 prev_highlight.index = index;
20634 prev_highlight.color = color;
20635 prev_highlight.options = options;
20636 }
20637 }
20638
20639 if !merged {
20640 row_highlights.insert(
20641 ix,
20642 RowHighlight {
20643 range,
20644 index,
20645 color,
20646 options,
20647 type_id: TypeId::of::<T>(),
20648 },
20649 );
20650 }
20651
20652 // If any of the following highlights intersect with this one, merge them.
20653 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20654 let highlight = &row_highlights[ix];
20655 if next_highlight
20656 .range
20657 .start
20658 .cmp(&highlight.range.end, &snapshot)
20659 .is_le()
20660 {
20661 if next_highlight
20662 .range
20663 .end
20664 .cmp(&highlight.range.end, &snapshot)
20665 .is_gt()
20666 {
20667 row_highlights[ix].range.end = next_highlight.range.end;
20668 }
20669 row_highlights.remove(ix + 1);
20670 } else {
20671 break;
20672 }
20673 }
20674 }
20675 }
20676
20677 /// Remove any highlighted row ranges of the given type that intersect the
20678 /// given ranges.
20679 pub fn remove_highlighted_rows<T: 'static>(
20680 &mut self,
20681 ranges_to_remove: Vec<Range<Anchor>>,
20682 cx: &mut Context<Self>,
20683 ) {
20684 let snapshot = self.buffer().read(cx).snapshot(cx);
20685 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20686 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20687 row_highlights.retain(|highlight| {
20688 while let Some(range_to_remove) = ranges_to_remove.peek() {
20689 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20690 Ordering::Less | Ordering::Equal => {
20691 ranges_to_remove.next();
20692 }
20693 Ordering::Greater => {
20694 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20695 Ordering::Less | Ordering::Equal => {
20696 return false;
20697 }
20698 Ordering::Greater => break,
20699 }
20700 }
20701 }
20702 }
20703
20704 true
20705 })
20706 }
20707
20708 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20709 pub fn clear_row_highlights<T: 'static>(&mut self) {
20710 self.highlighted_rows.remove(&TypeId::of::<T>());
20711 }
20712
20713 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20714 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20715 self.highlighted_rows
20716 .get(&TypeId::of::<T>())
20717 .map_or(&[] as &[_], |vec| vec.as_slice())
20718 .iter()
20719 .map(|highlight| (highlight.range.clone(), highlight.color))
20720 }
20721
20722 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20723 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20724 /// Allows to ignore certain kinds of highlights.
20725 pub fn highlighted_display_rows(
20726 &self,
20727 window: &mut Window,
20728 cx: &mut App,
20729 ) -> BTreeMap<DisplayRow, LineHighlight> {
20730 let snapshot = self.snapshot(window, cx);
20731 let mut used_highlight_orders = HashMap::default();
20732 self.highlighted_rows
20733 .iter()
20734 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20735 .fold(
20736 BTreeMap::<DisplayRow, LineHighlight>::new(),
20737 |mut unique_rows, highlight| {
20738 let start = highlight.range.start.to_display_point(&snapshot);
20739 let end = highlight.range.end.to_display_point(&snapshot);
20740 let start_row = start.row().0;
20741 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20742 && end.column() == 0
20743 {
20744 end.row().0.saturating_sub(1)
20745 } else {
20746 end.row().0
20747 };
20748 for row in start_row..=end_row {
20749 let used_index =
20750 used_highlight_orders.entry(row).or_insert(highlight.index);
20751 if highlight.index >= *used_index {
20752 *used_index = highlight.index;
20753 unique_rows.insert(
20754 DisplayRow(row),
20755 LineHighlight {
20756 include_gutter: highlight.options.include_gutter,
20757 border: None,
20758 background: highlight.color.into(),
20759 type_id: Some(highlight.type_id),
20760 },
20761 );
20762 }
20763 }
20764 unique_rows
20765 },
20766 )
20767 }
20768
20769 pub fn highlighted_display_row_for_autoscroll(
20770 &self,
20771 snapshot: &DisplaySnapshot,
20772 ) -> Option<DisplayRow> {
20773 self.highlighted_rows
20774 .values()
20775 .flat_map(|highlighted_rows| highlighted_rows.iter())
20776 .filter_map(|highlight| {
20777 if highlight.options.autoscroll {
20778 Some(highlight.range.start.to_display_point(snapshot).row())
20779 } else {
20780 None
20781 }
20782 })
20783 .min()
20784 }
20785
20786 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20787 self.highlight_background::<SearchWithinRange>(
20788 ranges,
20789 |colors| colors.colors().editor_document_highlight_read_background,
20790 cx,
20791 )
20792 }
20793
20794 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20795 self.breadcrumb_header = Some(new_header);
20796 }
20797
20798 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20799 self.clear_background_highlights::<SearchWithinRange>(cx);
20800 }
20801
20802 pub fn highlight_background<T: 'static>(
20803 &mut self,
20804 ranges: &[Range<Anchor>],
20805 color_fetcher: fn(&Theme) -> Hsla,
20806 cx: &mut Context<Self>,
20807 ) {
20808 self.background_highlights.insert(
20809 HighlightKey::Type(TypeId::of::<T>()),
20810 (color_fetcher, Arc::from(ranges)),
20811 );
20812 self.scrollbar_marker_state.dirty = true;
20813 cx.notify();
20814 }
20815
20816 pub fn highlight_background_key<T: 'static>(
20817 &mut self,
20818 key: usize,
20819 ranges: &[Range<Anchor>],
20820 color_fetcher: fn(&Theme) -> Hsla,
20821 cx: &mut Context<Self>,
20822 ) {
20823 self.background_highlights.insert(
20824 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20825 (color_fetcher, Arc::from(ranges)),
20826 );
20827 self.scrollbar_marker_state.dirty = true;
20828 cx.notify();
20829 }
20830
20831 pub fn clear_background_highlights<T: 'static>(
20832 &mut self,
20833 cx: &mut Context<Self>,
20834 ) -> Option<BackgroundHighlight> {
20835 let text_highlights = self
20836 .background_highlights
20837 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20838 if !text_highlights.1.is_empty() {
20839 self.scrollbar_marker_state.dirty = true;
20840 cx.notify();
20841 }
20842 Some(text_highlights)
20843 }
20844
20845 pub fn highlight_gutter<T: 'static>(
20846 &mut self,
20847 ranges: impl Into<Vec<Range<Anchor>>>,
20848 color_fetcher: fn(&App) -> Hsla,
20849 cx: &mut Context<Self>,
20850 ) {
20851 self.gutter_highlights
20852 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20853 cx.notify();
20854 }
20855
20856 pub fn clear_gutter_highlights<T: 'static>(
20857 &mut self,
20858 cx: &mut Context<Self>,
20859 ) -> Option<GutterHighlight> {
20860 cx.notify();
20861 self.gutter_highlights.remove(&TypeId::of::<T>())
20862 }
20863
20864 pub fn insert_gutter_highlight<T: 'static>(
20865 &mut self,
20866 range: Range<Anchor>,
20867 color_fetcher: fn(&App) -> Hsla,
20868 cx: &mut Context<Self>,
20869 ) {
20870 let snapshot = self.buffer().read(cx).snapshot(cx);
20871 let mut highlights = self
20872 .gutter_highlights
20873 .remove(&TypeId::of::<T>())
20874 .map(|(_, highlights)| highlights)
20875 .unwrap_or_default();
20876 let ix = highlights.binary_search_by(|highlight| {
20877 Ordering::Equal
20878 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20879 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20880 });
20881 if let Err(ix) = ix {
20882 highlights.insert(ix, range);
20883 }
20884 self.gutter_highlights
20885 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20886 }
20887
20888 pub fn remove_gutter_highlights<T: 'static>(
20889 &mut self,
20890 ranges_to_remove: Vec<Range<Anchor>>,
20891 cx: &mut Context<Self>,
20892 ) {
20893 let snapshot = self.buffer().read(cx).snapshot(cx);
20894 let Some((color_fetcher, mut gutter_highlights)) =
20895 self.gutter_highlights.remove(&TypeId::of::<T>())
20896 else {
20897 return;
20898 };
20899 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20900 gutter_highlights.retain(|highlight| {
20901 while let Some(range_to_remove) = ranges_to_remove.peek() {
20902 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20903 Ordering::Less | Ordering::Equal => {
20904 ranges_to_remove.next();
20905 }
20906 Ordering::Greater => {
20907 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20908 Ordering::Less | Ordering::Equal => {
20909 return false;
20910 }
20911 Ordering::Greater => break,
20912 }
20913 }
20914 }
20915 }
20916
20917 true
20918 });
20919 self.gutter_highlights
20920 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20921 }
20922
20923 #[cfg(feature = "test-support")]
20924 pub fn all_text_highlights(
20925 &self,
20926 window: &mut Window,
20927 cx: &mut Context<Self>,
20928 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20929 let snapshot = self.snapshot(window, cx);
20930 self.display_map.update(cx, |display_map, _| {
20931 display_map
20932 .all_text_highlights()
20933 .map(|highlight| {
20934 let (style, ranges) = highlight.as_ref();
20935 (
20936 *style,
20937 ranges
20938 .iter()
20939 .map(|range| range.clone().to_display_points(&snapshot))
20940 .collect(),
20941 )
20942 })
20943 .collect()
20944 })
20945 }
20946
20947 #[cfg(feature = "test-support")]
20948 pub fn all_text_background_highlights(
20949 &self,
20950 window: &mut Window,
20951 cx: &mut Context<Self>,
20952 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20953 let snapshot = self.snapshot(window, cx);
20954 let buffer = &snapshot.buffer_snapshot();
20955 let start = buffer.anchor_before(MultiBufferOffset(0));
20956 let end = buffer.anchor_after(buffer.len());
20957 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20958 }
20959
20960 #[cfg(any(test, feature = "test-support"))]
20961 pub fn sorted_background_highlights_in_range(
20962 &self,
20963 search_range: Range<Anchor>,
20964 display_snapshot: &DisplaySnapshot,
20965 theme: &Theme,
20966 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20967 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20968 res.sort_by(|a, b| {
20969 a.0.start
20970 .cmp(&b.0.start)
20971 .then_with(|| a.0.end.cmp(&b.0.end))
20972 .then_with(|| a.1.cmp(&b.1))
20973 });
20974 res
20975 }
20976
20977 #[cfg(feature = "test-support")]
20978 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20979 let snapshot = self.buffer().read(cx).snapshot(cx);
20980
20981 let highlights = self
20982 .background_highlights
20983 .get(&HighlightKey::Type(TypeId::of::<
20984 items::BufferSearchHighlights,
20985 >()));
20986
20987 if let Some((_color, ranges)) = highlights {
20988 ranges
20989 .iter()
20990 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20991 .collect_vec()
20992 } else {
20993 vec![]
20994 }
20995 }
20996
20997 fn document_highlights_for_position<'a>(
20998 &'a self,
20999 position: Anchor,
21000 buffer: &'a MultiBufferSnapshot,
21001 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21002 let read_highlights = self
21003 .background_highlights
21004 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21005 .map(|h| &h.1);
21006 let write_highlights = self
21007 .background_highlights
21008 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21009 .map(|h| &h.1);
21010 let left_position = position.bias_left(buffer);
21011 let right_position = position.bias_right(buffer);
21012 read_highlights
21013 .into_iter()
21014 .chain(write_highlights)
21015 .flat_map(move |ranges| {
21016 let start_ix = match ranges.binary_search_by(|probe| {
21017 let cmp = probe.end.cmp(&left_position, buffer);
21018 if cmp.is_ge() {
21019 Ordering::Greater
21020 } else {
21021 Ordering::Less
21022 }
21023 }) {
21024 Ok(i) | Err(i) => i,
21025 };
21026
21027 ranges[start_ix..]
21028 .iter()
21029 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21030 })
21031 }
21032
21033 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21034 self.background_highlights
21035 .get(&HighlightKey::Type(TypeId::of::<T>()))
21036 .is_some_and(|(_, highlights)| !highlights.is_empty())
21037 }
21038
21039 /// Returns all background highlights for a given range.
21040 ///
21041 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21042 pub fn background_highlights_in_range(
21043 &self,
21044 search_range: Range<Anchor>,
21045 display_snapshot: &DisplaySnapshot,
21046 theme: &Theme,
21047 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21048 let mut results = Vec::new();
21049 for (color_fetcher, ranges) in self.background_highlights.values() {
21050 let color = color_fetcher(theme);
21051 let start_ix = match ranges.binary_search_by(|probe| {
21052 let cmp = probe
21053 .end
21054 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21055 if cmp.is_gt() {
21056 Ordering::Greater
21057 } else {
21058 Ordering::Less
21059 }
21060 }) {
21061 Ok(i) | Err(i) => i,
21062 };
21063 for range in &ranges[start_ix..] {
21064 if range
21065 .start
21066 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21067 .is_ge()
21068 {
21069 break;
21070 }
21071
21072 let start = range.start.to_display_point(display_snapshot);
21073 let end = range.end.to_display_point(display_snapshot);
21074 results.push((start..end, color))
21075 }
21076 }
21077 results
21078 }
21079
21080 pub fn gutter_highlights_in_range(
21081 &self,
21082 search_range: Range<Anchor>,
21083 display_snapshot: &DisplaySnapshot,
21084 cx: &App,
21085 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21086 let mut results = Vec::new();
21087 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21088 let color = color_fetcher(cx);
21089 let start_ix = match ranges.binary_search_by(|probe| {
21090 let cmp = probe
21091 .end
21092 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21093 if cmp.is_gt() {
21094 Ordering::Greater
21095 } else {
21096 Ordering::Less
21097 }
21098 }) {
21099 Ok(i) | Err(i) => i,
21100 };
21101 for range in &ranges[start_ix..] {
21102 if range
21103 .start
21104 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21105 .is_ge()
21106 {
21107 break;
21108 }
21109
21110 let start = range.start.to_display_point(display_snapshot);
21111 let end = range.end.to_display_point(display_snapshot);
21112 results.push((start..end, color))
21113 }
21114 }
21115 results
21116 }
21117
21118 /// Get the text ranges corresponding to the redaction query
21119 pub fn redacted_ranges(
21120 &self,
21121 search_range: Range<Anchor>,
21122 display_snapshot: &DisplaySnapshot,
21123 cx: &App,
21124 ) -> Vec<Range<DisplayPoint>> {
21125 display_snapshot
21126 .buffer_snapshot()
21127 .redacted_ranges(search_range, |file| {
21128 if let Some(file) = file {
21129 file.is_private()
21130 && EditorSettings::get(
21131 Some(SettingsLocation {
21132 worktree_id: file.worktree_id(cx),
21133 path: file.path().as_ref(),
21134 }),
21135 cx,
21136 )
21137 .redact_private_values
21138 } else {
21139 false
21140 }
21141 })
21142 .map(|range| {
21143 range.start.to_display_point(display_snapshot)
21144 ..range.end.to_display_point(display_snapshot)
21145 })
21146 .collect()
21147 }
21148
21149 pub fn highlight_text_key<T: 'static>(
21150 &mut self,
21151 key: usize,
21152 ranges: Vec<Range<Anchor>>,
21153 style: HighlightStyle,
21154 merge: bool,
21155 cx: &mut Context<Self>,
21156 ) {
21157 self.display_map.update(cx, |map, cx| {
21158 map.highlight_text(
21159 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21160 ranges,
21161 style,
21162 merge,
21163 cx,
21164 );
21165 });
21166 cx.notify();
21167 }
21168
21169 pub fn highlight_text<T: 'static>(
21170 &mut self,
21171 ranges: Vec<Range<Anchor>>,
21172 style: HighlightStyle,
21173 cx: &mut Context<Self>,
21174 ) {
21175 self.display_map.update(cx, |map, cx| {
21176 map.highlight_text(
21177 HighlightKey::Type(TypeId::of::<T>()),
21178 ranges,
21179 style,
21180 false,
21181 cx,
21182 )
21183 });
21184 cx.notify();
21185 }
21186
21187 pub fn text_highlights<'a, T: 'static>(
21188 &'a self,
21189 cx: &'a App,
21190 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21191 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21192 }
21193
21194 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21195 let cleared = self
21196 .display_map
21197 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21198 if cleared {
21199 cx.notify();
21200 }
21201 }
21202
21203 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21204 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21205 && self.focus_handle.is_focused(window)
21206 }
21207
21208 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21209 self.show_cursor_when_unfocused = is_enabled;
21210 cx.notify();
21211 }
21212
21213 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21214 cx.notify();
21215 }
21216
21217 fn on_debug_session_event(
21218 &mut self,
21219 _session: Entity<Session>,
21220 event: &SessionEvent,
21221 cx: &mut Context<Self>,
21222 ) {
21223 if let SessionEvent::InvalidateInlineValue = event {
21224 self.refresh_inline_values(cx);
21225 }
21226 }
21227
21228 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21229 let Some(project) = self.project.clone() else {
21230 return;
21231 };
21232
21233 if !self.inline_value_cache.enabled {
21234 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21235 self.splice_inlays(&inlays, Vec::new(), cx);
21236 return;
21237 }
21238
21239 let current_execution_position = self
21240 .highlighted_rows
21241 .get(&TypeId::of::<ActiveDebugLine>())
21242 .and_then(|lines| lines.last().map(|line| line.range.end));
21243
21244 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21245 let inline_values = editor
21246 .update(cx, |editor, cx| {
21247 let Some(current_execution_position) = current_execution_position else {
21248 return Some(Task::ready(Ok(Vec::new())));
21249 };
21250
21251 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21252 let snapshot = buffer.snapshot(cx);
21253
21254 let excerpt = snapshot.excerpt_containing(
21255 current_execution_position..current_execution_position,
21256 )?;
21257
21258 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21259 })?;
21260
21261 let range =
21262 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21263
21264 project.inline_values(buffer, range, cx)
21265 })
21266 .ok()
21267 .flatten()?
21268 .await
21269 .context("refreshing debugger inlays")
21270 .log_err()?;
21271
21272 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21273
21274 for (buffer_id, inline_value) in inline_values
21275 .into_iter()
21276 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21277 {
21278 buffer_inline_values
21279 .entry(buffer_id)
21280 .or_default()
21281 .push(inline_value);
21282 }
21283
21284 editor
21285 .update(cx, |editor, cx| {
21286 let snapshot = editor.buffer.read(cx).snapshot(cx);
21287 let mut new_inlays = Vec::default();
21288
21289 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21290 let buffer_id = buffer_snapshot.remote_id();
21291 buffer_inline_values
21292 .get(&buffer_id)
21293 .into_iter()
21294 .flatten()
21295 .for_each(|hint| {
21296 let inlay = Inlay::debugger(
21297 post_inc(&mut editor.next_inlay_id),
21298 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21299 hint.text(),
21300 );
21301 if !inlay.text().chars().contains(&'\n') {
21302 new_inlays.push(inlay);
21303 }
21304 });
21305 }
21306
21307 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21308 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21309
21310 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21311 })
21312 .ok()?;
21313 Some(())
21314 });
21315 }
21316
21317 fn on_buffer_event(
21318 &mut self,
21319 multibuffer: &Entity<MultiBuffer>,
21320 event: &multi_buffer::Event,
21321 window: &mut Window,
21322 cx: &mut Context<Self>,
21323 ) {
21324 match event {
21325 multi_buffer::Event::Edited { edited_buffer } => {
21326 self.scrollbar_marker_state.dirty = true;
21327 self.active_indent_guides_state.dirty = true;
21328 self.refresh_active_diagnostics(cx);
21329 self.refresh_code_actions(window, cx);
21330 self.refresh_single_line_folds(window, cx);
21331 self.refresh_matching_bracket_highlights(window, cx);
21332 if self.has_active_edit_prediction() {
21333 self.update_visible_edit_prediction(window, cx);
21334 }
21335
21336 if let Some(buffer) = edited_buffer {
21337 if buffer.read(cx).file().is_none() {
21338 cx.emit(EditorEvent::TitleChanged);
21339 }
21340
21341 if self.project.is_some() {
21342 let buffer_id = buffer.read(cx).remote_id();
21343 self.register_buffer(buffer_id, cx);
21344 self.update_lsp_data(Some(buffer_id), window, cx);
21345 self.refresh_inlay_hints(
21346 InlayHintRefreshReason::BufferEdited(buffer_id),
21347 cx,
21348 );
21349 }
21350 }
21351
21352 cx.emit(EditorEvent::BufferEdited);
21353 cx.emit(SearchEvent::MatchesInvalidated);
21354
21355 let Some(project) = &self.project else { return };
21356 let (telemetry, is_via_ssh) = {
21357 let project = project.read(cx);
21358 let telemetry = project.client().telemetry().clone();
21359 let is_via_ssh = project.is_via_remote_server();
21360 (telemetry, is_via_ssh)
21361 };
21362 telemetry.log_edit_event("editor", is_via_ssh);
21363 }
21364 multi_buffer::Event::ExcerptsAdded {
21365 buffer,
21366 predecessor,
21367 excerpts,
21368 } => {
21369 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21370 let buffer_id = buffer.read(cx).remote_id();
21371 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21372 && let Some(project) = &self.project
21373 {
21374 update_uncommitted_diff_for_buffer(
21375 cx.entity(),
21376 project,
21377 [buffer.clone()],
21378 self.buffer.clone(),
21379 cx,
21380 )
21381 .detach();
21382 }
21383 self.update_lsp_data(Some(buffer_id), window, cx);
21384 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21385 self.colorize_brackets(false, cx);
21386 cx.emit(EditorEvent::ExcerptsAdded {
21387 buffer: buffer.clone(),
21388 predecessor: *predecessor,
21389 excerpts: excerpts.clone(),
21390 });
21391 }
21392 multi_buffer::Event::ExcerptsRemoved {
21393 ids,
21394 removed_buffer_ids,
21395 } => {
21396 if let Some(inlay_hints) = &mut self.inlay_hints {
21397 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21398 }
21399 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21400 for buffer_id in removed_buffer_ids {
21401 self.registered_buffers.remove(buffer_id);
21402 }
21403 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21404 cx.emit(EditorEvent::ExcerptsRemoved {
21405 ids: ids.clone(),
21406 removed_buffer_ids: removed_buffer_ids.clone(),
21407 });
21408 }
21409 multi_buffer::Event::ExcerptsEdited {
21410 excerpt_ids,
21411 buffer_ids,
21412 } => {
21413 self.display_map.update(cx, |map, cx| {
21414 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21415 });
21416 cx.emit(EditorEvent::ExcerptsEdited {
21417 ids: excerpt_ids.clone(),
21418 });
21419 }
21420 multi_buffer::Event::ExcerptsExpanded { ids } => {
21421 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21422 self.refresh_document_highlights(cx);
21423 for id in ids {
21424 self.fetched_tree_sitter_chunks.remove(id);
21425 }
21426 self.colorize_brackets(false, cx);
21427 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21428 }
21429 multi_buffer::Event::Reparsed(buffer_id) => {
21430 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21431 self.refresh_selected_text_highlights(true, window, cx);
21432 self.colorize_brackets(true, cx);
21433 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21434
21435 cx.emit(EditorEvent::Reparsed(*buffer_id));
21436 }
21437 multi_buffer::Event::DiffHunksToggled => {
21438 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21439 }
21440 multi_buffer::Event::LanguageChanged(buffer_id) => {
21441 self.registered_buffers.remove(&buffer_id);
21442 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21443 cx.emit(EditorEvent::Reparsed(*buffer_id));
21444 cx.notify();
21445 }
21446 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21447 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21448 multi_buffer::Event::FileHandleChanged
21449 | multi_buffer::Event::Reloaded
21450 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21451 multi_buffer::Event::DiagnosticsUpdated => {
21452 self.update_diagnostics_state(window, cx);
21453 }
21454 _ => {}
21455 };
21456 }
21457
21458 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21459 if !self.diagnostics_enabled() {
21460 return;
21461 }
21462 self.refresh_active_diagnostics(cx);
21463 self.refresh_inline_diagnostics(true, window, cx);
21464 self.scrollbar_marker_state.dirty = true;
21465 cx.notify();
21466 }
21467
21468 pub fn start_temporary_diff_override(&mut self) {
21469 self.load_diff_task.take();
21470 self.temporary_diff_override = true;
21471 }
21472
21473 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21474 self.temporary_diff_override = false;
21475 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21476 self.buffer.update(cx, |buffer, cx| {
21477 buffer.set_all_diff_hunks_collapsed(cx);
21478 });
21479
21480 if let Some(project) = self.project.clone() {
21481 self.load_diff_task = Some(
21482 update_uncommitted_diff_for_buffer(
21483 cx.entity(),
21484 &project,
21485 self.buffer.read(cx).all_buffers(),
21486 self.buffer.clone(),
21487 cx,
21488 )
21489 .shared(),
21490 );
21491 }
21492 }
21493
21494 fn on_display_map_changed(
21495 &mut self,
21496 _: Entity<DisplayMap>,
21497 _: &mut Window,
21498 cx: &mut Context<Self>,
21499 ) {
21500 cx.notify();
21501 }
21502
21503 fn fetch_accent_overrides(&self, cx: &App) -> Vec<SharedString> {
21504 if !self.mode.is_full() {
21505 return Vec::new();
21506 }
21507
21508 theme::ThemeSettings::get_global(cx)
21509 .theme_overrides
21510 .get(cx.theme().name.as_ref())
21511 .map(|theme_style| &theme_style.accents)
21512 .into_iter()
21513 .flatten()
21514 .flat_map(|accent| accent.0.clone())
21515 .collect()
21516 }
21517
21518 fn fetch_applicable_language_settings(
21519 &self,
21520 cx: &App,
21521 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21522 if !self.mode.is_full() {
21523 return HashMap::default();
21524 }
21525
21526 self.buffer().read(cx).all_buffers().into_iter().fold(
21527 HashMap::default(),
21528 |mut acc, buffer| {
21529 let buffer = buffer.read(cx);
21530 let language = buffer.language().map(|language| language.name());
21531 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21532 let file = buffer.file();
21533 v.insert(language_settings(language, file, cx).into_owned());
21534 }
21535 acc
21536 },
21537 )
21538 }
21539
21540 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21541 let new_language_settings = self.fetch_applicable_language_settings(cx);
21542 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21543 self.applicable_language_settings = new_language_settings;
21544
21545 let new_accent_overrides = self.fetch_accent_overrides(cx);
21546 let accent_overrides_changed = new_accent_overrides != self.accent_overrides;
21547 self.accent_overrides = new_accent_overrides;
21548
21549 if self.diagnostics_enabled() {
21550 let new_severity = EditorSettings::get_global(cx)
21551 .diagnostics_max_severity
21552 .unwrap_or(DiagnosticSeverity::Hint);
21553 self.set_max_diagnostics_severity(new_severity, cx);
21554 }
21555 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21556 self.update_edit_prediction_settings(cx);
21557 self.refresh_edit_prediction(true, false, window, cx);
21558 self.refresh_inline_values(cx);
21559 self.refresh_inlay_hints(
21560 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21561 self.selections.newest_anchor().head(),
21562 &self.buffer.read(cx).snapshot(cx),
21563 cx,
21564 )),
21565 cx,
21566 );
21567
21568 let old_cursor_shape = self.cursor_shape;
21569 let old_show_breadcrumbs = self.show_breadcrumbs;
21570
21571 {
21572 let editor_settings = EditorSettings::get_global(cx);
21573 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21574 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21575 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21576 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21577 }
21578
21579 if old_cursor_shape != self.cursor_shape {
21580 cx.emit(EditorEvent::CursorShapeChanged);
21581 }
21582
21583 if old_show_breadcrumbs != self.show_breadcrumbs {
21584 cx.emit(EditorEvent::BreadcrumbsChanged);
21585 }
21586
21587 let project_settings = ProjectSettings::get_global(cx);
21588 self.buffer_serialization = self
21589 .should_serialize_buffer()
21590 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21591
21592 if self.mode.is_full() {
21593 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21594 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21595 if self.show_inline_diagnostics != show_inline_diagnostics {
21596 self.show_inline_diagnostics = show_inline_diagnostics;
21597 self.refresh_inline_diagnostics(false, window, cx);
21598 }
21599
21600 if self.git_blame_inline_enabled != inline_blame_enabled {
21601 self.toggle_git_blame_inline_internal(false, window, cx);
21602 }
21603
21604 let minimap_settings = EditorSettings::get_global(cx).minimap;
21605 if self.minimap_visibility != MinimapVisibility::Disabled {
21606 if self.minimap_visibility.settings_visibility()
21607 != minimap_settings.minimap_enabled()
21608 {
21609 self.set_minimap_visibility(
21610 MinimapVisibility::for_mode(self.mode(), cx),
21611 window,
21612 cx,
21613 );
21614 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21615 minimap_entity.update(cx, |minimap_editor, cx| {
21616 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21617 })
21618 }
21619 }
21620
21621 if language_settings_changed || accent_overrides_changed {
21622 self.colorize_brackets(true, cx);
21623 }
21624
21625 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21626 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21627 }) {
21628 if !inlay_splice.is_empty() {
21629 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21630 }
21631 self.refresh_colors_for_visible_range(None, window, cx);
21632 }
21633 }
21634
21635 cx.notify();
21636 }
21637
21638 pub fn set_searchable(&mut self, searchable: bool) {
21639 self.searchable = searchable;
21640 }
21641
21642 pub fn searchable(&self) -> bool {
21643 self.searchable
21644 }
21645
21646 pub fn open_excerpts_in_split(
21647 &mut self,
21648 _: &OpenExcerptsSplit,
21649 window: &mut Window,
21650 cx: &mut Context<Self>,
21651 ) {
21652 self.open_excerpts_common(None, true, window, cx)
21653 }
21654
21655 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21656 self.open_excerpts_common(None, false, window, cx)
21657 }
21658
21659 fn open_excerpts_common(
21660 &mut self,
21661 jump_data: Option<JumpData>,
21662 split: bool,
21663 window: &mut Window,
21664 cx: &mut Context<Self>,
21665 ) {
21666 let Some(workspace) = self.workspace() else {
21667 cx.propagate();
21668 return;
21669 };
21670
21671 if self.buffer.read(cx).is_singleton() {
21672 cx.propagate();
21673 return;
21674 }
21675
21676 let mut new_selections_by_buffer = HashMap::default();
21677 match &jump_data {
21678 Some(JumpData::MultiBufferPoint {
21679 excerpt_id,
21680 position,
21681 anchor,
21682 line_offset_from_top,
21683 }) => {
21684 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21685 if let Some(buffer) = multi_buffer_snapshot
21686 .buffer_id_for_excerpt(*excerpt_id)
21687 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21688 {
21689 let buffer_snapshot = buffer.read(cx).snapshot();
21690 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21691 language::ToPoint::to_point(anchor, &buffer_snapshot)
21692 } else {
21693 buffer_snapshot.clip_point(*position, Bias::Left)
21694 };
21695 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21696 new_selections_by_buffer.insert(
21697 buffer,
21698 (
21699 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
21700 Some(*line_offset_from_top),
21701 ),
21702 );
21703 }
21704 }
21705 Some(JumpData::MultiBufferRow {
21706 row,
21707 line_offset_from_top,
21708 }) => {
21709 let point = MultiBufferPoint::new(row.0, 0);
21710 if let Some((buffer, buffer_point, _)) =
21711 self.buffer.read(cx).point_to_buffer_point(point, cx)
21712 {
21713 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21714 new_selections_by_buffer
21715 .entry(buffer)
21716 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21717 .0
21718 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
21719 }
21720 }
21721 None => {
21722 let selections = self
21723 .selections
21724 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
21725 let multi_buffer = self.buffer.read(cx);
21726 for selection in selections {
21727 for (snapshot, range, _, anchor) in multi_buffer
21728 .snapshot(cx)
21729 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21730 {
21731 if let Some(anchor) = anchor {
21732 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21733 else {
21734 continue;
21735 };
21736 let offset = text::ToOffset::to_offset(
21737 &anchor.text_anchor,
21738 &buffer_handle.read(cx).snapshot(),
21739 );
21740 let range = BufferOffset(offset)..BufferOffset(offset);
21741 new_selections_by_buffer
21742 .entry(buffer_handle)
21743 .or_insert((Vec::new(), None))
21744 .0
21745 .push(range)
21746 } else {
21747 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21748 else {
21749 continue;
21750 };
21751 new_selections_by_buffer
21752 .entry(buffer_handle)
21753 .or_insert((Vec::new(), None))
21754 .0
21755 .push(range)
21756 }
21757 }
21758 }
21759 }
21760 }
21761
21762 new_selections_by_buffer
21763 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21764
21765 if new_selections_by_buffer.is_empty() {
21766 return;
21767 }
21768
21769 // We defer the pane interaction because we ourselves are a workspace item
21770 // and activating a new item causes the pane to call a method on us reentrantly,
21771 // which panics if we're on the stack.
21772 window.defer(cx, move |window, cx| {
21773 workspace.update(cx, |workspace, cx| {
21774 let pane = if split {
21775 workspace.adjacent_pane(window, cx)
21776 } else {
21777 workspace.active_pane().clone()
21778 };
21779
21780 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21781 let editor = buffer
21782 .read(cx)
21783 .file()
21784 .is_none()
21785 .then(|| {
21786 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21787 // so `workspace.open_project_item` will never find them, always opening a new editor.
21788 // Instead, we try to activate the existing editor in the pane first.
21789 let (editor, pane_item_index) =
21790 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21791 let editor = item.downcast::<Editor>()?;
21792 let singleton_buffer =
21793 editor.read(cx).buffer().read(cx).as_singleton()?;
21794 if singleton_buffer == buffer {
21795 Some((editor, i))
21796 } else {
21797 None
21798 }
21799 })?;
21800 pane.update(cx, |pane, cx| {
21801 pane.activate_item(pane_item_index, true, true, window, cx)
21802 });
21803 Some(editor)
21804 })
21805 .flatten()
21806 .unwrap_or_else(|| {
21807 workspace.open_project_item::<Self>(
21808 pane.clone(),
21809 buffer,
21810 true,
21811 true,
21812 window,
21813 cx,
21814 )
21815 });
21816
21817 editor.update(cx, |editor, cx| {
21818 let autoscroll = match scroll_offset {
21819 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21820 None => Autoscroll::newest(),
21821 };
21822 let nav_history = editor.nav_history.take();
21823 editor.change_selections(
21824 SelectionEffects::scroll(autoscroll),
21825 window,
21826 cx,
21827 |s| {
21828 s.select_ranges(ranges.into_iter().map(|range| {
21829 // we checked that the editor is a singleton editor so the offsets are valid
21830 MultiBufferOffset(range.start.0)..MultiBufferOffset(range.end.0)
21831 }));
21832 },
21833 );
21834 editor.nav_history = nav_history;
21835 });
21836 }
21837 })
21838 });
21839 }
21840
21841 // For now, don't allow opening excerpts in buffers that aren't backed by
21842 // regular project files.
21843 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21844 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21845 }
21846
21847 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
21848 let snapshot = self.buffer.read(cx).read(cx);
21849 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21850 Some(
21851 ranges
21852 .iter()
21853 .map(move |range| {
21854 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21855 })
21856 .collect(),
21857 )
21858 }
21859
21860 fn selection_replacement_ranges(
21861 &self,
21862 range: Range<MultiBufferOffsetUtf16>,
21863 cx: &mut App,
21864 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
21865 let selections = self
21866 .selections
21867 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
21868 let newest_selection = selections
21869 .iter()
21870 .max_by_key(|selection| selection.id)
21871 .unwrap();
21872 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
21873 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
21874 let snapshot = self.buffer.read(cx).read(cx);
21875 selections
21876 .into_iter()
21877 .map(|mut selection| {
21878 selection.start.0.0 =
21879 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
21880 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
21881 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21882 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21883 })
21884 .collect()
21885 }
21886
21887 fn report_editor_event(
21888 &self,
21889 reported_event: ReportEditorEvent,
21890 file_extension: Option<String>,
21891 cx: &App,
21892 ) {
21893 if cfg!(any(test, feature = "test-support")) {
21894 return;
21895 }
21896
21897 let Some(project) = &self.project else { return };
21898
21899 // If None, we are in a file without an extension
21900 let file = self
21901 .buffer
21902 .read(cx)
21903 .as_singleton()
21904 .and_then(|b| b.read(cx).file());
21905 let file_extension = file_extension.or(file
21906 .as_ref()
21907 .and_then(|file| Path::new(file.file_name(cx)).extension())
21908 .and_then(|e| e.to_str())
21909 .map(|a| a.to_string()));
21910
21911 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
21912 .map(|vim_mode| vim_mode.0)
21913 .unwrap_or(false);
21914
21915 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21916 let copilot_enabled = edit_predictions_provider
21917 == language::language_settings::EditPredictionProvider::Copilot;
21918 let copilot_enabled_for_language = self
21919 .buffer
21920 .read(cx)
21921 .language_settings(cx)
21922 .show_edit_predictions;
21923
21924 let project = project.read(cx);
21925 let event_type = reported_event.event_type();
21926
21927 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21928 telemetry::event!(
21929 event_type,
21930 type = if auto_saved {"autosave"} else {"manual"},
21931 file_extension,
21932 vim_mode,
21933 copilot_enabled,
21934 copilot_enabled_for_language,
21935 edit_predictions_provider,
21936 is_via_ssh = project.is_via_remote_server(),
21937 );
21938 } else {
21939 telemetry::event!(
21940 event_type,
21941 file_extension,
21942 vim_mode,
21943 copilot_enabled,
21944 copilot_enabled_for_language,
21945 edit_predictions_provider,
21946 is_via_ssh = project.is_via_remote_server(),
21947 );
21948 };
21949 }
21950
21951 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21952 /// with each line being an array of {text, highlight} objects.
21953 fn copy_highlight_json(
21954 &mut self,
21955 _: &CopyHighlightJson,
21956 window: &mut Window,
21957 cx: &mut Context<Self>,
21958 ) {
21959 #[derive(Serialize)]
21960 struct Chunk<'a> {
21961 text: String,
21962 highlight: Option<&'a str>,
21963 }
21964
21965 let snapshot = self.buffer.read(cx).snapshot(cx);
21966 let range = self
21967 .selected_text_range(false, window, cx)
21968 .and_then(|selection| {
21969 if selection.range.is_empty() {
21970 None
21971 } else {
21972 Some(
21973 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
21974 selection.range.start,
21975 )))
21976 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
21977 selection.range.end,
21978 ))),
21979 )
21980 }
21981 })
21982 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
21983
21984 let chunks = snapshot.chunks(range, true);
21985 let mut lines = Vec::new();
21986 let mut line: VecDeque<Chunk> = VecDeque::new();
21987
21988 let Some(style) = self.style.as_ref() else {
21989 return;
21990 };
21991
21992 for chunk in chunks {
21993 let highlight = chunk
21994 .syntax_highlight_id
21995 .and_then(|id| id.name(&style.syntax));
21996 let mut chunk_lines = chunk.text.split('\n').peekable();
21997 while let Some(text) = chunk_lines.next() {
21998 let mut merged_with_last_token = false;
21999 if let Some(last_token) = line.back_mut()
22000 && last_token.highlight == highlight
22001 {
22002 last_token.text.push_str(text);
22003 merged_with_last_token = true;
22004 }
22005
22006 if !merged_with_last_token {
22007 line.push_back(Chunk {
22008 text: text.into(),
22009 highlight,
22010 });
22011 }
22012
22013 if chunk_lines.peek().is_some() {
22014 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22015 line.pop_front();
22016 }
22017 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22018 line.pop_back();
22019 }
22020
22021 lines.push(mem::take(&mut line));
22022 }
22023 }
22024 }
22025
22026 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22027 return;
22028 };
22029 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22030 }
22031
22032 pub fn open_context_menu(
22033 &mut self,
22034 _: &OpenContextMenu,
22035 window: &mut Window,
22036 cx: &mut Context<Self>,
22037 ) {
22038 self.request_autoscroll(Autoscroll::newest(), cx);
22039 let position = self
22040 .selections
22041 .newest_display(&self.display_snapshot(cx))
22042 .start;
22043 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22044 }
22045
22046 pub fn replay_insert_event(
22047 &mut self,
22048 text: &str,
22049 relative_utf16_range: Option<Range<isize>>,
22050 window: &mut Window,
22051 cx: &mut Context<Self>,
22052 ) {
22053 if !self.input_enabled {
22054 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22055 return;
22056 }
22057 if let Some(relative_utf16_range) = relative_utf16_range {
22058 let selections = self
22059 .selections
22060 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22061 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22062 let new_ranges = selections.into_iter().map(|range| {
22063 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22064 range
22065 .head()
22066 .0
22067 .0
22068 .saturating_add_signed(relative_utf16_range.start),
22069 ));
22070 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22071 range
22072 .head()
22073 .0
22074 .0
22075 .saturating_add_signed(relative_utf16_range.end),
22076 ));
22077 start..end
22078 });
22079 s.select_ranges(new_ranges);
22080 });
22081 }
22082
22083 self.handle_input(text, window, cx);
22084 }
22085
22086 pub fn is_focused(&self, window: &Window) -> bool {
22087 self.focus_handle.is_focused(window)
22088 }
22089
22090 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22091 cx.emit(EditorEvent::Focused);
22092
22093 if let Some(descendant) = self
22094 .last_focused_descendant
22095 .take()
22096 .and_then(|descendant| descendant.upgrade())
22097 {
22098 window.focus(&descendant);
22099 } else {
22100 if let Some(blame) = self.blame.as_ref() {
22101 blame.update(cx, GitBlame::focus)
22102 }
22103
22104 self.blink_manager.update(cx, BlinkManager::enable);
22105 self.show_cursor_names(window, cx);
22106 self.buffer.update(cx, |buffer, cx| {
22107 buffer.finalize_last_transaction(cx);
22108 if self.leader_id.is_none() {
22109 buffer.set_active_selections(
22110 &self.selections.disjoint_anchors_arc(),
22111 self.selections.line_mode(),
22112 self.cursor_shape,
22113 cx,
22114 );
22115 }
22116 });
22117 }
22118 }
22119
22120 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22121 cx.emit(EditorEvent::FocusedIn)
22122 }
22123
22124 fn handle_focus_out(
22125 &mut self,
22126 event: FocusOutEvent,
22127 _window: &mut Window,
22128 cx: &mut Context<Self>,
22129 ) {
22130 if event.blurred != self.focus_handle {
22131 self.last_focused_descendant = Some(event.blurred);
22132 }
22133 self.selection_drag_state = SelectionDragState::None;
22134 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22135 }
22136
22137 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22138 self.blink_manager.update(cx, BlinkManager::disable);
22139 self.buffer
22140 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22141
22142 if let Some(blame) = self.blame.as_ref() {
22143 blame.update(cx, GitBlame::blur)
22144 }
22145 if !self.hover_state.focused(window, cx) {
22146 hide_hover(self, cx);
22147 }
22148 if !self
22149 .context_menu
22150 .borrow()
22151 .as_ref()
22152 .is_some_and(|context_menu| context_menu.focused(window, cx))
22153 {
22154 self.hide_context_menu(window, cx);
22155 }
22156 self.take_active_edit_prediction(cx);
22157 cx.emit(EditorEvent::Blurred);
22158 cx.notify();
22159 }
22160
22161 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22162 let mut pending: String = window
22163 .pending_input_keystrokes()
22164 .into_iter()
22165 .flatten()
22166 .filter_map(|keystroke| keystroke.key_char.clone())
22167 .collect();
22168
22169 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22170 pending = "".to_string();
22171 }
22172
22173 let existing_pending = self
22174 .text_highlights::<PendingInput>(cx)
22175 .map(|(_, ranges)| ranges.to_vec());
22176 if existing_pending.is_none() && pending.is_empty() {
22177 return;
22178 }
22179 let transaction =
22180 self.transact(window, cx, |this, window, cx| {
22181 let selections = this
22182 .selections
22183 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22184 let edits = selections
22185 .iter()
22186 .map(|selection| (selection.end..selection.end, pending.clone()));
22187 this.edit(edits, cx);
22188 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22189 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22190 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22191 }));
22192 });
22193 if let Some(existing_ranges) = existing_pending {
22194 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22195 this.edit(edits, cx);
22196 }
22197 });
22198
22199 let snapshot = self.snapshot(window, cx);
22200 let ranges = self
22201 .selections
22202 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22203 .into_iter()
22204 .map(|selection| {
22205 snapshot.buffer_snapshot().anchor_after(selection.end)
22206 ..snapshot
22207 .buffer_snapshot()
22208 .anchor_before(selection.end + pending.len())
22209 })
22210 .collect();
22211
22212 if pending.is_empty() {
22213 self.clear_highlights::<PendingInput>(cx);
22214 } else {
22215 self.highlight_text::<PendingInput>(
22216 ranges,
22217 HighlightStyle {
22218 underline: Some(UnderlineStyle {
22219 thickness: px(1.),
22220 color: None,
22221 wavy: false,
22222 }),
22223 ..Default::default()
22224 },
22225 cx,
22226 );
22227 }
22228
22229 self.ime_transaction = self.ime_transaction.or(transaction);
22230 if let Some(transaction) = self.ime_transaction {
22231 self.buffer.update(cx, |buffer, cx| {
22232 buffer.group_until_transaction(transaction, cx);
22233 });
22234 }
22235
22236 if self.text_highlights::<PendingInput>(cx).is_none() {
22237 self.ime_transaction.take();
22238 }
22239 }
22240
22241 pub fn register_action_renderer(
22242 &mut self,
22243 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22244 ) -> Subscription {
22245 let id = self.next_editor_action_id.post_inc();
22246 self.editor_actions
22247 .borrow_mut()
22248 .insert(id, Box::new(listener));
22249
22250 let editor_actions = self.editor_actions.clone();
22251 Subscription::new(move || {
22252 editor_actions.borrow_mut().remove(&id);
22253 })
22254 }
22255
22256 pub fn register_action<A: Action>(
22257 &mut self,
22258 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22259 ) -> Subscription {
22260 let id = self.next_editor_action_id.post_inc();
22261 let listener = Arc::new(listener);
22262 self.editor_actions.borrow_mut().insert(
22263 id,
22264 Box::new(move |_, window, _| {
22265 let listener = listener.clone();
22266 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22267 let action = action.downcast_ref().unwrap();
22268 if phase == DispatchPhase::Bubble {
22269 listener(action, window, cx)
22270 }
22271 })
22272 }),
22273 );
22274
22275 let editor_actions = self.editor_actions.clone();
22276 Subscription::new(move || {
22277 editor_actions.borrow_mut().remove(&id);
22278 })
22279 }
22280
22281 pub fn file_header_size(&self) -> u32 {
22282 FILE_HEADER_HEIGHT
22283 }
22284
22285 pub fn restore(
22286 &mut self,
22287 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22288 window: &mut Window,
22289 cx: &mut Context<Self>,
22290 ) {
22291 let workspace = self.workspace();
22292 let project = self.project();
22293 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22294 let mut tasks = Vec::new();
22295 for (buffer_id, changes) in revert_changes {
22296 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22297 buffer.update(cx, |buffer, cx| {
22298 buffer.edit(
22299 changes
22300 .into_iter()
22301 .map(|(range, text)| (range, text.to_string())),
22302 None,
22303 cx,
22304 );
22305 });
22306
22307 if let Some(project) =
22308 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22309 {
22310 project.update(cx, |project, cx| {
22311 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22312 })
22313 }
22314 }
22315 }
22316 tasks
22317 });
22318 cx.spawn_in(window, async move |_, cx| {
22319 for (buffer, task) in save_tasks {
22320 let result = task.await;
22321 if result.is_err() {
22322 let Some(path) = buffer
22323 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22324 .ok()
22325 else {
22326 continue;
22327 };
22328 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22329 let Some(task) = cx
22330 .update_window_entity(workspace, |workspace, window, cx| {
22331 workspace
22332 .open_path_preview(path, None, false, false, false, window, cx)
22333 })
22334 .ok()
22335 else {
22336 continue;
22337 };
22338 task.await.log_err();
22339 }
22340 }
22341 }
22342 })
22343 .detach();
22344 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22345 selections.refresh()
22346 });
22347 }
22348
22349 pub fn to_pixel_point(
22350 &self,
22351 source: multi_buffer::Anchor,
22352 editor_snapshot: &EditorSnapshot,
22353 window: &mut Window,
22354 ) -> Option<gpui::Point<Pixels>> {
22355 let source_point = source.to_display_point(editor_snapshot);
22356 self.display_to_pixel_point(source_point, editor_snapshot, window)
22357 }
22358
22359 pub fn display_to_pixel_point(
22360 &self,
22361 source: DisplayPoint,
22362 editor_snapshot: &EditorSnapshot,
22363 window: &mut Window,
22364 ) -> Option<gpui::Point<Pixels>> {
22365 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22366 let text_layout_details = self.text_layout_details(window);
22367 let scroll_top = text_layout_details
22368 .scroll_anchor
22369 .scroll_position(editor_snapshot)
22370 .y;
22371
22372 if source.row().as_f64() < scroll_top.floor() {
22373 return None;
22374 }
22375 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22376 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22377 Some(gpui::Point::new(source_x, source_y))
22378 }
22379
22380 pub fn has_visible_completions_menu(&self) -> bool {
22381 !self.edit_prediction_preview_is_active()
22382 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22383 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22384 })
22385 }
22386
22387 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22388 if self.mode.is_minimap() {
22389 return;
22390 }
22391 self.addons
22392 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22393 }
22394
22395 pub fn unregister_addon<T: Addon>(&mut self) {
22396 self.addons.remove(&std::any::TypeId::of::<T>());
22397 }
22398
22399 pub fn addon<T: Addon>(&self) -> Option<&T> {
22400 let type_id = std::any::TypeId::of::<T>();
22401 self.addons
22402 .get(&type_id)
22403 .and_then(|item| item.to_any().downcast_ref::<T>())
22404 }
22405
22406 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22407 let type_id = std::any::TypeId::of::<T>();
22408 self.addons
22409 .get_mut(&type_id)
22410 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22411 }
22412
22413 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22414 let text_layout_details = self.text_layout_details(window);
22415 let style = &text_layout_details.editor_style;
22416 let font_id = window.text_system().resolve_font(&style.text.font());
22417 let font_size = style.text.font_size.to_pixels(window.rem_size());
22418 let line_height = style.text.line_height_in_pixels(window.rem_size());
22419 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22420 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22421
22422 CharacterDimensions {
22423 em_width,
22424 em_advance,
22425 line_height,
22426 }
22427 }
22428
22429 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22430 self.load_diff_task.clone()
22431 }
22432
22433 fn read_metadata_from_db(
22434 &mut self,
22435 item_id: u64,
22436 workspace_id: WorkspaceId,
22437 window: &mut Window,
22438 cx: &mut Context<Editor>,
22439 ) {
22440 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22441 && !self.mode.is_minimap()
22442 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22443 {
22444 let buffer_snapshot = OnceCell::new();
22445
22446 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22447 && !folds.is_empty()
22448 {
22449 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22450 self.fold_ranges(
22451 folds
22452 .into_iter()
22453 .map(|(start, end)| {
22454 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22455 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22456 })
22457 .collect(),
22458 false,
22459 window,
22460 cx,
22461 );
22462 }
22463
22464 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22465 && !selections.is_empty()
22466 {
22467 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22468 // skip adding the initial selection to selection history
22469 self.selection_history.mode = SelectionHistoryMode::Skipping;
22470 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22471 s.select_ranges(selections.into_iter().map(|(start, end)| {
22472 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22473 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22474 }));
22475 });
22476 self.selection_history.mode = SelectionHistoryMode::Normal;
22477 };
22478 }
22479
22480 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22481 }
22482
22483 fn update_lsp_data(
22484 &mut self,
22485 for_buffer: Option<BufferId>,
22486 window: &mut Window,
22487 cx: &mut Context<'_, Self>,
22488 ) {
22489 self.pull_diagnostics(for_buffer, window, cx);
22490 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22491 }
22492
22493 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22494 if self.ignore_lsp_data() {
22495 return;
22496 }
22497 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22498 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22499 }
22500 }
22501
22502 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22503 if self.ignore_lsp_data() {
22504 return;
22505 }
22506
22507 if !self.registered_buffers.contains_key(&buffer_id)
22508 && let Some(project) = self.project.as_ref()
22509 {
22510 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22511 project.update(cx, |project, cx| {
22512 self.registered_buffers.insert(
22513 buffer_id,
22514 project.register_buffer_with_language_servers(&buffer, cx),
22515 );
22516 });
22517 } else {
22518 self.registered_buffers.remove(&buffer_id);
22519 }
22520 }
22521 }
22522
22523 fn ignore_lsp_data(&self) -> bool {
22524 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22525 // skip any LSP updates for it.
22526 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22527 }
22528}
22529
22530fn edit_for_markdown_paste<'a>(
22531 buffer: &MultiBufferSnapshot,
22532 range: Range<MultiBufferOffset>,
22533 to_insert: &'a str,
22534 url: Option<url::Url>,
22535) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
22536 if url.is_none() {
22537 return (range, Cow::Borrowed(to_insert));
22538 };
22539
22540 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22541
22542 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22543 Cow::Borrowed(to_insert)
22544 } else {
22545 Cow::Owned(format!("[{old_text}]({to_insert})"))
22546 };
22547 (range, new_text)
22548}
22549
22550fn process_completion_for_edit(
22551 completion: &Completion,
22552 intent: CompletionIntent,
22553 buffer: &Entity<Buffer>,
22554 cursor_position: &text::Anchor,
22555 cx: &mut Context<Editor>,
22556) -> CompletionEdit {
22557 let buffer = buffer.read(cx);
22558 let buffer_snapshot = buffer.snapshot();
22559 let (snippet, new_text) = if completion.is_snippet() {
22560 let mut snippet_source = completion.new_text.clone();
22561 // Workaround for typescript language server issues so that methods don't expand within
22562 // strings and functions with type expressions. The previous point is used because the query
22563 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22564 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22565 let previous_point = if previous_point.column > 0 {
22566 cursor_position.to_previous_offset(&buffer_snapshot)
22567 } else {
22568 cursor_position.to_offset(&buffer_snapshot)
22569 };
22570 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22571 && scope.prefers_label_for_snippet_in_completion()
22572 && let Some(label) = completion.label()
22573 && matches!(
22574 completion.kind(),
22575 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22576 )
22577 {
22578 snippet_source = label;
22579 }
22580 match Snippet::parse(&snippet_source).log_err() {
22581 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22582 None => (None, completion.new_text.clone()),
22583 }
22584 } else {
22585 (None, completion.new_text.clone())
22586 };
22587
22588 let mut range_to_replace = {
22589 let replace_range = &completion.replace_range;
22590 if let CompletionSource::Lsp {
22591 insert_range: Some(insert_range),
22592 ..
22593 } = &completion.source
22594 {
22595 debug_assert_eq!(
22596 insert_range.start, replace_range.start,
22597 "insert_range and replace_range should start at the same position"
22598 );
22599 debug_assert!(
22600 insert_range
22601 .start
22602 .cmp(cursor_position, &buffer_snapshot)
22603 .is_le(),
22604 "insert_range should start before or at cursor position"
22605 );
22606 debug_assert!(
22607 replace_range
22608 .start
22609 .cmp(cursor_position, &buffer_snapshot)
22610 .is_le(),
22611 "replace_range should start before or at cursor position"
22612 );
22613
22614 let should_replace = match intent {
22615 CompletionIntent::CompleteWithInsert => false,
22616 CompletionIntent::CompleteWithReplace => true,
22617 CompletionIntent::Complete | CompletionIntent::Compose => {
22618 let insert_mode =
22619 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22620 .completions
22621 .lsp_insert_mode;
22622 match insert_mode {
22623 LspInsertMode::Insert => false,
22624 LspInsertMode::Replace => true,
22625 LspInsertMode::ReplaceSubsequence => {
22626 let mut text_to_replace = buffer.chars_for_range(
22627 buffer.anchor_before(replace_range.start)
22628 ..buffer.anchor_after(replace_range.end),
22629 );
22630 let mut current_needle = text_to_replace.next();
22631 for haystack_ch in completion.label.text.chars() {
22632 if let Some(needle_ch) = current_needle
22633 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22634 {
22635 current_needle = text_to_replace.next();
22636 }
22637 }
22638 current_needle.is_none()
22639 }
22640 LspInsertMode::ReplaceSuffix => {
22641 if replace_range
22642 .end
22643 .cmp(cursor_position, &buffer_snapshot)
22644 .is_gt()
22645 {
22646 let range_after_cursor = *cursor_position..replace_range.end;
22647 let text_after_cursor = buffer
22648 .text_for_range(
22649 buffer.anchor_before(range_after_cursor.start)
22650 ..buffer.anchor_after(range_after_cursor.end),
22651 )
22652 .collect::<String>()
22653 .to_ascii_lowercase();
22654 completion
22655 .label
22656 .text
22657 .to_ascii_lowercase()
22658 .ends_with(&text_after_cursor)
22659 } else {
22660 true
22661 }
22662 }
22663 }
22664 }
22665 };
22666
22667 if should_replace {
22668 replace_range.clone()
22669 } else {
22670 insert_range.clone()
22671 }
22672 } else {
22673 replace_range.clone()
22674 }
22675 };
22676
22677 if range_to_replace
22678 .end
22679 .cmp(cursor_position, &buffer_snapshot)
22680 .is_lt()
22681 {
22682 range_to_replace.end = *cursor_position;
22683 }
22684
22685 let replace_range = range_to_replace.to_offset(buffer);
22686 CompletionEdit {
22687 new_text,
22688 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
22689 snippet,
22690 }
22691}
22692
22693struct CompletionEdit {
22694 new_text: String,
22695 replace_range: Range<BufferOffset>,
22696 snippet: Option<Snippet>,
22697}
22698
22699fn insert_extra_newline_brackets(
22700 buffer: &MultiBufferSnapshot,
22701 range: Range<MultiBufferOffset>,
22702 language: &language::LanguageScope,
22703) -> bool {
22704 let leading_whitespace_len = buffer
22705 .reversed_chars_at(range.start)
22706 .take_while(|c| c.is_whitespace() && *c != '\n')
22707 .map(|c| c.len_utf8())
22708 .sum::<usize>();
22709 let trailing_whitespace_len = buffer
22710 .chars_at(range.end)
22711 .take_while(|c| c.is_whitespace() && *c != '\n')
22712 .map(|c| c.len_utf8())
22713 .sum::<usize>();
22714 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22715
22716 language.brackets().any(|(pair, enabled)| {
22717 let pair_start = pair.start.trim_end();
22718 let pair_end = pair.end.trim_start();
22719
22720 enabled
22721 && pair.newline
22722 && buffer.contains_str_at(range.end, pair_end)
22723 && buffer.contains_str_at(
22724 range.start.saturating_sub_usize(pair_start.len()),
22725 pair_start,
22726 )
22727 })
22728}
22729
22730fn insert_extra_newline_tree_sitter(
22731 buffer: &MultiBufferSnapshot,
22732 range: Range<MultiBufferOffset>,
22733) -> bool {
22734 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22735 [(buffer, range, _)] => (*buffer, range.clone()),
22736 _ => return false,
22737 };
22738 let pair = {
22739 let mut result: Option<BracketMatch<usize>> = None;
22740
22741 for pair in buffer
22742 .all_bracket_ranges(range.start.0..range.end.0)
22743 .filter(move |pair| {
22744 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
22745 })
22746 {
22747 let len = pair.close_range.end - pair.open_range.start;
22748
22749 if let Some(existing) = &result {
22750 let existing_len = existing.close_range.end - existing.open_range.start;
22751 if len > existing_len {
22752 continue;
22753 }
22754 }
22755
22756 result = Some(pair);
22757 }
22758
22759 result
22760 };
22761 let Some(pair) = pair else {
22762 return false;
22763 };
22764 pair.newline_only
22765 && buffer
22766 .chars_for_range(pair.open_range.end..range.start.0)
22767 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
22768 .all(|c| c.is_whitespace() && c != '\n')
22769}
22770
22771fn update_uncommitted_diff_for_buffer(
22772 editor: Entity<Editor>,
22773 project: &Entity<Project>,
22774 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22775 buffer: Entity<MultiBuffer>,
22776 cx: &mut App,
22777) -> Task<()> {
22778 let mut tasks = Vec::new();
22779 project.update(cx, |project, cx| {
22780 for buffer in buffers {
22781 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22782 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22783 }
22784 }
22785 });
22786 cx.spawn(async move |cx| {
22787 let diffs = future::join_all(tasks).await;
22788 if editor
22789 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22790 .unwrap_or(false)
22791 {
22792 return;
22793 }
22794
22795 buffer
22796 .update(cx, |buffer, cx| {
22797 for diff in diffs.into_iter().flatten() {
22798 buffer.add_diff(diff, cx);
22799 }
22800 })
22801 .ok();
22802 })
22803}
22804
22805fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22806 let tab_size = tab_size.get() as usize;
22807 let mut width = offset;
22808
22809 for ch in text.chars() {
22810 width += if ch == '\t' {
22811 tab_size - (width % tab_size)
22812 } else {
22813 1
22814 };
22815 }
22816
22817 width - offset
22818}
22819
22820#[cfg(test)]
22821mod tests {
22822 use super::*;
22823
22824 #[test]
22825 fn test_string_size_with_expanded_tabs() {
22826 let nz = |val| NonZeroU32::new(val).unwrap();
22827 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22828 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22829 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22830 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22831 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22832 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22833 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22834 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22835 }
22836}
22837
22838/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22839struct WordBreakingTokenizer<'a> {
22840 input: &'a str,
22841}
22842
22843impl<'a> WordBreakingTokenizer<'a> {
22844 fn new(input: &'a str) -> Self {
22845 Self { input }
22846 }
22847}
22848
22849fn is_char_ideographic(ch: char) -> bool {
22850 use unicode_script::Script::*;
22851 use unicode_script::UnicodeScript;
22852 matches!(ch.script(), Han | Tangut | Yi)
22853}
22854
22855fn is_grapheme_ideographic(text: &str) -> bool {
22856 text.chars().any(is_char_ideographic)
22857}
22858
22859fn is_grapheme_whitespace(text: &str) -> bool {
22860 text.chars().any(|x| x.is_whitespace())
22861}
22862
22863fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22864 text.chars()
22865 .next()
22866 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22867}
22868
22869#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22870enum WordBreakToken<'a> {
22871 Word { token: &'a str, grapheme_len: usize },
22872 InlineWhitespace { token: &'a str, grapheme_len: usize },
22873 Newline,
22874}
22875
22876impl<'a> Iterator for WordBreakingTokenizer<'a> {
22877 /// Yields a span, the count of graphemes in the token, and whether it was
22878 /// whitespace. Note that it also breaks at word boundaries.
22879 type Item = WordBreakToken<'a>;
22880
22881 fn next(&mut self) -> Option<Self::Item> {
22882 use unicode_segmentation::UnicodeSegmentation;
22883 if self.input.is_empty() {
22884 return None;
22885 }
22886
22887 let mut iter = self.input.graphemes(true).peekable();
22888 let mut offset = 0;
22889 let mut grapheme_len = 0;
22890 if let Some(first_grapheme) = iter.next() {
22891 let is_newline = first_grapheme == "\n";
22892 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22893 offset += first_grapheme.len();
22894 grapheme_len += 1;
22895 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22896 if let Some(grapheme) = iter.peek().copied()
22897 && should_stay_with_preceding_ideograph(grapheme)
22898 {
22899 offset += grapheme.len();
22900 grapheme_len += 1;
22901 }
22902 } else {
22903 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22904 let mut next_word_bound = words.peek().copied();
22905 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22906 next_word_bound = words.next();
22907 }
22908 while let Some(grapheme) = iter.peek().copied() {
22909 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22910 break;
22911 };
22912 if is_grapheme_whitespace(grapheme) != is_whitespace
22913 || (grapheme == "\n") != is_newline
22914 {
22915 break;
22916 };
22917 offset += grapheme.len();
22918 grapheme_len += 1;
22919 iter.next();
22920 }
22921 }
22922 let token = &self.input[..offset];
22923 self.input = &self.input[offset..];
22924 if token == "\n" {
22925 Some(WordBreakToken::Newline)
22926 } else if is_whitespace {
22927 Some(WordBreakToken::InlineWhitespace {
22928 token,
22929 grapheme_len,
22930 })
22931 } else {
22932 Some(WordBreakToken::Word {
22933 token,
22934 grapheme_len,
22935 })
22936 }
22937 } else {
22938 None
22939 }
22940 }
22941}
22942
22943#[test]
22944fn test_word_breaking_tokenizer() {
22945 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22946 ("", &[]),
22947 (" ", &[whitespace(" ", 2)]),
22948 ("Ʒ", &[word("Ʒ", 1)]),
22949 ("Ǽ", &[word("Ǽ", 1)]),
22950 ("⋑", &[word("⋑", 1)]),
22951 ("⋑⋑", &[word("⋑⋑", 2)]),
22952 (
22953 "原理,进而",
22954 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22955 ),
22956 (
22957 "hello world",
22958 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22959 ),
22960 (
22961 "hello, world",
22962 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22963 ),
22964 (
22965 " hello world",
22966 &[
22967 whitespace(" ", 2),
22968 word("hello", 5),
22969 whitespace(" ", 1),
22970 word("world", 5),
22971 ],
22972 ),
22973 (
22974 "这是什么 \n 钢笔",
22975 &[
22976 word("这", 1),
22977 word("是", 1),
22978 word("什", 1),
22979 word("么", 1),
22980 whitespace(" ", 1),
22981 newline(),
22982 whitespace(" ", 1),
22983 word("钢", 1),
22984 word("笔", 1),
22985 ],
22986 ),
22987 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22988 ];
22989
22990 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22991 WordBreakToken::Word {
22992 token,
22993 grapheme_len,
22994 }
22995 }
22996
22997 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22998 WordBreakToken::InlineWhitespace {
22999 token,
23000 grapheme_len,
23001 }
23002 }
23003
23004 fn newline() -> WordBreakToken<'static> {
23005 WordBreakToken::Newline
23006 }
23007
23008 for (input, result) in tests {
23009 assert_eq!(
23010 WordBreakingTokenizer::new(input)
23011 .collect::<Vec<_>>()
23012 .as_slice(),
23013 *result,
23014 );
23015 }
23016}
23017
23018fn wrap_with_prefix(
23019 first_line_prefix: String,
23020 subsequent_lines_prefix: String,
23021 unwrapped_text: String,
23022 wrap_column: usize,
23023 tab_size: NonZeroU32,
23024 preserve_existing_whitespace: bool,
23025) -> String {
23026 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23027 let subsequent_lines_prefix_len =
23028 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23029 let mut wrapped_text = String::new();
23030 let mut current_line = first_line_prefix;
23031 let mut is_first_line = true;
23032
23033 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23034 let mut current_line_len = first_line_prefix_len;
23035 let mut in_whitespace = false;
23036 for token in tokenizer {
23037 let have_preceding_whitespace = in_whitespace;
23038 match token {
23039 WordBreakToken::Word {
23040 token,
23041 grapheme_len,
23042 } => {
23043 in_whitespace = false;
23044 let current_prefix_len = if is_first_line {
23045 first_line_prefix_len
23046 } else {
23047 subsequent_lines_prefix_len
23048 };
23049 if current_line_len + grapheme_len > wrap_column
23050 && current_line_len != current_prefix_len
23051 {
23052 wrapped_text.push_str(current_line.trim_end());
23053 wrapped_text.push('\n');
23054 is_first_line = false;
23055 current_line = subsequent_lines_prefix.clone();
23056 current_line_len = subsequent_lines_prefix_len;
23057 }
23058 current_line.push_str(token);
23059 current_line_len += grapheme_len;
23060 }
23061 WordBreakToken::InlineWhitespace {
23062 mut token,
23063 mut grapheme_len,
23064 } => {
23065 in_whitespace = true;
23066 if have_preceding_whitespace && !preserve_existing_whitespace {
23067 continue;
23068 }
23069 if !preserve_existing_whitespace {
23070 // Keep a single whitespace grapheme as-is
23071 if let Some(first) =
23072 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23073 {
23074 token = first;
23075 } else {
23076 token = " ";
23077 }
23078 grapheme_len = 1;
23079 }
23080 let current_prefix_len = if is_first_line {
23081 first_line_prefix_len
23082 } else {
23083 subsequent_lines_prefix_len
23084 };
23085 if current_line_len + grapheme_len > wrap_column {
23086 wrapped_text.push_str(current_line.trim_end());
23087 wrapped_text.push('\n');
23088 is_first_line = false;
23089 current_line = subsequent_lines_prefix.clone();
23090 current_line_len = subsequent_lines_prefix_len;
23091 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23092 current_line.push_str(token);
23093 current_line_len += grapheme_len;
23094 }
23095 }
23096 WordBreakToken::Newline => {
23097 in_whitespace = true;
23098 let current_prefix_len = if is_first_line {
23099 first_line_prefix_len
23100 } else {
23101 subsequent_lines_prefix_len
23102 };
23103 if preserve_existing_whitespace {
23104 wrapped_text.push_str(current_line.trim_end());
23105 wrapped_text.push('\n');
23106 is_first_line = false;
23107 current_line = subsequent_lines_prefix.clone();
23108 current_line_len = subsequent_lines_prefix_len;
23109 } else if have_preceding_whitespace {
23110 continue;
23111 } else if current_line_len + 1 > wrap_column
23112 && current_line_len != current_prefix_len
23113 {
23114 wrapped_text.push_str(current_line.trim_end());
23115 wrapped_text.push('\n');
23116 is_first_line = false;
23117 current_line = subsequent_lines_prefix.clone();
23118 current_line_len = subsequent_lines_prefix_len;
23119 } else if current_line_len != current_prefix_len {
23120 current_line.push(' ');
23121 current_line_len += 1;
23122 }
23123 }
23124 }
23125 }
23126
23127 if !current_line.is_empty() {
23128 wrapped_text.push_str(¤t_line);
23129 }
23130 wrapped_text
23131}
23132
23133#[test]
23134fn test_wrap_with_prefix() {
23135 assert_eq!(
23136 wrap_with_prefix(
23137 "# ".to_string(),
23138 "# ".to_string(),
23139 "abcdefg".to_string(),
23140 4,
23141 NonZeroU32::new(4).unwrap(),
23142 false,
23143 ),
23144 "# abcdefg"
23145 );
23146 assert_eq!(
23147 wrap_with_prefix(
23148 "".to_string(),
23149 "".to_string(),
23150 "\thello world".to_string(),
23151 8,
23152 NonZeroU32::new(4).unwrap(),
23153 false,
23154 ),
23155 "hello\nworld"
23156 );
23157 assert_eq!(
23158 wrap_with_prefix(
23159 "// ".to_string(),
23160 "// ".to_string(),
23161 "xx \nyy zz aa bb cc".to_string(),
23162 12,
23163 NonZeroU32::new(4).unwrap(),
23164 false,
23165 ),
23166 "// xx yy zz\n// aa bb cc"
23167 );
23168 assert_eq!(
23169 wrap_with_prefix(
23170 String::new(),
23171 String::new(),
23172 "这是什么 \n 钢笔".to_string(),
23173 3,
23174 NonZeroU32::new(4).unwrap(),
23175 false,
23176 ),
23177 "这是什\n么 钢\n笔"
23178 );
23179 assert_eq!(
23180 wrap_with_prefix(
23181 String::new(),
23182 String::new(),
23183 format!("foo{}bar", '\u{2009}'), // thin space
23184 80,
23185 NonZeroU32::new(4).unwrap(),
23186 false,
23187 ),
23188 format!("foo{}bar", '\u{2009}')
23189 );
23190}
23191
23192pub trait CollaborationHub {
23193 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23194 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23195 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23196}
23197
23198impl CollaborationHub for Entity<Project> {
23199 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23200 self.read(cx).collaborators()
23201 }
23202
23203 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23204 self.read(cx).user_store().read(cx).participant_indices()
23205 }
23206
23207 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23208 let this = self.read(cx);
23209 let user_ids = this.collaborators().values().map(|c| c.user_id);
23210 this.user_store().read(cx).participant_names(user_ids, cx)
23211 }
23212}
23213
23214pub trait SemanticsProvider {
23215 fn hover(
23216 &self,
23217 buffer: &Entity<Buffer>,
23218 position: text::Anchor,
23219 cx: &mut App,
23220 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23221
23222 fn inline_values(
23223 &self,
23224 buffer_handle: Entity<Buffer>,
23225 range: Range<text::Anchor>,
23226 cx: &mut App,
23227 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23228
23229 fn applicable_inlay_chunks(
23230 &self,
23231 buffer: &Entity<Buffer>,
23232 ranges: &[Range<text::Anchor>],
23233 cx: &mut App,
23234 ) -> Vec<Range<BufferRow>>;
23235
23236 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23237
23238 fn inlay_hints(
23239 &self,
23240 invalidate: InvalidationStrategy,
23241 buffer: Entity<Buffer>,
23242 ranges: Vec<Range<text::Anchor>>,
23243 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23244 cx: &mut App,
23245 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23246
23247 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23248
23249 fn document_highlights(
23250 &self,
23251 buffer: &Entity<Buffer>,
23252 position: text::Anchor,
23253 cx: &mut App,
23254 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23255
23256 fn definitions(
23257 &self,
23258 buffer: &Entity<Buffer>,
23259 position: text::Anchor,
23260 kind: GotoDefinitionKind,
23261 cx: &mut App,
23262 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23263
23264 fn range_for_rename(
23265 &self,
23266 buffer: &Entity<Buffer>,
23267 position: text::Anchor,
23268 cx: &mut App,
23269 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23270
23271 fn perform_rename(
23272 &self,
23273 buffer: &Entity<Buffer>,
23274 position: text::Anchor,
23275 new_name: String,
23276 cx: &mut App,
23277 ) -> Option<Task<Result<ProjectTransaction>>>;
23278}
23279
23280pub trait CompletionProvider {
23281 fn completions(
23282 &self,
23283 excerpt_id: ExcerptId,
23284 buffer: &Entity<Buffer>,
23285 buffer_position: text::Anchor,
23286 trigger: CompletionContext,
23287 window: &mut Window,
23288 cx: &mut Context<Editor>,
23289 ) -> Task<Result<Vec<CompletionResponse>>>;
23290
23291 fn resolve_completions(
23292 &self,
23293 _buffer: Entity<Buffer>,
23294 _completion_indices: Vec<usize>,
23295 _completions: Rc<RefCell<Box<[Completion]>>>,
23296 _cx: &mut Context<Editor>,
23297 ) -> Task<Result<bool>> {
23298 Task::ready(Ok(false))
23299 }
23300
23301 fn apply_additional_edits_for_completion(
23302 &self,
23303 _buffer: Entity<Buffer>,
23304 _completions: Rc<RefCell<Box<[Completion]>>>,
23305 _completion_index: usize,
23306 _push_to_history: bool,
23307 _cx: &mut Context<Editor>,
23308 ) -> Task<Result<Option<language::Transaction>>> {
23309 Task::ready(Ok(None))
23310 }
23311
23312 fn is_completion_trigger(
23313 &self,
23314 buffer: &Entity<Buffer>,
23315 position: language::Anchor,
23316 text: &str,
23317 trigger_in_words: bool,
23318 menu_is_open: bool,
23319 cx: &mut Context<Editor>,
23320 ) -> bool;
23321
23322 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23323
23324 fn sort_completions(&self) -> bool {
23325 true
23326 }
23327
23328 fn filter_completions(&self) -> bool {
23329 true
23330 }
23331
23332 fn show_snippets(&self) -> bool {
23333 false
23334 }
23335}
23336
23337pub trait CodeActionProvider {
23338 fn id(&self) -> Arc<str>;
23339
23340 fn code_actions(
23341 &self,
23342 buffer: &Entity<Buffer>,
23343 range: Range<text::Anchor>,
23344 window: &mut Window,
23345 cx: &mut App,
23346 ) -> Task<Result<Vec<CodeAction>>>;
23347
23348 fn apply_code_action(
23349 &self,
23350 buffer_handle: Entity<Buffer>,
23351 action: CodeAction,
23352 excerpt_id: ExcerptId,
23353 push_to_history: bool,
23354 window: &mut Window,
23355 cx: &mut App,
23356 ) -> Task<Result<ProjectTransaction>>;
23357}
23358
23359impl CodeActionProvider for Entity<Project> {
23360 fn id(&self) -> Arc<str> {
23361 "project".into()
23362 }
23363
23364 fn code_actions(
23365 &self,
23366 buffer: &Entity<Buffer>,
23367 range: Range<text::Anchor>,
23368 _window: &mut Window,
23369 cx: &mut App,
23370 ) -> Task<Result<Vec<CodeAction>>> {
23371 self.update(cx, |project, cx| {
23372 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23373 let code_actions = project.code_actions(buffer, range, None, cx);
23374 cx.background_spawn(async move {
23375 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23376 Ok(code_lens_actions
23377 .context("code lens fetch")?
23378 .into_iter()
23379 .flatten()
23380 .chain(
23381 code_actions
23382 .context("code action fetch")?
23383 .into_iter()
23384 .flatten(),
23385 )
23386 .collect())
23387 })
23388 })
23389 }
23390
23391 fn apply_code_action(
23392 &self,
23393 buffer_handle: Entity<Buffer>,
23394 action: CodeAction,
23395 _excerpt_id: ExcerptId,
23396 push_to_history: bool,
23397 _window: &mut Window,
23398 cx: &mut App,
23399 ) -> Task<Result<ProjectTransaction>> {
23400 self.update(cx, |project, cx| {
23401 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23402 })
23403 }
23404}
23405
23406fn snippet_completions(
23407 project: &Project,
23408 buffer: &Entity<Buffer>,
23409 buffer_anchor: text::Anchor,
23410 classifier: CharClassifier,
23411 cx: &mut App,
23412) -> Task<Result<CompletionResponse>> {
23413 let languages = buffer.read(cx).languages_at(buffer_anchor);
23414 let snippet_store = project.snippets().read(cx);
23415
23416 let scopes: Vec<_> = languages
23417 .iter()
23418 .filter_map(|language| {
23419 let language_name = language.lsp_id();
23420 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23421
23422 if snippets.is_empty() {
23423 None
23424 } else {
23425 Some((language.default_scope(), snippets))
23426 }
23427 })
23428 .collect();
23429
23430 if scopes.is_empty() {
23431 return Task::ready(Ok(CompletionResponse {
23432 completions: vec![],
23433 display_options: CompletionDisplayOptions::default(),
23434 is_incomplete: false,
23435 }));
23436 }
23437
23438 let snapshot = buffer.read(cx).text_snapshot();
23439 let executor = cx.background_executor().clone();
23440
23441 cx.background_spawn(async move {
23442 let is_word_char = |c| classifier.is_word(c);
23443
23444 let mut is_incomplete = false;
23445 let mut completions: Vec<Completion> = Vec::new();
23446
23447 const MAX_PREFIX_LEN: usize = 128;
23448 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23449 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23450 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23451
23452 let max_buffer_window: String = snapshot
23453 .text_for_range(window_start..buffer_offset)
23454 .collect();
23455
23456 if max_buffer_window.is_empty() {
23457 return Ok(CompletionResponse {
23458 completions: vec![],
23459 display_options: CompletionDisplayOptions::default(),
23460 is_incomplete: true,
23461 });
23462 }
23463
23464 for (_scope, snippets) in scopes.into_iter() {
23465 // Sort snippets by word count to match longer snippet prefixes first.
23466 let mut sorted_snippet_candidates = snippets
23467 .iter()
23468 .enumerate()
23469 .flat_map(|(snippet_ix, snippet)| {
23470 snippet
23471 .prefix
23472 .iter()
23473 .enumerate()
23474 .map(move |(prefix_ix, prefix)| {
23475 let word_count =
23476 snippet_candidate_suffixes(prefix, is_word_char).count();
23477 ((snippet_ix, prefix_ix), prefix, word_count)
23478 })
23479 })
23480 .collect_vec();
23481 sorted_snippet_candidates
23482 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23483
23484 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23485
23486 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23487 .take(
23488 sorted_snippet_candidates
23489 .first()
23490 .map(|(_, _, word_count)| *word_count)
23491 .unwrap_or_default(),
23492 )
23493 .collect_vec();
23494
23495 const MAX_RESULTS: usize = 100;
23496 // Each match also remembers how many characters from the buffer it consumed
23497 let mut matches: Vec<(StringMatch, usize)> = vec![];
23498
23499 let mut snippet_list_cutoff_index = 0;
23500 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23501 let word_count = buffer_index + 1;
23502 // Increase `snippet_list_cutoff_index` until we have all of the
23503 // snippets with sufficiently many words.
23504 while sorted_snippet_candidates
23505 .get(snippet_list_cutoff_index)
23506 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23507 *snippet_word_count >= word_count
23508 })
23509 {
23510 snippet_list_cutoff_index += 1;
23511 }
23512
23513 // Take only the candidates with at least `word_count` many words
23514 let snippet_candidates_at_word_len =
23515 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23516
23517 let candidates = snippet_candidates_at_word_len
23518 .iter()
23519 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23520 .enumerate() // index in `sorted_snippet_candidates`
23521 // First char must match
23522 .filter(|(_ix, prefix)| {
23523 itertools::equal(
23524 prefix
23525 .chars()
23526 .next()
23527 .into_iter()
23528 .flat_map(|c| c.to_lowercase()),
23529 buffer_window
23530 .chars()
23531 .next()
23532 .into_iter()
23533 .flat_map(|c| c.to_lowercase()),
23534 )
23535 })
23536 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23537 .collect::<Vec<StringMatchCandidate>>();
23538
23539 matches.extend(
23540 fuzzy::match_strings(
23541 &candidates,
23542 &buffer_window,
23543 buffer_window.chars().any(|c| c.is_uppercase()),
23544 true,
23545 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23546 &Default::default(),
23547 executor.clone(),
23548 )
23549 .await
23550 .into_iter()
23551 .map(|string_match| (string_match, buffer_window.len())),
23552 );
23553
23554 if matches.len() >= MAX_RESULTS {
23555 break;
23556 }
23557 }
23558
23559 let to_lsp = |point: &text::Anchor| {
23560 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23561 point_to_lsp(end)
23562 };
23563 let lsp_end = to_lsp(&buffer_anchor);
23564
23565 if matches.len() >= MAX_RESULTS {
23566 is_incomplete = true;
23567 }
23568
23569 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23570 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23571 sorted_snippet_candidates[string_match.candidate_id];
23572 let snippet = &snippets[snippet_index];
23573 let start = buffer_offset - buffer_window_len;
23574 let start = snapshot.anchor_before(start);
23575 let range = start..buffer_anchor;
23576 let lsp_start = to_lsp(&start);
23577 let lsp_range = lsp::Range {
23578 start: lsp_start,
23579 end: lsp_end,
23580 };
23581 Completion {
23582 replace_range: range,
23583 new_text: snippet.body.clone(),
23584 source: CompletionSource::Lsp {
23585 insert_range: None,
23586 server_id: LanguageServerId(usize::MAX),
23587 resolved: true,
23588 lsp_completion: Box::new(lsp::CompletionItem {
23589 label: snippet.prefix.first().unwrap().clone(),
23590 kind: Some(CompletionItemKind::SNIPPET),
23591 label_details: snippet.description.as_ref().map(|description| {
23592 lsp::CompletionItemLabelDetails {
23593 detail: Some(description.clone()),
23594 description: None,
23595 }
23596 }),
23597 insert_text_format: Some(InsertTextFormat::SNIPPET),
23598 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23599 lsp::InsertReplaceEdit {
23600 new_text: snippet.body.clone(),
23601 insert: lsp_range,
23602 replace: lsp_range,
23603 },
23604 )),
23605 filter_text: Some(snippet.body.clone()),
23606 sort_text: Some(char::MAX.to_string()),
23607 ..lsp::CompletionItem::default()
23608 }),
23609 lsp_defaults: None,
23610 },
23611 label: CodeLabel {
23612 text: matching_prefix.clone(),
23613 runs: Vec::new(),
23614 filter_range: 0..matching_prefix.len(),
23615 },
23616 icon_path: None,
23617 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23618 single_line: snippet.name.clone().into(),
23619 plain_text: snippet
23620 .description
23621 .clone()
23622 .map(|description| description.into()),
23623 }),
23624 insert_text_mode: None,
23625 confirm: None,
23626 match_start: Some(start),
23627 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23628 }
23629 }));
23630 }
23631
23632 Ok(CompletionResponse {
23633 completions,
23634 display_options: CompletionDisplayOptions::default(),
23635 is_incomplete,
23636 })
23637 })
23638}
23639
23640impl CompletionProvider for Entity<Project> {
23641 fn completions(
23642 &self,
23643 _excerpt_id: ExcerptId,
23644 buffer: &Entity<Buffer>,
23645 buffer_position: text::Anchor,
23646 options: CompletionContext,
23647 _window: &mut Window,
23648 cx: &mut Context<Editor>,
23649 ) -> Task<Result<Vec<CompletionResponse>>> {
23650 self.update(cx, |project, cx| {
23651 let task = project.completions(buffer, buffer_position, options, cx);
23652 cx.background_spawn(task)
23653 })
23654 }
23655
23656 fn resolve_completions(
23657 &self,
23658 buffer: Entity<Buffer>,
23659 completion_indices: Vec<usize>,
23660 completions: Rc<RefCell<Box<[Completion]>>>,
23661 cx: &mut Context<Editor>,
23662 ) -> Task<Result<bool>> {
23663 self.update(cx, |project, cx| {
23664 project.lsp_store().update(cx, |lsp_store, cx| {
23665 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23666 })
23667 })
23668 }
23669
23670 fn apply_additional_edits_for_completion(
23671 &self,
23672 buffer: Entity<Buffer>,
23673 completions: Rc<RefCell<Box<[Completion]>>>,
23674 completion_index: usize,
23675 push_to_history: bool,
23676 cx: &mut Context<Editor>,
23677 ) -> Task<Result<Option<language::Transaction>>> {
23678 self.update(cx, |project, cx| {
23679 project.lsp_store().update(cx, |lsp_store, cx| {
23680 lsp_store.apply_additional_edits_for_completion(
23681 buffer,
23682 completions,
23683 completion_index,
23684 push_to_history,
23685 cx,
23686 )
23687 })
23688 })
23689 }
23690
23691 fn is_completion_trigger(
23692 &self,
23693 buffer: &Entity<Buffer>,
23694 position: language::Anchor,
23695 text: &str,
23696 trigger_in_words: bool,
23697 menu_is_open: bool,
23698 cx: &mut Context<Editor>,
23699 ) -> bool {
23700 let mut chars = text.chars();
23701 let char = if let Some(char) = chars.next() {
23702 char
23703 } else {
23704 return false;
23705 };
23706 if chars.next().is_some() {
23707 return false;
23708 }
23709
23710 let buffer = buffer.read(cx);
23711 let snapshot = buffer.snapshot();
23712 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23713 return false;
23714 }
23715 let classifier = snapshot
23716 .char_classifier_at(position)
23717 .scope_context(Some(CharScopeContext::Completion));
23718 if trigger_in_words && classifier.is_word(char) {
23719 return true;
23720 }
23721
23722 buffer.completion_triggers().contains(text)
23723 }
23724
23725 fn show_snippets(&self) -> bool {
23726 true
23727 }
23728}
23729
23730impl SemanticsProvider for Entity<Project> {
23731 fn hover(
23732 &self,
23733 buffer: &Entity<Buffer>,
23734 position: text::Anchor,
23735 cx: &mut App,
23736 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23737 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23738 }
23739
23740 fn document_highlights(
23741 &self,
23742 buffer: &Entity<Buffer>,
23743 position: text::Anchor,
23744 cx: &mut App,
23745 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23746 Some(self.update(cx, |project, cx| {
23747 project.document_highlights(buffer, position, cx)
23748 }))
23749 }
23750
23751 fn definitions(
23752 &self,
23753 buffer: &Entity<Buffer>,
23754 position: text::Anchor,
23755 kind: GotoDefinitionKind,
23756 cx: &mut App,
23757 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23758 Some(self.update(cx, |project, cx| match kind {
23759 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23760 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23761 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23762 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23763 }))
23764 }
23765
23766 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23767 self.update(cx, |project, cx| {
23768 if project
23769 .active_debug_session(cx)
23770 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23771 {
23772 return true;
23773 }
23774
23775 buffer.update(cx, |buffer, cx| {
23776 project.any_language_server_supports_inlay_hints(buffer, cx)
23777 })
23778 })
23779 }
23780
23781 fn inline_values(
23782 &self,
23783 buffer_handle: Entity<Buffer>,
23784 range: Range<text::Anchor>,
23785 cx: &mut App,
23786 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23787 self.update(cx, |project, cx| {
23788 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23789
23790 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23791 })
23792 }
23793
23794 fn applicable_inlay_chunks(
23795 &self,
23796 buffer: &Entity<Buffer>,
23797 ranges: &[Range<text::Anchor>],
23798 cx: &mut App,
23799 ) -> Vec<Range<BufferRow>> {
23800 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23801 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23802 })
23803 }
23804
23805 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23806 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23807 lsp_store.invalidate_inlay_hints(for_buffers)
23808 });
23809 }
23810
23811 fn inlay_hints(
23812 &self,
23813 invalidate: InvalidationStrategy,
23814 buffer: Entity<Buffer>,
23815 ranges: Vec<Range<text::Anchor>>,
23816 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23817 cx: &mut App,
23818 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23819 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23820 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23821 }))
23822 }
23823
23824 fn range_for_rename(
23825 &self,
23826 buffer: &Entity<Buffer>,
23827 position: text::Anchor,
23828 cx: &mut App,
23829 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23830 Some(self.update(cx, |project, cx| {
23831 let buffer = buffer.clone();
23832 let task = project.prepare_rename(buffer.clone(), position, cx);
23833 cx.spawn(async move |_, cx| {
23834 Ok(match task.await? {
23835 PrepareRenameResponse::Success(range) => Some(range),
23836 PrepareRenameResponse::InvalidPosition => None,
23837 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23838 // Fallback on using TreeSitter info to determine identifier range
23839 buffer.read_with(cx, |buffer, _| {
23840 let snapshot = buffer.snapshot();
23841 let (range, kind) = snapshot.surrounding_word(position, None);
23842 if kind != Some(CharKind::Word) {
23843 return None;
23844 }
23845 Some(
23846 snapshot.anchor_before(range.start)
23847 ..snapshot.anchor_after(range.end),
23848 )
23849 })?
23850 }
23851 })
23852 })
23853 }))
23854 }
23855
23856 fn perform_rename(
23857 &self,
23858 buffer: &Entity<Buffer>,
23859 position: text::Anchor,
23860 new_name: String,
23861 cx: &mut App,
23862 ) -> Option<Task<Result<ProjectTransaction>>> {
23863 Some(self.update(cx, |project, cx| {
23864 project.perform_rename(buffer.clone(), position, new_name, cx)
23865 }))
23866 }
23867}
23868
23869fn consume_contiguous_rows(
23870 contiguous_row_selections: &mut Vec<Selection<Point>>,
23871 selection: &Selection<Point>,
23872 display_map: &DisplaySnapshot,
23873 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23874) -> (MultiBufferRow, MultiBufferRow) {
23875 contiguous_row_selections.push(selection.clone());
23876 let start_row = starting_row(selection, display_map);
23877 let mut end_row = ending_row(selection, display_map);
23878
23879 while let Some(next_selection) = selections.peek() {
23880 if next_selection.start.row <= end_row.0 {
23881 end_row = ending_row(next_selection, display_map);
23882 contiguous_row_selections.push(selections.next().unwrap().clone());
23883 } else {
23884 break;
23885 }
23886 }
23887 (start_row, end_row)
23888}
23889
23890fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23891 if selection.start.column > 0 {
23892 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23893 } else {
23894 MultiBufferRow(selection.start.row)
23895 }
23896}
23897
23898fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23899 if next_selection.end.column > 0 || next_selection.is_empty() {
23900 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23901 } else {
23902 MultiBufferRow(next_selection.end.row)
23903 }
23904}
23905
23906impl EditorSnapshot {
23907 pub fn remote_selections_in_range<'a>(
23908 &'a self,
23909 range: &'a Range<Anchor>,
23910 collaboration_hub: &dyn CollaborationHub,
23911 cx: &'a App,
23912 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23913 let participant_names = collaboration_hub.user_names(cx);
23914 let participant_indices = collaboration_hub.user_participant_indices(cx);
23915 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23916 let collaborators_by_replica_id = collaborators_by_peer_id
23917 .values()
23918 .map(|collaborator| (collaborator.replica_id, collaborator))
23919 .collect::<HashMap<_, _>>();
23920 self.buffer_snapshot()
23921 .selections_in_range(range, false)
23922 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23923 if replica_id == ReplicaId::AGENT {
23924 Some(RemoteSelection {
23925 replica_id,
23926 selection,
23927 cursor_shape,
23928 line_mode,
23929 collaborator_id: CollaboratorId::Agent,
23930 user_name: Some("Agent".into()),
23931 color: cx.theme().players().agent(),
23932 })
23933 } else {
23934 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23935 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23936 let user_name = participant_names.get(&collaborator.user_id).cloned();
23937 Some(RemoteSelection {
23938 replica_id,
23939 selection,
23940 cursor_shape,
23941 line_mode,
23942 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23943 user_name,
23944 color: if let Some(index) = participant_index {
23945 cx.theme().players().color_for_participant(index.0)
23946 } else {
23947 cx.theme().players().absent()
23948 },
23949 })
23950 }
23951 })
23952 }
23953
23954 pub fn hunks_for_ranges(
23955 &self,
23956 ranges: impl IntoIterator<Item = Range<Point>>,
23957 ) -> Vec<MultiBufferDiffHunk> {
23958 let mut hunks = Vec::new();
23959 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23960 HashMap::default();
23961 for query_range in ranges {
23962 let query_rows =
23963 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23964 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23965 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23966 ) {
23967 // Include deleted hunks that are adjacent to the query range, because
23968 // otherwise they would be missed.
23969 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23970 if hunk.status().is_deleted() {
23971 intersects_range |= hunk.row_range.start == query_rows.end;
23972 intersects_range |= hunk.row_range.end == query_rows.start;
23973 }
23974 if intersects_range {
23975 if !processed_buffer_rows
23976 .entry(hunk.buffer_id)
23977 .or_default()
23978 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23979 {
23980 continue;
23981 }
23982 hunks.push(hunk);
23983 }
23984 }
23985 }
23986
23987 hunks
23988 }
23989
23990 fn display_diff_hunks_for_rows<'a>(
23991 &'a self,
23992 display_rows: Range<DisplayRow>,
23993 folded_buffers: &'a HashSet<BufferId>,
23994 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23995 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23996 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23997
23998 self.buffer_snapshot()
23999 .diff_hunks_in_range(buffer_start..buffer_end)
24000 .filter_map(|hunk| {
24001 if folded_buffers.contains(&hunk.buffer_id) {
24002 return None;
24003 }
24004
24005 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24006 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24007
24008 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24009 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24010
24011 let display_hunk = if hunk_display_start.column() != 0 {
24012 DisplayDiffHunk::Folded {
24013 display_row: hunk_display_start.row(),
24014 }
24015 } else {
24016 let mut end_row = hunk_display_end.row();
24017 if hunk_display_end.column() > 0 {
24018 end_row.0 += 1;
24019 }
24020 let is_created_file = hunk.is_created_file();
24021 DisplayDiffHunk::Unfolded {
24022 status: hunk.status(),
24023 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24024 ..hunk.diff_base_byte_range.end.0,
24025 display_row_range: hunk_display_start.row()..end_row,
24026 multi_buffer_range: Anchor::range_in_buffer(
24027 hunk.excerpt_id,
24028 hunk.buffer_id,
24029 hunk.buffer_range,
24030 ),
24031 is_created_file,
24032 }
24033 };
24034
24035 Some(display_hunk)
24036 })
24037 }
24038
24039 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24040 self.display_snapshot
24041 .buffer_snapshot()
24042 .language_at(position)
24043 }
24044
24045 pub fn is_focused(&self) -> bool {
24046 self.is_focused
24047 }
24048
24049 pub fn placeholder_text(&self) -> Option<String> {
24050 self.placeholder_display_snapshot
24051 .as_ref()
24052 .map(|display_map| display_map.text())
24053 }
24054
24055 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24056 self.scroll_anchor.scroll_position(&self.display_snapshot)
24057 }
24058
24059 fn gutter_dimensions(
24060 &self,
24061 font_id: FontId,
24062 font_size: Pixels,
24063 max_line_number_width: Pixels,
24064 cx: &App,
24065 ) -> Option<GutterDimensions> {
24066 if !self.show_gutter {
24067 return None;
24068 }
24069
24070 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
24071 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
24072
24073 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24074 matches!(
24075 ProjectSettings::get_global(cx).git.git_gutter,
24076 GitGutterSetting::TrackedFiles
24077 )
24078 });
24079 let gutter_settings = EditorSettings::get_global(cx).gutter;
24080 let show_line_numbers = self
24081 .show_line_numbers
24082 .unwrap_or(gutter_settings.line_numbers);
24083 let line_gutter_width = if show_line_numbers {
24084 // Avoid flicker-like gutter resizes when the line number gains another digit by
24085 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24086 let min_width_for_number_on_gutter =
24087 ch_advance * gutter_settings.min_line_number_digits as f32;
24088 max_line_number_width.max(min_width_for_number_on_gutter)
24089 } else {
24090 0.0.into()
24091 };
24092
24093 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24094 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24095
24096 let git_blame_entries_width =
24097 self.git_blame_gutter_max_author_length
24098 .map(|max_author_length| {
24099 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24100 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24101
24102 /// The number of characters to dedicate to gaps and margins.
24103 const SPACING_WIDTH: usize = 4;
24104
24105 let max_char_count = max_author_length.min(renderer.max_author_length())
24106 + ::git::SHORT_SHA_LENGTH
24107 + MAX_RELATIVE_TIMESTAMP.len()
24108 + SPACING_WIDTH;
24109
24110 ch_advance * max_char_count
24111 });
24112
24113 let is_singleton = self.buffer_snapshot().is_singleton();
24114
24115 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24116 left_padding += if !is_singleton {
24117 ch_width * 4.0
24118 } else if show_runnables || show_breakpoints {
24119 ch_width * 3.0
24120 } else if show_git_gutter && show_line_numbers {
24121 ch_width * 2.0
24122 } else if show_git_gutter || show_line_numbers {
24123 ch_width
24124 } else {
24125 px(0.)
24126 };
24127
24128 let shows_folds = is_singleton && gutter_settings.folds;
24129
24130 let right_padding = if shows_folds && show_line_numbers {
24131 ch_width * 4.0
24132 } else if shows_folds || (!is_singleton && show_line_numbers) {
24133 ch_width * 3.0
24134 } else if show_line_numbers {
24135 ch_width
24136 } else {
24137 px(0.)
24138 };
24139
24140 Some(GutterDimensions {
24141 left_padding,
24142 right_padding,
24143 width: line_gutter_width + left_padding + right_padding,
24144 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24145 git_blame_entries_width,
24146 })
24147 }
24148
24149 pub fn render_crease_toggle(
24150 &self,
24151 buffer_row: MultiBufferRow,
24152 row_contains_cursor: bool,
24153 editor: Entity<Editor>,
24154 window: &mut Window,
24155 cx: &mut App,
24156 ) -> Option<AnyElement> {
24157 let folded = self.is_line_folded(buffer_row);
24158 let mut is_foldable = false;
24159
24160 if let Some(crease) = self
24161 .crease_snapshot
24162 .query_row(buffer_row, self.buffer_snapshot())
24163 {
24164 is_foldable = true;
24165 match crease {
24166 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24167 if let Some(render_toggle) = render_toggle {
24168 let toggle_callback =
24169 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24170 if folded {
24171 editor.update(cx, |editor, cx| {
24172 editor.fold_at(buffer_row, window, cx)
24173 });
24174 } else {
24175 editor.update(cx, |editor, cx| {
24176 editor.unfold_at(buffer_row, window, cx)
24177 });
24178 }
24179 });
24180 return Some((render_toggle)(
24181 buffer_row,
24182 folded,
24183 toggle_callback,
24184 window,
24185 cx,
24186 ));
24187 }
24188 }
24189 }
24190 }
24191
24192 is_foldable |= self.starts_indent(buffer_row);
24193
24194 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24195 Some(
24196 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24197 .toggle_state(folded)
24198 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24199 if folded {
24200 this.unfold_at(buffer_row, window, cx);
24201 } else {
24202 this.fold_at(buffer_row, window, cx);
24203 }
24204 }))
24205 .into_any_element(),
24206 )
24207 } else {
24208 None
24209 }
24210 }
24211
24212 pub fn render_crease_trailer(
24213 &self,
24214 buffer_row: MultiBufferRow,
24215 window: &mut Window,
24216 cx: &mut App,
24217 ) -> Option<AnyElement> {
24218 let folded = self.is_line_folded(buffer_row);
24219 if let Crease::Inline { render_trailer, .. } = self
24220 .crease_snapshot
24221 .query_row(buffer_row, self.buffer_snapshot())?
24222 {
24223 let render_trailer = render_trailer.as_ref()?;
24224 Some(render_trailer(buffer_row, folded, window, cx))
24225 } else {
24226 None
24227 }
24228 }
24229}
24230
24231impl Deref for EditorSnapshot {
24232 type Target = DisplaySnapshot;
24233
24234 fn deref(&self) -> &Self::Target {
24235 &self.display_snapshot
24236 }
24237}
24238
24239#[derive(Clone, Debug, PartialEq, Eq)]
24240pub enum EditorEvent {
24241 InputIgnored {
24242 text: Arc<str>,
24243 },
24244 InputHandled {
24245 utf16_range_to_replace: Option<Range<isize>>,
24246 text: Arc<str>,
24247 },
24248 ExcerptsAdded {
24249 buffer: Entity<Buffer>,
24250 predecessor: ExcerptId,
24251 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24252 },
24253 ExcerptsRemoved {
24254 ids: Vec<ExcerptId>,
24255 removed_buffer_ids: Vec<BufferId>,
24256 },
24257 BufferFoldToggled {
24258 ids: Vec<ExcerptId>,
24259 folded: bool,
24260 },
24261 ExcerptsEdited {
24262 ids: Vec<ExcerptId>,
24263 },
24264 ExcerptsExpanded {
24265 ids: Vec<ExcerptId>,
24266 },
24267 BufferEdited,
24268 Edited {
24269 transaction_id: clock::Lamport,
24270 },
24271 Reparsed(BufferId),
24272 Focused,
24273 FocusedIn,
24274 Blurred,
24275 DirtyChanged,
24276 Saved,
24277 TitleChanged,
24278 SelectionsChanged {
24279 local: bool,
24280 },
24281 ScrollPositionChanged {
24282 local: bool,
24283 autoscroll: bool,
24284 },
24285 TransactionUndone {
24286 transaction_id: clock::Lamport,
24287 },
24288 TransactionBegun {
24289 transaction_id: clock::Lamport,
24290 },
24291 CursorShapeChanged,
24292 BreadcrumbsChanged,
24293 PushedToNavHistory {
24294 anchor: Anchor,
24295 is_deactivate: bool,
24296 },
24297}
24298
24299impl EventEmitter<EditorEvent> for Editor {}
24300
24301impl Focusable for Editor {
24302 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24303 self.focus_handle.clone()
24304 }
24305}
24306
24307impl Render for Editor {
24308 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24309 let settings = ThemeSettings::get_global(cx);
24310
24311 let mut text_style = match self.mode {
24312 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24313 color: cx.theme().colors().editor_foreground,
24314 font_family: settings.ui_font.family.clone(),
24315 font_features: settings.ui_font.features.clone(),
24316 font_fallbacks: settings.ui_font.fallbacks.clone(),
24317 font_size: rems(0.875).into(),
24318 font_weight: settings.ui_font.weight,
24319 line_height: relative(settings.buffer_line_height.value()),
24320 ..Default::default()
24321 },
24322 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24323 color: cx.theme().colors().editor_foreground,
24324 font_family: settings.buffer_font.family.clone(),
24325 font_features: settings.buffer_font.features.clone(),
24326 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24327 font_size: settings.buffer_font_size(cx).into(),
24328 font_weight: settings.buffer_font.weight,
24329 line_height: relative(settings.buffer_line_height.value()),
24330 ..Default::default()
24331 },
24332 };
24333 if let Some(text_style_refinement) = &self.text_style_refinement {
24334 text_style.refine(text_style_refinement)
24335 }
24336
24337 let background = match self.mode {
24338 EditorMode::SingleLine => cx.theme().system().transparent,
24339 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24340 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24341 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24342 };
24343
24344 EditorElement::new(
24345 &cx.entity(),
24346 EditorStyle {
24347 background,
24348 border: cx.theme().colors().border,
24349 local_player: cx.theme().players().local(),
24350 text: text_style,
24351 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24352 syntax: cx.theme().syntax().clone(),
24353 status: cx.theme().status().clone(),
24354 inlay_hints_style: make_inlay_hints_style(cx),
24355 edit_prediction_styles: make_suggestion_styles(cx),
24356 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24357 show_underlines: self.diagnostics_enabled(),
24358 },
24359 )
24360 }
24361}
24362
24363impl EntityInputHandler for Editor {
24364 fn text_for_range(
24365 &mut self,
24366 range_utf16: Range<usize>,
24367 adjusted_range: &mut Option<Range<usize>>,
24368 _: &mut Window,
24369 cx: &mut Context<Self>,
24370 ) -> Option<String> {
24371 let snapshot = self.buffer.read(cx).read(cx);
24372 let start = snapshot.clip_offset_utf16(
24373 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
24374 Bias::Left,
24375 );
24376 let end = snapshot.clip_offset_utf16(
24377 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
24378 Bias::Right,
24379 );
24380 if (start.0.0..end.0.0) != range_utf16 {
24381 adjusted_range.replace(start.0.0..end.0.0);
24382 }
24383 Some(snapshot.text_for_range(start..end).collect())
24384 }
24385
24386 fn selected_text_range(
24387 &mut self,
24388 ignore_disabled_input: bool,
24389 _: &mut Window,
24390 cx: &mut Context<Self>,
24391 ) -> Option<UTF16Selection> {
24392 // Prevent the IME menu from appearing when holding down an alphabetic key
24393 // while input is disabled.
24394 if !ignore_disabled_input && !self.input_enabled {
24395 return None;
24396 }
24397
24398 let selection = self
24399 .selections
24400 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24401 let range = selection.range();
24402
24403 Some(UTF16Selection {
24404 range: range.start.0.0..range.end.0.0,
24405 reversed: selection.reversed,
24406 })
24407 }
24408
24409 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24410 let snapshot = self.buffer.read(cx).read(cx);
24411 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24412 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
24413 }
24414
24415 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24416 self.clear_highlights::<InputComposition>(cx);
24417 self.ime_transaction.take();
24418 }
24419
24420 fn replace_text_in_range(
24421 &mut self,
24422 range_utf16: Option<Range<usize>>,
24423 text: &str,
24424 window: &mut Window,
24425 cx: &mut Context<Self>,
24426 ) {
24427 if !self.input_enabled {
24428 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24429 return;
24430 }
24431
24432 self.transact(window, cx, |this, window, cx| {
24433 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24434 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24435 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24436 Some(this.selection_replacement_ranges(range_utf16, cx))
24437 } else {
24438 this.marked_text_ranges(cx)
24439 };
24440
24441 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24442 let newest_selection_id = this.selections.newest_anchor().id;
24443 this.selections
24444 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24445 .iter()
24446 .zip(ranges_to_replace.iter())
24447 .find_map(|(selection, range)| {
24448 if selection.id == newest_selection_id {
24449 Some(
24450 (range.start.0.0 as isize - selection.head().0.0 as isize)
24451 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24452 )
24453 } else {
24454 None
24455 }
24456 })
24457 });
24458
24459 cx.emit(EditorEvent::InputHandled {
24460 utf16_range_to_replace: range_to_replace,
24461 text: text.into(),
24462 });
24463
24464 if let Some(new_selected_ranges) = new_selected_ranges {
24465 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24466 selections.select_ranges(new_selected_ranges)
24467 });
24468 this.backspace(&Default::default(), window, cx);
24469 }
24470
24471 this.handle_input(text, window, cx);
24472 });
24473
24474 if let Some(transaction) = self.ime_transaction {
24475 self.buffer.update(cx, |buffer, cx| {
24476 buffer.group_until_transaction(transaction, cx);
24477 });
24478 }
24479
24480 self.unmark_text(window, cx);
24481 }
24482
24483 fn replace_and_mark_text_in_range(
24484 &mut self,
24485 range_utf16: Option<Range<usize>>,
24486 text: &str,
24487 new_selected_range_utf16: Option<Range<usize>>,
24488 window: &mut Window,
24489 cx: &mut Context<Self>,
24490 ) {
24491 if !self.input_enabled {
24492 return;
24493 }
24494
24495 let transaction = self.transact(window, cx, |this, window, cx| {
24496 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24497 let snapshot = this.buffer.read(cx).read(cx);
24498 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24499 for marked_range in &mut marked_ranges {
24500 marked_range.end = marked_range.start + relative_range_utf16.end;
24501 marked_range.start += relative_range_utf16.start;
24502 marked_range.start =
24503 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24504 marked_range.end =
24505 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24506 }
24507 }
24508 Some(marked_ranges)
24509 } else if let Some(range_utf16) = range_utf16 {
24510 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24511 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24512 Some(this.selection_replacement_ranges(range_utf16, cx))
24513 } else {
24514 None
24515 };
24516
24517 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24518 let newest_selection_id = this.selections.newest_anchor().id;
24519 this.selections
24520 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24521 .iter()
24522 .zip(ranges_to_replace.iter())
24523 .find_map(|(selection, range)| {
24524 if selection.id == newest_selection_id {
24525 Some(
24526 (range.start.0.0 as isize - selection.head().0.0 as isize)
24527 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24528 )
24529 } else {
24530 None
24531 }
24532 })
24533 });
24534
24535 cx.emit(EditorEvent::InputHandled {
24536 utf16_range_to_replace: range_to_replace,
24537 text: text.into(),
24538 });
24539
24540 if let Some(ranges) = ranges_to_replace {
24541 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24542 s.select_ranges(ranges)
24543 });
24544 }
24545
24546 let marked_ranges = {
24547 let snapshot = this.buffer.read(cx).read(cx);
24548 this.selections
24549 .disjoint_anchors_arc()
24550 .iter()
24551 .map(|selection| {
24552 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24553 })
24554 .collect::<Vec<_>>()
24555 };
24556
24557 if text.is_empty() {
24558 this.unmark_text(window, cx);
24559 } else {
24560 this.highlight_text::<InputComposition>(
24561 marked_ranges.clone(),
24562 HighlightStyle {
24563 underline: Some(UnderlineStyle {
24564 thickness: px(1.),
24565 color: None,
24566 wavy: false,
24567 }),
24568 ..Default::default()
24569 },
24570 cx,
24571 );
24572 }
24573
24574 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24575 let use_autoclose = this.use_autoclose;
24576 let use_auto_surround = this.use_auto_surround;
24577 this.set_use_autoclose(false);
24578 this.set_use_auto_surround(false);
24579 this.handle_input(text, window, cx);
24580 this.set_use_autoclose(use_autoclose);
24581 this.set_use_auto_surround(use_auto_surround);
24582
24583 if let Some(new_selected_range) = new_selected_range_utf16 {
24584 let snapshot = this.buffer.read(cx).read(cx);
24585 let new_selected_ranges = marked_ranges
24586 .into_iter()
24587 .map(|marked_range| {
24588 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24589 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
24590 insertion_start.0 + new_selected_range.start,
24591 ));
24592 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
24593 insertion_start.0 + new_selected_range.end,
24594 ));
24595 snapshot.clip_offset_utf16(new_start, Bias::Left)
24596 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24597 })
24598 .collect::<Vec<_>>();
24599
24600 drop(snapshot);
24601 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24602 selections.select_ranges(new_selected_ranges)
24603 });
24604 }
24605 });
24606
24607 self.ime_transaction = self.ime_transaction.or(transaction);
24608 if let Some(transaction) = self.ime_transaction {
24609 self.buffer.update(cx, |buffer, cx| {
24610 buffer.group_until_transaction(transaction, cx);
24611 });
24612 }
24613
24614 if self.text_highlights::<InputComposition>(cx).is_none() {
24615 self.ime_transaction.take();
24616 }
24617 }
24618
24619 fn bounds_for_range(
24620 &mut self,
24621 range_utf16: Range<usize>,
24622 element_bounds: gpui::Bounds<Pixels>,
24623 window: &mut Window,
24624 cx: &mut Context<Self>,
24625 ) -> Option<gpui::Bounds<Pixels>> {
24626 let text_layout_details = self.text_layout_details(window);
24627 let CharacterDimensions {
24628 em_width,
24629 em_advance,
24630 line_height,
24631 } = self.character_dimensions(window);
24632
24633 let snapshot = self.snapshot(window, cx);
24634 let scroll_position = snapshot.scroll_position();
24635 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24636
24637 let start =
24638 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
24639 let x = Pixels::from(
24640 ScrollOffset::from(
24641 snapshot.x_for_display_point(start, &text_layout_details)
24642 + self.gutter_dimensions.full_width(),
24643 ) - scroll_left,
24644 );
24645 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24646
24647 Some(Bounds {
24648 origin: element_bounds.origin + point(x, y),
24649 size: size(em_width, line_height),
24650 })
24651 }
24652
24653 fn character_index_for_point(
24654 &mut self,
24655 point: gpui::Point<Pixels>,
24656 _window: &mut Window,
24657 _cx: &mut Context<Self>,
24658 ) -> Option<usize> {
24659 let position_map = self.last_position_map.as_ref()?;
24660 if !position_map.text_hitbox.contains(&point) {
24661 return None;
24662 }
24663 let display_point = position_map.point_for_position(point).previous_valid;
24664 let anchor = position_map
24665 .snapshot
24666 .display_point_to_anchor(display_point, Bias::Left);
24667 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24668 Some(utf16_offset.0.0)
24669 }
24670
24671 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24672 self.input_enabled
24673 }
24674}
24675
24676trait SelectionExt {
24677 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24678 fn spanned_rows(
24679 &self,
24680 include_end_if_at_line_start: bool,
24681 map: &DisplaySnapshot,
24682 ) -> Range<MultiBufferRow>;
24683}
24684
24685impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24686 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24687 let start = self
24688 .start
24689 .to_point(map.buffer_snapshot())
24690 .to_display_point(map);
24691 let end = self
24692 .end
24693 .to_point(map.buffer_snapshot())
24694 .to_display_point(map);
24695 if self.reversed {
24696 end..start
24697 } else {
24698 start..end
24699 }
24700 }
24701
24702 fn spanned_rows(
24703 &self,
24704 include_end_if_at_line_start: bool,
24705 map: &DisplaySnapshot,
24706 ) -> Range<MultiBufferRow> {
24707 let start = self.start.to_point(map.buffer_snapshot());
24708 let mut end = self.end.to_point(map.buffer_snapshot());
24709 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24710 end.row -= 1;
24711 }
24712
24713 let buffer_start = map.prev_line_boundary(start).0;
24714 let buffer_end = map.next_line_boundary(end).0;
24715 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24716 }
24717}
24718
24719impl<T: InvalidationRegion> InvalidationStack<T> {
24720 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24721 where
24722 S: Clone + ToOffset,
24723 {
24724 while let Some(region) = self.last() {
24725 let all_selections_inside_invalidation_ranges =
24726 if selections.len() == region.ranges().len() {
24727 selections
24728 .iter()
24729 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24730 .all(|(selection, invalidation_range)| {
24731 let head = selection.head().to_offset(buffer);
24732 invalidation_range.start <= head && invalidation_range.end >= head
24733 })
24734 } else {
24735 false
24736 };
24737
24738 if all_selections_inside_invalidation_ranges {
24739 break;
24740 } else {
24741 self.pop();
24742 }
24743 }
24744 }
24745}
24746
24747impl<T> Default for InvalidationStack<T> {
24748 fn default() -> Self {
24749 Self(Default::default())
24750 }
24751}
24752
24753impl<T> Deref for InvalidationStack<T> {
24754 type Target = Vec<T>;
24755
24756 fn deref(&self) -> &Self::Target {
24757 &self.0
24758 }
24759}
24760
24761impl<T> DerefMut for InvalidationStack<T> {
24762 fn deref_mut(&mut self) -> &mut Self::Target {
24763 &mut self.0
24764 }
24765}
24766
24767impl InvalidationRegion for SnippetState {
24768 fn ranges(&self) -> &[Range<Anchor>] {
24769 &self.ranges[self.active_index]
24770 }
24771}
24772
24773fn edit_prediction_edit_text(
24774 current_snapshot: &BufferSnapshot,
24775 edits: &[(Range<Anchor>, impl AsRef<str>)],
24776 edit_preview: &EditPreview,
24777 include_deletions: bool,
24778 cx: &App,
24779) -> HighlightedText {
24780 let edits = edits
24781 .iter()
24782 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24783 .collect::<Vec<_>>();
24784
24785 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24786}
24787
24788fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24789 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24790 // Just show the raw edit text with basic styling
24791 let mut text = String::new();
24792 let mut highlights = Vec::new();
24793
24794 let insertion_highlight_style = HighlightStyle {
24795 color: Some(cx.theme().colors().text),
24796 ..Default::default()
24797 };
24798
24799 for (_, edit_text) in edits {
24800 let start_offset = text.len();
24801 text.push_str(edit_text);
24802 let end_offset = text.len();
24803
24804 if start_offset < end_offset {
24805 highlights.push((start_offset..end_offset, insertion_highlight_style));
24806 }
24807 }
24808
24809 HighlightedText {
24810 text: text.into(),
24811 highlights,
24812 }
24813}
24814
24815pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24816 match severity {
24817 lsp::DiagnosticSeverity::ERROR => colors.error,
24818 lsp::DiagnosticSeverity::WARNING => colors.warning,
24819 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24820 lsp::DiagnosticSeverity::HINT => colors.info,
24821 _ => colors.ignored,
24822 }
24823}
24824
24825pub fn styled_runs_for_code_label<'a>(
24826 label: &'a CodeLabel,
24827 syntax_theme: &'a theme::SyntaxTheme,
24828) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24829 let fade_out = HighlightStyle {
24830 fade_out: Some(0.35),
24831 ..Default::default()
24832 };
24833
24834 let mut prev_end = label.filter_range.end;
24835 label
24836 .runs
24837 .iter()
24838 .enumerate()
24839 .flat_map(move |(ix, (range, highlight_id))| {
24840 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24841 style
24842 } else {
24843 return Default::default();
24844 };
24845 let muted_style = style.highlight(fade_out);
24846
24847 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24848 if range.start >= label.filter_range.end {
24849 if range.start > prev_end {
24850 runs.push((prev_end..range.start, fade_out));
24851 }
24852 runs.push((range.clone(), muted_style));
24853 } else if range.end <= label.filter_range.end {
24854 runs.push((range.clone(), style));
24855 } else {
24856 runs.push((range.start..label.filter_range.end, style));
24857 runs.push((label.filter_range.end..range.end, muted_style));
24858 }
24859 prev_end = cmp::max(prev_end, range.end);
24860
24861 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24862 runs.push((prev_end..label.text.len(), fade_out));
24863 }
24864
24865 runs
24866 })
24867}
24868
24869pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24870 let mut prev_index = 0;
24871 let mut prev_codepoint: Option<char> = None;
24872 text.char_indices()
24873 .chain([(text.len(), '\0')])
24874 .filter_map(move |(index, codepoint)| {
24875 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24876 let is_boundary = index == text.len()
24877 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24878 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24879 if is_boundary {
24880 let chunk = &text[prev_index..index];
24881 prev_index = index;
24882 Some(chunk)
24883 } else {
24884 None
24885 }
24886 })
24887}
24888
24889/// Given a string of text immediately before the cursor, iterates over possible
24890/// strings a snippet could match to. More precisely: returns an iterator over
24891/// suffixes of `text` created by splitting at word boundaries (before & after
24892/// every non-word character).
24893///
24894/// Shorter suffixes are returned first.
24895pub(crate) fn snippet_candidate_suffixes(
24896 text: &str,
24897 is_word_char: impl Fn(char) -> bool,
24898) -> impl std::iter::Iterator<Item = &str> {
24899 let mut prev_index = text.len();
24900 let mut prev_codepoint = None;
24901 text.char_indices()
24902 .rev()
24903 .chain([(0, '\0')])
24904 .filter_map(move |(index, codepoint)| {
24905 let prev_index = std::mem::replace(&mut prev_index, index);
24906 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24907 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
24908 None
24909 } else {
24910 let chunk = &text[prev_index..]; // go to end of string
24911 Some(chunk)
24912 }
24913 })
24914}
24915
24916pub trait RangeToAnchorExt: Sized {
24917 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24918
24919 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24920 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24921 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24922 }
24923}
24924
24925impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24926 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24927 let start_offset = self.start.to_offset(snapshot);
24928 let end_offset = self.end.to_offset(snapshot);
24929 if start_offset == end_offset {
24930 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24931 } else {
24932 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24933 }
24934 }
24935}
24936
24937pub trait RowExt {
24938 fn as_f64(&self) -> f64;
24939
24940 fn next_row(&self) -> Self;
24941
24942 fn previous_row(&self) -> Self;
24943
24944 fn minus(&self, other: Self) -> u32;
24945}
24946
24947impl RowExt for DisplayRow {
24948 fn as_f64(&self) -> f64 {
24949 self.0 as _
24950 }
24951
24952 fn next_row(&self) -> Self {
24953 Self(self.0 + 1)
24954 }
24955
24956 fn previous_row(&self) -> Self {
24957 Self(self.0.saturating_sub(1))
24958 }
24959
24960 fn minus(&self, other: Self) -> u32 {
24961 self.0 - other.0
24962 }
24963}
24964
24965impl RowExt for MultiBufferRow {
24966 fn as_f64(&self) -> f64 {
24967 self.0 as _
24968 }
24969
24970 fn next_row(&self) -> Self {
24971 Self(self.0 + 1)
24972 }
24973
24974 fn previous_row(&self) -> Self {
24975 Self(self.0.saturating_sub(1))
24976 }
24977
24978 fn minus(&self, other: Self) -> u32 {
24979 self.0 - other.0
24980 }
24981}
24982
24983trait RowRangeExt {
24984 type Row;
24985
24986 fn len(&self) -> usize;
24987
24988 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24989}
24990
24991impl RowRangeExt for Range<MultiBufferRow> {
24992 type Row = MultiBufferRow;
24993
24994 fn len(&self) -> usize {
24995 (self.end.0 - self.start.0) as usize
24996 }
24997
24998 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24999 (self.start.0..self.end.0).map(MultiBufferRow)
25000 }
25001}
25002
25003impl RowRangeExt for Range<DisplayRow> {
25004 type Row = DisplayRow;
25005
25006 fn len(&self) -> usize {
25007 (self.end.0 - self.start.0) as usize
25008 }
25009
25010 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25011 (self.start.0..self.end.0).map(DisplayRow)
25012 }
25013}
25014
25015/// If select range has more than one line, we
25016/// just point the cursor to range.start.
25017fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25018 if range.start.row == range.end.row {
25019 range
25020 } else {
25021 range.start..range.start
25022 }
25023}
25024pub struct KillRing(ClipboardItem);
25025impl Global for KillRing {}
25026
25027const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25028
25029enum BreakpointPromptEditAction {
25030 Log,
25031 Condition,
25032 HitCondition,
25033}
25034
25035struct BreakpointPromptEditor {
25036 pub(crate) prompt: Entity<Editor>,
25037 editor: WeakEntity<Editor>,
25038 breakpoint_anchor: Anchor,
25039 breakpoint: Breakpoint,
25040 edit_action: BreakpointPromptEditAction,
25041 block_ids: HashSet<CustomBlockId>,
25042 editor_margins: Arc<Mutex<EditorMargins>>,
25043 _subscriptions: Vec<Subscription>,
25044}
25045
25046impl BreakpointPromptEditor {
25047 const MAX_LINES: u8 = 4;
25048
25049 fn new(
25050 editor: WeakEntity<Editor>,
25051 breakpoint_anchor: Anchor,
25052 breakpoint: Breakpoint,
25053 edit_action: BreakpointPromptEditAction,
25054 window: &mut Window,
25055 cx: &mut Context<Self>,
25056 ) -> Self {
25057 let base_text = match edit_action {
25058 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25059 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25060 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25061 }
25062 .map(|msg| msg.to_string())
25063 .unwrap_or_default();
25064
25065 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25066 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25067
25068 let prompt = cx.new(|cx| {
25069 let mut prompt = Editor::new(
25070 EditorMode::AutoHeight {
25071 min_lines: 1,
25072 max_lines: Some(Self::MAX_LINES as usize),
25073 },
25074 buffer,
25075 None,
25076 window,
25077 cx,
25078 );
25079 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25080 prompt.set_show_cursor_when_unfocused(false, cx);
25081 prompt.set_placeholder_text(
25082 match edit_action {
25083 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25084 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25085 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25086 },
25087 window,
25088 cx,
25089 );
25090
25091 prompt
25092 });
25093
25094 Self {
25095 prompt,
25096 editor,
25097 breakpoint_anchor,
25098 breakpoint,
25099 edit_action,
25100 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25101 block_ids: Default::default(),
25102 _subscriptions: vec![],
25103 }
25104 }
25105
25106 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25107 self.block_ids.extend(block_ids)
25108 }
25109
25110 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25111 if let Some(editor) = self.editor.upgrade() {
25112 let message = self
25113 .prompt
25114 .read(cx)
25115 .buffer
25116 .read(cx)
25117 .as_singleton()
25118 .expect("A multi buffer in breakpoint prompt isn't possible")
25119 .read(cx)
25120 .as_rope()
25121 .to_string();
25122
25123 editor.update(cx, |editor, cx| {
25124 editor.edit_breakpoint_at_anchor(
25125 self.breakpoint_anchor,
25126 self.breakpoint.clone(),
25127 match self.edit_action {
25128 BreakpointPromptEditAction::Log => {
25129 BreakpointEditAction::EditLogMessage(message.into())
25130 }
25131 BreakpointPromptEditAction::Condition => {
25132 BreakpointEditAction::EditCondition(message.into())
25133 }
25134 BreakpointPromptEditAction::HitCondition => {
25135 BreakpointEditAction::EditHitCondition(message.into())
25136 }
25137 },
25138 cx,
25139 );
25140
25141 editor.remove_blocks(self.block_ids.clone(), None, cx);
25142 cx.focus_self(window);
25143 });
25144 }
25145 }
25146
25147 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25148 self.editor
25149 .update(cx, |editor, cx| {
25150 editor.remove_blocks(self.block_ids.clone(), None, cx);
25151 window.focus(&editor.focus_handle);
25152 })
25153 .log_err();
25154 }
25155
25156 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25157 let settings = ThemeSettings::get_global(cx);
25158 let text_style = TextStyle {
25159 color: if self.prompt.read(cx).read_only(cx) {
25160 cx.theme().colors().text_disabled
25161 } else {
25162 cx.theme().colors().text
25163 },
25164 font_family: settings.buffer_font.family.clone(),
25165 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25166 font_size: settings.buffer_font_size(cx).into(),
25167 font_weight: settings.buffer_font.weight,
25168 line_height: relative(settings.buffer_line_height.value()),
25169 ..Default::default()
25170 };
25171 EditorElement::new(
25172 &self.prompt,
25173 EditorStyle {
25174 background: cx.theme().colors().editor_background,
25175 local_player: cx.theme().players().local(),
25176 text: text_style,
25177 ..Default::default()
25178 },
25179 )
25180 }
25181}
25182
25183impl Render for BreakpointPromptEditor {
25184 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25185 let editor_margins = *self.editor_margins.lock();
25186 let gutter_dimensions = editor_margins.gutter;
25187 h_flex()
25188 .key_context("Editor")
25189 .bg(cx.theme().colors().editor_background)
25190 .border_y_1()
25191 .border_color(cx.theme().status().info_border)
25192 .size_full()
25193 .py(window.line_height() / 2.5)
25194 .on_action(cx.listener(Self::confirm))
25195 .on_action(cx.listener(Self::cancel))
25196 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25197 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25198 }
25199}
25200
25201impl Focusable for BreakpointPromptEditor {
25202 fn focus_handle(&self, cx: &App) -> FocusHandle {
25203 self.prompt.focus_handle(cx)
25204 }
25205}
25206
25207fn all_edits_insertions_or_deletions(
25208 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25209 snapshot: &MultiBufferSnapshot,
25210) -> bool {
25211 let mut all_insertions = true;
25212 let mut all_deletions = true;
25213
25214 for (range, new_text) in edits.iter() {
25215 let range_is_empty = range.to_offset(snapshot).is_empty();
25216 let text_is_empty = new_text.is_empty();
25217
25218 if range_is_empty != text_is_empty {
25219 if range_is_empty {
25220 all_deletions = false;
25221 } else {
25222 all_insertions = false;
25223 }
25224 } else {
25225 return false;
25226 }
25227
25228 if !all_insertions && !all_deletions {
25229 return false;
25230 }
25231 }
25232 all_insertions || all_deletions
25233}
25234
25235struct MissingEditPredictionKeybindingTooltip;
25236
25237impl Render for MissingEditPredictionKeybindingTooltip {
25238 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25239 ui::tooltip_container(cx, |container, cx| {
25240 container
25241 .flex_shrink_0()
25242 .max_w_80()
25243 .min_h(rems_from_px(124.))
25244 .justify_between()
25245 .child(
25246 v_flex()
25247 .flex_1()
25248 .text_ui_sm(cx)
25249 .child(Label::new("Conflict with Accept Keybinding"))
25250 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25251 )
25252 .child(
25253 h_flex()
25254 .pb_1()
25255 .gap_1()
25256 .items_end()
25257 .w_full()
25258 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25259 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25260 }))
25261 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25262 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25263 })),
25264 )
25265 })
25266 }
25267}
25268
25269#[derive(Debug, Clone, Copy, PartialEq)]
25270pub struct LineHighlight {
25271 pub background: Background,
25272 pub border: Option<gpui::Hsla>,
25273 pub include_gutter: bool,
25274 pub type_id: Option<TypeId>,
25275}
25276
25277struct LineManipulationResult {
25278 pub new_text: String,
25279 pub line_count_before: usize,
25280 pub line_count_after: usize,
25281}
25282
25283fn render_diff_hunk_controls(
25284 row: u32,
25285 status: &DiffHunkStatus,
25286 hunk_range: Range<Anchor>,
25287 is_created_file: bool,
25288 line_height: Pixels,
25289 editor: &Entity<Editor>,
25290 _window: &mut Window,
25291 cx: &mut App,
25292) -> AnyElement {
25293 h_flex()
25294 .h(line_height)
25295 .mr_1()
25296 .gap_1()
25297 .px_0p5()
25298 .pb_1()
25299 .border_x_1()
25300 .border_b_1()
25301 .border_color(cx.theme().colors().border_variant)
25302 .rounded_b_lg()
25303 .bg(cx.theme().colors().editor_background)
25304 .gap_1()
25305 .block_mouse_except_scroll()
25306 .shadow_md()
25307 .child(if status.has_secondary_hunk() {
25308 Button::new(("stage", row as u64), "Stage")
25309 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25310 .tooltip({
25311 let focus_handle = editor.focus_handle(cx);
25312 move |_window, cx| {
25313 Tooltip::for_action_in(
25314 "Stage Hunk",
25315 &::git::ToggleStaged,
25316 &focus_handle,
25317 cx,
25318 )
25319 }
25320 })
25321 .on_click({
25322 let editor = editor.clone();
25323 move |_event, _window, cx| {
25324 editor.update(cx, |editor, cx| {
25325 editor.stage_or_unstage_diff_hunks(
25326 true,
25327 vec![hunk_range.start..hunk_range.start],
25328 cx,
25329 );
25330 });
25331 }
25332 })
25333 } else {
25334 Button::new(("unstage", row as u64), "Unstage")
25335 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25336 .tooltip({
25337 let focus_handle = editor.focus_handle(cx);
25338 move |_window, cx| {
25339 Tooltip::for_action_in(
25340 "Unstage Hunk",
25341 &::git::ToggleStaged,
25342 &focus_handle,
25343 cx,
25344 )
25345 }
25346 })
25347 .on_click({
25348 let editor = editor.clone();
25349 move |_event, _window, cx| {
25350 editor.update(cx, |editor, cx| {
25351 editor.stage_or_unstage_diff_hunks(
25352 false,
25353 vec![hunk_range.start..hunk_range.start],
25354 cx,
25355 );
25356 });
25357 }
25358 })
25359 })
25360 .child(
25361 Button::new(("restore", row as u64), "Restore")
25362 .tooltip({
25363 let focus_handle = editor.focus_handle(cx);
25364 move |_window, cx| {
25365 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25366 }
25367 })
25368 .on_click({
25369 let editor = editor.clone();
25370 move |_event, window, cx| {
25371 editor.update(cx, |editor, cx| {
25372 let snapshot = editor.snapshot(window, cx);
25373 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25374 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25375 });
25376 }
25377 })
25378 .disabled(is_created_file),
25379 )
25380 .when(
25381 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25382 |el| {
25383 el.child(
25384 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25385 .shape(IconButtonShape::Square)
25386 .icon_size(IconSize::Small)
25387 // .disabled(!has_multiple_hunks)
25388 .tooltip({
25389 let focus_handle = editor.focus_handle(cx);
25390 move |_window, cx| {
25391 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25392 }
25393 })
25394 .on_click({
25395 let editor = editor.clone();
25396 move |_event, window, cx| {
25397 editor.update(cx, |editor, cx| {
25398 let snapshot = editor.snapshot(window, cx);
25399 let position =
25400 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25401 editor.go_to_hunk_before_or_after_position(
25402 &snapshot,
25403 position,
25404 Direction::Next,
25405 window,
25406 cx,
25407 );
25408 editor.expand_selected_diff_hunks(cx);
25409 });
25410 }
25411 }),
25412 )
25413 .child(
25414 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25415 .shape(IconButtonShape::Square)
25416 .icon_size(IconSize::Small)
25417 // .disabled(!has_multiple_hunks)
25418 .tooltip({
25419 let focus_handle = editor.focus_handle(cx);
25420 move |_window, cx| {
25421 Tooltip::for_action_in(
25422 "Previous Hunk",
25423 &GoToPreviousHunk,
25424 &focus_handle,
25425 cx,
25426 )
25427 }
25428 })
25429 .on_click({
25430 let editor = editor.clone();
25431 move |_event, window, cx| {
25432 editor.update(cx, |editor, cx| {
25433 let snapshot = editor.snapshot(window, cx);
25434 let point =
25435 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25436 editor.go_to_hunk_before_or_after_position(
25437 &snapshot,
25438 point,
25439 Direction::Prev,
25440 window,
25441 cx,
25442 );
25443 editor.expand_selected_diff_hunks(cx);
25444 });
25445 }
25446 }),
25447 )
25448 },
25449 )
25450 .into_any_element()
25451}
25452
25453pub fn multibuffer_context_lines(cx: &App) -> u32 {
25454 EditorSettings::try_get(cx)
25455 .map(|settings| settings.excerpt_context_lines)
25456 .unwrap_or(2)
25457 .min(32)
25458}