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//! * [`inlay_hint_cache`] - is a storage of inlay hints out of LSP requests, responsible for querying LSP and updating `display_map`'s state accordingly.
11//!
12//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
13//!
14//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
15pub mod actions;
16mod blink_manager;
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 inlay_hint_cache;
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 proposed_changes_editor;
37mod rust_analyzer_ext;
38pub mod scroll;
39mod selections_collection;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
69 RowInfo, ToOffset, ToPoint,
70};
71pub use proposed_changes_editor::{
72 ProposedChangeLocation, ProposedChangesEditor, ProposedChangesEditorToolbar,
73};
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79};
80use aho_corasick::AhoCorasick;
81use anyhow::{Context as _, Result, anyhow};
82use blink_manager::BlinkManager;
83use buffer_diff::DiffHunkStatus;
84use client::{Collaborator, ParticipantIndex, parse_zed_link};
85use clock::ReplicaId;
86use code_context_menus::{
87 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
88 CompletionsMenu, ContextMenuOrigin,
89};
90use collections::{BTreeMap, HashMap, HashSet, VecDeque};
91use convert_case::{Case, Casing};
92use dap::TelemetrySpawnLocation;
93use display_map::*;
94use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
95use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
96use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
97use futures::{
98 FutureExt, StreamExt as _,
99 future::{self, Shared, join},
100 stream::FuturesUnordered,
101};
102use fuzzy::{StringMatch, StringMatchCandidate};
103use git::blame::{GitBlame, GlobalBlameRenderer};
104use gpui::{
105 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
106 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
107 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
108 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
109 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
110 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
111 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
112 div, point, prelude::*, pulsating_between, px, relative, size,
113};
114use hover_links::{HoverLink, HoveredLinkState, InlayHighlight, find_file};
115use hover_popover::{HoverState, hide_hover};
116use indent_guides::ActiveIndentGuidesState;
117use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
118use itertools::{Either, Itertools};
119use language::{
120 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
121 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
122 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
123 IndentSize, Language, OffsetRangeExt, Point, Runnable, RunnableRange, Selection, SelectionGoal,
124 TextObject, TransactionId, TreeSitterOptions, WordsQuery,
125 language_settings::{
126 self, InlayHintSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
127 all_language_settings, language_settings,
128 },
129 point_from_lsp, point_to_lsp, text_diff_with_options,
130};
131use linked_editing_ranges::refresh_linked_ranges;
132use lsp::{
133 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
134 LanguageServerId,
135};
136use lsp_colors::LspColorData;
137use markdown::Markdown;
138use mouse_context_menu::MouseContextMenu;
139use movement::TextLayoutDetails;
140use multi_buffer::{
141 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
142 ToOffsetUtf16,
143};
144use parking_lot::Mutex;
145use persistence::DB;
146use project::{
147 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
148 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint,
149 Location, LocationLink, PrepareRenameResponse, Project, ProjectItem, ProjectPath,
150 ProjectTransaction, TaskSourceKind,
151 debugger::{
152 breakpoint_store::{
153 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
154 BreakpointStore, BreakpointStoreEvent,
155 },
156 session::{Session, SessionEvent},
157 },
158 git_store::{GitStoreEvent, RepositoryEvent},
159 lsp_store::{CompletionDocumentation, FormatTrigger, LspFormatTarget, OpenLspBufferHandle},
160 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
161};
162use rand::seq::SliceRandom;
163use rpc::{ErrorCode, ErrorExt, proto::PeerId};
164use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
165use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
166use serde::{Deserialize, Serialize};
167use settings::{GitGutterSetting, Settings, SettingsLocation, SettingsStore, update_settings_file};
168use smallvec::{SmallVec, smallvec};
169use snippet::Snippet;
170use std::{
171 any::{Any, TypeId},
172 borrow::Cow,
173 cell::{OnceCell, RefCell},
174 cmp::{self, Ordering, Reverse},
175 iter::{self, Peekable},
176 mem,
177 num::NonZeroU32,
178 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
179 path::{Path, PathBuf},
180 rc::Rc,
181 sync::Arc,
182 time::{Duration, Instant},
183};
184use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
185use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
186use theme::{
187 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
188 observe_buffer_font_size_adjustment,
189};
190use ui::{
191 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
192 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
193};
194use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
195use workspace::{
196 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
197 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
198 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
199 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
200 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
201 searchable::SearchEvent,
202};
203
204use crate::{
205 code_context_menus::CompletionsMenuSource,
206 editor_settings::MultiCursorModifier,
207 hover_links::{find_url, find_url_from_range},
208 scroll::{ScrollOffset, ScrollPixelOffset},
209 selections_collection::resolve_selections_wrapping_blocks,
210 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
211};
212
213pub const FILE_HEADER_HEIGHT: u32 = 2;
214pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
215const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
216const MAX_LINE_LEN: usize = 1024;
217const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
218const MAX_SELECTION_HISTORY_LEN: usize = 1024;
219pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
220#[doc(hidden)]
221pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
222pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
223
224pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
225pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
226pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
227pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
228
229pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
230pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
231pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
232
233pub type RenderDiffHunkControlsFn = Arc<
234 dyn Fn(
235 u32,
236 &DiffHunkStatus,
237 Range<Anchor>,
238 bool,
239 Pixels,
240 &Entity<Editor>,
241 &mut Window,
242 &mut App,
243 ) -> AnyElement,
244>;
245
246enum ReportEditorEvent {
247 Saved { auto_saved: bool },
248 EditorOpened,
249 Closed,
250}
251
252impl ReportEditorEvent {
253 pub fn event_type(&self) -> &'static str {
254 match self {
255 Self::Saved { .. } => "Editor Saved",
256 Self::EditorOpened => "Editor Opened",
257 Self::Closed => "Editor Closed",
258 }
259 }
260}
261
262struct InlineValueCache {
263 enabled: bool,
264 inlays: Vec<InlayId>,
265 refresh_task: Task<Option<()>>,
266}
267
268impl InlineValueCache {
269 fn new(enabled: bool) -> Self {
270 Self {
271 enabled,
272 inlays: Vec::new(),
273 refresh_task: Task::ready(None),
274 }
275 }
276}
277
278#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
279pub enum InlayId {
280 EditPrediction(u32),
281 DebuggerValue(u32),
282 // LSP
283 Hint(u32),
284 Color(u32),
285}
286
287impl InlayId {
288 fn id(&self) -> u32 {
289 match self {
290 Self::EditPrediction(id) => *id,
291 Self::DebuggerValue(id) => *id,
292 Self::Hint(id) => *id,
293 Self::Color(id) => *id,
294 }
295 }
296}
297
298pub enum ActiveDebugLine {}
299pub enum DebugStackFrameLine {}
300enum DocumentHighlightRead {}
301enum DocumentHighlightWrite {}
302enum InputComposition {}
303pub enum PendingInput {}
304enum SelectedTextHighlight {}
305
306pub enum ConflictsOuter {}
307pub enum ConflictsOurs {}
308pub enum ConflictsTheirs {}
309pub enum ConflictsOursMarker {}
310pub enum ConflictsTheirsMarker {}
311
312#[derive(Debug, Copy, Clone, PartialEq, Eq)]
313pub enum Navigated {
314 Yes,
315 No,
316}
317
318impl Navigated {
319 pub fn from_bool(yes: bool) -> Navigated {
320 if yes { Navigated::Yes } else { Navigated::No }
321 }
322}
323
324#[derive(Debug, Clone, PartialEq, Eq)]
325enum DisplayDiffHunk {
326 Folded {
327 display_row: DisplayRow,
328 },
329 Unfolded {
330 is_created_file: bool,
331 diff_base_byte_range: Range<usize>,
332 display_row_range: Range<DisplayRow>,
333 multi_buffer_range: Range<Anchor>,
334 status: DiffHunkStatus,
335 },
336}
337
338pub enum HideMouseCursorOrigin {
339 TypingAction,
340 MovementAction,
341}
342
343pub fn init_settings(cx: &mut App) {
344 EditorSettings::register(cx);
345}
346
347pub fn init(cx: &mut App) {
348 init_settings(cx);
349
350 cx.set_global(GlobalBlameRenderer(Arc::new(())));
351
352 workspace::register_project_item::<Editor>(cx);
353 workspace::FollowableViewRegistry::register::<Editor>(cx);
354 workspace::register_serializable_item::<Editor>(cx);
355
356 cx.observe_new(
357 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
358 workspace.register_action(Editor::new_file);
359 workspace.register_action(Editor::new_file_split);
360 workspace.register_action(Editor::new_file_vertical);
361 workspace.register_action(Editor::new_file_horizontal);
362 workspace.register_action(Editor::cancel_language_server_work);
363 workspace.register_action(Editor::toggle_focus);
364 },
365 )
366 .detach();
367
368 cx.on_action(move |_: &workspace::NewFile, cx| {
369 let app_state = workspace::AppState::global(cx);
370 if let Some(app_state) = app_state.upgrade() {
371 workspace::open_new(
372 Default::default(),
373 app_state,
374 cx,
375 |workspace, window, cx| {
376 Editor::new_file(workspace, &Default::default(), window, cx)
377 },
378 )
379 .detach();
380 }
381 });
382 cx.on_action(move |_: &workspace::NewWindow, cx| {
383 let app_state = workspace::AppState::global(cx);
384 if let Some(app_state) = app_state.upgrade() {
385 workspace::open_new(
386 Default::default(),
387 app_state,
388 cx,
389 |workspace, window, cx| {
390 cx.activate(true);
391 Editor::new_file(workspace, &Default::default(), window, cx)
392 },
393 )
394 .detach();
395 }
396 });
397}
398
399pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
400 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
401}
402
403pub trait DiagnosticRenderer {
404 fn render_group(
405 &self,
406 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
407 buffer_id: BufferId,
408 snapshot: EditorSnapshot,
409 editor: WeakEntity<Editor>,
410 cx: &mut App,
411 ) -> Vec<BlockProperties<Anchor>>;
412
413 fn render_hover(
414 &self,
415 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
416 range: Range<Point>,
417 buffer_id: BufferId,
418 cx: &mut App,
419 ) -> Option<Entity<markdown::Markdown>>;
420
421 fn open_link(
422 &self,
423 editor: &mut Editor,
424 link: SharedString,
425 window: &mut Window,
426 cx: &mut Context<Editor>,
427 );
428}
429
430pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
431
432impl GlobalDiagnosticRenderer {
433 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
434 cx.try_global::<Self>().map(|g| g.0.clone())
435 }
436}
437
438impl gpui::Global for GlobalDiagnosticRenderer {}
439pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
440 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
441}
442
443pub struct SearchWithinRange;
444
445trait InvalidationRegion {
446 fn ranges(&self) -> &[Range<Anchor>];
447}
448
449#[derive(Clone, Debug, PartialEq)]
450pub enum SelectPhase {
451 Begin {
452 position: DisplayPoint,
453 add: bool,
454 click_count: usize,
455 },
456 BeginColumnar {
457 position: DisplayPoint,
458 reset: bool,
459 mode: ColumnarMode,
460 goal_column: u32,
461 },
462 Extend {
463 position: DisplayPoint,
464 click_count: usize,
465 },
466 Update {
467 position: DisplayPoint,
468 goal_column: u32,
469 scroll_delta: gpui::Point<f32>,
470 },
471 End,
472}
473
474#[derive(Clone, Debug, PartialEq)]
475pub enum ColumnarMode {
476 FromMouse,
477 FromSelection,
478}
479
480#[derive(Clone, Debug)]
481pub enum SelectMode {
482 Character,
483 Word(Range<Anchor>),
484 Line(Range<Anchor>),
485 All,
486}
487
488#[derive(Clone, PartialEq, Eq, Debug)]
489pub enum EditorMode {
490 SingleLine,
491 AutoHeight {
492 min_lines: usize,
493 max_lines: Option<usize>,
494 },
495 Full {
496 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
497 scale_ui_elements_with_buffer_font_size: bool,
498 /// When set to `true`, the editor will render a background for the active line.
499 show_active_line_background: bool,
500 /// When set to `true`, the editor's height will be determined by its content.
501 sized_by_content: bool,
502 },
503 Minimap {
504 parent: WeakEntity<Editor>,
505 },
506}
507
508impl EditorMode {
509 pub fn full() -> Self {
510 Self::Full {
511 scale_ui_elements_with_buffer_font_size: true,
512 show_active_line_background: true,
513 sized_by_content: false,
514 }
515 }
516
517 #[inline]
518 pub fn is_full(&self) -> bool {
519 matches!(self, Self::Full { .. })
520 }
521
522 #[inline]
523 pub fn is_single_line(&self) -> bool {
524 matches!(self, Self::SingleLine { .. })
525 }
526
527 #[inline]
528 fn is_minimap(&self) -> bool {
529 matches!(self, Self::Minimap { .. })
530 }
531}
532
533#[derive(Copy, Clone, Debug)]
534pub enum SoftWrap {
535 /// Prefer not to wrap at all.
536 ///
537 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
538 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
539 GitDiff,
540 /// Prefer a single line generally, unless an overly long line is encountered.
541 None,
542 /// Soft wrap lines that exceed the editor width.
543 EditorWidth,
544 /// Soft wrap lines at the preferred line length.
545 Column(u32),
546 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
547 Bounded(u32),
548}
549
550#[derive(Clone)]
551pub struct EditorStyle {
552 pub background: Hsla,
553 pub border: Hsla,
554 pub local_player: PlayerColor,
555 pub text: TextStyle,
556 pub scrollbar_width: Pixels,
557 pub syntax: Arc<SyntaxTheme>,
558 pub status: StatusColors,
559 pub inlay_hints_style: HighlightStyle,
560 pub edit_prediction_styles: EditPredictionStyles,
561 pub unnecessary_code_fade: f32,
562 pub show_underlines: bool,
563}
564
565impl Default for EditorStyle {
566 fn default() -> Self {
567 Self {
568 background: Hsla::default(),
569 border: Hsla::default(),
570 local_player: PlayerColor::default(),
571 text: TextStyle::default(),
572 scrollbar_width: Pixels::default(),
573 syntax: Default::default(),
574 // HACK: Status colors don't have a real default.
575 // We should look into removing the status colors from the editor
576 // style and retrieve them directly from the theme.
577 status: StatusColors::dark(),
578 inlay_hints_style: HighlightStyle::default(),
579 edit_prediction_styles: EditPredictionStyles {
580 insertion: HighlightStyle::default(),
581 whitespace: HighlightStyle::default(),
582 },
583 unnecessary_code_fade: Default::default(),
584 show_underlines: true,
585 }
586 }
587}
588
589pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
590 let show_background = language_settings::language_settings(None, None, cx)
591 .inlay_hints
592 .show_background;
593
594 let mut style = cx.theme().syntax().get("hint");
595
596 if style.color.is_none() {
597 style.color = Some(cx.theme().status().hint);
598 }
599
600 if !show_background {
601 style.background_color = None;
602 return style;
603 }
604
605 if style.background_color.is_none() {
606 style.background_color = Some(cx.theme().status().hint_background);
607 }
608
609 style
610}
611
612pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
613 EditPredictionStyles {
614 insertion: HighlightStyle {
615 color: Some(cx.theme().status().predictive),
616 ..HighlightStyle::default()
617 },
618 whitespace: HighlightStyle {
619 background_color: Some(cx.theme().status().created_background),
620 ..HighlightStyle::default()
621 },
622 }
623}
624
625type CompletionId = usize;
626
627pub(crate) enum EditDisplayMode {
628 TabAccept,
629 DiffPopover,
630 Inline,
631}
632
633enum EditPrediction {
634 Edit {
635 edits: Vec<(Range<Anchor>, String)>,
636 edit_preview: Option<EditPreview>,
637 display_mode: EditDisplayMode,
638 snapshot: BufferSnapshot,
639 },
640 /// Move to a specific location in the active editor
641 MoveWithin {
642 target: Anchor,
643 snapshot: BufferSnapshot,
644 },
645 /// Move to a specific location in a different editor (not the active one)
646 MoveOutside {
647 target: language::Anchor,
648 snapshot: BufferSnapshot,
649 },
650}
651
652struct EditPredictionState {
653 inlay_ids: Vec<InlayId>,
654 completion: EditPrediction,
655 completion_id: Option<SharedString>,
656 invalidation_range: Option<Range<Anchor>>,
657}
658
659enum EditPredictionSettings {
660 Disabled,
661 Enabled {
662 show_in_menu: bool,
663 preview_requires_modifier: bool,
664 },
665}
666
667enum EditPredictionHighlight {}
668
669#[derive(Debug, Clone)]
670struct InlineDiagnostic {
671 message: SharedString,
672 group_id: usize,
673 is_primary: bool,
674 start: Point,
675 severity: lsp::DiagnosticSeverity,
676}
677
678pub enum MenuEditPredictionsPolicy {
679 Never,
680 ByProvider,
681}
682
683pub enum EditPredictionPreview {
684 /// Modifier is not pressed
685 Inactive { released_too_fast: bool },
686 /// Modifier pressed
687 Active {
688 since: Instant,
689 previous_scroll_position: Option<ScrollAnchor>,
690 },
691}
692
693impl EditPredictionPreview {
694 pub fn released_too_fast(&self) -> bool {
695 match self {
696 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
697 EditPredictionPreview::Active { .. } => false,
698 }
699 }
700
701 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
702 if let EditPredictionPreview::Active {
703 previous_scroll_position,
704 ..
705 } = self
706 {
707 *previous_scroll_position = scroll_position;
708 }
709 }
710}
711
712pub struct ContextMenuOptions {
713 pub min_entries_visible: usize,
714 pub max_entries_visible: usize,
715 pub placement: Option<ContextMenuPlacement>,
716}
717
718#[derive(Debug, Clone, PartialEq, Eq)]
719pub enum ContextMenuPlacement {
720 Above,
721 Below,
722}
723
724#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
725struct EditorActionId(usize);
726
727impl EditorActionId {
728 pub fn post_inc(&mut self) -> Self {
729 let answer = self.0;
730
731 *self = Self(answer + 1);
732
733 Self(answer)
734 }
735}
736
737// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
738// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
739
740type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
741type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
742
743#[derive(Default)]
744struct ScrollbarMarkerState {
745 scrollbar_size: Size<Pixels>,
746 dirty: bool,
747 markers: Arc<[PaintQuad]>,
748 pending_refresh: Option<Task<Result<()>>>,
749}
750
751impl ScrollbarMarkerState {
752 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
753 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
754 }
755}
756
757#[derive(Clone, Copy, PartialEq, Eq)]
758pub enum MinimapVisibility {
759 Disabled,
760 Enabled {
761 /// The configuration currently present in the users settings.
762 setting_configuration: bool,
763 /// Whether to override the currently set visibility from the users setting.
764 toggle_override: bool,
765 },
766}
767
768impl MinimapVisibility {
769 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
770 if mode.is_full() {
771 Self::Enabled {
772 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
773 toggle_override: false,
774 }
775 } else {
776 Self::Disabled
777 }
778 }
779
780 fn hidden(&self) -> Self {
781 match *self {
782 Self::Enabled {
783 setting_configuration,
784 ..
785 } => Self::Enabled {
786 setting_configuration,
787 toggle_override: setting_configuration,
788 },
789 Self::Disabled => Self::Disabled,
790 }
791 }
792
793 fn disabled(&self) -> bool {
794 matches!(*self, Self::Disabled)
795 }
796
797 fn settings_visibility(&self) -> bool {
798 match *self {
799 Self::Enabled {
800 setting_configuration,
801 ..
802 } => setting_configuration,
803 _ => false,
804 }
805 }
806
807 fn visible(&self) -> bool {
808 match *self {
809 Self::Enabled {
810 setting_configuration,
811 toggle_override,
812 } => setting_configuration ^ toggle_override,
813 _ => false,
814 }
815 }
816
817 fn toggle_visibility(&self) -> Self {
818 match *self {
819 Self::Enabled {
820 toggle_override,
821 setting_configuration,
822 } => Self::Enabled {
823 setting_configuration,
824 toggle_override: !toggle_override,
825 },
826 Self::Disabled => Self::Disabled,
827 }
828 }
829}
830
831#[derive(Clone, Debug)]
832struct RunnableTasks {
833 templates: Vec<(TaskSourceKind, TaskTemplate)>,
834 offset: multi_buffer::Anchor,
835 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
836 column: u32,
837 // Values of all named captures, including those starting with '_'
838 extra_variables: HashMap<String, String>,
839 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
840 context_range: Range<BufferOffset>,
841}
842
843impl RunnableTasks {
844 fn resolve<'a>(
845 &'a self,
846 cx: &'a task::TaskContext,
847 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
848 self.templates.iter().filter_map(|(kind, template)| {
849 template
850 .resolve_task(&kind.to_id_base(), cx)
851 .map(|task| (kind.clone(), task))
852 })
853 }
854}
855
856#[derive(Clone)]
857pub struct ResolvedTasks {
858 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
859 position: Anchor,
860}
861
862#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
863struct BufferOffset(usize);
864
865/// Addons allow storing per-editor state in other crates (e.g. Vim)
866pub trait Addon: 'static {
867 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
868
869 fn render_buffer_header_controls(
870 &self,
871 _: &ExcerptInfo,
872 _: &Window,
873 _: &App,
874 ) -> Option<AnyElement> {
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 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 inlay_hint_cache: InlayHintCache,
1126 next_inlay_id: u32,
1127 next_color_inlay_id: u32,
1128 _subscriptions: Vec<Subscription>,
1129 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1130 gutter_dimensions: GutterDimensions,
1131 style: Option<EditorStyle>,
1132 text_style_refinement: Option<TextStyleRefinement>,
1133 next_editor_action_id: EditorActionId,
1134 editor_actions: Rc<
1135 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1136 >,
1137 use_autoclose: bool,
1138 use_auto_surround: bool,
1139 auto_replace_emoji_shortcode: bool,
1140 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1141 show_git_blame_gutter: bool,
1142 show_git_blame_inline: bool,
1143 show_git_blame_inline_delay_task: Option<Task<()>>,
1144 git_blame_inline_enabled: bool,
1145 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1146 serialize_dirty_buffers: bool,
1147 show_selection_menu: Option<bool>,
1148 blame: Option<Entity<GitBlame>>,
1149 blame_subscription: Option<Subscription>,
1150 custom_context_menu: Option<
1151 Box<
1152 dyn 'static
1153 + Fn(
1154 &mut Self,
1155 DisplayPoint,
1156 &mut Window,
1157 &mut Context<Self>,
1158 ) -> Option<Entity<ui::ContextMenu>>,
1159 >,
1160 >,
1161 last_bounds: Option<Bounds<Pixels>>,
1162 last_position_map: Option<Rc<PositionMap>>,
1163 expect_bounds_change: Option<Bounds<Pixels>>,
1164 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1165 tasks_update_task: Option<Task<()>>,
1166 breakpoint_store: Option<Entity<BreakpointStore>>,
1167 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1168 hovered_diff_hunk_row: Option<DisplayRow>,
1169 pull_diagnostics_task: Task<()>,
1170 in_project_search: bool,
1171 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1172 breadcrumb_header: Option<String>,
1173 focused_block: Option<FocusedBlock>,
1174 next_scroll_position: NextScrollCursorCenterTopBottom,
1175 addons: HashMap<TypeId, Box<dyn Addon>>,
1176 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1177 load_diff_task: Option<Shared<Task<()>>>,
1178 /// Whether we are temporarily displaying a diff other than git's
1179 temporary_diff_override: bool,
1180 selection_mark_mode: bool,
1181 toggle_fold_multiple_buffers: Task<()>,
1182 _scroll_cursor_center_top_bottom_task: Task<()>,
1183 serialize_selections: Task<()>,
1184 serialize_folds: Task<()>,
1185 mouse_cursor_hidden: bool,
1186 minimap: Option<Entity<Self>>,
1187 hide_mouse_mode: HideMouseMode,
1188 pub change_list: ChangeList,
1189 inline_value_cache: InlineValueCache,
1190 selection_drag_state: SelectionDragState,
1191 colors: Option<LspColorData>,
1192 post_scroll_update: Task<()>,
1193 refresh_colors_task: Task<()>,
1194 folding_newlines: Task<()>,
1195 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1196}
1197
1198#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1199enum NextScrollCursorCenterTopBottom {
1200 #[default]
1201 Center,
1202 Top,
1203 Bottom,
1204}
1205
1206impl NextScrollCursorCenterTopBottom {
1207 fn next(&self) -> Self {
1208 match self {
1209 Self::Center => Self::Top,
1210 Self::Top => Self::Bottom,
1211 Self::Bottom => Self::Center,
1212 }
1213 }
1214}
1215
1216#[derive(Clone)]
1217pub struct EditorSnapshot {
1218 pub mode: EditorMode,
1219 show_gutter: bool,
1220 show_line_numbers: Option<bool>,
1221 show_git_diff_gutter: Option<bool>,
1222 show_code_actions: Option<bool>,
1223 show_runnables: Option<bool>,
1224 show_breakpoints: Option<bool>,
1225 git_blame_gutter_max_author_length: Option<usize>,
1226 pub display_snapshot: DisplaySnapshot,
1227 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1228 is_focused: bool,
1229 scroll_anchor: ScrollAnchor,
1230 ongoing_scroll: OngoingScroll,
1231 current_line_highlight: CurrentLineHighlight,
1232 gutter_hovered: bool,
1233}
1234
1235#[derive(Default, Debug, Clone, Copy)]
1236pub struct GutterDimensions {
1237 pub left_padding: Pixels,
1238 pub right_padding: Pixels,
1239 pub width: Pixels,
1240 pub margin: Pixels,
1241 pub git_blame_entries_width: Option<Pixels>,
1242}
1243
1244impl GutterDimensions {
1245 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1246 Self {
1247 margin: Self::default_gutter_margin(font_id, font_size, cx),
1248 ..Default::default()
1249 }
1250 }
1251
1252 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1253 -cx.text_system().descent(font_id, font_size)
1254 }
1255 /// The full width of the space taken up by the gutter.
1256 pub fn full_width(&self) -> Pixels {
1257 self.margin + self.width
1258 }
1259
1260 /// The width of the space reserved for the fold indicators,
1261 /// use alongside 'justify_end' and `gutter_width` to
1262 /// right align content with the line numbers
1263 pub fn fold_area_width(&self) -> Pixels {
1264 self.margin + self.right_padding
1265 }
1266}
1267
1268struct CharacterDimensions {
1269 em_width: Pixels,
1270 em_advance: Pixels,
1271 line_height: Pixels,
1272}
1273
1274#[derive(Debug)]
1275pub struct RemoteSelection {
1276 pub replica_id: ReplicaId,
1277 pub selection: Selection<Anchor>,
1278 pub cursor_shape: CursorShape,
1279 pub collaborator_id: CollaboratorId,
1280 pub line_mode: bool,
1281 pub user_name: Option<SharedString>,
1282 pub color: PlayerColor,
1283}
1284
1285#[derive(Clone, Debug)]
1286struct SelectionHistoryEntry {
1287 selections: Arc<[Selection<Anchor>]>,
1288 select_next_state: Option<SelectNextState>,
1289 select_prev_state: Option<SelectNextState>,
1290 add_selections_state: Option<AddSelectionsState>,
1291}
1292
1293#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1294enum SelectionHistoryMode {
1295 Normal,
1296 Undoing,
1297 Redoing,
1298 Skipping,
1299}
1300
1301#[derive(Clone, PartialEq, Eq, Hash)]
1302struct HoveredCursor {
1303 replica_id: ReplicaId,
1304 selection_id: usize,
1305}
1306
1307impl Default for SelectionHistoryMode {
1308 fn default() -> Self {
1309 Self::Normal
1310 }
1311}
1312
1313#[derive(Debug)]
1314/// SelectionEffects controls the side-effects of updating the selection.
1315///
1316/// The default behaviour does "what you mostly want":
1317/// - it pushes to the nav history if the cursor moved by >10 lines
1318/// - it re-triggers completion requests
1319/// - it scrolls to fit
1320///
1321/// You might want to modify these behaviours. For example when doing a "jump"
1322/// like go to definition, we always want to add to nav history; but when scrolling
1323/// in vim mode we never do.
1324///
1325/// Similarly, you might want to disable scrolling if you don't want the viewport to
1326/// move.
1327#[derive(Clone)]
1328pub struct SelectionEffects {
1329 nav_history: Option<bool>,
1330 completions: bool,
1331 scroll: Option<Autoscroll>,
1332}
1333
1334impl Default for SelectionEffects {
1335 fn default() -> Self {
1336 Self {
1337 nav_history: None,
1338 completions: true,
1339 scroll: Some(Autoscroll::fit()),
1340 }
1341 }
1342}
1343impl SelectionEffects {
1344 pub fn scroll(scroll: Autoscroll) -> Self {
1345 Self {
1346 scroll: Some(scroll),
1347 ..Default::default()
1348 }
1349 }
1350
1351 pub fn no_scroll() -> Self {
1352 Self {
1353 scroll: None,
1354 ..Default::default()
1355 }
1356 }
1357
1358 pub fn completions(self, completions: bool) -> Self {
1359 Self {
1360 completions,
1361 ..self
1362 }
1363 }
1364
1365 pub fn nav_history(self, nav_history: bool) -> Self {
1366 Self {
1367 nav_history: Some(nav_history),
1368 ..self
1369 }
1370 }
1371}
1372
1373struct DeferredSelectionEffectsState {
1374 changed: bool,
1375 effects: SelectionEffects,
1376 old_cursor_position: Anchor,
1377 history_entry: SelectionHistoryEntry,
1378}
1379
1380#[derive(Default)]
1381struct SelectionHistory {
1382 #[allow(clippy::type_complexity)]
1383 selections_by_transaction:
1384 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1385 mode: SelectionHistoryMode,
1386 undo_stack: VecDeque<SelectionHistoryEntry>,
1387 redo_stack: VecDeque<SelectionHistoryEntry>,
1388}
1389
1390impl SelectionHistory {
1391 #[track_caller]
1392 fn insert_transaction(
1393 &mut self,
1394 transaction_id: TransactionId,
1395 selections: Arc<[Selection<Anchor>]>,
1396 ) {
1397 if selections.is_empty() {
1398 log::error!(
1399 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1400 std::panic::Location::caller()
1401 );
1402 return;
1403 }
1404 self.selections_by_transaction
1405 .insert(transaction_id, (selections, None));
1406 }
1407
1408 #[allow(clippy::type_complexity)]
1409 fn transaction(
1410 &self,
1411 transaction_id: TransactionId,
1412 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1413 self.selections_by_transaction.get(&transaction_id)
1414 }
1415
1416 #[allow(clippy::type_complexity)]
1417 fn transaction_mut(
1418 &mut self,
1419 transaction_id: TransactionId,
1420 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1421 self.selections_by_transaction.get_mut(&transaction_id)
1422 }
1423
1424 fn push(&mut self, entry: SelectionHistoryEntry) {
1425 if !entry.selections.is_empty() {
1426 match self.mode {
1427 SelectionHistoryMode::Normal => {
1428 self.push_undo(entry);
1429 self.redo_stack.clear();
1430 }
1431 SelectionHistoryMode::Undoing => self.push_redo(entry),
1432 SelectionHistoryMode::Redoing => self.push_undo(entry),
1433 SelectionHistoryMode::Skipping => {}
1434 }
1435 }
1436 }
1437
1438 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1439 if self
1440 .undo_stack
1441 .back()
1442 .is_none_or(|e| e.selections != entry.selections)
1443 {
1444 self.undo_stack.push_back(entry);
1445 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1446 self.undo_stack.pop_front();
1447 }
1448 }
1449 }
1450
1451 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1452 if self
1453 .redo_stack
1454 .back()
1455 .is_none_or(|e| e.selections != entry.selections)
1456 {
1457 self.redo_stack.push_back(entry);
1458 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1459 self.redo_stack.pop_front();
1460 }
1461 }
1462 }
1463}
1464
1465#[derive(Clone, Copy)]
1466pub struct RowHighlightOptions {
1467 pub autoscroll: bool,
1468 pub include_gutter: bool,
1469}
1470
1471impl Default for RowHighlightOptions {
1472 fn default() -> Self {
1473 Self {
1474 autoscroll: Default::default(),
1475 include_gutter: true,
1476 }
1477 }
1478}
1479
1480struct RowHighlight {
1481 index: usize,
1482 range: Range<Anchor>,
1483 color: Hsla,
1484 options: RowHighlightOptions,
1485 type_id: TypeId,
1486}
1487
1488#[derive(Clone, Debug)]
1489struct AddSelectionsState {
1490 groups: Vec<AddSelectionsGroup>,
1491}
1492
1493#[derive(Clone, Debug)]
1494struct AddSelectionsGroup {
1495 above: bool,
1496 stack: Vec<usize>,
1497}
1498
1499#[derive(Clone)]
1500struct SelectNextState {
1501 query: AhoCorasick,
1502 wordwise: bool,
1503 done: bool,
1504}
1505
1506impl std::fmt::Debug for SelectNextState {
1507 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1508 f.debug_struct(std::any::type_name::<Self>())
1509 .field("wordwise", &self.wordwise)
1510 .field("done", &self.done)
1511 .finish()
1512 }
1513}
1514
1515#[derive(Debug)]
1516struct AutocloseRegion {
1517 selection_id: usize,
1518 range: Range<Anchor>,
1519 pair: BracketPair,
1520}
1521
1522#[derive(Debug)]
1523struct SnippetState {
1524 ranges: Vec<Vec<Range<Anchor>>>,
1525 active_index: usize,
1526 choices: Vec<Option<Vec<String>>>,
1527}
1528
1529#[doc(hidden)]
1530pub struct RenameState {
1531 pub range: Range<Anchor>,
1532 pub old_name: Arc<str>,
1533 pub editor: Entity<Editor>,
1534 block_id: CustomBlockId,
1535}
1536
1537struct InvalidationStack<T>(Vec<T>);
1538
1539struct RegisteredEditPredictionProvider {
1540 provider: Arc<dyn EditPredictionProviderHandle>,
1541 _subscription: Subscription,
1542}
1543
1544#[derive(Debug, PartialEq, Eq)]
1545pub struct ActiveDiagnosticGroup {
1546 pub active_range: Range<Anchor>,
1547 pub active_message: String,
1548 pub group_id: usize,
1549 pub blocks: HashSet<CustomBlockId>,
1550}
1551
1552#[derive(Debug, PartialEq, Eq)]
1553
1554pub(crate) enum ActiveDiagnostic {
1555 None,
1556 All,
1557 Group(ActiveDiagnosticGroup),
1558}
1559
1560#[derive(Serialize, Deserialize, Clone, Debug)]
1561pub struct ClipboardSelection {
1562 /// The number of bytes in this selection.
1563 pub len: usize,
1564 /// Whether this was a full-line selection.
1565 pub is_entire_line: bool,
1566 /// The indentation of the first line when this content was originally copied.
1567 pub first_line_indent: u32,
1568}
1569
1570// selections, scroll behavior, was newest selection reversed
1571type SelectSyntaxNodeHistoryState = (
1572 Box<[Selection<usize>]>,
1573 SelectSyntaxNodeScrollBehavior,
1574 bool,
1575);
1576
1577#[derive(Default)]
1578struct SelectSyntaxNodeHistory {
1579 stack: Vec<SelectSyntaxNodeHistoryState>,
1580 // disable temporarily to allow changing selections without losing the stack
1581 pub disable_clearing: bool,
1582}
1583
1584impl SelectSyntaxNodeHistory {
1585 pub fn try_clear(&mut self) {
1586 if !self.disable_clearing {
1587 self.stack.clear();
1588 }
1589 }
1590
1591 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1592 self.stack.push(selection);
1593 }
1594
1595 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1596 self.stack.pop()
1597 }
1598}
1599
1600enum SelectSyntaxNodeScrollBehavior {
1601 CursorTop,
1602 FitSelection,
1603 CursorBottom,
1604}
1605
1606#[derive(Debug)]
1607pub(crate) struct NavigationData {
1608 cursor_anchor: Anchor,
1609 cursor_position: Point,
1610 scroll_anchor: ScrollAnchor,
1611 scroll_top_row: u32,
1612}
1613
1614#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1615pub enum GotoDefinitionKind {
1616 Symbol,
1617 Declaration,
1618 Type,
1619 Implementation,
1620}
1621
1622#[derive(Debug, Clone)]
1623enum InlayHintRefreshReason {
1624 ModifiersChanged(bool),
1625 Toggle(bool),
1626 SettingsChange(InlayHintSettings),
1627 NewLinesShown,
1628 BufferEdited(HashSet<Arc<Language>>),
1629 RefreshRequested,
1630 ExcerptsRemoved(Vec<ExcerptId>),
1631}
1632
1633impl InlayHintRefreshReason {
1634 fn description(&self) -> &'static str {
1635 match self {
1636 Self::ModifiersChanged(_) => "modifiers changed",
1637 Self::Toggle(_) => "toggle",
1638 Self::SettingsChange(_) => "settings change",
1639 Self::NewLinesShown => "new lines shown",
1640 Self::BufferEdited(_) => "buffer edited",
1641 Self::RefreshRequested => "refresh requested",
1642 Self::ExcerptsRemoved(_) => "excerpts removed",
1643 }
1644 }
1645}
1646
1647pub enum FormatTarget {
1648 Buffers(HashSet<Entity<Buffer>>),
1649 Ranges(Vec<Range<MultiBufferPoint>>),
1650}
1651
1652pub(crate) struct FocusedBlock {
1653 id: BlockId,
1654 focus_handle: WeakFocusHandle,
1655}
1656
1657#[derive(Clone)]
1658enum JumpData {
1659 MultiBufferRow {
1660 row: MultiBufferRow,
1661 line_offset_from_top: u32,
1662 },
1663 MultiBufferPoint {
1664 excerpt_id: ExcerptId,
1665 position: Point,
1666 anchor: text::Anchor,
1667 line_offset_from_top: u32,
1668 },
1669}
1670
1671pub enum MultibufferSelectionMode {
1672 First,
1673 All,
1674}
1675
1676#[derive(Clone, Copy, Debug, Default)]
1677pub struct RewrapOptions {
1678 pub override_language_settings: bool,
1679 pub preserve_existing_whitespace: bool,
1680}
1681
1682impl Editor {
1683 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1684 let buffer = cx.new(|cx| Buffer::local("", cx));
1685 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1686 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1687 }
1688
1689 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1690 let buffer = cx.new(|cx| Buffer::local("", cx));
1691 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1692 Self::new(EditorMode::full(), buffer, None, window, cx)
1693 }
1694
1695 pub fn auto_height(
1696 min_lines: usize,
1697 max_lines: usize,
1698 window: &mut Window,
1699 cx: &mut Context<Self>,
1700 ) -> Self {
1701 let buffer = cx.new(|cx| Buffer::local("", cx));
1702 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1703 Self::new(
1704 EditorMode::AutoHeight {
1705 min_lines,
1706 max_lines: Some(max_lines),
1707 },
1708 buffer,
1709 None,
1710 window,
1711 cx,
1712 )
1713 }
1714
1715 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1716 /// The editor grows as tall as needed to fit its content.
1717 pub fn auto_height_unbounded(
1718 min_lines: usize,
1719 window: &mut Window,
1720 cx: &mut Context<Self>,
1721 ) -> Self {
1722 let buffer = cx.new(|cx| Buffer::local("", cx));
1723 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1724 Self::new(
1725 EditorMode::AutoHeight {
1726 min_lines,
1727 max_lines: None,
1728 },
1729 buffer,
1730 None,
1731 window,
1732 cx,
1733 )
1734 }
1735
1736 pub fn for_buffer(
1737 buffer: Entity<Buffer>,
1738 project: Option<Entity<Project>>,
1739 window: &mut Window,
1740 cx: &mut Context<Self>,
1741 ) -> Self {
1742 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1743 Self::new(EditorMode::full(), buffer, project, window, cx)
1744 }
1745
1746 pub fn for_multibuffer(
1747 buffer: Entity<MultiBuffer>,
1748 project: Option<Entity<Project>>,
1749 window: &mut Window,
1750 cx: &mut Context<Self>,
1751 ) -> Self {
1752 Self::new(EditorMode::full(), buffer, project, window, cx)
1753 }
1754
1755 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1756 let mut clone = Self::new(
1757 self.mode.clone(),
1758 self.buffer.clone(),
1759 self.project.clone(),
1760 window,
1761 cx,
1762 );
1763 self.display_map.update(cx, |display_map, cx| {
1764 let snapshot = display_map.snapshot(cx);
1765 clone.display_map.update(cx, |display_map, cx| {
1766 display_map.set_state(&snapshot, cx);
1767 });
1768 });
1769 clone.folds_did_change(cx);
1770 clone.selections.clone_state(&self.selections);
1771 clone.scroll_manager.clone_state(&self.scroll_manager);
1772 clone.searchable = self.searchable;
1773 clone.read_only = self.read_only;
1774 clone
1775 }
1776
1777 pub fn new(
1778 mode: EditorMode,
1779 buffer: Entity<MultiBuffer>,
1780 project: Option<Entity<Project>>,
1781 window: &mut Window,
1782 cx: &mut Context<Self>,
1783 ) -> Self {
1784 Editor::new_internal(mode, buffer, project, None, window, cx)
1785 }
1786
1787 fn new_internal(
1788 mode: EditorMode,
1789 multi_buffer: Entity<MultiBuffer>,
1790 project: Option<Entity<Project>>,
1791 display_map: Option<Entity<DisplayMap>>,
1792 window: &mut Window,
1793 cx: &mut Context<Self>,
1794 ) -> Self {
1795 debug_assert!(
1796 display_map.is_none() || mode.is_minimap(),
1797 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1798 );
1799
1800 let full_mode = mode.is_full();
1801 let is_minimap = mode.is_minimap();
1802 let diagnostics_max_severity = if full_mode {
1803 EditorSettings::get_global(cx)
1804 .diagnostics_max_severity
1805 .unwrap_or(DiagnosticSeverity::Hint)
1806 } else {
1807 DiagnosticSeverity::Off
1808 };
1809 let style = window.text_style();
1810 let font_size = style.font_size.to_pixels(window.rem_size());
1811 let editor = cx.entity().downgrade();
1812 let fold_placeholder = FoldPlaceholder {
1813 constrain_width: false,
1814 render: Arc::new(move |fold_id, fold_range, cx| {
1815 let editor = editor.clone();
1816 div()
1817 .id(fold_id)
1818 .bg(cx.theme().colors().ghost_element_background)
1819 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1820 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1821 .rounded_xs()
1822 .size_full()
1823 .cursor_pointer()
1824 .child("⋯")
1825 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1826 .on_click(move |_, _window, cx| {
1827 editor
1828 .update(cx, |editor, cx| {
1829 editor.unfold_ranges(
1830 &[fold_range.start..fold_range.end],
1831 true,
1832 false,
1833 cx,
1834 );
1835 cx.stop_propagation();
1836 })
1837 .ok();
1838 })
1839 .into_any()
1840 }),
1841 merge_adjacent: true,
1842 ..FoldPlaceholder::default()
1843 };
1844 let display_map = display_map.unwrap_or_else(|| {
1845 cx.new(|cx| {
1846 DisplayMap::new(
1847 multi_buffer.clone(),
1848 style.font(),
1849 font_size,
1850 None,
1851 FILE_HEADER_HEIGHT,
1852 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1853 fold_placeholder,
1854 diagnostics_max_severity,
1855 cx,
1856 )
1857 })
1858 });
1859
1860 let selections = SelectionsCollection::new(display_map.clone(), multi_buffer.clone());
1861
1862 let blink_manager = cx.new(|cx| {
1863 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1864 if is_minimap {
1865 blink_manager.disable(cx);
1866 }
1867 blink_manager
1868 });
1869
1870 let soft_wrap_mode_override =
1871 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1872
1873 let mut project_subscriptions = Vec::new();
1874 if full_mode && let Some(project) = project.as_ref() {
1875 project_subscriptions.push(cx.subscribe_in(
1876 project,
1877 window,
1878 |editor, _, event, window, cx| match event {
1879 project::Event::RefreshCodeLens => {
1880 // we always query lens with actions, without storing them, always refreshing them
1881 }
1882 project::Event::RefreshInlayHints => {
1883 editor.refresh_inlay_hints(InlayHintRefreshReason::RefreshRequested, cx);
1884 }
1885 project::Event::LanguageServerRemoved(..) => {
1886 if editor.tasks_update_task.is_none() {
1887 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1888 }
1889 editor.registered_buffers.clear();
1890 editor.register_visible_buffers(cx);
1891 }
1892 project::Event::LanguageServerAdded(..) => {
1893 if editor.tasks_update_task.is_none() {
1894 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1895 }
1896 }
1897 project::Event::SnippetEdit(id, snippet_edits) => {
1898 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1899 let focus_handle = editor.focus_handle(cx);
1900 if focus_handle.is_focused(window) {
1901 let snapshot = buffer.read(cx).snapshot();
1902 for (range, snippet) in snippet_edits {
1903 let editor_range =
1904 language::range_from_lsp(*range).to_offset(&snapshot);
1905 editor
1906 .insert_snippet(
1907 &[editor_range],
1908 snippet.clone(),
1909 window,
1910 cx,
1911 )
1912 .ok();
1913 }
1914 }
1915 }
1916 }
1917 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1918 let buffer_id = *buffer_id;
1919 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1920 let registered = editor.register_buffer(buffer_id, cx);
1921 if registered {
1922 editor.update_lsp_data(Some(buffer_id), window, cx);
1923 editor.refresh_inlay_hints(
1924 InlayHintRefreshReason::RefreshRequested,
1925 cx,
1926 );
1927 refresh_linked_ranges(editor, window, cx);
1928 editor.refresh_code_actions(window, cx);
1929 editor.refresh_document_highlights(cx);
1930 }
1931 }
1932 }
1933
1934 project::Event::EntryRenamed(transaction) => {
1935 let Some(workspace) = editor.workspace() else {
1936 return;
1937 };
1938 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1939 else {
1940 return;
1941 };
1942 if active_editor.entity_id() == cx.entity_id() {
1943 let edited_buffers_already_open = {
1944 let other_editors: Vec<Entity<Editor>> = workspace
1945 .read(cx)
1946 .panes()
1947 .iter()
1948 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1949 .filter(|editor| editor.entity_id() != cx.entity_id())
1950 .collect();
1951
1952 transaction.0.keys().all(|buffer| {
1953 other_editors.iter().any(|editor| {
1954 let multi_buffer = editor.read(cx).buffer();
1955 multi_buffer.read(cx).is_singleton()
1956 && multi_buffer.read(cx).as_singleton().map_or(
1957 false,
1958 |singleton| {
1959 singleton.entity_id() == buffer.entity_id()
1960 },
1961 )
1962 })
1963 })
1964 };
1965
1966 if !edited_buffers_already_open {
1967 let workspace = workspace.downgrade();
1968 let transaction = transaction.clone();
1969 cx.defer_in(window, move |_, window, cx| {
1970 cx.spawn_in(window, async move |editor, cx| {
1971 Self::open_project_transaction(
1972 &editor,
1973 workspace,
1974 transaction,
1975 "Rename".to_string(),
1976 cx,
1977 )
1978 .await
1979 .ok()
1980 })
1981 .detach();
1982 });
1983 }
1984 }
1985 }
1986
1987 _ => {}
1988 },
1989 ));
1990 if let Some(task_inventory) = project
1991 .read(cx)
1992 .task_store()
1993 .read(cx)
1994 .task_inventory()
1995 .cloned()
1996 {
1997 project_subscriptions.push(cx.observe_in(
1998 &task_inventory,
1999 window,
2000 |editor, _, window, cx| {
2001 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2002 },
2003 ));
2004 };
2005
2006 project_subscriptions.push(cx.subscribe_in(
2007 &project.read(cx).breakpoint_store(),
2008 window,
2009 |editor, _, event, window, cx| match event {
2010 BreakpointStoreEvent::ClearDebugLines => {
2011 editor.clear_row_highlights::<ActiveDebugLine>();
2012 editor.refresh_inline_values(cx);
2013 }
2014 BreakpointStoreEvent::SetDebugLine => {
2015 if editor.go_to_active_debug_line(window, cx) {
2016 cx.stop_propagation();
2017 }
2018
2019 editor.refresh_inline_values(cx);
2020 }
2021 _ => {}
2022 },
2023 ));
2024 let git_store = project.read(cx).git_store().clone();
2025 let project = project.clone();
2026 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2027 if let GitStoreEvent::RepositoryUpdated(
2028 _,
2029 RepositoryEvent::Updated {
2030 new_instance: true, ..
2031 },
2032 _,
2033 ) = event
2034 {
2035 this.load_diff_task = Some(
2036 update_uncommitted_diff_for_buffer(
2037 cx.entity(),
2038 &project,
2039 this.buffer.read(cx).all_buffers(),
2040 this.buffer.clone(),
2041 cx,
2042 )
2043 .shared(),
2044 );
2045 }
2046 }));
2047 }
2048
2049 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2050
2051 let inlay_hint_settings =
2052 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2053 let focus_handle = cx.focus_handle();
2054 if !is_minimap {
2055 cx.on_focus(&focus_handle, window, Self::handle_focus)
2056 .detach();
2057 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2058 .detach();
2059 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2060 .detach();
2061 cx.on_blur(&focus_handle, window, Self::handle_blur)
2062 .detach();
2063 cx.observe_pending_input(window, Self::observe_pending_input)
2064 .detach();
2065 }
2066
2067 let show_indent_guides =
2068 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2069 Some(false)
2070 } else {
2071 None
2072 };
2073
2074 let breakpoint_store = match (&mode, project.as_ref()) {
2075 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2076 _ => None,
2077 };
2078
2079 let mut code_action_providers = Vec::new();
2080 let mut load_uncommitted_diff = None;
2081 if let Some(project) = project.clone() {
2082 load_uncommitted_diff = Some(
2083 update_uncommitted_diff_for_buffer(
2084 cx.entity(),
2085 &project,
2086 multi_buffer.read(cx).all_buffers(),
2087 multi_buffer.clone(),
2088 cx,
2089 )
2090 .shared(),
2091 );
2092 code_action_providers.push(Rc::new(project) as Rc<_>);
2093 }
2094
2095 let mut editor = Self {
2096 focus_handle,
2097 show_cursor_when_unfocused: false,
2098 last_focused_descendant: None,
2099 buffer: multi_buffer.clone(),
2100 display_map: display_map.clone(),
2101 placeholder_display_map: None,
2102 selections,
2103 scroll_manager: ScrollManager::new(cx),
2104 columnar_selection_state: None,
2105 add_selections_state: None,
2106 select_next_state: None,
2107 select_prev_state: None,
2108 selection_history: SelectionHistory::default(),
2109 defer_selection_effects: false,
2110 deferred_selection_effects_state: None,
2111 autoclose_regions: Vec::new(),
2112 snippet_stack: InvalidationStack::default(),
2113 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2114 ime_transaction: None,
2115 active_diagnostics: ActiveDiagnostic::None,
2116 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2117 inline_diagnostics_update: Task::ready(()),
2118 inline_diagnostics: Vec::new(),
2119 soft_wrap_mode_override,
2120 diagnostics_max_severity,
2121 hard_wrap: None,
2122 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2123 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2124 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2125 project,
2126 blink_manager: blink_manager.clone(),
2127 show_local_selections: true,
2128 show_scrollbars: ScrollbarAxes {
2129 horizontal: full_mode,
2130 vertical: full_mode,
2131 },
2132 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2133 offset_content: !matches!(mode, EditorMode::SingleLine),
2134 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2135 show_gutter: full_mode,
2136 show_line_numbers: (!full_mode).then_some(false),
2137 use_relative_line_numbers: None,
2138 disable_expand_excerpt_buttons: !full_mode,
2139 show_git_diff_gutter: None,
2140 show_code_actions: None,
2141 show_runnables: None,
2142 show_breakpoints: None,
2143 show_wrap_guides: None,
2144 show_indent_guides,
2145 highlight_order: 0,
2146 highlighted_rows: HashMap::default(),
2147 background_highlights: HashMap::default(),
2148 gutter_highlights: HashMap::default(),
2149 scrollbar_marker_state: ScrollbarMarkerState::default(),
2150 active_indent_guides_state: ActiveIndentGuidesState::default(),
2151 nav_history: None,
2152 context_menu: RefCell::new(None),
2153 context_menu_options: None,
2154 mouse_context_menu: None,
2155 completion_tasks: Vec::new(),
2156 inline_blame_popover: None,
2157 inline_blame_popover_show_task: None,
2158 signature_help_state: SignatureHelpState::default(),
2159 auto_signature_help: None,
2160 find_all_references_task_sources: Vec::new(),
2161 next_completion_id: 0,
2162 next_inlay_id: 0,
2163 code_action_providers,
2164 available_code_actions: None,
2165 code_actions_task: None,
2166 quick_selection_highlight_task: None,
2167 debounced_selection_highlight_task: None,
2168 document_highlights_task: None,
2169 linked_editing_range_task: None,
2170 pending_rename: None,
2171 searchable: !is_minimap,
2172 cursor_shape: EditorSettings::get_global(cx)
2173 .cursor_shape
2174 .unwrap_or_default(),
2175 current_line_highlight: None,
2176 autoindent_mode: Some(AutoindentMode::EachLine),
2177 collapse_matches: false,
2178 workspace: None,
2179 input_enabled: !is_minimap,
2180 use_modal_editing: full_mode,
2181 read_only: is_minimap,
2182 use_autoclose: true,
2183 use_auto_surround: true,
2184 auto_replace_emoji_shortcode: false,
2185 jsx_tag_auto_close_enabled_in_any_buffer: false,
2186 leader_id: None,
2187 remote_id: None,
2188 hover_state: HoverState::default(),
2189 pending_mouse_down: None,
2190 hovered_link_state: None,
2191 edit_prediction_provider: None,
2192 active_edit_prediction: None,
2193 stale_edit_prediction_in_menu: None,
2194 edit_prediction_preview: EditPredictionPreview::Inactive {
2195 released_too_fast: false,
2196 },
2197 inline_diagnostics_enabled: full_mode,
2198 diagnostics_enabled: full_mode,
2199 word_completions_enabled: full_mode,
2200 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2201 inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
2202 gutter_hovered: false,
2203 pixel_position_of_newest_cursor: None,
2204 last_bounds: None,
2205 last_position_map: None,
2206 expect_bounds_change: None,
2207 gutter_dimensions: GutterDimensions::default(),
2208 style: None,
2209 show_cursor_names: false,
2210 hovered_cursors: HashMap::default(),
2211 next_editor_action_id: EditorActionId::default(),
2212 editor_actions: Rc::default(),
2213 edit_predictions_hidden_for_vim_mode: false,
2214 show_edit_predictions_override: None,
2215 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2216 edit_prediction_settings: EditPredictionSettings::Disabled,
2217 edit_prediction_indent_conflict: false,
2218 edit_prediction_requires_modifier_in_indent_conflict: true,
2219 custom_context_menu: None,
2220 show_git_blame_gutter: false,
2221 show_git_blame_inline: false,
2222 show_selection_menu: None,
2223 show_git_blame_inline_delay_task: None,
2224 git_blame_inline_enabled: full_mode
2225 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2226 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2227 serialize_dirty_buffers: !is_minimap
2228 && ProjectSettings::get_global(cx)
2229 .session
2230 .restore_unsaved_buffers,
2231 blame: None,
2232 blame_subscription: None,
2233 tasks: BTreeMap::default(),
2234
2235 breakpoint_store,
2236 gutter_breakpoint_indicator: (None, None),
2237 hovered_diff_hunk_row: None,
2238 _subscriptions: (!is_minimap)
2239 .then(|| {
2240 vec![
2241 cx.observe(&multi_buffer, Self::on_buffer_changed),
2242 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2243 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2244 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2245 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2246 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2247 cx.observe_window_activation(window, |editor, window, cx| {
2248 let active = window.is_window_active();
2249 editor.blink_manager.update(cx, |blink_manager, cx| {
2250 if active {
2251 blink_manager.enable(cx);
2252 } else {
2253 blink_manager.disable(cx);
2254 }
2255 });
2256 if active {
2257 editor.show_mouse_cursor(cx);
2258 }
2259 }),
2260 ]
2261 })
2262 .unwrap_or_default(),
2263 tasks_update_task: None,
2264 pull_diagnostics_task: Task::ready(()),
2265 colors: None,
2266 refresh_colors_task: Task::ready(()),
2267 next_color_inlay_id: 0,
2268 post_scroll_update: Task::ready(()),
2269 linked_edit_ranges: Default::default(),
2270 in_project_search: false,
2271 previous_search_ranges: None,
2272 breadcrumb_header: None,
2273 focused_block: None,
2274 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2275 addons: HashMap::default(),
2276 registered_buffers: HashMap::default(),
2277 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2278 selection_mark_mode: false,
2279 toggle_fold_multiple_buffers: Task::ready(()),
2280 serialize_selections: Task::ready(()),
2281 serialize_folds: Task::ready(()),
2282 text_style_refinement: None,
2283 load_diff_task: load_uncommitted_diff,
2284 temporary_diff_override: false,
2285 mouse_cursor_hidden: false,
2286 minimap: None,
2287 hide_mouse_mode: EditorSettings::get_global(cx)
2288 .hide_mouse
2289 .unwrap_or_default(),
2290 change_list: ChangeList::new(),
2291 mode,
2292 selection_drag_state: SelectionDragState::None,
2293 folding_newlines: Task::ready(()),
2294 lookup_key: None,
2295 };
2296
2297 if is_minimap {
2298 return editor;
2299 }
2300
2301 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2302 editor
2303 ._subscriptions
2304 .push(cx.observe(breakpoints, |_, _, cx| {
2305 cx.notify();
2306 }));
2307 }
2308 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2309 editor._subscriptions.extend(project_subscriptions);
2310
2311 editor._subscriptions.push(cx.subscribe_in(
2312 &cx.entity(),
2313 window,
2314 |editor, _, e: &EditorEvent, window, cx| match e {
2315 EditorEvent::ScrollPositionChanged { local, .. } => {
2316 if *local {
2317 let new_anchor = editor.scroll_manager.anchor();
2318 let snapshot = editor.snapshot(window, cx);
2319 editor.update_restoration_data(cx, move |data| {
2320 data.scroll_position = (
2321 new_anchor.top_row(snapshot.buffer_snapshot()),
2322 new_anchor.offset,
2323 );
2324 });
2325 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2326 editor.inline_blame_popover.take();
2327 }
2328 }
2329 EditorEvent::Edited { .. } => {
2330 if !vim_enabled(cx) {
2331 let display_map = editor.display_snapshot(cx);
2332 let selections = editor.selections.all_adjusted_display(&display_map);
2333 let pop_state = editor
2334 .change_list
2335 .last()
2336 .map(|previous| {
2337 previous.len() == selections.len()
2338 && previous.iter().enumerate().all(|(ix, p)| {
2339 p.to_display_point(&display_map).row()
2340 == selections[ix].head().row()
2341 })
2342 })
2343 .unwrap_or(false);
2344 let new_positions = selections
2345 .into_iter()
2346 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2347 .collect();
2348 editor
2349 .change_list
2350 .push_to_change_list(pop_state, new_positions);
2351 }
2352 }
2353 _ => (),
2354 },
2355 ));
2356
2357 if let Some(dap_store) = editor
2358 .project
2359 .as_ref()
2360 .map(|project| project.read(cx).dap_store())
2361 {
2362 let weak_editor = cx.weak_entity();
2363
2364 editor
2365 ._subscriptions
2366 .push(
2367 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2368 let session_entity = cx.entity();
2369 weak_editor
2370 .update(cx, |editor, cx| {
2371 editor._subscriptions.push(
2372 cx.subscribe(&session_entity, Self::on_debug_session_event),
2373 );
2374 })
2375 .ok();
2376 }),
2377 );
2378
2379 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2380 editor
2381 ._subscriptions
2382 .push(cx.subscribe(&session, Self::on_debug_session_event));
2383 }
2384 }
2385
2386 // skip adding the initial selection to selection history
2387 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2388 editor.end_selection(window, cx);
2389 editor.selection_history.mode = SelectionHistoryMode::Normal;
2390
2391 editor.scroll_manager.show_scrollbars(window, cx);
2392 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2393
2394 if full_mode {
2395 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2396 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2397
2398 if editor.git_blame_inline_enabled {
2399 editor.start_git_blame_inline(false, window, cx);
2400 }
2401
2402 editor.go_to_active_debug_line(window, cx);
2403
2404 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2405 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2406 }
2407
2408 editor.minimap =
2409 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2410 editor.colors = Some(LspColorData::new(cx));
2411 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2412 }
2413
2414 editor
2415 }
2416
2417 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2418 self.selections.display_map(cx)
2419 }
2420
2421 pub fn deploy_mouse_context_menu(
2422 &mut self,
2423 position: gpui::Point<Pixels>,
2424 context_menu: Entity<ContextMenu>,
2425 window: &mut Window,
2426 cx: &mut Context<Self>,
2427 ) {
2428 self.mouse_context_menu = Some(MouseContextMenu::new(
2429 self,
2430 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2431 context_menu,
2432 window,
2433 cx,
2434 ));
2435 }
2436
2437 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2438 self.mouse_context_menu
2439 .as_ref()
2440 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2441 }
2442
2443 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2444 if self
2445 .selections
2446 .pending_anchor()
2447 .is_some_and(|pending_selection| {
2448 let snapshot = self.buffer().read(cx).snapshot(cx);
2449 pending_selection.range().includes(range, &snapshot)
2450 })
2451 {
2452 return true;
2453 }
2454
2455 self.selections
2456 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2457 .into_iter()
2458 .any(|selection| {
2459 // This is needed to cover a corner case, if we just check for an existing
2460 // selection in the fold range, having a cursor at the start of the fold
2461 // marks it as selected. Non-empty selections don't cause this.
2462 let length = selection.end - selection.start;
2463 length > 0
2464 })
2465 }
2466
2467 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2468 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2469 }
2470
2471 fn key_context_internal(
2472 &self,
2473 has_active_edit_prediction: bool,
2474 window: &mut Window,
2475 cx: &mut App,
2476 ) -> KeyContext {
2477 let mut key_context = KeyContext::new_with_defaults();
2478 key_context.add("Editor");
2479 let mode = match self.mode {
2480 EditorMode::SingleLine => "single_line",
2481 EditorMode::AutoHeight { .. } => "auto_height",
2482 EditorMode::Minimap { .. } => "minimap",
2483 EditorMode::Full { .. } => "full",
2484 };
2485
2486 if EditorSettings::jupyter_enabled(cx) {
2487 key_context.add("jupyter");
2488 }
2489
2490 key_context.set("mode", mode);
2491 if self.pending_rename.is_some() {
2492 key_context.add("renaming");
2493 }
2494
2495 match self.context_menu.borrow().as_ref() {
2496 Some(CodeContextMenu::Completions(menu)) => {
2497 if menu.visible() {
2498 key_context.add("menu");
2499 key_context.add("showing_completions");
2500 }
2501 }
2502 Some(CodeContextMenu::CodeActions(menu)) => {
2503 if menu.visible() {
2504 key_context.add("menu");
2505 key_context.add("showing_code_actions")
2506 }
2507 }
2508 None => {}
2509 }
2510
2511 if self.signature_help_state.has_multiple_signatures() {
2512 key_context.add("showing_signature_help");
2513 }
2514
2515 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2516 if !self.focus_handle(cx).contains_focused(window, cx)
2517 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2518 {
2519 for addon in self.addons.values() {
2520 addon.extend_key_context(&mut key_context, cx)
2521 }
2522 }
2523
2524 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2525 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2526 Some(
2527 file.full_path(cx)
2528 .extension()?
2529 .to_string_lossy()
2530 .into_owned(),
2531 )
2532 }) {
2533 key_context.set("extension", extension);
2534 }
2535 } else {
2536 key_context.add("multibuffer");
2537 }
2538
2539 if has_active_edit_prediction {
2540 if self.edit_prediction_in_conflict() {
2541 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2542 } else {
2543 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2544 key_context.add("copilot_suggestion");
2545 }
2546 }
2547
2548 if self.selection_mark_mode {
2549 key_context.add("selection_mode");
2550 }
2551
2552 let disjoint = self.selections.disjoint_anchors();
2553 let snapshot = self.snapshot(window, cx);
2554 let snapshot = snapshot.buffer_snapshot();
2555 if self.mode == EditorMode::SingleLine
2556 && let [selection] = disjoint
2557 && selection.start == selection.end
2558 && selection.end.to_offset(snapshot) == snapshot.len()
2559 {
2560 key_context.add("end_of_input");
2561 }
2562
2563 key_context
2564 }
2565
2566 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2567 self.last_bounds.as_ref()
2568 }
2569
2570 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2571 if self.mouse_cursor_hidden {
2572 self.mouse_cursor_hidden = false;
2573 cx.notify();
2574 }
2575 }
2576
2577 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2578 let hide_mouse_cursor = match origin {
2579 HideMouseCursorOrigin::TypingAction => {
2580 matches!(
2581 self.hide_mouse_mode,
2582 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2583 )
2584 }
2585 HideMouseCursorOrigin::MovementAction => {
2586 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2587 }
2588 };
2589 if self.mouse_cursor_hidden != hide_mouse_cursor {
2590 self.mouse_cursor_hidden = hide_mouse_cursor;
2591 cx.notify();
2592 }
2593 }
2594
2595 pub fn edit_prediction_in_conflict(&self) -> bool {
2596 if !self.show_edit_predictions_in_menu() {
2597 return false;
2598 }
2599
2600 let showing_completions = self
2601 .context_menu
2602 .borrow()
2603 .as_ref()
2604 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2605
2606 showing_completions
2607 || self.edit_prediction_requires_modifier()
2608 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2609 // bindings to insert tab characters.
2610 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2611 }
2612
2613 pub fn accept_edit_prediction_keybind(
2614 &self,
2615 accept_partial: bool,
2616 window: &mut Window,
2617 cx: &mut App,
2618 ) -> AcceptEditPredictionBinding {
2619 let key_context = self.key_context_internal(true, window, cx);
2620 let in_conflict = self.edit_prediction_in_conflict();
2621
2622 let bindings = if accept_partial {
2623 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2624 } else {
2625 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2626 };
2627
2628 // TODO: if the binding contains multiple keystrokes, display all of them, not
2629 // just the first one.
2630 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2631 !in_conflict
2632 || binding
2633 .keystrokes()
2634 .first()
2635 .is_some_and(|keystroke| keystroke.modifiers().modified())
2636 }))
2637 }
2638
2639 pub fn new_file(
2640 workspace: &mut Workspace,
2641 _: &workspace::NewFile,
2642 window: &mut Window,
2643 cx: &mut Context<Workspace>,
2644 ) {
2645 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2646 "Failed to create buffer",
2647 window,
2648 cx,
2649 |e, _, _| match e.error_code() {
2650 ErrorCode::RemoteUpgradeRequired => Some(format!(
2651 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2652 e.error_tag("required").unwrap_or("the latest version")
2653 )),
2654 _ => None,
2655 },
2656 );
2657 }
2658
2659 pub fn new_in_workspace(
2660 workspace: &mut Workspace,
2661 window: &mut Window,
2662 cx: &mut Context<Workspace>,
2663 ) -> Task<Result<Entity<Editor>>> {
2664 let project = workspace.project().clone();
2665 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2666
2667 cx.spawn_in(window, async move |workspace, cx| {
2668 let buffer = create.await?;
2669 workspace.update_in(cx, |workspace, window, cx| {
2670 let editor =
2671 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2672 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2673 editor
2674 })
2675 })
2676 }
2677
2678 fn new_file_vertical(
2679 workspace: &mut Workspace,
2680 _: &workspace::NewFileSplitVertical,
2681 window: &mut Window,
2682 cx: &mut Context<Workspace>,
2683 ) {
2684 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2685 }
2686
2687 fn new_file_horizontal(
2688 workspace: &mut Workspace,
2689 _: &workspace::NewFileSplitHorizontal,
2690 window: &mut Window,
2691 cx: &mut Context<Workspace>,
2692 ) {
2693 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2694 }
2695
2696 fn new_file_split(
2697 workspace: &mut Workspace,
2698 action: &workspace::NewFileSplit,
2699 window: &mut Window,
2700 cx: &mut Context<Workspace>,
2701 ) {
2702 Self::new_file_in_direction(workspace, action.0, window, cx)
2703 }
2704
2705 fn new_file_in_direction(
2706 workspace: &mut Workspace,
2707 direction: SplitDirection,
2708 window: &mut Window,
2709 cx: &mut Context<Workspace>,
2710 ) {
2711 let project = workspace.project().clone();
2712 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2713
2714 cx.spawn_in(window, async move |workspace, cx| {
2715 let buffer = create.await?;
2716 workspace.update_in(cx, move |workspace, window, cx| {
2717 workspace.split_item(
2718 direction,
2719 Box::new(
2720 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2721 ),
2722 window,
2723 cx,
2724 )
2725 })?;
2726 anyhow::Ok(())
2727 })
2728 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2729 match e.error_code() {
2730 ErrorCode::RemoteUpgradeRequired => Some(format!(
2731 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2732 e.error_tag("required").unwrap_or("the latest version")
2733 )),
2734 _ => None,
2735 }
2736 });
2737 }
2738
2739 pub fn leader_id(&self) -> Option<CollaboratorId> {
2740 self.leader_id
2741 }
2742
2743 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2744 &self.buffer
2745 }
2746
2747 pub fn project(&self) -> Option<&Entity<Project>> {
2748 self.project.as_ref()
2749 }
2750
2751 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2752 self.workspace.as_ref()?.0.upgrade()
2753 }
2754
2755 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2756 self.buffer().read(cx).title(cx)
2757 }
2758
2759 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2760 let git_blame_gutter_max_author_length = self
2761 .render_git_blame_gutter(cx)
2762 .then(|| {
2763 if let Some(blame) = self.blame.as_ref() {
2764 let max_author_length =
2765 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2766 Some(max_author_length)
2767 } else {
2768 None
2769 }
2770 })
2771 .flatten();
2772
2773 EditorSnapshot {
2774 mode: self.mode.clone(),
2775 show_gutter: self.show_gutter,
2776 show_line_numbers: self.show_line_numbers,
2777 show_git_diff_gutter: self.show_git_diff_gutter,
2778 show_code_actions: self.show_code_actions,
2779 show_runnables: self.show_runnables,
2780 show_breakpoints: self.show_breakpoints,
2781 git_blame_gutter_max_author_length,
2782 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2783 placeholder_display_snapshot: self
2784 .placeholder_display_map
2785 .as_ref()
2786 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2787 scroll_anchor: self.scroll_manager.anchor(),
2788 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2789 is_focused: self.focus_handle.is_focused(window),
2790 current_line_highlight: self
2791 .current_line_highlight
2792 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2793 gutter_hovered: self.gutter_hovered,
2794 }
2795 }
2796
2797 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2798 self.buffer.read(cx).language_at(point, cx)
2799 }
2800
2801 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2802 self.buffer.read(cx).read(cx).file_at(point).cloned()
2803 }
2804
2805 pub fn active_excerpt(
2806 &self,
2807 cx: &App,
2808 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2809 self.buffer
2810 .read(cx)
2811 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2812 }
2813
2814 pub fn mode(&self) -> &EditorMode {
2815 &self.mode
2816 }
2817
2818 pub fn set_mode(&mut self, mode: EditorMode) {
2819 self.mode = mode;
2820 }
2821
2822 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2823 self.collaboration_hub.as_deref()
2824 }
2825
2826 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2827 self.collaboration_hub = Some(hub);
2828 }
2829
2830 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2831 self.in_project_search = in_project_search;
2832 }
2833
2834 pub fn set_custom_context_menu(
2835 &mut self,
2836 f: impl 'static
2837 + Fn(
2838 &mut Self,
2839 DisplayPoint,
2840 &mut Window,
2841 &mut Context<Self>,
2842 ) -> Option<Entity<ui::ContextMenu>>,
2843 ) {
2844 self.custom_context_menu = Some(Box::new(f))
2845 }
2846
2847 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2848 self.completion_provider = provider;
2849 }
2850
2851 #[cfg(any(test, feature = "test-support"))]
2852 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2853 self.completion_provider.clone()
2854 }
2855
2856 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2857 self.semantics_provider.clone()
2858 }
2859
2860 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2861 self.semantics_provider = provider;
2862 }
2863
2864 pub fn set_edit_prediction_provider<T>(
2865 &mut self,
2866 provider: Option<Entity<T>>,
2867 window: &mut Window,
2868 cx: &mut Context<Self>,
2869 ) where
2870 T: EditPredictionProvider,
2871 {
2872 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2873 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2874 if this.focus_handle.is_focused(window) {
2875 this.update_visible_edit_prediction(window, cx);
2876 }
2877 }),
2878 provider: Arc::new(provider),
2879 });
2880 self.update_edit_prediction_settings(cx);
2881 self.refresh_edit_prediction(false, false, window, cx);
2882 }
2883
2884 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2885 self.placeholder_display_map
2886 .as_ref()
2887 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2888 }
2889
2890 pub fn set_placeholder_text(
2891 &mut self,
2892 placeholder_text: &str,
2893 window: &mut Window,
2894 cx: &mut Context<Self>,
2895 ) {
2896 let multibuffer = cx
2897 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2898
2899 let style = window.text_style();
2900
2901 self.placeholder_display_map = Some(cx.new(|cx| {
2902 DisplayMap::new(
2903 multibuffer,
2904 style.font(),
2905 style.font_size.to_pixels(window.rem_size()),
2906 None,
2907 FILE_HEADER_HEIGHT,
2908 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2909 Default::default(),
2910 DiagnosticSeverity::Off,
2911 cx,
2912 )
2913 }));
2914 cx.notify();
2915 }
2916
2917 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2918 self.cursor_shape = cursor_shape;
2919
2920 // Disrupt blink for immediate user feedback that the cursor shape has changed
2921 self.blink_manager.update(cx, BlinkManager::show_cursor);
2922
2923 cx.notify();
2924 }
2925
2926 pub fn set_current_line_highlight(
2927 &mut self,
2928 current_line_highlight: Option<CurrentLineHighlight>,
2929 ) {
2930 self.current_line_highlight = current_line_highlight;
2931 }
2932
2933 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
2934 self.collapse_matches = collapse_matches;
2935 }
2936
2937 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
2938 if self.collapse_matches {
2939 return range.start..range.start;
2940 }
2941 range.clone()
2942 }
2943
2944 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
2945 if self.display_map.read(cx).clip_at_line_ends != clip {
2946 self.display_map
2947 .update(cx, |map, _| map.clip_at_line_ends = clip);
2948 }
2949 }
2950
2951 pub fn set_input_enabled(&mut self, input_enabled: bool) {
2952 self.input_enabled = input_enabled;
2953 }
2954
2955 pub fn set_edit_predictions_hidden_for_vim_mode(
2956 &mut self,
2957 hidden: bool,
2958 window: &mut Window,
2959 cx: &mut Context<Self>,
2960 ) {
2961 if hidden != self.edit_predictions_hidden_for_vim_mode {
2962 self.edit_predictions_hidden_for_vim_mode = hidden;
2963 if hidden {
2964 self.update_visible_edit_prediction(window, cx);
2965 } else {
2966 self.refresh_edit_prediction(true, false, window, cx);
2967 }
2968 }
2969 }
2970
2971 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
2972 self.menu_edit_predictions_policy = value;
2973 }
2974
2975 pub fn set_autoindent(&mut self, autoindent: bool) {
2976 if autoindent {
2977 self.autoindent_mode = Some(AutoindentMode::EachLine);
2978 } else {
2979 self.autoindent_mode = None;
2980 }
2981 }
2982
2983 pub fn read_only(&self, cx: &App) -> bool {
2984 self.read_only || self.buffer.read(cx).read_only()
2985 }
2986
2987 pub fn set_read_only(&mut self, read_only: bool) {
2988 self.read_only = read_only;
2989 }
2990
2991 pub fn set_use_autoclose(&mut self, autoclose: bool) {
2992 self.use_autoclose = autoclose;
2993 }
2994
2995 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
2996 self.use_auto_surround = auto_surround;
2997 }
2998
2999 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3000 self.auto_replace_emoji_shortcode = auto_replace;
3001 }
3002
3003 pub fn toggle_edit_predictions(
3004 &mut self,
3005 _: &ToggleEditPrediction,
3006 window: &mut Window,
3007 cx: &mut Context<Self>,
3008 ) {
3009 if self.show_edit_predictions_override.is_some() {
3010 self.set_show_edit_predictions(None, window, cx);
3011 } else {
3012 let show_edit_predictions = !self.edit_predictions_enabled();
3013 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3014 }
3015 }
3016
3017 pub fn set_show_edit_predictions(
3018 &mut self,
3019 show_edit_predictions: Option<bool>,
3020 window: &mut Window,
3021 cx: &mut Context<Self>,
3022 ) {
3023 self.show_edit_predictions_override = show_edit_predictions;
3024 self.update_edit_prediction_settings(cx);
3025
3026 if let Some(false) = show_edit_predictions {
3027 self.discard_edit_prediction(false, cx);
3028 } else {
3029 self.refresh_edit_prediction(false, true, window, cx);
3030 }
3031 }
3032
3033 fn edit_predictions_disabled_in_scope(
3034 &self,
3035 buffer: &Entity<Buffer>,
3036 buffer_position: language::Anchor,
3037 cx: &App,
3038 ) -> bool {
3039 let snapshot = buffer.read(cx).snapshot();
3040 let settings = snapshot.settings_at(buffer_position, cx);
3041
3042 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3043 return false;
3044 };
3045
3046 scope.override_name().is_some_and(|scope_name| {
3047 settings
3048 .edit_predictions_disabled_in
3049 .iter()
3050 .any(|s| s == scope_name)
3051 })
3052 }
3053
3054 pub fn set_use_modal_editing(&mut self, to: bool) {
3055 self.use_modal_editing = to;
3056 }
3057
3058 pub fn use_modal_editing(&self) -> bool {
3059 self.use_modal_editing
3060 }
3061
3062 fn selections_did_change(
3063 &mut self,
3064 local: bool,
3065 old_cursor_position: &Anchor,
3066 effects: SelectionEffects,
3067 window: &mut Window,
3068 cx: &mut Context<Self>,
3069 ) {
3070 window.invalidate_character_coordinates();
3071
3072 // Copy selections to primary selection buffer
3073 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3074 if local {
3075 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3076 let buffer_handle = self.buffer.read(cx).read(cx);
3077
3078 let mut text = String::new();
3079 for (index, selection) in selections.iter().enumerate() {
3080 let text_for_selection = buffer_handle
3081 .text_for_range(selection.start..selection.end)
3082 .collect::<String>();
3083
3084 text.push_str(&text_for_selection);
3085 if index != selections.len() - 1 {
3086 text.push('\n');
3087 }
3088 }
3089
3090 if !text.is_empty() {
3091 cx.write_to_primary(ClipboardItem::new_string(text));
3092 }
3093 }
3094
3095 let selection_anchors = self.selections.disjoint_anchors_arc();
3096
3097 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3098 self.buffer.update(cx, |buffer, cx| {
3099 buffer.set_active_selections(
3100 &selection_anchors,
3101 self.selections.line_mode(),
3102 self.cursor_shape,
3103 cx,
3104 )
3105 });
3106 }
3107 let display_map = self
3108 .display_map
3109 .update(cx, |display_map, cx| display_map.snapshot(cx));
3110 let buffer = display_map.buffer_snapshot();
3111 if self.selections.count() == 1 {
3112 self.add_selections_state = None;
3113 }
3114 self.select_next_state = None;
3115 self.select_prev_state = None;
3116 self.select_syntax_node_history.try_clear();
3117 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3118 self.snippet_stack.invalidate(&selection_anchors, buffer);
3119 self.take_rename(false, window, cx);
3120
3121 let newest_selection = self.selections.newest_anchor();
3122 let new_cursor_position = newest_selection.head();
3123 let selection_start = newest_selection.start;
3124
3125 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3126 self.push_to_nav_history(
3127 *old_cursor_position,
3128 Some(new_cursor_position.to_point(buffer)),
3129 false,
3130 effects.nav_history == Some(true),
3131 cx,
3132 );
3133 }
3134
3135 if local {
3136 if let Some(buffer_id) = new_cursor_position.buffer_id {
3137 self.register_buffer(buffer_id, cx);
3138 }
3139
3140 let mut context_menu = self.context_menu.borrow_mut();
3141 let completion_menu = match context_menu.as_ref() {
3142 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3143 Some(CodeContextMenu::CodeActions(_)) => {
3144 *context_menu = None;
3145 None
3146 }
3147 None => None,
3148 };
3149 let completion_position = completion_menu.map(|menu| menu.initial_position);
3150 drop(context_menu);
3151
3152 if effects.completions
3153 && let Some(completion_position) = completion_position
3154 {
3155 let start_offset = selection_start.to_offset(buffer);
3156 let position_matches = start_offset == completion_position.to_offset(buffer);
3157 let continue_showing = if position_matches {
3158 if self.snippet_stack.is_empty() {
3159 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3160 == Some(CharKind::Word)
3161 } else {
3162 // Snippet choices can be shown even when the cursor is in whitespace.
3163 // Dismissing the menu with actions like backspace is handled by
3164 // invalidation regions.
3165 true
3166 }
3167 } else {
3168 false
3169 };
3170
3171 if continue_showing {
3172 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3173 } else {
3174 self.hide_context_menu(window, cx);
3175 }
3176 }
3177
3178 hide_hover(self, cx);
3179
3180 if old_cursor_position.to_display_point(&display_map).row()
3181 != new_cursor_position.to_display_point(&display_map).row()
3182 {
3183 self.available_code_actions.take();
3184 }
3185 self.refresh_code_actions(window, cx);
3186 self.refresh_document_highlights(cx);
3187 refresh_linked_ranges(self, window, cx);
3188
3189 self.refresh_selected_text_highlights(false, window, cx);
3190 self.refresh_matching_bracket_highlights(window, cx);
3191 self.update_visible_edit_prediction(window, cx);
3192 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3193 self.inline_blame_popover.take();
3194 if self.git_blame_inline_enabled {
3195 self.start_inline_blame_timer(window, cx);
3196 }
3197 }
3198
3199 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3200 cx.emit(EditorEvent::SelectionsChanged { local });
3201
3202 let selections = &self.selections.disjoint_anchors_arc();
3203 if selections.len() == 1 {
3204 cx.emit(SearchEvent::ActiveMatchChanged)
3205 }
3206 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3207 let inmemory_selections = selections
3208 .iter()
3209 .map(|s| {
3210 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3211 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3212 })
3213 .collect();
3214 self.update_restoration_data(cx, |data| {
3215 data.selections = inmemory_selections;
3216 });
3217
3218 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3219 && let Some(workspace_id) =
3220 self.workspace.as_ref().and_then(|workspace| workspace.1)
3221 {
3222 let snapshot = self.buffer().read(cx).snapshot(cx);
3223 let selections = selections.clone();
3224 let background_executor = cx.background_executor().clone();
3225 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3226 self.serialize_selections = cx.background_spawn(async move {
3227 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3228 let db_selections = selections
3229 .iter()
3230 .map(|selection| {
3231 (
3232 selection.start.to_offset(&snapshot),
3233 selection.end.to_offset(&snapshot),
3234 )
3235 })
3236 .collect();
3237
3238 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3239 .await
3240 .with_context(|| {
3241 format!(
3242 "persisting editor selections for editor {editor_id}, \
3243 workspace {workspace_id:?}"
3244 )
3245 })
3246 .log_err();
3247 });
3248 }
3249 }
3250
3251 cx.notify();
3252 }
3253
3254 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3255 use text::ToOffset as _;
3256 use text::ToPoint as _;
3257
3258 if self.mode.is_minimap()
3259 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3260 {
3261 return;
3262 }
3263
3264 if !self.buffer().read(cx).is_singleton() {
3265 return;
3266 }
3267
3268 let display_snapshot = self
3269 .display_map
3270 .update(cx, |display_map, cx| display_map.snapshot(cx));
3271 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3272 return;
3273 };
3274 let inmemory_folds = display_snapshot
3275 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3276 .map(|fold| {
3277 fold.range.start.text_anchor.to_point(&snapshot)
3278 ..fold.range.end.text_anchor.to_point(&snapshot)
3279 })
3280 .collect();
3281 self.update_restoration_data(cx, |data| {
3282 data.folds = inmemory_folds;
3283 });
3284
3285 let Some(workspace_id) = self.workspace.as_ref().and_then(|workspace| workspace.1) else {
3286 return;
3287 };
3288 let background_executor = cx.background_executor().clone();
3289 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3290 let db_folds = display_snapshot
3291 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3292 .map(|fold| {
3293 (
3294 fold.range.start.text_anchor.to_offset(&snapshot),
3295 fold.range.end.text_anchor.to_offset(&snapshot),
3296 )
3297 })
3298 .collect();
3299 self.serialize_folds = cx.background_spawn(async move {
3300 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3301 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3302 .await
3303 .with_context(|| {
3304 format!(
3305 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3306 )
3307 })
3308 .log_err();
3309 });
3310 }
3311
3312 pub fn sync_selections(
3313 &mut self,
3314 other: Entity<Editor>,
3315 cx: &mut Context<Self>,
3316 ) -> gpui::Subscription {
3317 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3318 if !other_selections.is_empty() {
3319 self.selections.change_with(cx, |selections| {
3320 selections.select_anchors(other_selections);
3321 });
3322 }
3323
3324 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3325 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3326 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3327 if other_selections.is_empty() {
3328 return;
3329 }
3330 this.selections.change_with(cx, |selections| {
3331 selections.select_anchors(other_selections);
3332 });
3333 }
3334 });
3335
3336 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3337 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3338 let these_selections = this.selections.disjoint_anchors().to_vec();
3339 if these_selections.is_empty() {
3340 return;
3341 }
3342 other.update(cx, |other_editor, cx| {
3343 other_editor.selections.change_with(cx, |selections| {
3344 selections.select_anchors(these_selections);
3345 })
3346 });
3347 }
3348 });
3349
3350 Subscription::join(other_subscription, this_subscription)
3351 }
3352
3353 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3354 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3355 /// effects of selection change occur at the end of the transaction.
3356 pub fn change_selections<R>(
3357 &mut self,
3358 effects: SelectionEffects,
3359 window: &mut Window,
3360 cx: &mut Context<Self>,
3361 change: impl FnOnce(&mut MutableSelectionsCollection<'_>) -> R,
3362 ) -> R {
3363 if let Some(state) = &mut self.deferred_selection_effects_state {
3364 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3365 state.effects.completions = effects.completions;
3366 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3367 let (changed, result) = self.selections.change_with(cx, change);
3368 state.changed |= changed;
3369 return result;
3370 }
3371 let mut state = DeferredSelectionEffectsState {
3372 changed: false,
3373 effects,
3374 old_cursor_position: self.selections.newest_anchor().head(),
3375 history_entry: SelectionHistoryEntry {
3376 selections: self.selections.disjoint_anchors_arc(),
3377 select_next_state: self.select_next_state.clone(),
3378 select_prev_state: self.select_prev_state.clone(),
3379 add_selections_state: self.add_selections_state.clone(),
3380 },
3381 };
3382 let (changed, result) = self.selections.change_with(cx, change);
3383 state.changed = state.changed || changed;
3384 if self.defer_selection_effects {
3385 self.deferred_selection_effects_state = Some(state);
3386 } else {
3387 self.apply_selection_effects(state, window, cx);
3388 }
3389 result
3390 }
3391
3392 /// Defers the effects of selection change, so that the effects of multiple calls to
3393 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3394 /// to selection history and the state of popovers based on selection position aren't
3395 /// erroneously updated.
3396 pub fn with_selection_effects_deferred<R>(
3397 &mut self,
3398 window: &mut Window,
3399 cx: &mut Context<Self>,
3400 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3401 ) -> R {
3402 let already_deferred = self.defer_selection_effects;
3403 self.defer_selection_effects = true;
3404 let result = update(self, window, cx);
3405 if !already_deferred {
3406 self.defer_selection_effects = false;
3407 if let Some(state) = self.deferred_selection_effects_state.take() {
3408 self.apply_selection_effects(state, window, cx);
3409 }
3410 }
3411 result
3412 }
3413
3414 fn apply_selection_effects(
3415 &mut self,
3416 state: DeferredSelectionEffectsState,
3417 window: &mut Window,
3418 cx: &mut Context<Self>,
3419 ) {
3420 if state.changed {
3421 self.selection_history.push(state.history_entry);
3422
3423 if let Some(autoscroll) = state.effects.scroll {
3424 self.request_autoscroll(autoscroll, cx);
3425 }
3426
3427 let old_cursor_position = &state.old_cursor_position;
3428
3429 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3430
3431 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3432 self.show_signature_help(&ShowSignatureHelp, window, cx);
3433 }
3434 }
3435 }
3436
3437 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3438 where
3439 I: IntoIterator<Item = (Range<S>, T)>,
3440 S: ToOffset,
3441 T: Into<Arc<str>>,
3442 {
3443 if self.read_only(cx) {
3444 return;
3445 }
3446
3447 self.buffer
3448 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3449 }
3450
3451 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3452 where
3453 I: IntoIterator<Item = (Range<S>, T)>,
3454 S: ToOffset,
3455 T: Into<Arc<str>>,
3456 {
3457 if self.read_only(cx) {
3458 return;
3459 }
3460
3461 self.buffer.update(cx, |buffer, cx| {
3462 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3463 });
3464 }
3465
3466 pub fn edit_with_block_indent<I, S, T>(
3467 &mut self,
3468 edits: I,
3469 original_indent_columns: Vec<Option<u32>>,
3470 cx: &mut Context<Self>,
3471 ) where
3472 I: IntoIterator<Item = (Range<S>, T)>,
3473 S: ToOffset,
3474 T: Into<Arc<str>>,
3475 {
3476 if self.read_only(cx) {
3477 return;
3478 }
3479
3480 self.buffer.update(cx, |buffer, cx| {
3481 buffer.edit(
3482 edits,
3483 Some(AutoindentMode::Block {
3484 original_indent_columns,
3485 }),
3486 cx,
3487 )
3488 });
3489 }
3490
3491 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3492 self.hide_context_menu(window, cx);
3493
3494 match phase {
3495 SelectPhase::Begin {
3496 position,
3497 add,
3498 click_count,
3499 } => self.begin_selection(position, add, click_count, window, cx),
3500 SelectPhase::BeginColumnar {
3501 position,
3502 goal_column,
3503 reset,
3504 mode,
3505 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3506 SelectPhase::Extend {
3507 position,
3508 click_count,
3509 } => self.extend_selection(position, click_count, window, cx),
3510 SelectPhase::Update {
3511 position,
3512 goal_column,
3513 scroll_delta,
3514 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3515 SelectPhase::End => self.end_selection(window, cx),
3516 }
3517 }
3518
3519 fn extend_selection(
3520 &mut self,
3521 position: DisplayPoint,
3522 click_count: usize,
3523 window: &mut Window,
3524 cx: &mut Context<Self>,
3525 ) {
3526 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3527 let tail = self.selections.newest::<usize>(&display_map).tail();
3528 let click_count = click_count.max(match self.selections.select_mode() {
3529 SelectMode::Character => 1,
3530 SelectMode::Word(_) => 2,
3531 SelectMode::Line(_) => 3,
3532 SelectMode::All => 4,
3533 });
3534 self.begin_selection(position, false, click_count, window, cx);
3535
3536 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3537
3538 let current_selection = match self.selections.select_mode() {
3539 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3540 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3541 };
3542
3543 let mut pending_selection = self
3544 .selections
3545 .pending_anchor()
3546 .cloned()
3547 .expect("extend_selection not called with pending selection");
3548
3549 if pending_selection
3550 .start
3551 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3552 == Ordering::Greater
3553 {
3554 pending_selection.start = current_selection.start;
3555 }
3556 if pending_selection
3557 .end
3558 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3559 == Ordering::Less
3560 {
3561 pending_selection.end = current_selection.end;
3562 pending_selection.reversed = true;
3563 }
3564
3565 let mut pending_mode = self.selections.pending_mode().unwrap();
3566 match &mut pending_mode {
3567 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3568 _ => {}
3569 }
3570
3571 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3572 SelectionEffects::scroll(Autoscroll::fit())
3573 } else {
3574 SelectionEffects::no_scroll()
3575 };
3576
3577 self.change_selections(effects, window, cx, |s| {
3578 s.set_pending(pending_selection.clone(), pending_mode);
3579 s.set_is_extending(true);
3580 });
3581 }
3582
3583 fn begin_selection(
3584 &mut self,
3585 position: DisplayPoint,
3586 add: bool,
3587 click_count: usize,
3588 window: &mut Window,
3589 cx: &mut Context<Self>,
3590 ) {
3591 if !self.focus_handle.is_focused(window) {
3592 self.last_focused_descendant = None;
3593 window.focus(&self.focus_handle);
3594 }
3595
3596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3597 let buffer = display_map.buffer_snapshot();
3598 let position = display_map.clip_point(position, Bias::Left);
3599
3600 let start;
3601 let end;
3602 let mode;
3603 let mut auto_scroll;
3604 match click_count {
3605 1 => {
3606 start = buffer.anchor_before(position.to_point(&display_map));
3607 end = start;
3608 mode = SelectMode::Character;
3609 auto_scroll = true;
3610 }
3611 2 => {
3612 let position = display_map
3613 .clip_point(position, Bias::Left)
3614 .to_offset(&display_map, Bias::Left);
3615 let (range, _) = buffer.surrounding_word(position, None);
3616 start = buffer.anchor_before(range.start);
3617 end = buffer.anchor_before(range.end);
3618 mode = SelectMode::Word(start..end);
3619 auto_scroll = true;
3620 }
3621 3 => {
3622 let position = display_map
3623 .clip_point(position, Bias::Left)
3624 .to_point(&display_map);
3625 let line_start = display_map.prev_line_boundary(position).0;
3626 let next_line_start = buffer.clip_point(
3627 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3628 Bias::Left,
3629 );
3630 start = buffer.anchor_before(line_start);
3631 end = buffer.anchor_before(next_line_start);
3632 mode = SelectMode::Line(start..end);
3633 auto_scroll = true;
3634 }
3635 _ => {
3636 start = buffer.anchor_before(0);
3637 end = buffer.anchor_before(buffer.len());
3638 mode = SelectMode::All;
3639 auto_scroll = false;
3640 }
3641 }
3642 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3643
3644 let point_to_delete: Option<usize> = {
3645 let selected_points: Vec<Selection<Point>> =
3646 self.selections.disjoint_in_range(start..end, &display_map);
3647
3648 if !add || click_count > 1 {
3649 None
3650 } else if !selected_points.is_empty() {
3651 Some(selected_points[0].id)
3652 } else {
3653 let clicked_point_already_selected =
3654 self.selections.disjoint_anchors().iter().find(|selection| {
3655 selection.start.to_point(buffer) == start.to_point(buffer)
3656 || selection.end.to_point(buffer) == end.to_point(buffer)
3657 });
3658
3659 clicked_point_already_selected.map(|selection| selection.id)
3660 }
3661 };
3662
3663 let selections_count = self.selections.count();
3664 let effects = if auto_scroll {
3665 SelectionEffects::default()
3666 } else {
3667 SelectionEffects::no_scroll()
3668 };
3669
3670 self.change_selections(effects, window, cx, |s| {
3671 if let Some(point_to_delete) = point_to_delete {
3672 s.delete(point_to_delete);
3673
3674 if selections_count == 1 {
3675 s.set_pending_anchor_range(start..end, mode);
3676 }
3677 } else {
3678 if !add {
3679 s.clear_disjoint();
3680 }
3681
3682 s.set_pending_anchor_range(start..end, mode);
3683 }
3684 });
3685 }
3686
3687 fn begin_columnar_selection(
3688 &mut self,
3689 position: DisplayPoint,
3690 goal_column: u32,
3691 reset: bool,
3692 mode: ColumnarMode,
3693 window: &mut Window,
3694 cx: &mut Context<Self>,
3695 ) {
3696 if !self.focus_handle.is_focused(window) {
3697 self.last_focused_descendant = None;
3698 window.focus(&self.focus_handle);
3699 }
3700
3701 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3702
3703 if reset {
3704 let pointer_position = display_map
3705 .buffer_snapshot()
3706 .anchor_before(position.to_point(&display_map));
3707
3708 self.change_selections(
3709 SelectionEffects::scroll(Autoscroll::newest()),
3710 window,
3711 cx,
3712 |s| {
3713 s.clear_disjoint();
3714 s.set_pending_anchor_range(
3715 pointer_position..pointer_position,
3716 SelectMode::Character,
3717 );
3718 },
3719 );
3720 };
3721
3722 let tail = self.selections.newest::<Point>(&display_map).tail();
3723 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3724 self.columnar_selection_state = match mode {
3725 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3726 selection_tail: selection_anchor,
3727 display_point: if reset {
3728 if position.column() != goal_column {
3729 Some(DisplayPoint::new(position.row(), goal_column))
3730 } else {
3731 None
3732 }
3733 } else {
3734 None
3735 },
3736 }),
3737 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3738 selection_tail: selection_anchor,
3739 }),
3740 };
3741
3742 if !reset {
3743 self.select_columns(position, goal_column, &display_map, window, cx);
3744 }
3745 }
3746
3747 fn update_selection(
3748 &mut self,
3749 position: DisplayPoint,
3750 goal_column: u32,
3751 scroll_delta: gpui::Point<f32>,
3752 window: &mut Window,
3753 cx: &mut Context<Self>,
3754 ) {
3755 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3756
3757 if self.columnar_selection_state.is_some() {
3758 self.select_columns(position, goal_column, &display_map, window, cx);
3759 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3760 let buffer = display_map.buffer_snapshot();
3761 let head;
3762 let tail;
3763 let mode = self.selections.pending_mode().unwrap();
3764 match &mode {
3765 SelectMode::Character => {
3766 head = position.to_point(&display_map);
3767 tail = pending.tail().to_point(buffer);
3768 }
3769 SelectMode::Word(original_range) => {
3770 let offset = display_map
3771 .clip_point(position, Bias::Left)
3772 .to_offset(&display_map, Bias::Left);
3773 let original_range = original_range.to_offset(buffer);
3774
3775 let head_offset = if buffer.is_inside_word(offset, None)
3776 || original_range.contains(&offset)
3777 {
3778 let (word_range, _) = buffer.surrounding_word(offset, None);
3779 if word_range.start < original_range.start {
3780 word_range.start
3781 } else {
3782 word_range.end
3783 }
3784 } else {
3785 offset
3786 };
3787
3788 head = head_offset.to_point(buffer);
3789 if head_offset <= original_range.start {
3790 tail = original_range.end.to_point(buffer);
3791 } else {
3792 tail = original_range.start.to_point(buffer);
3793 }
3794 }
3795 SelectMode::Line(original_range) => {
3796 let original_range = original_range.to_point(display_map.buffer_snapshot());
3797
3798 let position = display_map
3799 .clip_point(position, Bias::Left)
3800 .to_point(&display_map);
3801 let line_start = display_map.prev_line_boundary(position).0;
3802 let next_line_start = buffer.clip_point(
3803 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3804 Bias::Left,
3805 );
3806
3807 if line_start < original_range.start {
3808 head = line_start
3809 } else {
3810 head = next_line_start
3811 }
3812
3813 if head <= original_range.start {
3814 tail = original_range.end;
3815 } else {
3816 tail = original_range.start;
3817 }
3818 }
3819 SelectMode::All => {
3820 return;
3821 }
3822 };
3823
3824 if head < tail {
3825 pending.start = buffer.anchor_before(head);
3826 pending.end = buffer.anchor_before(tail);
3827 pending.reversed = true;
3828 } else {
3829 pending.start = buffer.anchor_before(tail);
3830 pending.end = buffer.anchor_before(head);
3831 pending.reversed = false;
3832 }
3833
3834 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3835 s.set_pending(pending.clone(), mode);
3836 });
3837 } else {
3838 log::error!("update_selection dispatched with no pending selection");
3839 return;
3840 }
3841
3842 self.apply_scroll_delta(scroll_delta, window, cx);
3843 cx.notify();
3844 }
3845
3846 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3847 self.columnar_selection_state.take();
3848 if let Some(pending_mode) = self.selections.pending_mode() {
3849 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3850 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3851 s.select(selections);
3852 s.clear_pending();
3853 if s.is_extending() {
3854 s.set_is_extending(false);
3855 } else {
3856 s.set_select_mode(pending_mode);
3857 }
3858 });
3859 }
3860 }
3861
3862 fn select_columns(
3863 &mut self,
3864 head: DisplayPoint,
3865 goal_column: u32,
3866 display_map: &DisplaySnapshot,
3867 window: &mut Window,
3868 cx: &mut Context<Self>,
3869 ) {
3870 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3871 return;
3872 };
3873
3874 let tail = match columnar_state {
3875 ColumnarSelectionState::FromMouse {
3876 selection_tail,
3877 display_point,
3878 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3879 ColumnarSelectionState::FromSelection { selection_tail } => {
3880 selection_tail.to_display_point(display_map)
3881 }
3882 };
3883
3884 let start_row = cmp::min(tail.row(), head.row());
3885 let end_row = cmp::max(tail.row(), head.row());
3886 let start_column = cmp::min(tail.column(), goal_column);
3887 let end_column = cmp::max(tail.column(), goal_column);
3888 let reversed = start_column < tail.column();
3889
3890 let selection_ranges = (start_row.0..=end_row.0)
3891 .map(DisplayRow)
3892 .filter_map(|row| {
3893 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3894 || start_column <= display_map.line_len(row))
3895 && !display_map.is_block_line(row)
3896 {
3897 let start = display_map
3898 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3899 .to_point(display_map);
3900 let end = display_map
3901 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3902 .to_point(display_map);
3903 if reversed {
3904 Some(end..start)
3905 } else {
3906 Some(start..end)
3907 }
3908 } else {
3909 None
3910 }
3911 })
3912 .collect::<Vec<_>>();
3913 if selection_ranges.is_empty() {
3914 return;
3915 }
3916
3917 let ranges = match columnar_state {
3918 ColumnarSelectionState::FromMouse { .. } => {
3919 let mut non_empty_ranges = selection_ranges
3920 .iter()
3921 .filter(|selection_range| selection_range.start != selection_range.end)
3922 .peekable();
3923 if non_empty_ranges.peek().is_some() {
3924 non_empty_ranges.cloned().collect()
3925 } else {
3926 selection_ranges
3927 }
3928 }
3929 _ => selection_ranges,
3930 };
3931
3932 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3933 s.select_ranges(ranges);
3934 });
3935 cx.notify();
3936 }
3937
3938 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
3939 self.selections
3940 .all_adjusted(snapshot)
3941 .iter()
3942 .any(|selection| !selection.is_empty())
3943 }
3944
3945 pub fn has_pending_nonempty_selection(&self) -> bool {
3946 let pending_nonempty_selection = match self.selections.pending_anchor() {
3947 Some(Selection { start, end, .. }) => start != end,
3948 None => false,
3949 };
3950
3951 pending_nonempty_selection
3952 || (self.columnar_selection_state.is_some()
3953 && self.selections.disjoint_anchors().len() > 1)
3954 }
3955
3956 pub fn has_pending_selection(&self) -> bool {
3957 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
3958 }
3959
3960 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
3961 self.selection_mark_mode = false;
3962 self.selection_drag_state = SelectionDragState::None;
3963
3964 if self.clear_expanded_diff_hunks(cx) {
3965 cx.notify();
3966 return;
3967 }
3968 if self.dismiss_menus_and_popups(true, window, cx) {
3969 return;
3970 }
3971
3972 if self.mode.is_full()
3973 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
3974 {
3975 return;
3976 }
3977
3978 cx.propagate();
3979 }
3980
3981 pub fn dismiss_menus_and_popups(
3982 &mut self,
3983 is_user_requested: bool,
3984 window: &mut Window,
3985 cx: &mut Context<Self>,
3986 ) -> bool {
3987 if self.take_rename(false, window, cx).is_some() {
3988 return true;
3989 }
3990
3991 if self.hide_blame_popover(true, cx) {
3992 return true;
3993 }
3994
3995 if hide_hover(self, cx) {
3996 return true;
3997 }
3998
3999 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4000 return true;
4001 }
4002
4003 if self.hide_context_menu(window, cx).is_some() {
4004 return true;
4005 }
4006
4007 if self.mouse_context_menu.take().is_some() {
4008 return true;
4009 }
4010
4011 if is_user_requested && self.discard_edit_prediction(true, cx) {
4012 return true;
4013 }
4014
4015 if self.snippet_stack.pop().is_some() {
4016 return true;
4017 }
4018
4019 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4020 self.dismiss_diagnostics(cx);
4021 return true;
4022 }
4023
4024 false
4025 }
4026
4027 fn linked_editing_ranges_for(
4028 &self,
4029 selection: Range<text::Anchor>,
4030 cx: &App,
4031 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4032 if self.linked_edit_ranges.is_empty() {
4033 return None;
4034 }
4035 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4036 selection.end.buffer_id.and_then(|end_buffer_id| {
4037 if selection.start.buffer_id != Some(end_buffer_id) {
4038 return None;
4039 }
4040 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4041 let snapshot = buffer.read(cx).snapshot();
4042 self.linked_edit_ranges
4043 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4044 .map(|ranges| (ranges, snapshot, buffer))
4045 })?;
4046 use text::ToOffset as TO;
4047 // find offset from the start of current range to current cursor position
4048 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4049
4050 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4051 let start_difference = start_offset - start_byte_offset;
4052 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4053 let end_difference = end_offset - start_byte_offset;
4054 // Current range has associated linked ranges.
4055 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4056 for range in linked_ranges.iter() {
4057 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4058 let end_offset = start_offset + end_difference;
4059 let start_offset = start_offset + start_difference;
4060 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4061 continue;
4062 }
4063 if self.selections.disjoint_anchor_ranges().any(|s| {
4064 if s.start.buffer_id != selection.start.buffer_id
4065 || s.end.buffer_id != selection.end.buffer_id
4066 {
4067 return false;
4068 }
4069 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4070 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4071 }) {
4072 continue;
4073 }
4074 let start = buffer_snapshot.anchor_after(start_offset);
4075 let end = buffer_snapshot.anchor_after(end_offset);
4076 linked_edits
4077 .entry(buffer.clone())
4078 .or_default()
4079 .push(start..end);
4080 }
4081 Some(linked_edits)
4082 }
4083
4084 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4085 let text: Arc<str> = text.into();
4086
4087 if self.read_only(cx) {
4088 return;
4089 }
4090
4091 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4092
4093 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4094 let mut bracket_inserted = false;
4095 let mut edits = Vec::new();
4096 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4097 let mut new_selections = Vec::with_capacity(selections.len());
4098 let mut new_autoclose_regions = Vec::new();
4099 let snapshot = self.buffer.read(cx).read(cx);
4100 let mut clear_linked_edit_ranges = false;
4101
4102 for (selection, autoclose_region) in
4103 self.selections_with_autoclose_regions(selections, &snapshot)
4104 {
4105 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4106 // Determine if the inserted text matches the opening or closing
4107 // bracket of any of this language's bracket pairs.
4108 let mut bracket_pair = None;
4109 let mut is_bracket_pair_start = false;
4110 let mut is_bracket_pair_end = false;
4111 if !text.is_empty() {
4112 let mut bracket_pair_matching_end = None;
4113 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4114 // and they are removing the character that triggered IME popup.
4115 for (pair, enabled) in scope.brackets() {
4116 if !pair.close && !pair.surround {
4117 continue;
4118 }
4119
4120 if enabled && pair.start.ends_with(text.as_ref()) {
4121 let prefix_len = pair.start.len() - text.len();
4122 let preceding_text_matches_prefix = prefix_len == 0
4123 || (selection.start.column >= (prefix_len as u32)
4124 && snapshot.contains_str_at(
4125 Point::new(
4126 selection.start.row,
4127 selection.start.column - (prefix_len as u32),
4128 ),
4129 &pair.start[..prefix_len],
4130 ));
4131 if preceding_text_matches_prefix {
4132 bracket_pair = Some(pair.clone());
4133 is_bracket_pair_start = true;
4134 break;
4135 }
4136 }
4137 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4138 {
4139 // take first bracket pair matching end, but don't break in case a later bracket
4140 // pair matches start
4141 bracket_pair_matching_end = Some(pair.clone());
4142 }
4143 }
4144 if let Some(end) = bracket_pair_matching_end
4145 && bracket_pair.is_none()
4146 {
4147 bracket_pair = Some(end);
4148 is_bracket_pair_end = true;
4149 }
4150 }
4151
4152 if let Some(bracket_pair) = bracket_pair {
4153 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4154 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4155 let auto_surround =
4156 self.use_auto_surround && snapshot_settings.use_auto_surround;
4157 if selection.is_empty() {
4158 if is_bracket_pair_start {
4159 // If the inserted text is a suffix of an opening bracket and the
4160 // selection is preceded by the rest of the opening bracket, then
4161 // insert the closing bracket.
4162 let following_text_allows_autoclose = snapshot
4163 .chars_at(selection.start)
4164 .next()
4165 .is_none_or(|c| scope.should_autoclose_before(c));
4166
4167 let preceding_text_allows_autoclose = selection.start.column == 0
4168 || snapshot
4169 .reversed_chars_at(selection.start)
4170 .next()
4171 .is_none_or(|c| {
4172 bracket_pair.start != bracket_pair.end
4173 || !snapshot
4174 .char_classifier_at(selection.start)
4175 .is_word(c)
4176 });
4177
4178 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4179 && bracket_pair.start.len() == 1
4180 {
4181 let target = bracket_pair.start.chars().next().unwrap();
4182 let current_line_count = snapshot
4183 .reversed_chars_at(selection.start)
4184 .take_while(|&c| c != '\n')
4185 .filter(|&c| c == target)
4186 .count();
4187 current_line_count % 2 == 1
4188 } else {
4189 false
4190 };
4191
4192 if autoclose
4193 && bracket_pair.close
4194 && following_text_allows_autoclose
4195 && preceding_text_allows_autoclose
4196 && !is_closing_quote
4197 {
4198 let anchor = snapshot.anchor_before(selection.end);
4199 new_selections.push((selection.map(|_| anchor), text.len()));
4200 new_autoclose_regions.push((
4201 anchor,
4202 text.len(),
4203 selection.id,
4204 bracket_pair.clone(),
4205 ));
4206 edits.push((
4207 selection.range(),
4208 format!("{}{}", text, bracket_pair.end).into(),
4209 ));
4210 bracket_inserted = true;
4211 continue;
4212 }
4213 }
4214
4215 if let Some(region) = autoclose_region {
4216 // If the selection is followed by an auto-inserted closing bracket,
4217 // then don't insert that closing bracket again; just move the selection
4218 // past the closing bracket.
4219 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4220 && text.as_ref() == region.pair.end.as_str()
4221 && snapshot.contains_str_at(region.range.end, text.as_ref());
4222 if should_skip {
4223 let anchor = snapshot.anchor_after(selection.end);
4224 new_selections
4225 .push((selection.map(|_| anchor), region.pair.end.len()));
4226 continue;
4227 }
4228 }
4229
4230 let always_treat_brackets_as_autoclosed = snapshot
4231 .language_settings_at(selection.start, cx)
4232 .always_treat_brackets_as_autoclosed;
4233 if always_treat_brackets_as_autoclosed
4234 && is_bracket_pair_end
4235 && snapshot.contains_str_at(selection.end, text.as_ref())
4236 {
4237 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4238 // and the inserted text is a closing bracket and the selection is followed
4239 // by the closing bracket then move the selection past the closing bracket.
4240 let anchor = snapshot.anchor_after(selection.end);
4241 new_selections.push((selection.map(|_| anchor), text.len()));
4242 continue;
4243 }
4244 }
4245 // If an opening bracket is 1 character long and is typed while
4246 // text is selected, then surround that text with the bracket pair.
4247 else if auto_surround
4248 && bracket_pair.surround
4249 && is_bracket_pair_start
4250 && bracket_pair.start.chars().count() == 1
4251 {
4252 edits.push((selection.start..selection.start, text.clone()));
4253 edits.push((
4254 selection.end..selection.end,
4255 bracket_pair.end.as_str().into(),
4256 ));
4257 bracket_inserted = true;
4258 new_selections.push((
4259 Selection {
4260 id: selection.id,
4261 start: snapshot.anchor_after(selection.start),
4262 end: snapshot.anchor_before(selection.end),
4263 reversed: selection.reversed,
4264 goal: selection.goal,
4265 },
4266 0,
4267 ));
4268 continue;
4269 }
4270 }
4271 }
4272
4273 if self.auto_replace_emoji_shortcode
4274 && selection.is_empty()
4275 && text.as_ref().ends_with(':')
4276 && let Some(possible_emoji_short_code) =
4277 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4278 && !possible_emoji_short_code.is_empty()
4279 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4280 {
4281 let emoji_shortcode_start = Point::new(
4282 selection.start.row,
4283 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4284 );
4285
4286 // Remove shortcode from buffer
4287 edits.push((
4288 emoji_shortcode_start..selection.start,
4289 "".to_string().into(),
4290 ));
4291 new_selections.push((
4292 Selection {
4293 id: selection.id,
4294 start: snapshot.anchor_after(emoji_shortcode_start),
4295 end: snapshot.anchor_before(selection.start),
4296 reversed: selection.reversed,
4297 goal: selection.goal,
4298 },
4299 0,
4300 ));
4301
4302 // Insert emoji
4303 let selection_start_anchor = snapshot.anchor_after(selection.start);
4304 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4305 edits.push((selection.start..selection.end, emoji.to_string().into()));
4306
4307 continue;
4308 }
4309
4310 // If not handling any auto-close operation, then just replace the selected
4311 // text with the given input and move the selection to the end of the
4312 // newly inserted text.
4313 let anchor = snapshot.anchor_after(selection.end);
4314 if !self.linked_edit_ranges.is_empty() {
4315 let start_anchor = snapshot.anchor_before(selection.start);
4316
4317 let is_word_char = text.chars().next().is_none_or(|char| {
4318 let classifier = snapshot
4319 .char_classifier_at(start_anchor.to_offset(&snapshot))
4320 .scope_context(Some(CharScopeContext::LinkedEdit));
4321 classifier.is_word(char)
4322 });
4323
4324 if is_word_char {
4325 if let Some(ranges) = self
4326 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4327 {
4328 for (buffer, edits) in ranges {
4329 linked_edits
4330 .entry(buffer.clone())
4331 .or_default()
4332 .extend(edits.into_iter().map(|range| (range, text.clone())));
4333 }
4334 }
4335 } else {
4336 clear_linked_edit_ranges = true;
4337 }
4338 }
4339
4340 new_selections.push((selection.map(|_| anchor), 0));
4341 edits.push((selection.start..selection.end, text.clone()));
4342 }
4343
4344 drop(snapshot);
4345
4346 self.transact(window, cx, |this, window, cx| {
4347 if clear_linked_edit_ranges {
4348 this.linked_edit_ranges.clear();
4349 }
4350 let initial_buffer_versions =
4351 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4352
4353 this.buffer.update(cx, |buffer, cx| {
4354 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4355 });
4356 for (buffer, edits) in linked_edits {
4357 buffer.update(cx, |buffer, cx| {
4358 let snapshot = buffer.snapshot();
4359 let edits = edits
4360 .into_iter()
4361 .map(|(range, text)| {
4362 use text::ToPoint as TP;
4363 let end_point = TP::to_point(&range.end, &snapshot);
4364 let start_point = TP::to_point(&range.start, &snapshot);
4365 (start_point..end_point, text)
4366 })
4367 .sorted_by_key(|(range, _)| range.start);
4368 buffer.edit(edits, None, cx);
4369 })
4370 }
4371 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4372 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4373 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4374 let new_selections =
4375 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4376 .zip(new_selection_deltas)
4377 .map(|(selection, delta)| Selection {
4378 id: selection.id,
4379 start: selection.start + delta,
4380 end: selection.end + delta,
4381 reversed: selection.reversed,
4382 goal: SelectionGoal::None,
4383 })
4384 .collect::<Vec<_>>();
4385
4386 let mut i = 0;
4387 for (position, delta, selection_id, pair) in new_autoclose_regions {
4388 let position = position.to_offset(map.buffer_snapshot()) + delta;
4389 let start = map.buffer_snapshot().anchor_before(position);
4390 let end = map.buffer_snapshot().anchor_after(position);
4391 while let Some(existing_state) = this.autoclose_regions.get(i) {
4392 match existing_state
4393 .range
4394 .start
4395 .cmp(&start, map.buffer_snapshot())
4396 {
4397 Ordering::Less => i += 1,
4398 Ordering::Greater => break,
4399 Ordering::Equal => {
4400 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4401 Ordering::Less => i += 1,
4402 Ordering::Equal => break,
4403 Ordering::Greater => break,
4404 }
4405 }
4406 }
4407 }
4408 this.autoclose_regions.insert(
4409 i,
4410 AutocloseRegion {
4411 selection_id,
4412 range: start..end,
4413 pair,
4414 },
4415 );
4416 }
4417
4418 let had_active_edit_prediction = this.has_active_edit_prediction();
4419 this.change_selections(
4420 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4421 window,
4422 cx,
4423 |s| s.select(new_selections),
4424 );
4425
4426 if !bracket_inserted
4427 && let Some(on_type_format_task) =
4428 this.trigger_on_type_formatting(text.to_string(), window, cx)
4429 {
4430 on_type_format_task.detach_and_log_err(cx);
4431 }
4432
4433 let editor_settings = EditorSettings::get_global(cx);
4434 if bracket_inserted
4435 && (editor_settings.auto_signature_help
4436 || editor_settings.show_signature_help_after_edits)
4437 {
4438 this.show_signature_help(&ShowSignatureHelp, window, cx);
4439 }
4440
4441 let trigger_in_words =
4442 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4443 if this.hard_wrap.is_some() {
4444 let latest: Range<Point> = this.selections.newest(&map).range();
4445 if latest.is_empty()
4446 && this
4447 .buffer()
4448 .read(cx)
4449 .snapshot(cx)
4450 .line_len(MultiBufferRow(latest.start.row))
4451 == latest.start.column
4452 {
4453 this.rewrap_impl(
4454 RewrapOptions {
4455 override_language_settings: true,
4456 preserve_existing_whitespace: true,
4457 },
4458 cx,
4459 )
4460 }
4461 }
4462 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4463 refresh_linked_ranges(this, window, cx);
4464 this.refresh_edit_prediction(true, false, window, cx);
4465 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4466 });
4467 }
4468
4469 fn find_possible_emoji_shortcode_at_position(
4470 snapshot: &MultiBufferSnapshot,
4471 position: Point,
4472 ) -> Option<String> {
4473 let mut chars = Vec::new();
4474 let mut found_colon = false;
4475 for char in snapshot.reversed_chars_at(position).take(100) {
4476 // Found a possible emoji shortcode in the middle of the buffer
4477 if found_colon {
4478 if char.is_whitespace() {
4479 chars.reverse();
4480 return Some(chars.iter().collect());
4481 }
4482 // If the previous character is not a whitespace, we are in the middle of a word
4483 // and we only want to complete the shortcode if the word is made up of other emojis
4484 let mut containing_word = String::new();
4485 for ch in snapshot
4486 .reversed_chars_at(position)
4487 .skip(chars.len() + 1)
4488 .take(100)
4489 {
4490 if ch.is_whitespace() {
4491 break;
4492 }
4493 containing_word.push(ch);
4494 }
4495 let containing_word = containing_word.chars().rev().collect::<String>();
4496 if util::word_consists_of_emojis(containing_word.as_str()) {
4497 chars.reverse();
4498 return Some(chars.iter().collect());
4499 }
4500 }
4501
4502 if char.is_whitespace() || !char.is_ascii() {
4503 return None;
4504 }
4505 if char == ':' {
4506 found_colon = true;
4507 } else {
4508 chars.push(char);
4509 }
4510 }
4511 // Found a possible emoji shortcode at the beginning of the buffer
4512 chars.reverse();
4513 Some(chars.iter().collect())
4514 }
4515
4516 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4517 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4518 self.transact(window, cx, |this, window, cx| {
4519 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4520 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4521 let multi_buffer = this.buffer.read(cx);
4522 let buffer = multi_buffer.snapshot(cx);
4523 selections
4524 .iter()
4525 .map(|selection| {
4526 let start_point = selection.start.to_point(&buffer);
4527 let mut existing_indent =
4528 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4529 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4530 let start = selection.start;
4531 let end = selection.end;
4532 let selection_is_empty = start == end;
4533 let language_scope = buffer.language_scope_at(start);
4534 let (
4535 comment_delimiter,
4536 doc_delimiter,
4537 insert_extra_newline,
4538 indent_on_newline,
4539 indent_on_extra_newline,
4540 ) = if let Some(language) = &language_scope {
4541 let mut insert_extra_newline =
4542 insert_extra_newline_brackets(&buffer, start..end, language)
4543 || insert_extra_newline_tree_sitter(&buffer, start..end);
4544
4545 // Comment extension on newline is allowed only for cursor selections
4546 let comment_delimiter = maybe!({
4547 if !selection_is_empty {
4548 return None;
4549 }
4550
4551 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4552 return None;
4553 }
4554
4555 let delimiters = language.line_comment_prefixes();
4556 let max_len_of_delimiter =
4557 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4558 let (snapshot, range) =
4559 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4560
4561 let num_of_whitespaces = snapshot
4562 .chars_for_range(range.clone())
4563 .take_while(|c| c.is_whitespace())
4564 .count();
4565 let comment_candidate = snapshot
4566 .chars_for_range(range.clone())
4567 .skip(num_of_whitespaces)
4568 .take(max_len_of_delimiter)
4569 .collect::<String>();
4570 let (delimiter, trimmed_len) = delimiters
4571 .iter()
4572 .filter_map(|delimiter| {
4573 let prefix = delimiter.trim_end();
4574 if comment_candidate.starts_with(prefix) {
4575 Some((delimiter, prefix.len()))
4576 } else {
4577 None
4578 }
4579 })
4580 .max_by_key(|(_, len)| *len)?;
4581
4582 if let Some(BlockCommentConfig {
4583 start: block_start, ..
4584 }) = language.block_comment()
4585 {
4586 let block_start_trimmed = block_start.trim_end();
4587 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4588 let line_content = snapshot
4589 .chars_for_range(range)
4590 .skip(num_of_whitespaces)
4591 .take(block_start_trimmed.len())
4592 .collect::<String>();
4593
4594 if line_content.starts_with(block_start_trimmed) {
4595 return None;
4596 }
4597 }
4598 }
4599
4600 let cursor_is_placed_after_comment_marker =
4601 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4602 if cursor_is_placed_after_comment_marker {
4603 Some(delimiter.clone())
4604 } else {
4605 None
4606 }
4607 });
4608
4609 let mut indent_on_newline = IndentSize::spaces(0);
4610 let mut indent_on_extra_newline = IndentSize::spaces(0);
4611
4612 let doc_delimiter = maybe!({
4613 if !selection_is_empty {
4614 return None;
4615 }
4616
4617 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4618 return None;
4619 }
4620
4621 let BlockCommentConfig {
4622 start: start_tag,
4623 end: end_tag,
4624 prefix: delimiter,
4625 tab_size: len,
4626 } = language.documentation_comment()?;
4627 let is_within_block_comment = buffer
4628 .language_scope_at(start_point)
4629 .is_some_and(|scope| scope.override_name() == Some("comment"));
4630 if !is_within_block_comment {
4631 return None;
4632 }
4633
4634 let (snapshot, range) =
4635 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4636
4637 let num_of_whitespaces = snapshot
4638 .chars_for_range(range.clone())
4639 .take_while(|c| c.is_whitespace())
4640 .count();
4641
4642 // 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.
4643 let column = start_point.column;
4644 let cursor_is_after_start_tag = {
4645 let start_tag_len = start_tag.len();
4646 let start_tag_line = snapshot
4647 .chars_for_range(range.clone())
4648 .skip(num_of_whitespaces)
4649 .take(start_tag_len)
4650 .collect::<String>();
4651 if start_tag_line.starts_with(start_tag.as_ref()) {
4652 num_of_whitespaces + start_tag_len <= column as usize
4653 } else {
4654 false
4655 }
4656 };
4657
4658 let cursor_is_after_delimiter = {
4659 let delimiter_trim = delimiter.trim_end();
4660 let delimiter_line = snapshot
4661 .chars_for_range(range.clone())
4662 .skip(num_of_whitespaces)
4663 .take(delimiter_trim.len())
4664 .collect::<String>();
4665 if delimiter_line.starts_with(delimiter_trim) {
4666 num_of_whitespaces + delimiter_trim.len() <= column as usize
4667 } else {
4668 false
4669 }
4670 };
4671
4672 let cursor_is_before_end_tag_if_exists = {
4673 let mut char_position = 0u32;
4674 let mut end_tag_offset = None;
4675
4676 'outer: for chunk in snapshot.text_for_range(range) {
4677 if let Some(byte_pos) = chunk.find(&**end_tag) {
4678 let chars_before_match =
4679 chunk[..byte_pos].chars().count() as u32;
4680 end_tag_offset =
4681 Some(char_position + chars_before_match);
4682 break 'outer;
4683 }
4684 char_position += chunk.chars().count() as u32;
4685 }
4686
4687 if let Some(end_tag_offset) = end_tag_offset {
4688 let cursor_is_before_end_tag = column <= end_tag_offset;
4689 if cursor_is_after_start_tag {
4690 if cursor_is_before_end_tag {
4691 insert_extra_newline = true;
4692 }
4693 let cursor_is_at_start_of_end_tag =
4694 column == end_tag_offset;
4695 if cursor_is_at_start_of_end_tag {
4696 indent_on_extra_newline.len = *len;
4697 }
4698 }
4699 cursor_is_before_end_tag
4700 } else {
4701 true
4702 }
4703 };
4704
4705 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4706 && cursor_is_before_end_tag_if_exists
4707 {
4708 if cursor_is_after_start_tag {
4709 indent_on_newline.len = *len;
4710 }
4711 Some(delimiter.clone())
4712 } else {
4713 None
4714 }
4715 });
4716
4717 (
4718 comment_delimiter,
4719 doc_delimiter,
4720 insert_extra_newline,
4721 indent_on_newline,
4722 indent_on_extra_newline,
4723 )
4724 } else {
4725 (
4726 None,
4727 None,
4728 false,
4729 IndentSize::default(),
4730 IndentSize::default(),
4731 )
4732 };
4733
4734 let prevent_auto_indent = doc_delimiter.is_some();
4735 let delimiter = comment_delimiter.or(doc_delimiter);
4736
4737 let capacity_for_delimiter =
4738 delimiter.as_deref().map(str::len).unwrap_or_default();
4739 let mut new_text = String::with_capacity(
4740 1 + capacity_for_delimiter
4741 + existing_indent.len as usize
4742 + indent_on_newline.len as usize
4743 + indent_on_extra_newline.len as usize,
4744 );
4745 new_text.push('\n');
4746 new_text.extend(existing_indent.chars());
4747 new_text.extend(indent_on_newline.chars());
4748
4749 if let Some(delimiter) = &delimiter {
4750 new_text.push_str(delimiter);
4751 }
4752
4753 if insert_extra_newline {
4754 new_text.push('\n');
4755 new_text.extend(existing_indent.chars());
4756 new_text.extend(indent_on_extra_newline.chars());
4757 }
4758
4759 let anchor = buffer.anchor_after(end);
4760 let new_selection = selection.map(|_| anchor);
4761 (
4762 ((start..end, new_text), prevent_auto_indent),
4763 (insert_extra_newline, new_selection),
4764 )
4765 })
4766 .unzip()
4767 };
4768
4769 let mut auto_indent_edits = Vec::new();
4770 let mut edits = Vec::new();
4771 for (edit, prevent_auto_indent) in edits_with_flags {
4772 if prevent_auto_indent {
4773 edits.push(edit);
4774 } else {
4775 auto_indent_edits.push(edit);
4776 }
4777 }
4778 if !edits.is_empty() {
4779 this.edit(edits, cx);
4780 }
4781 if !auto_indent_edits.is_empty() {
4782 this.edit_with_autoindent(auto_indent_edits, cx);
4783 }
4784
4785 let buffer = this.buffer.read(cx).snapshot(cx);
4786 let new_selections = selection_info
4787 .into_iter()
4788 .map(|(extra_newline_inserted, new_selection)| {
4789 let mut cursor = new_selection.end.to_point(&buffer);
4790 if extra_newline_inserted {
4791 cursor.row -= 1;
4792 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4793 }
4794 new_selection.map(|_| cursor)
4795 })
4796 .collect();
4797
4798 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4799 this.refresh_edit_prediction(true, false, window, cx);
4800 });
4801 }
4802
4803 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4804 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4805
4806 let buffer = self.buffer.read(cx);
4807 let snapshot = buffer.snapshot(cx);
4808
4809 let mut edits = Vec::new();
4810 let mut rows = Vec::new();
4811
4812 for (rows_inserted, selection) in self
4813 .selections
4814 .all_adjusted(&self.display_snapshot(cx))
4815 .into_iter()
4816 .enumerate()
4817 {
4818 let cursor = selection.head();
4819 let row = cursor.row;
4820
4821 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4822
4823 let newline = "\n".to_string();
4824 edits.push((start_of_line..start_of_line, newline));
4825
4826 rows.push(row + rows_inserted as u32);
4827 }
4828
4829 self.transact(window, cx, |editor, window, cx| {
4830 editor.edit(edits, cx);
4831
4832 editor.change_selections(Default::default(), window, cx, |s| {
4833 let mut index = 0;
4834 s.move_cursors_with(|map, _, _| {
4835 let row = rows[index];
4836 index += 1;
4837
4838 let point = Point::new(row, 0);
4839 let boundary = map.next_line_boundary(point).1;
4840 let clipped = map.clip_point(boundary, Bias::Left);
4841
4842 (clipped, SelectionGoal::None)
4843 });
4844 });
4845
4846 let mut indent_edits = Vec::new();
4847 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4848 for row in rows {
4849 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4850 for (row, indent) in indents {
4851 if indent.len == 0 {
4852 continue;
4853 }
4854
4855 let text = match indent.kind {
4856 IndentKind::Space => " ".repeat(indent.len as usize),
4857 IndentKind::Tab => "\t".repeat(indent.len as usize),
4858 };
4859 let point = Point::new(row.0, 0);
4860 indent_edits.push((point..point, text));
4861 }
4862 }
4863 editor.edit(indent_edits, cx);
4864 });
4865 }
4866
4867 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4868 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4869
4870 let buffer = self.buffer.read(cx);
4871 let snapshot = buffer.snapshot(cx);
4872
4873 let mut edits = Vec::new();
4874 let mut rows = Vec::new();
4875 let mut rows_inserted = 0;
4876
4877 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4878 let cursor = selection.head();
4879 let row = cursor.row;
4880
4881 let point = Point::new(row + 1, 0);
4882 let start_of_line = snapshot.clip_point(point, Bias::Left);
4883
4884 let newline = "\n".to_string();
4885 edits.push((start_of_line..start_of_line, newline));
4886
4887 rows_inserted += 1;
4888 rows.push(row + rows_inserted);
4889 }
4890
4891 self.transact(window, cx, |editor, window, cx| {
4892 editor.edit(edits, cx);
4893
4894 editor.change_selections(Default::default(), window, cx, |s| {
4895 let mut index = 0;
4896 s.move_cursors_with(|map, _, _| {
4897 let row = rows[index];
4898 index += 1;
4899
4900 let point = Point::new(row, 0);
4901 let boundary = map.next_line_boundary(point).1;
4902 let clipped = map.clip_point(boundary, Bias::Left);
4903
4904 (clipped, SelectionGoal::None)
4905 });
4906 });
4907
4908 let mut indent_edits = Vec::new();
4909 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4910 for row in rows {
4911 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4912 for (row, indent) in indents {
4913 if indent.len == 0 {
4914 continue;
4915 }
4916
4917 let text = match indent.kind {
4918 IndentKind::Space => " ".repeat(indent.len as usize),
4919 IndentKind::Tab => "\t".repeat(indent.len as usize),
4920 };
4921 let point = Point::new(row.0, 0);
4922 indent_edits.push((point..point, text));
4923 }
4924 }
4925 editor.edit(indent_edits, cx);
4926 });
4927 }
4928
4929 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4930 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
4931 original_indent_columns: Vec::new(),
4932 });
4933 self.insert_with_autoindent_mode(text, autoindent, window, cx);
4934 }
4935
4936 fn insert_with_autoindent_mode(
4937 &mut self,
4938 text: &str,
4939 autoindent_mode: Option<AutoindentMode>,
4940 window: &mut Window,
4941 cx: &mut Context<Self>,
4942 ) {
4943 if self.read_only(cx) {
4944 return;
4945 }
4946
4947 let text: Arc<str> = text.into();
4948 self.transact(window, cx, |this, window, cx| {
4949 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
4950 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
4951 let anchors = {
4952 let snapshot = buffer.read(cx);
4953 old_selections
4954 .iter()
4955 .map(|s| {
4956 let anchor = snapshot.anchor_after(s.head());
4957 s.map(|_| anchor)
4958 })
4959 .collect::<Vec<_>>()
4960 };
4961 buffer.edit(
4962 old_selections
4963 .iter()
4964 .map(|s| (s.start..s.end, text.clone())),
4965 autoindent_mode,
4966 cx,
4967 );
4968 anchors
4969 });
4970
4971 this.change_selections(Default::default(), window, cx, |s| {
4972 s.select_anchors(selection_anchors);
4973 });
4974
4975 cx.notify();
4976 });
4977 }
4978
4979 fn trigger_completion_on_input(
4980 &mut self,
4981 text: &str,
4982 trigger_in_words: bool,
4983 window: &mut Window,
4984 cx: &mut Context<Self>,
4985 ) {
4986 let completions_source = self
4987 .context_menu
4988 .borrow()
4989 .as_ref()
4990 .and_then(|menu| match menu {
4991 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
4992 CodeContextMenu::CodeActions(_) => None,
4993 });
4994
4995 match completions_source {
4996 Some(CompletionsMenuSource::Words { .. }) => {
4997 self.open_or_update_completions_menu(
4998 Some(CompletionsMenuSource::Words {
4999 ignore_threshold: false,
5000 }),
5001 None,
5002 window,
5003 cx,
5004 );
5005 }
5006 Some(CompletionsMenuSource::Normal)
5007 | Some(CompletionsMenuSource::SnippetChoices)
5008 | None
5009 if self.is_completion_trigger(
5010 text,
5011 trigger_in_words,
5012 completions_source.is_some(),
5013 cx,
5014 ) =>
5015 {
5016 self.show_completions(
5017 &ShowCompletions {
5018 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
5019 },
5020 window,
5021 cx,
5022 )
5023 }
5024 _ => {
5025 self.hide_context_menu(window, cx);
5026 }
5027 }
5028 }
5029
5030 fn is_completion_trigger(
5031 &self,
5032 text: &str,
5033 trigger_in_words: bool,
5034 menu_is_open: bool,
5035 cx: &mut Context<Self>,
5036 ) -> bool {
5037 let position = self.selections.newest_anchor().head();
5038 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5039 return false;
5040 };
5041
5042 if let Some(completion_provider) = &self.completion_provider {
5043 completion_provider.is_completion_trigger(
5044 &buffer,
5045 position.text_anchor,
5046 text,
5047 trigger_in_words,
5048 menu_is_open,
5049 cx,
5050 )
5051 } else {
5052 false
5053 }
5054 }
5055
5056 /// If any empty selections is touching the start of its innermost containing autoclose
5057 /// region, expand it to select the brackets.
5058 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5059 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5060 let buffer = self.buffer.read(cx).read(cx);
5061 let new_selections = self
5062 .selections_with_autoclose_regions(selections, &buffer)
5063 .map(|(mut selection, region)| {
5064 if !selection.is_empty() {
5065 return selection;
5066 }
5067
5068 if let Some(region) = region {
5069 let mut range = region.range.to_offset(&buffer);
5070 if selection.start == range.start && range.start >= region.pair.start.len() {
5071 range.start -= region.pair.start.len();
5072 if buffer.contains_str_at(range.start, ®ion.pair.start)
5073 && buffer.contains_str_at(range.end, ®ion.pair.end)
5074 {
5075 range.end += region.pair.end.len();
5076 selection.start = range.start;
5077 selection.end = range.end;
5078
5079 return selection;
5080 }
5081 }
5082 }
5083
5084 let always_treat_brackets_as_autoclosed = buffer
5085 .language_settings_at(selection.start, cx)
5086 .always_treat_brackets_as_autoclosed;
5087
5088 if !always_treat_brackets_as_autoclosed {
5089 return selection;
5090 }
5091
5092 if let Some(scope) = buffer.language_scope_at(selection.start) {
5093 for (pair, enabled) in scope.brackets() {
5094 if !enabled || !pair.close {
5095 continue;
5096 }
5097
5098 if buffer.contains_str_at(selection.start, &pair.end) {
5099 let pair_start_len = pair.start.len();
5100 if buffer.contains_str_at(
5101 selection.start.saturating_sub(pair_start_len),
5102 &pair.start,
5103 ) {
5104 selection.start -= pair_start_len;
5105 selection.end += pair.end.len();
5106
5107 return selection;
5108 }
5109 }
5110 }
5111 }
5112
5113 selection
5114 })
5115 .collect();
5116
5117 drop(buffer);
5118 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5119 selections.select(new_selections)
5120 });
5121 }
5122
5123 /// Iterate the given selections, and for each one, find the smallest surrounding
5124 /// autoclose region. This uses the ordering of the selections and the autoclose
5125 /// regions to avoid repeated comparisons.
5126 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5127 &'a self,
5128 selections: impl IntoIterator<Item = Selection<D>>,
5129 buffer: &'a MultiBufferSnapshot,
5130 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5131 let mut i = 0;
5132 let mut regions = self.autoclose_regions.as_slice();
5133 selections.into_iter().map(move |selection| {
5134 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5135
5136 let mut enclosing = None;
5137 while let Some(pair_state) = regions.get(i) {
5138 if pair_state.range.end.to_offset(buffer) < range.start {
5139 regions = ®ions[i + 1..];
5140 i = 0;
5141 } else if pair_state.range.start.to_offset(buffer) > range.end {
5142 break;
5143 } else {
5144 if pair_state.selection_id == selection.id {
5145 enclosing = Some(pair_state);
5146 }
5147 i += 1;
5148 }
5149 }
5150
5151 (selection, enclosing)
5152 })
5153 }
5154
5155 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5156 fn invalidate_autoclose_regions(
5157 &mut self,
5158 mut selections: &[Selection<Anchor>],
5159 buffer: &MultiBufferSnapshot,
5160 ) {
5161 self.autoclose_regions.retain(|state| {
5162 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5163 return false;
5164 }
5165
5166 let mut i = 0;
5167 while let Some(selection) = selections.get(i) {
5168 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5169 selections = &selections[1..];
5170 continue;
5171 }
5172 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5173 break;
5174 }
5175 if selection.id == state.selection_id {
5176 return true;
5177 } else {
5178 i += 1;
5179 }
5180 }
5181 false
5182 });
5183 }
5184
5185 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5186 let offset = position.to_offset(buffer);
5187 let (word_range, kind) =
5188 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5189 if offset > word_range.start && kind == Some(CharKind::Word) {
5190 Some(
5191 buffer
5192 .text_for_range(word_range.start..offset)
5193 .collect::<String>(),
5194 )
5195 } else {
5196 None
5197 }
5198 }
5199
5200 pub fn toggle_inline_values(
5201 &mut self,
5202 _: &ToggleInlineValues,
5203 _: &mut Window,
5204 cx: &mut Context<Self>,
5205 ) {
5206 self.inline_value_cache.enabled = !self.inline_value_cache.enabled;
5207
5208 self.refresh_inline_values(cx);
5209 }
5210
5211 pub fn toggle_inlay_hints(
5212 &mut self,
5213 _: &ToggleInlayHints,
5214 _: &mut Window,
5215 cx: &mut Context<Self>,
5216 ) {
5217 self.refresh_inlay_hints(
5218 InlayHintRefreshReason::Toggle(!self.inlay_hints_enabled()),
5219 cx,
5220 );
5221 }
5222
5223 pub fn inlay_hints_enabled(&self) -> bool {
5224 self.inlay_hint_cache.enabled
5225 }
5226
5227 pub fn inline_values_enabled(&self) -> bool {
5228 self.inline_value_cache.enabled
5229 }
5230
5231 #[cfg(any(test, feature = "test-support"))]
5232 pub fn inline_value_inlays(&self, cx: &App) -> Vec<Inlay> {
5233 self.display_map
5234 .read(cx)
5235 .current_inlays()
5236 .filter(|inlay| matches!(inlay.id, InlayId::DebuggerValue(_)))
5237 .cloned()
5238 .collect()
5239 }
5240
5241 #[cfg(any(test, feature = "test-support"))]
5242 pub fn all_inlays(&self, cx: &App) -> Vec<Inlay> {
5243 self.display_map
5244 .read(cx)
5245 .current_inlays()
5246 .cloned()
5247 .collect()
5248 }
5249
5250 fn refresh_inlay_hints(&mut self, reason: InlayHintRefreshReason, cx: &mut Context<Self>) {
5251 if self.semantics_provider.is_none() || !self.mode.is_full() {
5252 return;
5253 }
5254
5255 let reason_description = reason.description();
5256 let ignore_debounce = matches!(
5257 reason,
5258 InlayHintRefreshReason::SettingsChange(_)
5259 | InlayHintRefreshReason::Toggle(_)
5260 | InlayHintRefreshReason::ExcerptsRemoved(_)
5261 | InlayHintRefreshReason::ModifiersChanged(_)
5262 );
5263 let (invalidate_cache, required_languages) = match reason {
5264 InlayHintRefreshReason::ModifiersChanged(enabled) => {
5265 match self.inlay_hint_cache.modifiers_override(enabled) {
5266 Some(enabled) => {
5267 if enabled {
5268 (InvalidationStrategy::RefreshRequested, None)
5269 } else {
5270 self.clear_inlay_hints(cx);
5271 return;
5272 }
5273 }
5274 None => return,
5275 }
5276 }
5277 InlayHintRefreshReason::Toggle(enabled) => {
5278 if self.inlay_hint_cache.toggle(enabled) {
5279 if enabled {
5280 (InvalidationStrategy::RefreshRequested, None)
5281 } else {
5282 self.clear_inlay_hints(cx);
5283 return;
5284 }
5285 } else {
5286 return;
5287 }
5288 }
5289 InlayHintRefreshReason::SettingsChange(new_settings) => {
5290 match self.inlay_hint_cache.update_settings(
5291 &self.buffer,
5292 new_settings,
5293 self.visible_inlay_hints(cx).cloned().collect::<Vec<_>>(),
5294 cx,
5295 ) {
5296 ControlFlow::Break(Some(InlaySplice {
5297 to_remove,
5298 to_insert,
5299 })) => {
5300 self.splice_inlays(&to_remove, to_insert, cx);
5301 return;
5302 }
5303 ControlFlow::Break(None) => return,
5304 ControlFlow::Continue(()) => (InvalidationStrategy::RefreshRequested, None),
5305 }
5306 }
5307 InlayHintRefreshReason::ExcerptsRemoved(excerpts_removed) => {
5308 if let Some(InlaySplice {
5309 to_remove,
5310 to_insert,
5311 }) = self.inlay_hint_cache.remove_excerpts(&excerpts_removed)
5312 {
5313 self.splice_inlays(&to_remove, to_insert, cx);
5314 }
5315 self.display_map.update(cx, |display_map, cx| {
5316 display_map.remove_inlays_for_excerpts(&excerpts_removed, cx)
5317 });
5318 return;
5319 }
5320 InlayHintRefreshReason::NewLinesShown => (InvalidationStrategy::None, None),
5321 InlayHintRefreshReason::BufferEdited(buffer_languages) => {
5322 (InvalidationStrategy::BufferEdited, Some(buffer_languages))
5323 }
5324 InlayHintRefreshReason::RefreshRequested => {
5325 (InvalidationStrategy::RefreshRequested, None)
5326 }
5327 };
5328
5329 let mut visible_excerpts = self.visible_excerpts(required_languages.as_ref(), cx);
5330 visible_excerpts.retain(|_, (buffer, _, _)| {
5331 self.registered_buffers
5332 .contains_key(&buffer.read(cx).remote_id())
5333 });
5334
5335 if let Some(InlaySplice {
5336 to_remove,
5337 to_insert,
5338 }) = self.inlay_hint_cache.spawn_hint_refresh(
5339 reason_description,
5340 visible_excerpts,
5341 invalidate_cache,
5342 ignore_debounce,
5343 cx,
5344 ) {
5345 self.splice_inlays(&to_remove, to_insert, cx);
5346 }
5347 }
5348
5349 pub fn clear_inlay_hints(&self, cx: &mut Context<Editor>) {
5350 self.splice_inlays(
5351 &self
5352 .visible_inlay_hints(cx)
5353 .map(|inlay| inlay.id)
5354 .collect::<Vec<_>>(),
5355 Vec::new(),
5356 cx,
5357 );
5358 }
5359
5360 fn visible_inlay_hints<'a>(
5361 &'a self,
5362 cx: &'a Context<Editor>,
5363 ) -> impl Iterator<Item = &'a Inlay> {
5364 self.display_map
5365 .read(cx)
5366 .current_inlays()
5367 .filter(move |inlay| matches!(inlay.id, InlayId::Hint(_)))
5368 }
5369
5370 pub fn visible_excerpts(
5371 &self,
5372 restrict_to_languages: Option<&HashSet<Arc<Language>>>,
5373 cx: &mut Context<Editor>,
5374 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5375 let Some(project) = self.project() else {
5376 return HashMap::default();
5377 };
5378 let project = project.read(cx);
5379 let multi_buffer = self.buffer().read(cx);
5380 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5381 let multi_buffer_visible_start = self
5382 .scroll_manager
5383 .anchor()
5384 .anchor
5385 .to_point(&multi_buffer_snapshot);
5386 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5387 multi_buffer_visible_start
5388 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5389 Bias::Left,
5390 );
5391 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
5392 multi_buffer_snapshot
5393 .range_to_buffer_ranges(multi_buffer_visible_range)
5394 .into_iter()
5395 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5396 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5397 let buffer_file = project::File::from_dyn(buffer.file())?;
5398 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5399 let worktree_entry = buffer_worktree
5400 .read(cx)
5401 .entry_for_id(buffer_file.project_entry_id()?)?;
5402 if worktree_entry.is_ignored {
5403 return None;
5404 }
5405
5406 let language = buffer.language()?;
5407 if let Some(restrict_to_languages) = restrict_to_languages
5408 && !restrict_to_languages.contains(language)
5409 {
5410 return None;
5411 }
5412 Some((
5413 excerpt_id,
5414 (
5415 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5416 buffer.version().clone(),
5417 excerpt_visible_range,
5418 ),
5419 ))
5420 })
5421 .collect()
5422 }
5423
5424 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5425 TextLayoutDetails {
5426 text_system: window.text_system().clone(),
5427 editor_style: self.style.clone().unwrap(),
5428 rem_size: window.rem_size(),
5429 scroll_anchor: self.scroll_manager.anchor(),
5430 visible_rows: self.visible_line_count(),
5431 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5432 }
5433 }
5434
5435 pub fn splice_inlays(
5436 &self,
5437 to_remove: &[InlayId],
5438 to_insert: Vec<Inlay>,
5439 cx: &mut Context<Self>,
5440 ) {
5441 self.display_map.update(cx, |display_map, cx| {
5442 display_map.splice_inlays(to_remove, to_insert, cx)
5443 });
5444 cx.notify();
5445 }
5446
5447 fn trigger_on_type_formatting(
5448 &self,
5449 input: String,
5450 window: &mut Window,
5451 cx: &mut Context<Self>,
5452 ) -> Option<Task<Result<()>>> {
5453 if input.len() != 1 {
5454 return None;
5455 }
5456
5457 let project = self.project()?;
5458 let position = self.selections.newest_anchor().head();
5459 let (buffer, buffer_position) = self
5460 .buffer
5461 .read(cx)
5462 .text_anchor_for_position(position, cx)?;
5463
5464 let settings = language_settings::language_settings(
5465 buffer
5466 .read(cx)
5467 .language_at(buffer_position)
5468 .map(|l| l.name()),
5469 buffer.read(cx).file(),
5470 cx,
5471 );
5472 if !settings.use_on_type_format {
5473 return None;
5474 }
5475
5476 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5477 // hence we do LSP request & edit on host side only — add formats to host's history.
5478 let push_to_lsp_host_history = true;
5479 // If this is not the host, append its history with new edits.
5480 let push_to_client_history = project.read(cx).is_via_collab();
5481
5482 let on_type_formatting = project.update(cx, |project, cx| {
5483 project.on_type_format(
5484 buffer.clone(),
5485 buffer_position,
5486 input,
5487 push_to_lsp_host_history,
5488 cx,
5489 )
5490 });
5491 Some(cx.spawn_in(window, async move |editor, cx| {
5492 if let Some(transaction) = on_type_formatting.await? {
5493 if push_to_client_history {
5494 buffer
5495 .update(cx, |buffer, _| {
5496 buffer.push_transaction(transaction, Instant::now());
5497 buffer.finalize_last_transaction();
5498 })
5499 .ok();
5500 }
5501 editor.update(cx, |editor, cx| {
5502 editor.refresh_document_highlights(cx);
5503 })?;
5504 }
5505 Ok(())
5506 }))
5507 }
5508
5509 pub fn show_word_completions(
5510 &mut self,
5511 _: &ShowWordCompletions,
5512 window: &mut Window,
5513 cx: &mut Context<Self>,
5514 ) {
5515 self.open_or_update_completions_menu(
5516 Some(CompletionsMenuSource::Words {
5517 ignore_threshold: true,
5518 }),
5519 None,
5520 window,
5521 cx,
5522 );
5523 }
5524
5525 pub fn show_completions(
5526 &mut self,
5527 options: &ShowCompletions,
5528 window: &mut Window,
5529 cx: &mut Context<Self>,
5530 ) {
5531 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5532 }
5533
5534 fn open_or_update_completions_menu(
5535 &mut self,
5536 requested_source: Option<CompletionsMenuSource>,
5537 trigger: Option<&str>,
5538 window: &mut Window,
5539 cx: &mut Context<Self>,
5540 ) {
5541 if self.pending_rename.is_some() {
5542 return;
5543 }
5544
5545 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5546
5547 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5548 // inserted and selected. To handle that case, the start of the selection is used so that
5549 // the menu starts with all choices.
5550 let position = self
5551 .selections
5552 .newest_anchor()
5553 .start
5554 .bias_right(&multibuffer_snapshot);
5555 if position.diff_base_anchor.is_some() {
5556 return;
5557 }
5558 let buffer_position = multibuffer_snapshot.anchor_before(position);
5559 let Some(buffer) = buffer_position
5560 .buffer_id
5561 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5562 else {
5563 return;
5564 };
5565 let buffer_snapshot = buffer.read(cx).snapshot();
5566
5567 let query: Option<Arc<String>> =
5568 Self::completion_query(&multibuffer_snapshot, buffer_position)
5569 .map(|query| query.into());
5570
5571 drop(multibuffer_snapshot);
5572
5573 // Hide the current completions menu when query is empty. Without this, cached
5574 // completions from before the trigger char may be reused (#32774).
5575 if query.is_none() {
5576 let menu_is_open = matches!(
5577 self.context_menu.borrow().as_ref(),
5578 Some(CodeContextMenu::Completions(_))
5579 );
5580 if menu_is_open {
5581 self.hide_context_menu(window, cx);
5582 }
5583 }
5584
5585 let mut ignore_word_threshold = false;
5586 let provider = match requested_source {
5587 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5588 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5589 ignore_word_threshold = ignore_threshold;
5590 None
5591 }
5592 Some(CompletionsMenuSource::SnippetChoices) => {
5593 log::error!("bug: SnippetChoices requested_source is not handled");
5594 None
5595 }
5596 };
5597
5598 let sort_completions = provider
5599 .as_ref()
5600 .is_some_and(|provider| provider.sort_completions());
5601
5602 let filter_completions = provider
5603 .as_ref()
5604 .is_none_or(|provider| provider.filter_completions());
5605
5606 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5607 if filter_completions {
5608 menu.filter(query.clone(), provider.clone(), window, cx);
5609 }
5610 // When `is_incomplete` is false, no need to re-query completions when the current query
5611 // is a suffix of the initial query.
5612 if !menu.is_incomplete {
5613 // If the new query is a suffix of the old query (typing more characters) and
5614 // the previous result was complete, the existing completions can be filtered.
5615 //
5616 // Note that this is always true for snippet completions.
5617 let query_matches = match (&menu.initial_query, &query) {
5618 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5619 (None, _) => true,
5620 _ => false,
5621 };
5622 if query_matches {
5623 let position_matches = if menu.initial_position == position {
5624 true
5625 } else {
5626 let snapshot = self.buffer.read(cx).read(cx);
5627 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5628 };
5629 if position_matches {
5630 return;
5631 }
5632 }
5633 }
5634 };
5635
5636 let trigger_kind = match trigger {
5637 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5638 CompletionTriggerKind::TRIGGER_CHARACTER
5639 }
5640 _ => CompletionTriggerKind::INVOKED,
5641 };
5642 let completion_context = CompletionContext {
5643 trigger_character: trigger.and_then(|trigger| {
5644 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5645 Some(String::from(trigger))
5646 } else {
5647 None
5648 }
5649 }),
5650 trigger_kind,
5651 };
5652
5653 let Anchor {
5654 excerpt_id: buffer_excerpt_id,
5655 text_anchor: buffer_position,
5656 ..
5657 } = buffer_position;
5658
5659 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5660 buffer_snapshot.surrounding_word(buffer_position, None)
5661 {
5662 let word_to_exclude = buffer_snapshot
5663 .text_for_range(word_range.clone())
5664 .collect::<String>();
5665 (
5666 buffer_snapshot.anchor_before(word_range.start)
5667 ..buffer_snapshot.anchor_after(buffer_position),
5668 Some(word_to_exclude),
5669 )
5670 } else {
5671 (buffer_position..buffer_position, None)
5672 };
5673
5674 let language = buffer_snapshot
5675 .language_at(buffer_position)
5676 .map(|language| language.name());
5677
5678 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5679 .completions
5680 .clone();
5681
5682 let show_completion_documentation = buffer_snapshot
5683 .settings_at(buffer_position, cx)
5684 .show_completion_documentation;
5685
5686 // The document can be large, so stay in reasonable bounds when searching for words,
5687 // otherwise completion pop-up might be slow to appear.
5688 const WORD_LOOKUP_ROWS: u32 = 5_000;
5689 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5690 let min_word_search = buffer_snapshot.clip_point(
5691 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5692 Bias::Left,
5693 );
5694 let max_word_search = buffer_snapshot.clip_point(
5695 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5696 Bias::Right,
5697 );
5698 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5699 ..buffer_snapshot.point_to_offset(max_word_search);
5700
5701 let skip_digits = query
5702 .as_ref()
5703 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5704
5705 let omit_word_completions = !self.word_completions_enabled
5706 || (!ignore_word_threshold
5707 && match &query {
5708 Some(query) => query.chars().count() < completion_settings.words_min_length,
5709 None => completion_settings.words_min_length != 0,
5710 });
5711
5712 let (mut words, provider_responses) = match &provider {
5713 Some(provider) => {
5714 let provider_responses = provider.completions(
5715 buffer_excerpt_id,
5716 &buffer,
5717 buffer_position,
5718 completion_context,
5719 window,
5720 cx,
5721 );
5722
5723 let words = match (omit_word_completions, completion_settings.words) {
5724 (true, _) | (_, WordsCompletionMode::Disabled) => {
5725 Task::ready(BTreeMap::default())
5726 }
5727 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5728 .background_spawn(async move {
5729 buffer_snapshot.words_in_range(WordsQuery {
5730 fuzzy_contents: None,
5731 range: word_search_range,
5732 skip_digits,
5733 })
5734 }),
5735 };
5736
5737 (words, provider_responses)
5738 }
5739 None => {
5740 let words = if omit_word_completions {
5741 Task::ready(BTreeMap::default())
5742 } else {
5743 cx.background_spawn(async move {
5744 buffer_snapshot.words_in_range(WordsQuery {
5745 fuzzy_contents: None,
5746 range: word_search_range,
5747 skip_digits,
5748 })
5749 })
5750 };
5751 (words, Task::ready(Ok(Vec::new())))
5752 }
5753 };
5754
5755 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5756
5757 let id = post_inc(&mut self.next_completion_id);
5758 let task = cx.spawn_in(window, async move |editor, cx| {
5759 let Ok(()) = editor.update(cx, |this, _| {
5760 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5761 }) else {
5762 return;
5763 };
5764
5765 // TODO: Ideally completions from different sources would be selectively re-queried, so
5766 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5767 let mut completions = Vec::new();
5768 let mut is_incomplete = false;
5769 let mut display_options: Option<CompletionDisplayOptions> = None;
5770 if let Some(provider_responses) = provider_responses.await.log_err()
5771 && !provider_responses.is_empty()
5772 {
5773 for response in provider_responses {
5774 completions.extend(response.completions);
5775 is_incomplete = is_incomplete || response.is_incomplete;
5776 match display_options.as_mut() {
5777 None => {
5778 display_options = Some(response.display_options);
5779 }
5780 Some(options) => options.merge(&response.display_options),
5781 }
5782 }
5783 if completion_settings.words == WordsCompletionMode::Fallback {
5784 words = Task::ready(BTreeMap::default());
5785 }
5786 }
5787 let display_options = display_options.unwrap_or_default();
5788
5789 let mut words = words.await;
5790 if let Some(word_to_exclude) = &word_to_exclude {
5791 words.remove(word_to_exclude);
5792 }
5793 for lsp_completion in &completions {
5794 words.remove(&lsp_completion.new_text);
5795 }
5796 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5797 replace_range: word_replace_range.clone(),
5798 new_text: word.clone(),
5799 label: CodeLabel::plain(word, None),
5800 icon_path: None,
5801 documentation: None,
5802 source: CompletionSource::BufferWord {
5803 word_range,
5804 resolved: false,
5805 },
5806 insert_text_mode: Some(InsertTextMode::AS_IS),
5807 confirm: None,
5808 }));
5809
5810 let menu = if completions.is_empty() {
5811 None
5812 } else {
5813 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5814 let languages = editor
5815 .workspace
5816 .as_ref()
5817 .and_then(|(workspace, _)| workspace.upgrade())
5818 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5819 let menu = CompletionsMenu::new(
5820 id,
5821 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5822 sort_completions,
5823 show_completion_documentation,
5824 position,
5825 query.clone(),
5826 is_incomplete,
5827 buffer.clone(),
5828 completions.into(),
5829 display_options,
5830 snippet_sort_order,
5831 languages,
5832 language,
5833 cx,
5834 );
5835
5836 let query = if filter_completions { query } else { None };
5837 let matches_task = if let Some(query) = query {
5838 menu.do_async_filtering(query, cx)
5839 } else {
5840 Task::ready(menu.unfiltered_matches())
5841 };
5842 (menu, matches_task)
5843 }) else {
5844 return;
5845 };
5846
5847 let matches = matches_task.await;
5848
5849 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5850 // Newer menu already set, so exit.
5851 if let Some(CodeContextMenu::Completions(prev_menu)) =
5852 editor.context_menu.borrow().as_ref()
5853 && prev_menu.id > id
5854 {
5855 return;
5856 };
5857
5858 // Only valid to take prev_menu because it the new menu is immediately set
5859 // below, or the menu is hidden.
5860 if let Some(CodeContextMenu::Completions(prev_menu)) =
5861 editor.context_menu.borrow_mut().take()
5862 {
5863 let position_matches =
5864 if prev_menu.initial_position == menu.initial_position {
5865 true
5866 } else {
5867 let snapshot = editor.buffer.read(cx).read(cx);
5868 prev_menu.initial_position.to_offset(&snapshot)
5869 == menu.initial_position.to_offset(&snapshot)
5870 };
5871 if position_matches {
5872 // Preserve markdown cache before `set_filter_results` because it will
5873 // try to populate the documentation cache.
5874 menu.preserve_markdown_cache(prev_menu);
5875 }
5876 };
5877
5878 menu.set_filter_results(matches, provider, window, cx);
5879 }) else {
5880 return;
5881 };
5882
5883 menu.visible().then_some(menu)
5884 };
5885
5886 editor
5887 .update_in(cx, |editor, window, cx| {
5888 if editor.focus_handle.is_focused(window)
5889 && let Some(menu) = menu
5890 {
5891 *editor.context_menu.borrow_mut() =
5892 Some(CodeContextMenu::Completions(menu));
5893
5894 crate::hover_popover::hide_hover(editor, cx);
5895 if editor.show_edit_predictions_in_menu() {
5896 editor.update_visible_edit_prediction(window, cx);
5897 } else {
5898 editor.discard_edit_prediction(false, cx);
5899 }
5900
5901 cx.notify();
5902 return;
5903 }
5904
5905 if editor.completion_tasks.len() <= 1 {
5906 // If there are no more completion tasks and the last menu was empty, we should hide it.
5907 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5908 // If it was already hidden and we don't show edit predictions in the menu,
5909 // we should also show the edit prediction when available.
5910 if was_hidden && editor.show_edit_predictions_in_menu() {
5911 editor.update_visible_edit_prediction(window, cx);
5912 }
5913 }
5914 })
5915 .ok();
5916 });
5917
5918 self.completion_tasks.push((id, task));
5919 }
5920
5921 #[cfg(feature = "test-support")]
5922 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5923 let menu = self.context_menu.borrow();
5924 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5925 let completions = menu.completions.borrow();
5926 Some(completions.to_vec())
5927 } else {
5928 None
5929 }
5930 }
5931
5932 pub fn with_completions_menu_matching_id<R>(
5933 &self,
5934 id: CompletionId,
5935 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5936 ) -> R {
5937 let mut context_menu = self.context_menu.borrow_mut();
5938 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5939 return f(None);
5940 };
5941 if completions_menu.id != id {
5942 return f(None);
5943 }
5944 f(Some(completions_menu))
5945 }
5946
5947 pub fn confirm_completion(
5948 &mut self,
5949 action: &ConfirmCompletion,
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::Complete, window, cx)
5955 }
5956
5957 pub fn confirm_completion_insert(
5958 &mut self,
5959 _: &ConfirmCompletionInsert,
5960 window: &mut Window,
5961 cx: &mut Context<Self>,
5962 ) -> Option<Task<Result<()>>> {
5963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5964 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5965 }
5966
5967 pub fn confirm_completion_replace(
5968 &mut self,
5969 _: &ConfirmCompletionReplace,
5970 window: &mut Window,
5971 cx: &mut Context<Self>,
5972 ) -> Option<Task<Result<()>>> {
5973 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5974 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5975 }
5976
5977 pub fn compose_completion(
5978 &mut self,
5979 action: &ComposeCompletion,
5980 window: &mut Window,
5981 cx: &mut Context<Self>,
5982 ) -> Option<Task<Result<()>>> {
5983 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5984 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5985 }
5986
5987 fn do_completion(
5988 &mut self,
5989 item_ix: Option<usize>,
5990 intent: CompletionIntent,
5991 window: &mut Window,
5992 cx: &mut Context<Editor>,
5993 ) -> Option<Task<Result<()>>> {
5994 use language::ToOffset as _;
5995
5996 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5997 else {
5998 return None;
5999 };
6000
6001 let candidate_id = {
6002 let entries = completions_menu.entries.borrow();
6003 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6004 if self.show_edit_predictions_in_menu() {
6005 self.discard_edit_prediction(true, cx);
6006 }
6007 mat.candidate_id
6008 };
6009
6010 let completion = completions_menu
6011 .completions
6012 .borrow()
6013 .get(candidate_id)?
6014 .clone();
6015 cx.stop_propagation();
6016
6017 let buffer_handle = completions_menu.buffer.clone();
6018
6019 let CompletionEdit {
6020 new_text,
6021 snippet,
6022 replace_range,
6023 } = process_completion_for_edit(
6024 &completion,
6025 intent,
6026 &buffer_handle,
6027 &completions_menu.initial_position.text_anchor,
6028 cx,
6029 );
6030
6031 let buffer = buffer_handle.read(cx);
6032 let snapshot = self.buffer.read(cx).snapshot(cx);
6033 let newest_anchor = self.selections.newest_anchor();
6034 let replace_range_multibuffer = {
6035 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6036 excerpt.map_range_from_buffer(replace_range.clone())
6037 };
6038 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6039 return None;
6040 }
6041
6042 let old_text = buffer
6043 .text_for_range(replace_range.clone())
6044 .collect::<String>();
6045 let lookbehind = newest_anchor
6046 .start
6047 .text_anchor
6048 .to_offset(buffer)
6049 .saturating_sub(replace_range.start);
6050 let lookahead = replace_range
6051 .end
6052 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6053 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6054 let suffix = &old_text[lookbehind.min(old_text.len())..];
6055
6056 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
6057 let mut ranges = Vec::new();
6058 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6059
6060 for selection in &selections {
6061 let range = if selection.id == newest_anchor.id {
6062 replace_range_multibuffer.clone()
6063 } else {
6064 let mut range = selection.range();
6065
6066 // if prefix is present, don't duplicate it
6067 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
6068 range.start = range.start.saturating_sub(lookbehind);
6069
6070 // if suffix is also present, mimic the newest cursor and replace it
6071 if selection.id != newest_anchor.id
6072 && snapshot.contains_str_at(range.end, suffix)
6073 {
6074 range.end += lookahead;
6075 }
6076 }
6077 range
6078 };
6079
6080 ranges.push(range.clone());
6081
6082 if !self.linked_edit_ranges.is_empty() {
6083 let start_anchor = snapshot.anchor_before(range.start);
6084 let end_anchor = snapshot.anchor_after(range.end);
6085 if let Some(ranges) = self
6086 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6087 {
6088 for (buffer, edits) in ranges {
6089 linked_edits
6090 .entry(buffer.clone())
6091 .or_default()
6092 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6093 }
6094 }
6095 }
6096 }
6097
6098 let common_prefix_len = old_text
6099 .chars()
6100 .zip(new_text.chars())
6101 .take_while(|(a, b)| a == b)
6102 .map(|(a, _)| a.len_utf8())
6103 .sum::<usize>();
6104
6105 cx.emit(EditorEvent::InputHandled {
6106 utf16_range_to_replace: None,
6107 text: new_text[common_prefix_len..].into(),
6108 });
6109
6110 self.transact(window, cx, |editor, window, cx| {
6111 if let Some(mut snippet) = snippet {
6112 snippet.text = new_text.to_string();
6113 editor
6114 .insert_snippet(&ranges, snippet, window, cx)
6115 .log_err();
6116 } else {
6117 editor.buffer.update(cx, |multi_buffer, cx| {
6118 let auto_indent = match completion.insert_text_mode {
6119 Some(InsertTextMode::AS_IS) => None,
6120 _ => editor.autoindent_mode.clone(),
6121 };
6122 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6123 multi_buffer.edit(edits, auto_indent, cx);
6124 });
6125 }
6126 for (buffer, edits) in linked_edits {
6127 buffer.update(cx, |buffer, cx| {
6128 let snapshot = buffer.snapshot();
6129 let edits = edits
6130 .into_iter()
6131 .map(|(range, text)| {
6132 use text::ToPoint as TP;
6133 let end_point = TP::to_point(&range.end, &snapshot);
6134 let start_point = TP::to_point(&range.start, &snapshot);
6135 (start_point..end_point, text)
6136 })
6137 .sorted_by_key(|(range, _)| range.start);
6138 buffer.edit(edits, None, cx);
6139 })
6140 }
6141
6142 editor.refresh_edit_prediction(true, false, window, cx);
6143 });
6144 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6145
6146 let show_new_completions_on_confirm = completion
6147 .confirm
6148 .as_ref()
6149 .is_some_and(|confirm| confirm(intent, window, cx));
6150 if show_new_completions_on_confirm {
6151 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6152 }
6153
6154 let provider = self.completion_provider.as_ref()?;
6155 drop(completion);
6156 let apply_edits = provider.apply_additional_edits_for_completion(
6157 buffer_handle,
6158 completions_menu.completions.clone(),
6159 candidate_id,
6160 true,
6161 cx,
6162 );
6163
6164 let editor_settings = EditorSettings::get_global(cx);
6165 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6166 // After the code completion is finished, users often want to know what signatures are needed.
6167 // so we should automatically call signature_help
6168 self.show_signature_help(&ShowSignatureHelp, window, cx);
6169 }
6170
6171 Some(cx.foreground_executor().spawn(async move {
6172 apply_edits.await?;
6173 Ok(())
6174 }))
6175 }
6176
6177 pub fn toggle_code_actions(
6178 &mut self,
6179 action: &ToggleCodeActions,
6180 window: &mut Window,
6181 cx: &mut Context<Self>,
6182 ) {
6183 let quick_launch = action.quick_launch;
6184 let mut context_menu = self.context_menu.borrow_mut();
6185 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6186 if code_actions.deployed_from == action.deployed_from {
6187 // Toggle if we're selecting the same one
6188 *context_menu = None;
6189 cx.notify();
6190 return;
6191 } else {
6192 // Otherwise, clear it and start a new one
6193 *context_menu = None;
6194 cx.notify();
6195 }
6196 }
6197 drop(context_menu);
6198 let snapshot = self.snapshot(window, cx);
6199 let deployed_from = action.deployed_from.clone();
6200 let action = action.clone();
6201 self.completion_tasks.clear();
6202 self.discard_edit_prediction(false, cx);
6203
6204 let multibuffer_point = match &action.deployed_from {
6205 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6206 DisplayPoint::new(*row, 0).to_point(&snapshot)
6207 }
6208 _ => self
6209 .selections
6210 .newest::<Point>(&snapshot.display_snapshot)
6211 .head(),
6212 };
6213 let Some((buffer, buffer_row)) = snapshot
6214 .buffer_snapshot()
6215 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6216 .and_then(|(buffer_snapshot, range)| {
6217 self.buffer()
6218 .read(cx)
6219 .buffer(buffer_snapshot.remote_id())
6220 .map(|buffer| (buffer, range.start.row))
6221 })
6222 else {
6223 return;
6224 };
6225 let buffer_id = buffer.read(cx).remote_id();
6226 let tasks = self
6227 .tasks
6228 .get(&(buffer_id, buffer_row))
6229 .map(|t| Arc::new(t.to_owned()));
6230
6231 if !self.focus_handle.is_focused(window) {
6232 return;
6233 }
6234 let project = self.project.clone();
6235
6236 let code_actions_task = match deployed_from {
6237 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6238 _ => self.code_actions(buffer_row, window, cx),
6239 };
6240
6241 let runnable_task = match deployed_from {
6242 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6243 _ => {
6244 let mut task_context_task = Task::ready(None);
6245 if let Some(tasks) = &tasks
6246 && let Some(project) = project
6247 {
6248 task_context_task =
6249 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6250 }
6251
6252 cx.spawn_in(window, {
6253 let buffer = buffer.clone();
6254 async move |editor, cx| {
6255 let task_context = task_context_task.await;
6256
6257 let resolved_tasks =
6258 tasks
6259 .zip(task_context.clone())
6260 .map(|(tasks, task_context)| ResolvedTasks {
6261 templates: tasks.resolve(&task_context).collect(),
6262 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6263 multibuffer_point.row,
6264 tasks.column,
6265 )),
6266 });
6267 let debug_scenarios = editor
6268 .update(cx, |editor, cx| {
6269 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6270 })?
6271 .await;
6272 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6273 }
6274 })
6275 }
6276 };
6277
6278 cx.spawn_in(window, async move |editor, cx| {
6279 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6280 let code_actions = code_actions_task.await;
6281 let spawn_straight_away = quick_launch
6282 && resolved_tasks
6283 .as_ref()
6284 .is_some_and(|tasks| tasks.templates.len() == 1)
6285 && code_actions
6286 .as_ref()
6287 .is_none_or(|actions| actions.is_empty())
6288 && debug_scenarios.is_empty();
6289
6290 editor.update_in(cx, |editor, window, cx| {
6291 crate::hover_popover::hide_hover(editor, cx);
6292 let actions = CodeActionContents::new(
6293 resolved_tasks,
6294 code_actions,
6295 debug_scenarios,
6296 task_context.unwrap_or_default(),
6297 );
6298
6299 // Don't show the menu if there are no actions available
6300 if actions.is_empty() {
6301 cx.notify();
6302 return Task::ready(Ok(()));
6303 }
6304
6305 *editor.context_menu.borrow_mut() =
6306 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6307 buffer,
6308 actions,
6309 selected_item: Default::default(),
6310 scroll_handle: UniformListScrollHandle::default(),
6311 deployed_from,
6312 }));
6313 cx.notify();
6314 if spawn_straight_away
6315 && let Some(task) = editor.confirm_code_action(
6316 &ConfirmCodeAction { item_ix: Some(0) },
6317 window,
6318 cx,
6319 )
6320 {
6321 return task;
6322 }
6323
6324 Task::ready(Ok(()))
6325 })
6326 })
6327 .detach_and_log_err(cx);
6328 }
6329
6330 fn debug_scenarios(
6331 &mut self,
6332 resolved_tasks: &Option<ResolvedTasks>,
6333 buffer: &Entity<Buffer>,
6334 cx: &mut App,
6335 ) -> Task<Vec<task::DebugScenario>> {
6336 maybe!({
6337 let project = self.project()?;
6338 let dap_store = project.read(cx).dap_store();
6339 let mut scenarios = vec![];
6340 let resolved_tasks = resolved_tasks.as_ref()?;
6341 let buffer = buffer.read(cx);
6342 let language = buffer.language()?;
6343 let file = buffer.file();
6344 let debug_adapter = language_settings(language.name().into(), file, cx)
6345 .debuggers
6346 .first()
6347 .map(SharedString::from)
6348 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6349
6350 dap_store.update(cx, |dap_store, cx| {
6351 for (_, task) in &resolved_tasks.templates {
6352 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6353 task.original_task().clone(),
6354 debug_adapter.clone().into(),
6355 task.display_label().to_owned().into(),
6356 cx,
6357 );
6358 scenarios.push(maybe_scenario);
6359 }
6360 });
6361 Some(cx.background_spawn(async move {
6362 futures::future::join_all(scenarios)
6363 .await
6364 .into_iter()
6365 .flatten()
6366 .collect::<Vec<_>>()
6367 }))
6368 })
6369 .unwrap_or_else(|| Task::ready(vec![]))
6370 }
6371
6372 fn code_actions(
6373 &mut self,
6374 buffer_row: u32,
6375 window: &mut Window,
6376 cx: &mut Context<Self>,
6377 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6378 let mut task = self.code_actions_task.take();
6379 cx.spawn_in(window, async move |editor, cx| {
6380 while let Some(prev_task) = task {
6381 prev_task.await.log_err();
6382 task = editor
6383 .update(cx, |this, _| this.code_actions_task.take())
6384 .ok()?;
6385 }
6386
6387 editor
6388 .update(cx, |editor, cx| {
6389 editor
6390 .available_code_actions
6391 .clone()
6392 .and_then(|(location, code_actions)| {
6393 let snapshot = location.buffer.read(cx).snapshot();
6394 let point_range = location.range.to_point(&snapshot);
6395 let point_range = point_range.start.row..=point_range.end.row;
6396 if point_range.contains(&buffer_row) {
6397 Some(code_actions)
6398 } else {
6399 None
6400 }
6401 })
6402 })
6403 .ok()
6404 .flatten()
6405 })
6406 }
6407
6408 pub fn confirm_code_action(
6409 &mut self,
6410 action: &ConfirmCodeAction,
6411 window: &mut Window,
6412 cx: &mut Context<Self>,
6413 ) -> Option<Task<Result<()>>> {
6414 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6415
6416 let actions_menu =
6417 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6418 menu
6419 } else {
6420 return None;
6421 };
6422
6423 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6424 let action = actions_menu.actions.get(action_ix)?;
6425 let title = action.label();
6426 let buffer = actions_menu.buffer;
6427 let workspace = self.workspace()?;
6428
6429 match action {
6430 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6431 workspace.update(cx, |workspace, cx| {
6432 workspace.schedule_resolved_task(
6433 task_source_kind,
6434 resolved_task,
6435 false,
6436 window,
6437 cx,
6438 );
6439
6440 Some(Task::ready(Ok(())))
6441 })
6442 }
6443 CodeActionsItem::CodeAction {
6444 excerpt_id,
6445 action,
6446 provider,
6447 } => {
6448 let apply_code_action =
6449 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6450 let workspace = workspace.downgrade();
6451 Some(cx.spawn_in(window, async move |editor, cx| {
6452 let project_transaction = apply_code_action.await?;
6453 Self::open_project_transaction(
6454 &editor,
6455 workspace,
6456 project_transaction,
6457 title,
6458 cx,
6459 )
6460 .await
6461 }))
6462 }
6463 CodeActionsItem::DebugScenario(scenario) => {
6464 let context = actions_menu.actions.context;
6465
6466 workspace.update(cx, |workspace, cx| {
6467 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6468 workspace.start_debug_session(
6469 scenario,
6470 context,
6471 Some(buffer),
6472 None,
6473 window,
6474 cx,
6475 );
6476 });
6477 Some(Task::ready(Ok(())))
6478 }
6479 }
6480 }
6481
6482 pub async fn open_project_transaction(
6483 editor: &WeakEntity<Editor>,
6484 workspace: WeakEntity<Workspace>,
6485 transaction: ProjectTransaction,
6486 title: String,
6487 cx: &mut AsyncWindowContext,
6488 ) -> Result<()> {
6489 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6490 cx.update(|_, cx| {
6491 entries.sort_unstable_by_key(|(buffer, _)| {
6492 buffer.read(cx).file().map(|f| f.path().clone())
6493 });
6494 })?;
6495 if entries.is_empty() {
6496 return Ok(());
6497 }
6498
6499 // If the project transaction's edits are all contained within this editor, then
6500 // avoid opening a new editor to display them.
6501
6502 if let [(buffer, transaction)] = &*entries {
6503 let excerpt = editor.update(cx, |editor, cx| {
6504 editor
6505 .buffer()
6506 .read(cx)
6507 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6508 })?;
6509 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6510 && excerpted_buffer == *buffer
6511 {
6512 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6513 let excerpt_range = excerpt_range.to_offset(buffer);
6514 buffer
6515 .edited_ranges_for_transaction::<usize>(transaction)
6516 .all(|range| {
6517 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6518 })
6519 })?;
6520
6521 if all_edits_within_excerpt {
6522 return Ok(());
6523 }
6524 }
6525 }
6526
6527 let mut ranges_to_highlight = Vec::new();
6528 let excerpt_buffer = cx.new(|cx| {
6529 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6530 for (buffer_handle, transaction) in &entries {
6531 let edited_ranges = buffer_handle
6532 .read(cx)
6533 .edited_ranges_for_transaction::<Point>(transaction)
6534 .collect::<Vec<_>>();
6535 let (ranges, _) = multibuffer.set_excerpts_for_path(
6536 PathKey::for_buffer(buffer_handle, cx),
6537 buffer_handle.clone(),
6538 edited_ranges,
6539 multibuffer_context_lines(cx),
6540 cx,
6541 );
6542
6543 ranges_to_highlight.extend(ranges);
6544 }
6545 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6546 multibuffer
6547 })?;
6548
6549 workspace.update_in(cx, |workspace, window, cx| {
6550 let project = workspace.project().clone();
6551 let editor =
6552 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6553 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6554 editor.update(cx, |editor, cx| {
6555 editor.highlight_background::<Self>(
6556 &ranges_to_highlight,
6557 |theme| theme.colors().editor_highlighted_line_background,
6558 cx,
6559 );
6560 });
6561 })?;
6562
6563 Ok(())
6564 }
6565
6566 pub fn clear_code_action_providers(&mut self) {
6567 self.code_action_providers.clear();
6568 self.available_code_actions.take();
6569 }
6570
6571 pub fn add_code_action_provider(
6572 &mut self,
6573 provider: Rc<dyn CodeActionProvider>,
6574 window: &mut Window,
6575 cx: &mut Context<Self>,
6576 ) {
6577 if self
6578 .code_action_providers
6579 .iter()
6580 .any(|existing_provider| existing_provider.id() == provider.id())
6581 {
6582 return;
6583 }
6584
6585 self.code_action_providers.push(provider);
6586 self.refresh_code_actions(window, cx);
6587 }
6588
6589 pub fn remove_code_action_provider(
6590 &mut self,
6591 id: Arc<str>,
6592 window: &mut Window,
6593 cx: &mut Context<Self>,
6594 ) {
6595 self.code_action_providers
6596 .retain(|provider| provider.id() != id);
6597 self.refresh_code_actions(window, cx);
6598 }
6599
6600 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6601 !self.code_action_providers.is_empty()
6602 && EditorSettings::get_global(cx).toolbar.code_actions
6603 }
6604
6605 pub fn has_available_code_actions(&self) -> bool {
6606 self.available_code_actions
6607 .as_ref()
6608 .is_some_and(|(_, actions)| !actions.is_empty())
6609 }
6610
6611 fn render_inline_code_actions(
6612 &self,
6613 icon_size: ui::IconSize,
6614 display_row: DisplayRow,
6615 is_active: bool,
6616 cx: &mut Context<Self>,
6617 ) -> AnyElement {
6618 let show_tooltip = !self.context_menu_visible();
6619 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6620 .icon_size(icon_size)
6621 .shape(ui::IconButtonShape::Square)
6622 .icon_color(ui::Color::Hidden)
6623 .toggle_state(is_active)
6624 .when(show_tooltip, |this| {
6625 this.tooltip({
6626 let focus_handle = self.focus_handle.clone();
6627 move |window, cx| {
6628 Tooltip::for_action_in(
6629 "Toggle Code Actions",
6630 &ToggleCodeActions {
6631 deployed_from: None,
6632 quick_launch: false,
6633 },
6634 &focus_handle,
6635 window,
6636 cx,
6637 )
6638 }
6639 })
6640 })
6641 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6642 window.focus(&editor.focus_handle(cx));
6643 editor.toggle_code_actions(
6644 &crate::actions::ToggleCodeActions {
6645 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6646 display_row,
6647 )),
6648 quick_launch: false,
6649 },
6650 window,
6651 cx,
6652 );
6653 }))
6654 .into_any_element()
6655 }
6656
6657 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6658 &self.context_menu
6659 }
6660
6661 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6662 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6663 cx.background_executor()
6664 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6665 .await;
6666
6667 let (start_buffer, start, _, end, newest_selection) = this
6668 .update(cx, |this, cx| {
6669 let newest_selection = this.selections.newest_anchor().clone();
6670 if newest_selection.head().diff_base_anchor.is_some() {
6671 return None;
6672 }
6673 let display_snapshot = this.display_snapshot(cx);
6674 let newest_selection_adjusted =
6675 this.selections.newest_adjusted(&display_snapshot);
6676 let buffer = this.buffer.read(cx);
6677
6678 let (start_buffer, start) =
6679 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6680 let (end_buffer, end) =
6681 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6682
6683 Some((start_buffer, start, end_buffer, end, newest_selection))
6684 })?
6685 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6686 .context(
6687 "Expected selection to lie in a single buffer when refreshing code actions",
6688 )?;
6689 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6690 let providers = this.code_action_providers.clone();
6691 let tasks = this
6692 .code_action_providers
6693 .iter()
6694 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6695 .collect::<Vec<_>>();
6696 (providers, tasks)
6697 })?;
6698
6699 let mut actions = Vec::new();
6700 for (provider, provider_actions) in
6701 providers.into_iter().zip(future::join_all(tasks).await)
6702 {
6703 if let Some(provider_actions) = provider_actions.log_err() {
6704 actions.extend(provider_actions.into_iter().map(|action| {
6705 AvailableCodeAction {
6706 excerpt_id: newest_selection.start.excerpt_id,
6707 action,
6708 provider: provider.clone(),
6709 }
6710 }));
6711 }
6712 }
6713
6714 this.update(cx, |this, cx| {
6715 this.available_code_actions = if actions.is_empty() {
6716 None
6717 } else {
6718 Some((
6719 Location {
6720 buffer: start_buffer,
6721 range: start..end,
6722 },
6723 actions.into(),
6724 ))
6725 };
6726 cx.notify();
6727 })
6728 }));
6729 }
6730
6731 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6732 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6733 self.show_git_blame_inline = false;
6734
6735 self.show_git_blame_inline_delay_task =
6736 Some(cx.spawn_in(window, async move |this, cx| {
6737 cx.background_executor().timer(delay).await;
6738
6739 this.update(cx, |this, cx| {
6740 this.show_git_blame_inline = true;
6741 cx.notify();
6742 })
6743 .log_err();
6744 }));
6745 }
6746 }
6747
6748 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6749 let snapshot = self.snapshot(window, cx);
6750 let cursor = self
6751 .selections
6752 .newest::<Point>(&snapshot.display_snapshot)
6753 .head();
6754 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6755 else {
6756 return;
6757 };
6758
6759 let Some(blame) = self.blame.as_ref() else {
6760 return;
6761 };
6762
6763 let row_info = RowInfo {
6764 buffer_id: Some(buffer.remote_id()),
6765 buffer_row: Some(point.row),
6766 ..Default::default()
6767 };
6768 let Some((buffer, blame_entry)) = blame
6769 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6770 .flatten()
6771 else {
6772 return;
6773 };
6774
6775 let anchor = self.selections.newest_anchor().head();
6776 let position = self.to_pixel_point(anchor, &snapshot, window);
6777 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6778 self.show_blame_popover(
6779 buffer,
6780 &blame_entry,
6781 position + last_bounds.origin,
6782 true,
6783 cx,
6784 );
6785 };
6786 }
6787
6788 fn show_blame_popover(
6789 &mut self,
6790 buffer: BufferId,
6791 blame_entry: &BlameEntry,
6792 position: gpui::Point<Pixels>,
6793 ignore_timeout: bool,
6794 cx: &mut Context<Self>,
6795 ) {
6796 if let Some(state) = &mut self.inline_blame_popover {
6797 state.hide_task.take();
6798 } else {
6799 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6800 let blame_entry = blame_entry.clone();
6801 let show_task = cx.spawn(async move |editor, cx| {
6802 if !ignore_timeout {
6803 cx.background_executor()
6804 .timer(std::time::Duration::from_millis(blame_popover_delay))
6805 .await;
6806 }
6807 editor
6808 .update(cx, |editor, cx| {
6809 editor.inline_blame_popover_show_task.take();
6810 let Some(blame) = editor.blame.as_ref() else {
6811 return;
6812 };
6813 let blame = blame.read(cx);
6814 let details = blame.details_for_entry(buffer, &blame_entry);
6815 let markdown = cx.new(|cx| {
6816 Markdown::new(
6817 details
6818 .as_ref()
6819 .map(|message| message.message.clone())
6820 .unwrap_or_default(),
6821 None,
6822 None,
6823 cx,
6824 )
6825 });
6826 editor.inline_blame_popover = Some(InlineBlamePopover {
6827 position,
6828 hide_task: None,
6829 popover_bounds: None,
6830 popover_state: InlineBlamePopoverState {
6831 scroll_handle: ScrollHandle::new(),
6832 commit_message: details,
6833 markdown,
6834 },
6835 keyboard_grace: ignore_timeout,
6836 });
6837 cx.notify();
6838 })
6839 .ok();
6840 });
6841 self.inline_blame_popover_show_task = Some(show_task);
6842 }
6843 }
6844
6845 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6846 self.inline_blame_popover_show_task.take();
6847 if let Some(state) = &mut self.inline_blame_popover {
6848 let hide_task = cx.spawn(async move |editor, cx| {
6849 if !ignore_timeout {
6850 cx.background_executor()
6851 .timer(std::time::Duration::from_millis(100))
6852 .await;
6853 }
6854 editor
6855 .update(cx, |editor, cx| {
6856 editor.inline_blame_popover.take();
6857 cx.notify();
6858 })
6859 .ok();
6860 });
6861 state.hide_task = Some(hide_task);
6862 true
6863 } else {
6864 false
6865 }
6866 }
6867
6868 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6869 if self.pending_rename.is_some() {
6870 return None;
6871 }
6872
6873 let provider = self.semantics_provider.clone()?;
6874 let buffer = self.buffer.read(cx);
6875 let newest_selection = self.selections.newest_anchor().clone();
6876 let cursor_position = newest_selection.head();
6877 let (cursor_buffer, cursor_buffer_position) =
6878 buffer.text_anchor_for_position(cursor_position, cx)?;
6879 let (tail_buffer, tail_buffer_position) =
6880 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6881 if cursor_buffer != tail_buffer {
6882 return None;
6883 }
6884
6885 let snapshot = cursor_buffer.read(cx).snapshot();
6886 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6887 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6888 if start_word_range != end_word_range {
6889 self.document_highlights_task.take();
6890 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6891 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6892 return None;
6893 }
6894
6895 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6896 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6897 cx.background_executor()
6898 .timer(Duration::from_millis(debounce))
6899 .await;
6900
6901 let highlights = if let Some(highlights) = cx
6902 .update(|cx| {
6903 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6904 })
6905 .ok()
6906 .flatten()
6907 {
6908 highlights.await.log_err()
6909 } else {
6910 None
6911 };
6912
6913 if let Some(highlights) = highlights {
6914 this.update(cx, |this, cx| {
6915 if this.pending_rename.is_some() {
6916 return;
6917 }
6918
6919 let buffer = this.buffer.read(cx);
6920 if buffer
6921 .text_anchor_for_position(cursor_position, cx)
6922 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6923 {
6924 return;
6925 }
6926
6927 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6928 let mut write_ranges = Vec::new();
6929 let mut read_ranges = Vec::new();
6930 for highlight in highlights {
6931 let buffer_id = cursor_buffer.read(cx).remote_id();
6932 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6933 {
6934 let start = highlight
6935 .range
6936 .start
6937 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6938 let end = highlight
6939 .range
6940 .end
6941 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6942 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6943 continue;
6944 }
6945
6946 let range =
6947 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6948 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6949 write_ranges.push(range);
6950 } else {
6951 read_ranges.push(range);
6952 }
6953 }
6954 }
6955
6956 this.highlight_background::<DocumentHighlightRead>(
6957 &read_ranges,
6958 |theme| theme.colors().editor_document_highlight_read_background,
6959 cx,
6960 );
6961 this.highlight_background::<DocumentHighlightWrite>(
6962 &write_ranges,
6963 |theme| theme.colors().editor_document_highlight_write_background,
6964 cx,
6965 );
6966 cx.notify();
6967 })
6968 .log_err();
6969 }
6970 }));
6971 None
6972 }
6973
6974 fn prepare_highlight_query_from_selection(
6975 &mut self,
6976 window: &Window,
6977 cx: &mut Context<Editor>,
6978 ) -> Option<(String, Range<Anchor>)> {
6979 if matches!(self.mode, EditorMode::SingleLine) {
6980 return None;
6981 }
6982 if !EditorSettings::get_global(cx).selection_highlight {
6983 return None;
6984 }
6985 if self.selections.count() != 1 || self.selections.line_mode() {
6986 return None;
6987 }
6988 let snapshot = self.snapshot(window, cx);
6989 let selection = self.selections.newest::<Point>(&snapshot);
6990 // If the selection spans multiple rows OR it is empty
6991 if selection.start.row != selection.end.row
6992 || selection.start.column == selection.end.column
6993 {
6994 return None;
6995 }
6996 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6997 let query = snapshot
6998 .buffer_snapshot()
6999 .text_for_range(selection_anchor_range.clone())
7000 .collect::<String>();
7001 if query.trim().is_empty() {
7002 return None;
7003 }
7004 Some((query, selection_anchor_range))
7005 }
7006
7007 fn update_selection_occurrence_highlights(
7008 &mut self,
7009 query_text: String,
7010 query_range: Range<Anchor>,
7011 multi_buffer_range_to_query: Range<Point>,
7012 use_debounce: bool,
7013 window: &mut Window,
7014 cx: &mut Context<Editor>,
7015 ) -> Task<()> {
7016 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7017 cx.spawn_in(window, async move |editor, cx| {
7018 if use_debounce {
7019 cx.background_executor()
7020 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7021 .await;
7022 }
7023 let match_task = cx.background_spawn(async move {
7024 let buffer_ranges = multi_buffer_snapshot
7025 .range_to_buffer_ranges(multi_buffer_range_to_query)
7026 .into_iter()
7027 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7028 let mut match_ranges = Vec::new();
7029 let Ok(regex) = project::search::SearchQuery::text(
7030 query_text.clone(),
7031 false,
7032 false,
7033 false,
7034 Default::default(),
7035 Default::default(),
7036 false,
7037 None,
7038 ) else {
7039 return Vec::default();
7040 };
7041 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7042 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7043 match_ranges.extend(
7044 regex
7045 .search(buffer_snapshot, Some(search_range.clone()))
7046 .await
7047 .into_iter()
7048 .filter_map(|match_range| {
7049 let match_start = buffer_snapshot
7050 .anchor_after(search_range.start + match_range.start);
7051 let match_end = buffer_snapshot
7052 .anchor_before(search_range.start + match_range.end);
7053 let match_anchor_range = Anchor::range_in_buffer(
7054 excerpt_id,
7055 buffer_snapshot.remote_id(),
7056 match_start..match_end,
7057 );
7058 (match_anchor_range != query_range).then_some(match_anchor_range)
7059 }),
7060 );
7061 }
7062 match_ranges
7063 });
7064 let match_ranges = match_task.await;
7065 editor
7066 .update_in(cx, |editor, _, cx| {
7067 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7068 if !match_ranges.is_empty() {
7069 editor.highlight_background::<SelectedTextHighlight>(
7070 &match_ranges,
7071 |theme| theme.colors().editor_document_highlight_bracket_background,
7072 cx,
7073 )
7074 }
7075 })
7076 .log_err();
7077 })
7078 }
7079
7080 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7081 struct NewlineFold;
7082 let type_id = std::any::TypeId::of::<NewlineFold>();
7083 if !self.mode.is_single_line() {
7084 return;
7085 }
7086 let snapshot = self.snapshot(window, cx);
7087 if snapshot.buffer_snapshot().max_point().row == 0 {
7088 return;
7089 }
7090 let task = cx.background_spawn(async move {
7091 let new_newlines = snapshot
7092 .buffer_chars_at(0)
7093 .filter_map(|(c, i)| {
7094 if c == '\n' {
7095 Some(
7096 snapshot.buffer_snapshot().anchor_after(i)
7097 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7098 )
7099 } else {
7100 None
7101 }
7102 })
7103 .collect::<Vec<_>>();
7104 let existing_newlines = snapshot
7105 .folds_in_range(0..snapshot.buffer_snapshot().len())
7106 .filter_map(|fold| {
7107 if fold.placeholder.type_tag == Some(type_id) {
7108 Some(fold.range.start..fold.range.end)
7109 } else {
7110 None
7111 }
7112 })
7113 .collect::<Vec<_>>();
7114
7115 (new_newlines, existing_newlines)
7116 });
7117 self.folding_newlines = cx.spawn(async move |this, cx| {
7118 let (new_newlines, existing_newlines) = task.await;
7119 if new_newlines == existing_newlines {
7120 return;
7121 }
7122 let placeholder = FoldPlaceholder {
7123 render: Arc::new(move |_, _, cx| {
7124 div()
7125 .bg(cx.theme().status().hint_background)
7126 .border_b_1()
7127 .size_full()
7128 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7129 .border_color(cx.theme().status().hint)
7130 .child("\\n")
7131 .into_any()
7132 }),
7133 constrain_width: false,
7134 merge_adjacent: false,
7135 type_tag: Some(type_id),
7136 };
7137 let creases = new_newlines
7138 .into_iter()
7139 .map(|range| Crease::simple(range, placeholder.clone()))
7140 .collect();
7141 this.update(cx, |this, cx| {
7142 this.display_map.update(cx, |display_map, cx| {
7143 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7144 display_map.fold(creases, cx);
7145 });
7146 })
7147 .ok();
7148 });
7149 }
7150
7151 fn refresh_selected_text_highlights(
7152 &mut self,
7153 on_buffer_edit: bool,
7154 window: &mut Window,
7155 cx: &mut Context<Editor>,
7156 ) {
7157 let Some((query_text, query_range)) =
7158 self.prepare_highlight_query_from_selection(window, cx)
7159 else {
7160 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7161 self.quick_selection_highlight_task.take();
7162 self.debounced_selection_highlight_task.take();
7163 return;
7164 };
7165 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7166 if on_buffer_edit
7167 || self
7168 .quick_selection_highlight_task
7169 .as_ref()
7170 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7171 {
7172 let multi_buffer_visible_start = self
7173 .scroll_manager
7174 .anchor()
7175 .anchor
7176 .to_point(&multi_buffer_snapshot);
7177 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7178 multi_buffer_visible_start
7179 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7180 Bias::Left,
7181 );
7182 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7183 self.quick_selection_highlight_task = Some((
7184 query_range.clone(),
7185 self.update_selection_occurrence_highlights(
7186 query_text.clone(),
7187 query_range.clone(),
7188 multi_buffer_visible_range,
7189 false,
7190 window,
7191 cx,
7192 ),
7193 ));
7194 }
7195 if on_buffer_edit
7196 || self
7197 .debounced_selection_highlight_task
7198 .as_ref()
7199 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7200 {
7201 let multi_buffer_start = multi_buffer_snapshot
7202 .anchor_before(0)
7203 .to_point(&multi_buffer_snapshot);
7204 let multi_buffer_end = multi_buffer_snapshot
7205 .anchor_after(multi_buffer_snapshot.len())
7206 .to_point(&multi_buffer_snapshot);
7207 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7208 self.debounced_selection_highlight_task = Some((
7209 query_range.clone(),
7210 self.update_selection_occurrence_highlights(
7211 query_text,
7212 query_range,
7213 multi_buffer_full_range,
7214 true,
7215 window,
7216 cx,
7217 ),
7218 ));
7219 }
7220 }
7221
7222 pub fn refresh_edit_prediction(
7223 &mut self,
7224 debounce: bool,
7225 user_requested: bool,
7226 window: &mut Window,
7227 cx: &mut Context<Self>,
7228 ) -> Option<()> {
7229 if DisableAiSettings::get_global(cx).disable_ai {
7230 return None;
7231 }
7232
7233 let provider = self.edit_prediction_provider()?;
7234 let cursor = self.selections.newest_anchor().head();
7235 let (buffer, cursor_buffer_position) =
7236 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7237
7238 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7239 self.discard_edit_prediction(false, cx);
7240 return None;
7241 }
7242
7243 self.update_visible_edit_prediction(window, cx);
7244
7245 if !user_requested
7246 && (!self.should_show_edit_predictions()
7247 || !self.is_focused(window)
7248 || buffer.read(cx).is_empty())
7249 {
7250 self.discard_edit_prediction(false, cx);
7251 return None;
7252 }
7253
7254 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7255 Some(())
7256 }
7257
7258 fn show_edit_predictions_in_menu(&self) -> bool {
7259 match self.edit_prediction_settings {
7260 EditPredictionSettings::Disabled => false,
7261 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7262 }
7263 }
7264
7265 pub fn edit_predictions_enabled(&self) -> bool {
7266 match self.edit_prediction_settings {
7267 EditPredictionSettings::Disabled => false,
7268 EditPredictionSettings::Enabled { .. } => true,
7269 }
7270 }
7271
7272 fn edit_prediction_requires_modifier(&self) -> bool {
7273 match self.edit_prediction_settings {
7274 EditPredictionSettings::Disabled => false,
7275 EditPredictionSettings::Enabled {
7276 preview_requires_modifier,
7277 ..
7278 } => preview_requires_modifier,
7279 }
7280 }
7281
7282 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7283 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7284 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7285 self.discard_edit_prediction(false, cx);
7286 } else {
7287 let selection = self.selections.newest_anchor();
7288 let cursor = selection.head();
7289
7290 if let Some((buffer, cursor_buffer_position)) =
7291 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7292 {
7293 self.edit_prediction_settings =
7294 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7295 }
7296 }
7297 }
7298
7299 fn edit_prediction_settings_at_position(
7300 &self,
7301 buffer: &Entity<Buffer>,
7302 buffer_position: language::Anchor,
7303 cx: &App,
7304 ) -> EditPredictionSettings {
7305 if !self.mode.is_full()
7306 || !self.show_edit_predictions_override.unwrap_or(true)
7307 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7308 {
7309 return EditPredictionSettings::Disabled;
7310 }
7311
7312 let buffer = buffer.read(cx);
7313
7314 let file = buffer.file();
7315
7316 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7317 return EditPredictionSettings::Disabled;
7318 };
7319
7320 let by_provider = matches!(
7321 self.menu_edit_predictions_policy,
7322 MenuEditPredictionsPolicy::ByProvider
7323 );
7324
7325 let show_in_menu = by_provider
7326 && self
7327 .edit_prediction_provider
7328 .as_ref()
7329 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7330
7331 let preview_requires_modifier =
7332 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7333
7334 EditPredictionSettings::Enabled {
7335 show_in_menu,
7336 preview_requires_modifier,
7337 }
7338 }
7339
7340 fn should_show_edit_predictions(&self) -> bool {
7341 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7342 }
7343
7344 pub fn edit_prediction_preview_is_active(&self) -> bool {
7345 matches!(
7346 self.edit_prediction_preview,
7347 EditPredictionPreview::Active { .. }
7348 )
7349 }
7350
7351 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7352 let cursor = self.selections.newest_anchor().head();
7353 if let Some((buffer, cursor_position)) =
7354 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7355 {
7356 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7357 } else {
7358 false
7359 }
7360 }
7361
7362 pub fn supports_minimap(&self, cx: &App) -> bool {
7363 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7364 }
7365
7366 fn edit_predictions_enabled_in_buffer(
7367 &self,
7368 buffer: &Entity<Buffer>,
7369 buffer_position: language::Anchor,
7370 cx: &App,
7371 ) -> bool {
7372 maybe!({
7373 if self.read_only(cx) {
7374 return Some(false);
7375 }
7376 let provider = self.edit_prediction_provider()?;
7377 if !provider.is_enabled(buffer, buffer_position, cx) {
7378 return Some(false);
7379 }
7380 let buffer = buffer.read(cx);
7381 let Some(file) = buffer.file() else {
7382 return Some(true);
7383 };
7384 let settings = all_language_settings(Some(file), cx);
7385 Some(settings.edit_predictions_enabled_for_file(file, cx))
7386 })
7387 .unwrap_or(false)
7388 }
7389
7390 fn cycle_edit_prediction(
7391 &mut self,
7392 direction: Direction,
7393 window: &mut Window,
7394 cx: &mut Context<Self>,
7395 ) -> Option<()> {
7396 let provider = self.edit_prediction_provider()?;
7397 let cursor = self.selections.newest_anchor().head();
7398 let (buffer, cursor_buffer_position) =
7399 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7400 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7401 return None;
7402 }
7403
7404 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7405 self.update_visible_edit_prediction(window, cx);
7406
7407 Some(())
7408 }
7409
7410 pub fn show_edit_prediction(
7411 &mut self,
7412 _: &ShowEditPrediction,
7413 window: &mut Window,
7414 cx: &mut Context<Self>,
7415 ) {
7416 if !self.has_active_edit_prediction() {
7417 self.refresh_edit_prediction(false, true, window, cx);
7418 return;
7419 }
7420
7421 self.update_visible_edit_prediction(window, cx);
7422 }
7423
7424 pub fn display_cursor_names(
7425 &mut self,
7426 _: &DisplayCursorNames,
7427 window: &mut Window,
7428 cx: &mut Context<Self>,
7429 ) {
7430 self.show_cursor_names(window, cx);
7431 }
7432
7433 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7434 self.show_cursor_names = true;
7435 cx.notify();
7436 cx.spawn_in(window, async move |this, cx| {
7437 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7438 this.update(cx, |this, cx| {
7439 this.show_cursor_names = false;
7440 cx.notify()
7441 })
7442 .ok()
7443 })
7444 .detach();
7445 }
7446
7447 pub fn next_edit_prediction(
7448 &mut self,
7449 _: &NextEditPrediction,
7450 window: &mut Window,
7451 cx: &mut Context<Self>,
7452 ) {
7453 if self.has_active_edit_prediction() {
7454 self.cycle_edit_prediction(Direction::Next, window, cx);
7455 } else {
7456 let is_copilot_disabled = self
7457 .refresh_edit_prediction(false, true, window, cx)
7458 .is_none();
7459 if is_copilot_disabled {
7460 cx.propagate();
7461 }
7462 }
7463 }
7464
7465 pub fn previous_edit_prediction(
7466 &mut self,
7467 _: &PreviousEditPrediction,
7468 window: &mut Window,
7469 cx: &mut Context<Self>,
7470 ) {
7471 if self.has_active_edit_prediction() {
7472 self.cycle_edit_prediction(Direction::Prev, window, cx);
7473 } else {
7474 let is_copilot_disabled = self
7475 .refresh_edit_prediction(false, true, window, cx)
7476 .is_none();
7477 if is_copilot_disabled {
7478 cx.propagate();
7479 }
7480 }
7481 }
7482
7483 pub fn accept_edit_prediction(
7484 &mut self,
7485 _: &AcceptEditPrediction,
7486 window: &mut Window,
7487 cx: &mut Context<Self>,
7488 ) {
7489 if self.show_edit_predictions_in_menu() {
7490 self.hide_context_menu(window, cx);
7491 }
7492
7493 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7494 return;
7495 };
7496
7497 match &active_edit_prediction.completion {
7498 EditPrediction::MoveWithin { target, .. } => {
7499 let target = *target;
7500
7501 if let Some(position_map) = &self.last_position_map {
7502 if position_map
7503 .visible_row_range
7504 .contains(&target.to_display_point(&position_map.snapshot).row())
7505 || !self.edit_prediction_requires_modifier()
7506 {
7507 self.unfold_ranges(&[target..target], true, false, cx);
7508 // Note that this is also done in vim's handler of the Tab action.
7509 self.change_selections(
7510 SelectionEffects::scroll(Autoscroll::newest()),
7511 window,
7512 cx,
7513 |selections| {
7514 selections.select_anchor_ranges([target..target]);
7515 },
7516 );
7517 self.clear_row_highlights::<EditPredictionPreview>();
7518
7519 self.edit_prediction_preview
7520 .set_previous_scroll_position(None);
7521 } else {
7522 self.edit_prediction_preview
7523 .set_previous_scroll_position(Some(
7524 position_map.snapshot.scroll_anchor,
7525 ));
7526
7527 self.highlight_rows::<EditPredictionPreview>(
7528 target..target,
7529 cx.theme().colors().editor_highlighted_line_background,
7530 RowHighlightOptions {
7531 autoscroll: true,
7532 ..Default::default()
7533 },
7534 cx,
7535 );
7536 self.request_autoscroll(Autoscroll::fit(), cx);
7537 }
7538 }
7539 }
7540 EditPrediction::MoveOutside { snapshot, target } => {
7541 if let Some(workspace) = self.workspace() {
7542 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7543 .detach_and_log_err(cx);
7544 }
7545 }
7546 EditPrediction::Edit { edits, .. } => {
7547 self.report_edit_prediction_event(
7548 active_edit_prediction.completion_id.clone(),
7549 true,
7550 cx,
7551 );
7552
7553 if let Some(provider) = self.edit_prediction_provider() {
7554 provider.accept(cx);
7555 }
7556
7557 // Store the transaction ID and selections before applying the edit
7558 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7559
7560 let snapshot = self.buffer.read(cx).snapshot(cx);
7561 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7562
7563 self.buffer.update(cx, |buffer, cx| {
7564 buffer.edit(edits.iter().cloned(), None, cx)
7565 });
7566
7567 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7568 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7569 });
7570
7571 let selections = self.selections.disjoint_anchors_arc();
7572 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7573 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7574 if has_new_transaction {
7575 self.selection_history
7576 .insert_transaction(transaction_id_now, selections);
7577 }
7578 }
7579
7580 self.update_visible_edit_prediction(window, cx);
7581 if self.active_edit_prediction.is_none() {
7582 self.refresh_edit_prediction(true, true, window, cx);
7583 }
7584
7585 cx.notify();
7586 }
7587 }
7588
7589 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7590 }
7591
7592 pub fn accept_partial_edit_prediction(
7593 &mut self,
7594 _: &AcceptPartialEditPrediction,
7595 window: &mut Window,
7596 cx: &mut Context<Self>,
7597 ) {
7598 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7599 return;
7600 };
7601 if self.selections.count() != 1 {
7602 return;
7603 }
7604
7605 match &active_edit_prediction.completion {
7606 EditPrediction::MoveWithin { target, .. } => {
7607 let target = *target;
7608 self.change_selections(
7609 SelectionEffects::scroll(Autoscroll::newest()),
7610 window,
7611 cx,
7612 |selections| {
7613 selections.select_anchor_ranges([target..target]);
7614 },
7615 );
7616 }
7617 EditPrediction::MoveOutside { snapshot, target } => {
7618 if let Some(workspace) = self.workspace() {
7619 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7620 .detach_and_log_err(cx);
7621 }
7622 }
7623 EditPrediction::Edit { edits, .. } => {
7624 self.report_edit_prediction_event(
7625 active_edit_prediction.completion_id.clone(),
7626 true,
7627 cx,
7628 );
7629
7630 // Find an insertion that starts at the cursor position.
7631 let snapshot = self.buffer.read(cx).snapshot(cx);
7632 let cursor_offset = self
7633 .selections
7634 .newest::<usize>(&self.display_snapshot(cx))
7635 .head();
7636 let insertion = edits.iter().find_map(|(range, text)| {
7637 let range = range.to_offset(&snapshot);
7638 if range.is_empty() && range.start == cursor_offset {
7639 Some(text)
7640 } else {
7641 None
7642 }
7643 });
7644
7645 if let Some(text) = insertion {
7646 let mut partial_completion = text
7647 .chars()
7648 .by_ref()
7649 .take_while(|c| c.is_alphabetic())
7650 .collect::<String>();
7651 if partial_completion.is_empty() {
7652 partial_completion = text
7653 .chars()
7654 .by_ref()
7655 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7656 .collect::<String>();
7657 }
7658
7659 cx.emit(EditorEvent::InputHandled {
7660 utf16_range_to_replace: None,
7661 text: partial_completion.clone().into(),
7662 });
7663
7664 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7665
7666 self.refresh_edit_prediction(true, true, window, cx);
7667 cx.notify();
7668 } else {
7669 self.accept_edit_prediction(&Default::default(), window, cx);
7670 }
7671 }
7672 }
7673 }
7674
7675 fn discard_edit_prediction(
7676 &mut self,
7677 should_report_edit_prediction_event: bool,
7678 cx: &mut Context<Self>,
7679 ) -> bool {
7680 if should_report_edit_prediction_event {
7681 let completion_id = self
7682 .active_edit_prediction
7683 .as_ref()
7684 .and_then(|active_completion| active_completion.completion_id.clone());
7685
7686 self.report_edit_prediction_event(completion_id, false, cx);
7687 }
7688
7689 if let Some(provider) = self.edit_prediction_provider() {
7690 provider.discard(cx);
7691 }
7692
7693 self.take_active_edit_prediction(cx)
7694 }
7695
7696 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7697 let Some(provider) = self.edit_prediction_provider() else {
7698 return;
7699 };
7700
7701 let Some((_, buffer, _)) = self
7702 .buffer
7703 .read(cx)
7704 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7705 else {
7706 return;
7707 };
7708
7709 let extension = buffer
7710 .read(cx)
7711 .file()
7712 .and_then(|file| Some(file.path().extension()?.to_string()));
7713
7714 let event_type = match accepted {
7715 true => "Edit Prediction Accepted",
7716 false => "Edit Prediction Discarded",
7717 };
7718 telemetry::event!(
7719 event_type,
7720 provider = provider.name(),
7721 prediction_id = id,
7722 suggestion_accepted = accepted,
7723 file_extension = extension,
7724 );
7725 }
7726
7727 fn open_editor_at_anchor(
7728 snapshot: &language::BufferSnapshot,
7729 target: language::Anchor,
7730 workspace: &Entity<Workspace>,
7731 window: &mut Window,
7732 cx: &mut App,
7733 ) -> Task<Result<()>> {
7734 workspace.update(cx, |workspace, cx| {
7735 let path = snapshot.file().map(|file| file.full_path(cx));
7736 let Some(path) =
7737 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7738 else {
7739 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7740 };
7741 let target = text::ToPoint::to_point(&target, snapshot);
7742 let item = workspace.open_path(path, None, true, window, cx);
7743 window.spawn(cx, async move |cx| {
7744 let Some(editor) = item.await?.downcast::<Editor>() else {
7745 return Ok(());
7746 };
7747 editor
7748 .update_in(cx, |editor, window, cx| {
7749 editor.go_to_singleton_buffer_point(target, window, cx);
7750 })
7751 .ok();
7752 anyhow::Ok(())
7753 })
7754 })
7755 }
7756
7757 pub fn has_active_edit_prediction(&self) -> bool {
7758 self.active_edit_prediction.is_some()
7759 }
7760
7761 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7762 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7763 return false;
7764 };
7765
7766 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7767 self.clear_highlights::<EditPredictionHighlight>(cx);
7768 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7769 true
7770 }
7771
7772 /// Returns true when we're displaying the edit prediction popover below the cursor
7773 /// like we are not previewing and the LSP autocomplete menu is visible
7774 /// or we are in `when_holding_modifier` mode.
7775 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7776 if self.edit_prediction_preview_is_active()
7777 || !self.show_edit_predictions_in_menu()
7778 || !self.edit_predictions_enabled()
7779 {
7780 return false;
7781 }
7782
7783 if self.has_visible_completions_menu() {
7784 return true;
7785 }
7786
7787 has_completion && self.edit_prediction_requires_modifier()
7788 }
7789
7790 fn handle_modifiers_changed(
7791 &mut self,
7792 modifiers: Modifiers,
7793 position_map: &PositionMap,
7794 window: &mut Window,
7795 cx: &mut Context<Self>,
7796 ) {
7797 if self.show_edit_predictions_in_menu() {
7798 self.update_edit_prediction_preview(&modifiers, window, cx);
7799 }
7800
7801 self.update_selection_mode(&modifiers, position_map, window, cx);
7802
7803 let mouse_position = window.mouse_position();
7804 if !position_map.text_hitbox.is_hovered(window) {
7805 return;
7806 }
7807
7808 self.update_hovered_link(
7809 position_map.point_for_position(mouse_position),
7810 &position_map.snapshot,
7811 modifiers,
7812 window,
7813 cx,
7814 )
7815 }
7816
7817 fn multi_cursor_modifier(invert: bool, modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7818 let multi_cursor_setting = EditorSettings::get_global(cx).multi_cursor_modifier;
7819 if invert {
7820 match multi_cursor_setting {
7821 MultiCursorModifier::Alt => modifiers.alt,
7822 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7823 }
7824 } else {
7825 match multi_cursor_setting {
7826 MultiCursorModifier::Alt => modifiers.secondary(),
7827 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7828 }
7829 }
7830 }
7831
7832 fn columnar_selection_mode(
7833 modifiers: &Modifiers,
7834 cx: &mut Context<Self>,
7835 ) -> Option<ColumnarMode> {
7836 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7837 if Self::multi_cursor_modifier(false, modifiers, cx) {
7838 Some(ColumnarMode::FromMouse)
7839 } else if Self::multi_cursor_modifier(true, modifiers, cx) {
7840 Some(ColumnarMode::FromSelection)
7841 } else {
7842 None
7843 }
7844 } else {
7845 None
7846 }
7847 }
7848
7849 fn update_selection_mode(
7850 &mut self,
7851 modifiers: &Modifiers,
7852 position_map: &PositionMap,
7853 window: &mut Window,
7854 cx: &mut Context<Self>,
7855 ) {
7856 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7857 return;
7858 };
7859 if self.selections.pending_anchor().is_none() {
7860 return;
7861 }
7862
7863 let mouse_position = window.mouse_position();
7864 let point_for_position = position_map.point_for_position(mouse_position);
7865 let position = point_for_position.previous_valid;
7866
7867 self.select(
7868 SelectPhase::BeginColumnar {
7869 position,
7870 reset: false,
7871 mode,
7872 goal_column: point_for_position.exact_unclipped.column(),
7873 },
7874 window,
7875 cx,
7876 );
7877 }
7878
7879 fn update_edit_prediction_preview(
7880 &mut self,
7881 modifiers: &Modifiers,
7882 window: &mut Window,
7883 cx: &mut Context<Self>,
7884 ) {
7885 let mut modifiers_held = false;
7886 if let Some(accept_keystroke) = self
7887 .accept_edit_prediction_keybind(false, window, cx)
7888 .keystroke()
7889 {
7890 modifiers_held = modifiers_held
7891 || (accept_keystroke.modifiers() == modifiers
7892 && accept_keystroke.modifiers().modified());
7893 };
7894 if let Some(accept_partial_keystroke) = self
7895 .accept_edit_prediction_keybind(true, window, cx)
7896 .keystroke()
7897 {
7898 modifiers_held = modifiers_held
7899 || (accept_partial_keystroke.modifiers() == modifiers
7900 && accept_partial_keystroke.modifiers().modified());
7901 }
7902
7903 if modifiers_held {
7904 if matches!(
7905 self.edit_prediction_preview,
7906 EditPredictionPreview::Inactive { .. }
7907 ) {
7908 self.edit_prediction_preview = EditPredictionPreview::Active {
7909 previous_scroll_position: None,
7910 since: Instant::now(),
7911 };
7912
7913 self.update_visible_edit_prediction(window, cx);
7914 cx.notify();
7915 }
7916 } else if let EditPredictionPreview::Active {
7917 previous_scroll_position,
7918 since,
7919 } = self.edit_prediction_preview
7920 {
7921 if let (Some(previous_scroll_position), Some(position_map)) =
7922 (previous_scroll_position, self.last_position_map.as_ref())
7923 {
7924 self.set_scroll_position(
7925 previous_scroll_position
7926 .scroll_position(&position_map.snapshot.display_snapshot),
7927 window,
7928 cx,
7929 );
7930 }
7931
7932 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7933 released_too_fast: since.elapsed() < Duration::from_millis(200),
7934 };
7935 self.clear_row_highlights::<EditPredictionPreview>();
7936 self.update_visible_edit_prediction(window, cx);
7937 cx.notify();
7938 }
7939 }
7940
7941 fn update_visible_edit_prediction(
7942 &mut self,
7943 _window: &mut Window,
7944 cx: &mut Context<Self>,
7945 ) -> Option<()> {
7946 if DisableAiSettings::get_global(cx).disable_ai {
7947 return None;
7948 }
7949
7950 if self.ime_transaction.is_some() {
7951 self.discard_edit_prediction(false, cx);
7952 return None;
7953 }
7954
7955 let selection = self.selections.newest_anchor();
7956 let cursor = selection.head();
7957 let multibuffer = self.buffer.read(cx).snapshot(cx);
7958 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7959 let excerpt_id = cursor.excerpt_id;
7960
7961 let show_in_menu = self.show_edit_predictions_in_menu();
7962 let completions_menu_has_precedence = !show_in_menu
7963 && (self.context_menu.borrow().is_some()
7964 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7965
7966 if completions_menu_has_precedence
7967 || !offset_selection.is_empty()
7968 || self
7969 .active_edit_prediction
7970 .as_ref()
7971 .is_some_and(|completion| {
7972 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7973 return false;
7974 };
7975 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7976 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7977 !invalidation_range.contains(&offset_selection.head())
7978 })
7979 {
7980 self.discard_edit_prediction(false, cx);
7981 return None;
7982 }
7983
7984 self.take_active_edit_prediction(cx);
7985 let Some(provider) = self.edit_prediction_provider() else {
7986 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7987 return None;
7988 };
7989
7990 let (buffer, cursor_buffer_position) =
7991 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7992
7993 self.edit_prediction_settings =
7994 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7995
7996 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7997
7998 if self.edit_prediction_indent_conflict {
7999 let cursor_point = cursor.to_point(&multibuffer);
8000
8001 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
8002
8003 if let Some((_, indent)) = indents.iter().next()
8004 && indent.len == cursor_point.column
8005 {
8006 self.edit_prediction_indent_conflict = false;
8007 }
8008 }
8009
8010 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8011
8012 let (completion_id, edits, edit_preview) = match edit_prediction {
8013 edit_prediction::EditPrediction::Local {
8014 id,
8015 edits,
8016 edit_preview,
8017 } => (id, edits, edit_preview),
8018 edit_prediction::EditPrediction::Jump {
8019 id,
8020 snapshot,
8021 target,
8022 } => {
8023 self.stale_edit_prediction_in_menu = None;
8024 self.active_edit_prediction = Some(EditPredictionState {
8025 inlay_ids: vec![],
8026 completion: EditPrediction::MoveOutside { snapshot, target },
8027 completion_id: id,
8028 invalidation_range: None,
8029 });
8030 cx.notify();
8031 return Some(());
8032 }
8033 };
8034
8035 let edits = edits
8036 .into_iter()
8037 .flat_map(|(range, new_text)| {
8038 Some((
8039 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8040 new_text,
8041 ))
8042 })
8043 .collect::<Vec<_>>();
8044 if edits.is_empty() {
8045 return None;
8046 }
8047
8048 let first_edit_start = edits.first().unwrap().0.start;
8049 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8050 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8051
8052 let last_edit_end = edits.last().unwrap().0.end;
8053 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8054 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8055
8056 let cursor_row = cursor.to_point(&multibuffer).row;
8057
8058 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8059
8060 let mut inlay_ids = Vec::new();
8061 let invalidation_row_range;
8062 let move_invalidation_row_range = if cursor_row < edit_start_row {
8063 Some(cursor_row..edit_end_row)
8064 } else if cursor_row > edit_end_row {
8065 Some(edit_start_row..cursor_row)
8066 } else {
8067 None
8068 };
8069 let supports_jump = self
8070 .edit_prediction_provider
8071 .as_ref()
8072 .map(|provider| provider.provider.supports_jump_to_edit())
8073 .unwrap_or(true);
8074
8075 let is_move = supports_jump
8076 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8077 let completion = if is_move {
8078 invalidation_row_range =
8079 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8080 let target = first_edit_start;
8081 EditPrediction::MoveWithin { target, snapshot }
8082 } else {
8083 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8084 && !self.edit_predictions_hidden_for_vim_mode;
8085
8086 if show_completions_in_buffer {
8087 if edits
8088 .iter()
8089 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8090 {
8091 let mut inlays = Vec::new();
8092 for (range, new_text) in &edits {
8093 let inlay = Inlay::edit_prediction(
8094 post_inc(&mut self.next_inlay_id),
8095 range.start,
8096 new_text.as_str(),
8097 );
8098 inlay_ids.push(inlay.id);
8099 inlays.push(inlay);
8100 }
8101
8102 self.splice_inlays(&[], inlays, cx);
8103 } else {
8104 let background_color = cx.theme().status().deleted_background;
8105 self.highlight_text::<EditPredictionHighlight>(
8106 edits.iter().map(|(range, _)| range.clone()).collect(),
8107 HighlightStyle {
8108 background_color: Some(background_color),
8109 ..Default::default()
8110 },
8111 cx,
8112 );
8113 }
8114 }
8115
8116 invalidation_row_range = edit_start_row..edit_end_row;
8117
8118 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8119 if provider.show_tab_accept_marker() {
8120 EditDisplayMode::TabAccept
8121 } else {
8122 EditDisplayMode::Inline
8123 }
8124 } else {
8125 EditDisplayMode::DiffPopover
8126 };
8127
8128 EditPrediction::Edit {
8129 edits,
8130 edit_preview,
8131 display_mode,
8132 snapshot,
8133 }
8134 };
8135
8136 let invalidation_range = multibuffer
8137 .anchor_before(Point::new(invalidation_row_range.start, 0))
8138 ..multibuffer.anchor_after(Point::new(
8139 invalidation_row_range.end,
8140 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8141 ));
8142
8143 self.stale_edit_prediction_in_menu = None;
8144 self.active_edit_prediction = Some(EditPredictionState {
8145 inlay_ids,
8146 completion,
8147 completion_id,
8148 invalidation_range: Some(invalidation_range),
8149 });
8150
8151 cx.notify();
8152
8153 Some(())
8154 }
8155
8156 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8157 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8158 }
8159
8160 fn clear_tasks(&mut self) {
8161 self.tasks.clear()
8162 }
8163
8164 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8165 if self.tasks.insert(key, value).is_some() {
8166 // This case should hopefully be rare, but just in case...
8167 log::error!(
8168 "multiple different run targets found on a single line, only the last target will be rendered"
8169 )
8170 }
8171 }
8172
8173 /// Get all display points of breakpoints that will be rendered within editor
8174 ///
8175 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8176 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8177 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8178 fn active_breakpoints(
8179 &self,
8180 range: Range<DisplayRow>,
8181 window: &mut Window,
8182 cx: &mut Context<Self>,
8183 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8184 let mut breakpoint_display_points = HashMap::default();
8185
8186 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8187 return breakpoint_display_points;
8188 };
8189
8190 let snapshot = self.snapshot(window, cx);
8191
8192 let multi_buffer_snapshot = snapshot.display_snapshot.buffer_snapshot();
8193 let Some(project) = self.project() else {
8194 return breakpoint_display_points;
8195 };
8196
8197 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8198 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8199
8200 for (buffer_snapshot, range, excerpt_id) in
8201 multi_buffer_snapshot.range_to_buffer_ranges(range)
8202 {
8203 let Some(buffer) = project
8204 .read(cx)
8205 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8206 else {
8207 continue;
8208 };
8209 let breakpoints = breakpoint_store.read(cx).breakpoints(
8210 &buffer,
8211 Some(
8212 buffer_snapshot.anchor_before(range.start)
8213 ..buffer_snapshot.anchor_after(range.end),
8214 ),
8215 buffer_snapshot,
8216 cx,
8217 );
8218 for (breakpoint, state) in breakpoints {
8219 let multi_buffer_anchor =
8220 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8221 let position = multi_buffer_anchor
8222 .to_point(multi_buffer_snapshot)
8223 .to_display_point(&snapshot);
8224
8225 breakpoint_display_points.insert(
8226 position.row(),
8227 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8228 );
8229 }
8230 }
8231
8232 breakpoint_display_points
8233 }
8234
8235 fn breakpoint_context_menu(
8236 &self,
8237 anchor: Anchor,
8238 window: &mut Window,
8239 cx: &mut Context<Self>,
8240 ) -> Entity<ui::ContextMenu> {
8241 let weak_editor = cx.weak_entity();
8242 let focus_handle = self.focus_handle(cx);
8243
8244 let row = self
8245 .buffer
8246 .read(cx)
8247 .snapshot(cx)
8248 .summary_for_anchor::<Point>(&anchor)
8249 .row;
8250
8251 let breakpoint = self
8252 .breakpoint_at_row(row, window, cx)
8253 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8254
8255 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8256 "Edit Log Breakpoint"
8257 } else {
8258 "Set Log Breakpoint"
8259 };
8260
8261 let condition_breakpoint_msg = if breakpoint
8262 .as_ref()
8263 .is_some_and(|bp| bp.1.condition.is_some())
8264 {
8265 "Edit Condition Breakpoint"
8266 } else {
8267 "Set Condition Breakpoint"
8268 };
8269
8270 let hit_condition_breakpoint_msg = if breakpoint
8271 .as_ref()
8272 .is_some_and(|bp| bp.1.hit_condition.is_some())
8273 {
8274 "Edit Hit Condition Breakpoint"
8275 } else {
8276 "Set Hit Condition Breakpoint"
8277 };
8278
8279 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8280 "Unset Breakpoint"
8281 } else {
8282 "Set Breakpoint"
8283 };
8284
8285 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8286
8287 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8288 BreakpointState::Enabled => Some("Disable"),
8289 BreakpointState::Disabled => Some("Enable"),
8290 });
8291
8292 let (anchor, breakpoint) =
8293 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8294
8295 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8296 menu.on_blur_subscription(Subscription::new(|| {}))
8297 .context(focus_handle)
8298 .when(run_to_cursor, |this| {
8299 let weak_editor = weak_editor.clone();
8300 this.entry("Run to cursor", None, move |window, cx| {
8301 weak_editor
8302 .update(cx, |editor, cx| {
8303 editor.change_selections(
8304 SelectionEffects::no_scroll(),
8305 window,
8306 cx,
8307 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8308 );
8309 })
8310 .ok();
8311
8312 window.dispatch_action(Box::new(RunToCursor), cx);
8313 })
8314 .separator()
8315 })
8316 .when_some(toggle_state_msg, |this, msg| {
8317 this.entry(msg, None, {
8318 let weak_editor = weak_editor.clone();
8319 let breakpoint = breakpoint.clone();
8320 move |_window, cx| {
8321 weak_editor
8322 .update(cx, |this, cx| {
8323 this.edit_breakpoint_at_anchor(
8324 anchor,
8325 breakpoint.as_ref().clone(),
8326 BreakpointEditAction::InvertState,
8327 cx,
8328 );
8329 })
8330 .log_err();
8331 }
8332 })
8333 })
8334 .entry(set_breakpoint_msg, None, {
8335 let weak_editor = weak_editor.clone();
8336 let breakpoint = breakpoint.clone();
8337 move |_window, cx| {
8338 weak_editor
8339 .update(cx, |this, cx| {
8340 this.edit_breakpoint_at_anchor(
8341 anchor,
8342 breakpoint.as_ref().clone(),
8343 BreakpointEditAction::Toggle,
8344 cx,
8345 );
8346 })
8347 .log_err();
8348 }
8349 })
8350 .entry(log_breakpoint_msg, None, {
8351 let breakpoint = breakpoint.clone();
8352 let weak_editor = weak_editor.clone();
8353 move |window, cx| {
8354 weak_editor
8355 .update(cx, |this, cx| {
8356 this.add_edit_breakpoint_block(
8357 anchor,
8358 breakpoint.as_ref(),
8359 BreakpointPromptEditAction::Log,
8360 window,
8361 cx,
8362 );
8363 })
8364 .log_err();
8365 }
8366 })
8367 .entry(condition_breakpoint_msg, None, {
8368 let breakpoint = breakpoint.clone();
8369 let weak_editor = weak_editor.clone();
8370 move |window, cx| {
8371 weak_editor
8372 .update(cx, |this, cx| {
8373 this.add_edit_breakpoint_block(
8374 anchor,
8375 breakpoint.as_ref(),
8376 BreakpointPromptEditAction::Condition,
8377 window,
8378 cx,
8379 );
8380 })
8381 .log_err();
8382 }
8383 })
8384 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8385 weak_editor
8386 .update(cx, |this, cx| {
8387 this.add_edit_breakpoint_block(
8388 anchor,
8389 breakpoint.as_ref(),
8390 BreakpointPromptEditAction::HitCondition,
8391 window,
8392 cx,
8393 );
8394 })
8395 .log_err();
8396 })
8397 })
8398 }
8399
8400 fn render_breakpoint(
8401 &self,
8402 position: Anchor,
8403 row: DisplayRow,
8404 breakpoint: &Breakpoint,
8405 state: Option<BreakpointSessionState>,
8406 cx: &mut Context<Self>,
8407 ) -> IconButton {
8408 let is_rejected = state.is_some_and(|s| !s.verified);
8409 // Is it a breakpoint that shows up when hovering over gutter?
8410 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8411 (false, false),
8412 |PhantomBreakpointIndicator {
8413 is_active,
8414 display_row,
8415 collides_with_existing_breakpoint,
8416 }| {
8417 (
8418 is_active && display_row == row,
8419 collides_with_existing_breakpoint,
8420 )
8421 },
8422 );
8423
8424 let (color, icon) = {
8425 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8426 (false, false) => ui::IconName::DebugBreakpoint,
8427 (true, false) => ui::IconName::DebugLogBreakpoint,
8428 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8429 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8430 };
8431
8432 let color = if is_phantom {
8433 Color::Hint
8434 } else if is_rejected {
8435 Color::Disabled
8436 } else {
8437 Color::Debugger
8438 };
8439
8440 (color, icon)
8441 };
8442
8443 let breakpoint = Arc::from(breakpoint.clone());
8444
8445 let alt_as_text = gpui::Keystroke {
8446 modifiers: Modifiers::secondary_key(),
8447 ..Default::default()
8448 };
8449 let primary_action_text = if breakpoint.is_disabled() {
8450 "Enable breakpoint"
8451 } else if is_phantom && !collides_with_existing {
8452 "Set breakpoint"
8453 } else {
8454 "Unset breakpoint"
8455 };
8456 let focus_handle = self.focus_handle.clone();
8457
8458 let meta = if is_rejected {
8459 SharedString::from("No executable code is associated with this line.")
8460 } else if collides_with_existing && !breakpoint.is_disabled() {
8461 SharedString::from(format!(
8462 "{alt_as_text}-click to disable,\nright-click for more options."
8463 ))
8464 } else {
8465 SharedString::from("Right-click for more options.")
8466 };
8467 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8468 .icon_size(IconSize::XSmall)
8469 .size(ui::ButtonSize::None)
8470 .when(is_rejected, |this| {
8471 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8472 })
8473 .icon_color(color)
8474 .style(ButtonStyle::Transparent)
8475 .on_click(cx.listener({
8476 move |editor, event: &ClickEvent, window, cx| {
8477 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8478 BreakpointEditAction::InvertState
8479 } else {
8480 BreakpointEditAction::Toggle
8481 };
8482
8483 window.focus(&editor.focus_handle(cx));
8484 editor.edit_breakpoint_at_anchor(
8485 position,
8486 breakpoint.as_ref().clone(),
8487 edit_action,
8488 cx,
8489 );
8490 }
8491 }))
8492 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8493 editor.set_breakpoint_context_menu(
8494 row,
8495 Some(position),
8496 event.position(),
8497 window,
8498 cx,
8499 );
8500 }))
8501 .tooltip(move |window, cx| {
8502 Tooltip::with_meta_in(
8503 primary_action_text,
8504 Some(&ToggleBreakpoint),
8505 meta.clone(),
8506 &focus_handle,
8507 window,
8508 cx,
8509 )
8510 })
8511 }
8512
8513 fn build_tasks_context(
8514 project: &Entity<Project>,
8515 buffer: &Entity<Buffer>,
8516 buffer_row: u32,
8517 tasks: &Arc<RunnableTasks>,
8518 cx: &mut Context<Self>,
8519 ) -> Task<Option<task::TaskContext>> {
8520 let position = Point::new(buffer_row, tasks.column);
8521 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8522 let location = Location {
8523 buffer: buffer.clone(),
8524 range: range_start..range_start,
8525 };
8526 // Fill in the environmental variables from the tree-sitter captures
8527 let mut captured_task_variables = TaskVariables::default();
8528 for (capture_name, value) in tasks.extra_variables.clone() {
8529 captured_task_variables.insert(
8530 task::VariableName::Custom(capture_name.into()),
8531 value.clone(),
8532 );
8533 }
8534 project.update(cx, |project, cx| {
8535 project.task_store().update(cx, |task_store, cx| {
8536 task_store.task_context_for_location(captured_task_variables, location, cx)
8537 })
8538 })
8539 }
8540
8541 pub fn spawn_nearest_task(
8542 &mut self,
8543 action: &SpawnNearestTask,
8544 window: &mut Window,
8545 cx: &mut Context<Self>,
8546 ) {
8547 let Some((workspace, _)) = self.workspace.clone() else {
8548 return;
8549 };
8550 let Some(project) = self.project.clone() else {
8551 return;
8552 };
8553
8554 // Try to find a closest, enclosing node using tree-sitter that has a task
8555 let Some((buffer, buffer_row, tasks)) = self
8556 .find_enclosing_node_task(cx)
8557 // Or find the task that's closest in row-distance.
8558 .or_else(|| self.find_closest_task(cx))
8559 else {
8560 return;
8561 };
8562
8563 let reveal_strategy = action.reveal;
8564 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8565 cx.spawn_in(window, async move |_, cx| {
8566 let context = task_context.await?;
8567 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8568
8569 let resolved = &mut resolved_task.resolved;
8570 resolved.reveal = reveal_strategy;
8571
8572 workspace
8573 .update_in(cx, |workspace, window, cx| {
8574 workspace.schedule_resolved_task(
8575 task_source_kind,
8576 resolved_task,
8577 false,
8578 window,
8579 cx,
8580 );
8581 })
8582 .ok()
8583 })
8584 .detach();
8585 }
8586
8587 fn find_closest_task(
8588 &mut self,
8589 cx: &mut Context<Self>,
8590 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8591 let cursor_row = self
8592 .selections
8593 .newest_adjusted(&self.display_snapshot(cx))
8594 .head()
8595 .row;
8596
8597 let ((buffer_id, row), tasks) = self
8598 .tasks
8599 .iter()
8600 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8601
8602 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8603 let tasks = Arc::new(tasks.to_owned());
8604 Some((buffer, *row, tasks))
8605 }
8606
8607 fn find_enclosing_node_task(
8608 &mut self,
8609 cx: &mut Context<Self>,
8610 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8611 let snapshot = self.buffer.read(cx).snapshot(cx);
8612 let offset = self
8613 .selections
8614 .newest::<usize>(&self.display_snapshot(cx))
8615 .head();
8616 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8617 let buffer_id = excerpt.buffer().remote_id();
8618
8619 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8620 let mut cursor = layer.node().walk();
8621
8622 while cursor.goto_first_child_for_byte(offset).is_some() {
8623 if cursor.node().end_byte() == offset {
8624 cursor.goto_next_sibling();
8625 }
8626 }
8627
8628 // Ascend to the smallest ancestor that contains the range and has a task.
8629 loop {
8630 let node = cursor.node();
8631 let node_range = node.byte_range();
8632 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8633
8634 // Check if this node contains our offset
8635 if node_range.start <= offset && node_range.end >= offset {
8636 // If it contains offset, check for task
8637 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8638 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8639 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8640 }
8641 }
8642
8643 if !cursor.goto_parent() {
8644 break;
8645 }
8646 }
8647 None
8648 }
8649
8650 fn render_run_indicator(
8651 &self,
8652 _style: &EditorStyle,
8653 is_active: bool,
8654 row: DisplayRow,
8655 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8656 cx: &mut Context<Self>,
8657 ) -> IconButton {
8658 let color = Color::Muted;
8659 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8660
8661 IconButton::new(
8662 ("run_indicator", row.0 as usize),
8663 ui::IconName::PlayOutlined,
8664 )
8665 .shape(ui::IconButtonShape::Square)
8666 .icon_size(IconSize::XSmall)
8667 .icon_color(color)
8668 .toggle_state(is_active)
8669 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8670 let quick_launch = match e {
8671 ClickEvent::Keyboard(_) => true,
8672 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8673 };
8674
8675 window.focus(&editor.focus_handle(cx));
8676 editor.toggle_code_actions(
8677 &ToggleCodeActions {
8678 deployed_from: Some(CodeActionSource::RunMenu(row)),
8679 quick_launch,
8680 },
8681 window,
8682 cx,
8683 );
8684 }))
8685 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8686 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8687 }))
8688 }
8689
8690 pub fn context_menu_visible(&self) -> bool {
8691 !self.edit_prediction_preview_is_active()
8692 && self
8693 .context_menu
8694 .borrow()
8695 .as_ref()
8696 .is_some_and(|menu| menu.visible())
8697 }
8698
8699 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8700 self.context_menu
8701 .borrow()
8702 .as_ref()
8703 .map(|menu| menu.origin())
8704 }
8705
8706 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8707 self.context_menu_options = Some(options);
8708 }
8709
8710 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8711 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8712
8713 fn render_edit_prediction_popover(
8714 &mut self,
8715 text_bounds: &Bounds<Pixels>,
8716 content_origin: gpui::Point<Pixels>,
8717 right_margin: Pixels,
8718 editor_snapshot: &EditorSnapshot,
8719 visible_row_range: Range<DisplayRow>,
8720 scroll_top: ScrollOffset,
8721 scroll_bottom: ScrollOffset,
8722 line_layouts: &[LineWithInvisibles],
8723 line_height: Pixels,
8724 scroll_position: gpui::Point<ScrollOffset>,
8725 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8726 newest_selection_head: Option<DisplayPoint>,
8727 editor_width: Pixels,
8728 style: &EditorStyle,
8729 window: &mut Window,
8730 cx: &mut App,
8731 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8732 if self.mode().is_minimap() {
8733 return None;
8734 }
8735 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8736
8737 if self.edit_prediction_visible_in_cursor_popover(true) {
8738 return None;
8739 }
8740
8741 match &active_edit_prediction.completion {
8742 EditPrediction::MoveWithin { target, .. } => {
8743 let target_display_point = target.to_display_point(editor_snapshot);
8744
8745 if self.edit_prediction_requires_modifier() {
8746 if !self.edit_prediction_preview_is_active() {
8747 return None;
8748 }
8749
8750 self.render_edit_prediction_modifier_jump_popover(
8751 text_bounds,
8752 content_origin,
8753 visible_row_range,
8754 line_layouts,
8755 line_height,
8756 scroll_pixel_position,
8757 newest_selection_head,
8758 target_display_point,
8759 window,
8760 cx,
8761 )
8762 } else {
8763 self.render_edit_prediction_eager_jump_popover(
8764 text_bounds,
8765 content_origin,
8766 editor_snapshot,
8767 visible_row_range,
8768 scroll_top,
8769 scroll_bottom,
8770 line_height,
8771 scroll_pixel_position,
8772 target_display_point,
8773 editor_width,
8774 window,
8775 cx,
8776 )
8777 }
8778 }
8779 EditPrediction::Edit {
8780 display_mode: EditDisplayMode::Inline,
8781 ..
8782 } => None,
8783 EditPrediction::Edit {
8784 display_mode: EditDisplayMode::TabAccept,
8785 edits,
8786 ..
8787 } => {
8788 let range = &edits.first()?.0;
8789 let target_display_point = range.end.to_display_point(editor_snapshot);
8790
8791 self.render_edit_prediction_end_of_line_popover(
8792 "Accept",
8793 editor_snapshot,
8794 visible_row_range,
8795 target_display_point,
8796 line_height,
8797 scroll_pixel_position,
8798 content_origin,
8799 editor_width,
8800 window,
8801 cx,
8802 )
8803 }
8804 EditPrediction::Edit {
8805 edits,
8806 edit_preview,
8807 display_mode: EditDisplayMode::DiffPopover,
8808 snapshot,
8809 } => self.render_edit_prediction_diff_popover(
8810 text_bounds,
8811 content_origin,
8812 right_margin,
8813 editor_snapshot,
8814 visible_row_range,
8815 line_layouts,
8816 line_height,
8817 scroll_position,
8818 scroll_pixel_position,
8819 newest_selection_head,
8820 editor_width,
8821 style,
8822 edits,
8823 edit_preview,
8824 snapshot,
8825 window,
8826 cx,
8827 ),
8828 EditPrediction::MoveOutside { snapshot, .. } => {
8829 let file_name = snapshot
8830 .file()
8831 .map(|file| file.file_name(cx))
8832 .unwrap_or("untitled");
8833 let mut element = self
8834 .render_edit_prediction_line_popover(
8835 format!("Jump to {file_name}"),
8836 Some(IconName::ZedPredict),
8837 window,
8838 cx,
8839 )
8840 .into_any();
8841
8842 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8843 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8844 let origin_y = text_bounds.size.height - size.height - px(30.);
8845 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8846 element.prepaint_at(origin, window, cx);
8847
8848 Some((element, origin))
8849 }
8850 }
8851 }
8852
8853 fn render_edit_prediction_modifier_jump_popover(
8854 &mut self,
8855 text_bounds: &Bounds<Pixels>,
8856 content_origin: gpui::Point<Pixels>,
8857 visible_row_range: Range<DisplayRow>,
8858 line_layouts: &[LineWithInvisibles],
8859 line_height: Pixels,
8860 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8861 newest_selection_head: Option<DisplayPoint>,
8862 target_display_point: DisplayPoint,
8863 window: &mut Window,
8864 cx: &mut App,
8865 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8866 let scrolled_content_origin =
8867 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8868
8869 const SCROLL_PADDING_Y: Pixels = px(12.);
8870
8871 if target_display_point.row() < visible_row_range.start {
8872 return self.render_edit_prediction_scroll_popover(
8873 |_| SCROLL_PADDING_Y,
8874 IconName::ArrowUp,
8875 visible_row_range,
8876 line_layouts,
8877 newest_selection_head,
8878 scrolled_content_origin,
8879 window,
8880 cx,
8881 );
8882 } else if target_display_point.row() >= visible_row_range.end {
8883 return self.render_edit_prediction_scroll_popover(
8884 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8885 IconName::ArrowDown,
8886 visible_row_range,
8887 line_layouts,
8888 newest_selection_head,
8889 scrolled_content_origin,
8890 window,
8891 cx,
8892 );
8893 }
8894
8895 const POLE_WIDTH: Pixels = px(2.);
8896
8897 let line_layout =
8898 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8899 let target_column = target_display_point.column() as usize;
8900
8901 let target_x = line_layout.x_for_index(target_column);
8902 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8903 - scroll_pixel_position.y;
8904
8905 let flag_on_right = target_x < text_bounds.size.width / 2.;
8906
8907 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8908 border_color.l += 0.001;
8909
8910 let mut element = v_flex()
8911 .items_end()
8912 .when(flag_on_right, |el| el.items_start())
8913 .child(if flag_on_right {
8914 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8915 .rounded_bl(px(0.))
8916 .rounded_tl(px(0.))
8917 .border_l_2()
8918 .border_color(border_color)
8919 } else {
8920 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8921 .rounded_br(px(0.))
8922 .rounded_tr(px(0.))
8923 .border_r_2()
8924 .border_color(border_color)
8925 })
8926 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8927 .into_any();
8928
8929 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8930
8931 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8932 - point(
8933 if flag_on_right {
8934 POLE_WIDTH
8935 } else {
8936 size.width - POLE_WIDTH
8937 },
8938 size.height - line_height,
8939 );
8940
8941 origin.x = origin.x.max(content_origin.x);
8942
8943 element.prepaint_at(origin, window, cx);
8944
8945 Some((element, origin))
8946 }
8947
8948 fn render_edit_prediction_scroll_popover(
8949 &mut self,
8950 to_y: impl Fn(Size<Pixels>) -> Pixels,
8951 scroll_icon: IconName,
8952 visible_row_range: Range<DisplayRow>,
8953 line_layouts: &[LineWithInvisibles],
8954 newest_selection_head: Option<DisplayPoint>,
8955 scrolled_content_origin: gpui::Point<Pixels>,
8956 window: &mut Window,
8957 cx: &mut App,
8958 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8959 let mut element = self
8960 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8961 .into_any();
8962
8963 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8964
8965 let cursor = newest_selection_head?;
8966 let cursor_row_layout =
8967 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8968 let cursor_column = cursor.column() as usize;
8969
8970 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8971
8972 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8973
8974 element.prepaint_at(origin, window, cx);
8975 Some((element, origin))
8976 }
8977
8978 fn render_edit_prediction_eager_jump_popover(
8979 &mut self,
8980 text_bounds: &Bounds<Pixels>,
8981 content_origin: gpui::Point<Pixels>,
8982 editor_snapshot: &EditorSnapshot,
8983 visible_row_range: Range<DisplayRow>,
8984 scroll_top: ScrollOffset,
8985 scroll_bottom: ScrollOffset,
8986 line_height: Pixels,
8987 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8988 target_display_point: DisplayPoint,
8989 editor_width: Pixels,
8990 window: &mut Window,
8991 cx: &mut App,
8992 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8993 if target_display_point.row().as_f64() < scroll_top {
8994 let mut element = self
8995 .render_edit_prediction_line_popover(
8996 "Jump to Edit",
8997 Some(IconName::ArrowUp),
8998 window,
8999 cx,
9000 )
9001 .into_any();
9002
9003 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9004 let offset = point(
9005 (text_bounds.size.width - size.width) / 2.,
9006 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9007 );
9008
9009 let origin = text_bounds.origin + offset;
9010 element.prepaint_at(origin, window, cx);
9011 Some((element, origin))
9012 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9013 let mut element = self
9014 .render_edit_prediction_line_popover(
9015 "Jump to Edit",
9016 Some(IconName::ArrowDown),
9017 window,
9018 cx,
9019 )
9020 .into_any();
9021
9022 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9023 let offset = point(
9024 (text_bounds.size.width - size.width) / 2.,
9025 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9026 );
9027
9028 let origin = text_bounds.origin + offset;
9029 element.prepaint_at(origin, window, cx);
9030 Some((element, origin))
9031 } else {
9032 self.render_edit_prediction_end_of_line_popover(
9033 "Jump to Edit",
9034 editor_snapshot,
9035 visible_row_range,
9036 target_display_point,
9037 line_height,
9038 scroll_pixel_position,
9039 content_origin,
9040 editor_width,
9041 window,
9042 cx,
9043 )
9044 }
9045 }
9046
9047 fn render_edit_prediction_end_of_line_popover(
9048 self: &mut Editor,
9049 label: &'static str,
9050 editor_snapshot: &EditorSnapshot,
9051 visible_row_range: Range<DisplayRow>,
9052 target_display_point: DisplayPoint,
9053 line_height: Pixels,
9054 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9055 content_origin: gpui::Point<Pixels>,
9056 editor_width: Pixels,
9057 window: &mut Window,
9058 cx: &mut App,
9059 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9060 let target_line_end = DisplayPoint::new(
9061 target_display_point.row(),
9062 editor_snapshot.line_len(target_display_point.row()),
9063 );
9064
9065 let mut element = self
9066 .render_edit_prediction_line_popover(label, None, window, cx)
9067 .into_any();
9068
9069 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9070
9071 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9072
9073 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9074 let mut origin = start_point
9075 + line_origin
9076 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9077 origin.x = origin.x.max(content_origin.x);
9078
9079 let max_x = content_origin.x + editor_width - size.width;
9080
9081 if origin.x > max_x {
9082 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9083
9084 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9085 origin.y += offset;
9086 IconName::ArrowUp
9087 } else {
9088 origin.y -= offset;
9089 IconName::ArrowDown
9090 };
9091
9092 element = self
9093 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9094 .into_any();
9095
9096 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9097
9098 origin.x = content_origin.x + editor_width - size.width - px(2.);
9099 }
9100
9101 element.prepaint_at(origin, window, cx);
9102 Some((element, origin))
9103 }
9104
9105 fn render_edit_prediction_diff_popover(
9106 self: &Editor,
9107 text_bounds: &Bounds<Pixels>,
9108 content_origin: gpui::Point<Pixels>,
9109 right_margin: Pixels,
9110 editor_snapshot: &EditorSnapshot,
9111 visible_row_range: Range<DisplayRow>,
9112 line_layouts: &[LineWithInvisibles],
9113 line_height: Pixels,
9114 scroll_position: gpui::Point<ScrollOffset>,
9115 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9116 newest_selection_head: Option<DisplayPoint>,
9117 editor_width: Pixels,
9118 style: &EditorStyle,
9119 edits: &Vec<(Range<Anchor>, String)>,
9120 edit_preview: &Option<language::EditPreview>,
9121 snapshot: &language::BufferSnapshot,
9122 window: &mut Window,
9123 cx: &mut App,
9124 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9125 let edit_start = edits
9126 .first()
9127 .unwrap()
9128 .0
9129 .start
9130 .to_display_point(editor_snapshot);
9131 let edit_end = edits
9132 .last()
9133 .unwrap()
9134 .0
9135 .end
9136 .to_display_point(editor_snapshot);
9137
9138 let is_visible = visible_row_range.contains(&edit_start.row())
9139 || visible_row_range.contains(&edit_end.row());
9140 if !is_visible {
9141 return None;
9142 }
9143
9144 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9145 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9146 } else {
9147 // Fallback for providers without edit_preview
9148 crate::edit_prediction_fallback_text(edits, cx)
9149 };
9150
9151 let styled_text = highlighted_edits.to_styled_text(&style.text);
9152 let line_count = highlighted_edits.text.lines().count();
9153
9154 const BORDER_WIDTH: Pixels = px(1.);
9155
9156 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9157 let has_keybind = keybind.is_some();
9158
9159 let mut element = h_flex()
9160 .items_start()
9161 .child(
9162 h_flex()
9163 .bg(cx.theme().colors().editor_background)
9164 .border(BORDER_WIDTH)
9165 .shadow_xs()
9166 .border_color(cx.theme().colors().border)
9167 .rounded_l_lg()
9168 .when(line_count > 1, |el| el.rounded_br_lg())
9169 .pr_1()
9170 .child(styled_text),
9171 )
9172 .child(
9173 h_flex()
9174 .h(line_height + BORDER_WIDTH * 2.)
9175 .px_1p5()
9176 .gap_1()
9177 // Workaround: For some reason, there's a gap if we don't do this
9178 .ml(-BORDER_WIDTH)
9179 .shadow(vec![gpui::BoxShadow {
9180 color: gpui::black().opacity(0.05),
9181 offset: point(px(1.), px(1.)),
9182 blur_radius: px(2.),
9183 spread_radius: px(0.),
9184 }])
9185 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9186 .border(BORDER_WIDTH)
9187 .border_color(cx.theme().colors().border)
9188 .rounded_r_lg()
9189 .id("edit_prediction_diff_popover_keybind")
9190 .when(!has_keybind, |el| {
9191 let status_colors = cx.theme().status();
9192
9193 el.bg(status_colors.error_background)
9194 .border_color(status_colors.error.opacity(0.6))
9195 .child(Icon::new(IconName::Info).color(Color::Error))
9196 .cursor_default()
9197 .hoverable_tooltip(move |_window, cx| {
9198 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9199 })
9200 })
9201 .children(keybind),
9202 )
9203 .into_any();
9204
9205 let longest_row =
9206 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9207 let longest_line_width = if visible_row_range.contains(&longest_row) {
9208 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9209 } else {
9210 layout_line(
9211 longest_row,
9212 editor_snapshot,
9213 style,
9214 editor_width,
9215 |_| false,
9216 window,
9217 cx,
9218 )
9219 .width
9220 };
9221
9222 let viewport_bounds =
9223 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9224 right: -right_margin,
9225 ..Default::default()
9226 });
9227
9228 let x_after_longest = Pixels::from(
9229 ScrollPixelOffset::from(
9230 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9231 ) - scroll_pixel_position.x,
9232 );
9233
9234 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9235
9236 // Fully visible if it can be displayed within the window (allow overlapping other
9237 // panes). However, this is only allowed if the popover starts within text_bounds.
9238 let can_position_to_the_right = x_after_longest < text_bounds.right()
9239 && x_after_longest + element_bounds.width < viewport_bounds.right();
9240
9241 let mut origin = if can_position_to_the_right {
9242 point(
9243 x_after_longest,
9244 text_bounds.origin.y
9245 + Pixels::from(
9246 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9247 - scroll_pixel_position.y,
9248 ),
9249 )
9250 } else {
9251 let cursor_row = newest_selection_head.map(|head| head.row());
9252 let above_edit = edit_start
9253 .row()
9254 .0
9255 .checked_sub(line_count as u32)
9256 .map(DisplayRow);
9257 let below_edit = Some(edit_end.row() + 1);
9258 let above_cursor =
9259 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9260 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9261
9262 // Place the edit popover adjacent to the edit if there is a location
9263 // available that is onscreen and does not obscure the cursor. Otherwise,
9264 // place it adjacent to the cursor.
9265 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9266 .into_iter()
9267 .flatten()
9268 .find(|&start_row| {
9269 let end_row = start_row + line_count as u32;
9270 visible_row_range.contains(&start_row)
9271 && visible_row_range.contains(&end_row)
9272 && cursor_row
9273 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9274 })?;
9275
9276 content_origin
9277 + point(
9278 Pixels::from(-scroll_pixel_position.x),
9279 Pixels::from(
9280 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9281 ),
9282 )
9283 };
9284
9285 origin.x -= BORDER_WIDTH;
9286
9287 window.defer_draw(element, origin, 1);
9288
9289 // Do not return an element, since it will already be drawn due to defer_draw.
9290 None
9291 }
9292
9293 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9294 px(30.)
9295 }
9296
9297 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9298 if self.read_only(cx) {
9299 cx.theme().players().read_only()
9300 } else {
9301 self.style.as_ref().unwrap().local_player
9302 }
9303 }
9304
9305 fn render_edit_prediction_accept_keybind(
9306 &self,
9307 window: &mut Window,
9308 cx: &mut App,
9309 ) -> Option<AnyElement> {
9310 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9311 let accept_keystroke = accept_binding.keystroke()?;
9312
9313 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9314
9315 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9316 Color::Accent
9317 } else {
9318 Color::Muted
9319 };
9320
9321 h_flex()
9322 .px_0p5()
9323 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9324 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9325 .text_size(TextSize::XSmall.rems(cx))
9326 .child(h_flex().children(ui::render_modifiers(
9327 accept_keystroke.modifiers(),
9328 PlatformStyle::platform(),
9329 Some(modifiers_color),
9330 Some(IconSize::XSmall.rems().into()),
9331 true,
9332 )))
9333 .when(is_platform_style_mac, |parent| {
9334 parent.child(accept_keystroke.key().to_string())
9335 })
9336 .when(!is_platform_style_mac, |parent| {
9337 parent.child(
9338 Key::new(
9339 util::capitalize(accept_keystroke.key()),
9340 Some(Color::Default),
9341 )
9342 .size(Some(IconSize::XSmall.rems().into())),
9343 )
9344 })
9345 .into_any()
9346 .into()
9347 }
9348
9349 fn render_edit_prediction_line_popover(
9350 &self,
9351 label: impl Into<SharedString>,
9352 icon: Option<IconName>,
9353 window: &mut Window,
9354 cx: &mut App,
9355 ) -> Stateful<Div> {
9356 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9357
9358 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9359 let has_keybind = keybind.is_some();
9360
9361 h_flex()
9362 .id("ep-line-popover")
9363 .py_0p5()
9364 .pl_1()
9365 .pr(padding_right)
9366 .gap_1()
9367 .rounded_md()
9368 .border_1()
9369 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9370 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9371 .shadow_xs()
9372 .when(!has_keybind, |el| {
9373 let status_colors = cx.theme().status();
9374
9375 el.bg(status_colors.error_background)
9376 .border_color(status_colors.error.opacity(0.6))
9377 .pl_2()
9378 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9379 .cursor_default()
9380 .hoverable_tooltip(move |_window, cx| {
9381 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9382 })
9383 })
9384 .children(keybind)
9385 .child(
9386 Label::new(label)
9387 .size(LabelSize::Small)
9388 .when(!has_keybind, |el| {
9389 el.color(cx.theme().status().error.into()).strikethrough()
9390 }),
9391 )
9392 .when(!has_keybind, |el| {
9393 el.child(
9394 h_flex().ml_1().child(
9395 Icon::new(IconName::Info)
9396 .size(IconSize::Small)
9397 .color(cx.theme().status().error.into()),
9398 ),
9399 )
9400 })
9401 .when_some(icon, |element, icon| {
9402 element.child(
9403 div()
9404 .mt(px(1.5))
9405 .child(Icon::new(icon).size(IconSize::Small)),
9406 )
9407 })
9408 }
9409
9410 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9411 let accent_color = cx.theme().colors().text_accent;
9412 let editor_bg_color = cx.theme().colors().editor_background;
9413 editor_bg_color.blend(accent_color.opacity(0.1))
9414 }
9415
9416 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9417 let accent_color = cx.theme().colors().text_accent;
9418 let editor_bg_color = cx.theme().colors().editor_background;
9419 editor_bg_color.blend(accent_color.opacity(0.6))
9420 }
9421 fn get_prediction_provider_icon_name(
9422 provider: &Option<RegisteredEditPredictionProvider>,
9423 ) -> IconName {
9424 match provider {
9425 Some(provider) => match provider.provider.name() {
9426 "copilot" => IconName::Copilot,
9427 "supermaven" => IconName::Supermaven,
9428 _ => IconName::ZedPredict,
9429 },
9430 None => IconName::ZedPredict,
9431 }
9432 }
9433
9434 fn render_edit_prediction_cursor_popover(
9435 &self,
9436 min_width: Pixels,
9437 max_width: Pixels,
9438 cursor_point: Point,
9439 style: &EditorStyle,
9440 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9441 _window: &Window,
9442 cx: &mut Context<Editor>,
9443 ) -> Option<AnyElement> {
9444 let provider = self.edit_prediction_provider.as_ref()?;
9445 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9446
9447 let is_refreshing = provider.provider.is_refreshing(cx);
9448
9449 fn pending_completion_container(icon: IconName) -> Div {
9450 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9451 }
9452
9453 let completion = match &self.active_edit_prediction {
9454 Some(prediction) => {
9455 if !self.has_visible_completions_menu() {
9456 const RADIUS: Pixels = px(6.);
9457 const BORDER_WIDTH: Pixels = px(1.);
9458
9459 return Some(
9460 h_flex()
9461 .elevation_2(cx)
9462 .border(BORDER_WIDTH)
9463 .border_color(cx.theme().colors().border)
9464 .when(accept_keystroke.is_none(), |el| {
9465 el.border_color(cx.theme().status().error)
9466 })
9467 .rounded(RADIUS)
9468 .rounded_tl(px(0.))
9469 .overflow_hidden()
9470 .child(div().px_1p5().child(match &prediction.completion {
9471 EditPrediction::MoveWithin { target, snapshot } => {
9472 use text::ToPoint as _;
9473 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9474 {
9475 Icon::new(IconName::ZedPredictDown)
9476 } else {
9477 Icon::new(IconName::ZedPredictUp)
9478 }
9479 }
9480 EditPrediction::MoveOutside { .. } => {
9481 // TODO [zeta2] custom icon for external jump?
9482 Icon::new(provider_icon)
9483 }
9484 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9485 }))
9486 .child(
9487 h_flex()
9488 .gap_1()
9489 .py_1()
9490 .px_2()
9491 .rounded_r(RADIUS - BORDER_WIDTH)
9492 .border_l_1()
9493 .border_color(cx.theme().colors().border)
9494 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9495 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9496 el.child(
9497 Label::new("Hold")
9498 .size(LabelSize::Small)
9499 .when(accept_keystroke.is_none(), |el| {
9500 el.strikethrough()
9501 })
9502 .line_height_style(LineHeightStyle::UiLabel),
9503 )
9504 })
9505 .id("edit_prediction_cursor_popover_keybind")
9506 .when(accept_keystroke.is_none(), |el| {
9507 let status_colors = cx.theme().status();
9508
9509 el.bg(status_colors.error_background)
9510 .border_color(status_colors.error.opacity(0.6))
9511 .child(Icon::new(IconName::Info).color(Color::Error))
9512 .cursor_default()
9513 .hoverable_tooltip(move |_window, cx| {
9514 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9515 .into()
9516 })
9517 })
9518 .when_some(
9519 accept_keystroke.as_ref(),
9520 |el, accept_keystroke| {
9521 el.child(h_flex().children(ui::render_modifiers(
9522 accept_keystroke.modifiers(),
9523 PlatformStyle::platform(),
9524 Some(Color::Default),
9525 Some(IconSize::XSmall.rems().into()),
9526 false,
9527 )))
9528 },
9529 ),
9530 )
9531 .into_any(),
9532 );
9533 }
9534
9535 self.render_edit_prediction_cursor_popover_preview(
9536 prediction,
9537 cursor_point,
9538 style,
9539 cx,
9540 )?
9541 }
9542
9543 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9544 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9545 stale_completion,
9546 cursor_point,
9547 style,
9548 cx,
9549 )?,
9550
9551 None => pending_completion_container(provider_icon)
9552 .child(Label::new("...").size(LabelSize::Small)),
9553 },
9554
9555 None => pending_completion_container(provider_icon)
9556 .child(Label::new("...").size(LabelSize::Small)),
9557 };
9558
9559 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9560 completion
9561 .with_animation(
9562 "loading-completion",
9563 Animation::new(Duration::from_secs(2))
9564 .repeat()
9565 .with_easing(pulsating_between(0.4, 0.8)),
9566 |label, delta| label.opacity(delta),
9567 )
9568 .into_any_element()
9569 } else {
9570 completion.into_any_element()
9571 };
9572
9573 let has_completion = self.active_edit_prediction.is_some();
9574
9575 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9576 Some(
9577 h_flex()
9578 .min_w(min_width)
9579 .max_w(max_width)
9580 .flex_1()
9581 .elevation_2(cx)
9582 .border_color(cx.theme().colors().border)
9583 .child(
9584 div()
9585 .flex_1()
9586 .py_1()
9587 .px_2()
9588 .overflow_hidden()
9589 .child(completion),
9590 )
9591 .when_some(accept_keystroke, |el, accept_keystroke| {
9592 if !accept_keystroke.modifiers().modified() {
9593 return el;
9594 }
9595
9596 el.child(
9597 h_flex()
9598 .h_full()
9599 .border_l_1()
9600 .rounded_r_lg()
9601 .border_color(cx.theme().colors().border)
9602 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9603 .gap_1()
9604 .py_1()
9605 .px_2()
9606 .child(
9607 h_flex()
9608 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9609 .when(is_platform_style_mac, |parent| parent.gap_1())
9610 .child(h_flex().children(ui::render_modifiers(
9611 accept_keystroke.modifiers(),
9612 PlatformStyle::platform(),
9613 Some(if !has_completion {
9614 Color::Muted
9615 } else {
9616 Color::Default
9617 }),
9618 None,
9619 false,
9620 ))),
9621 )
9622 .child(Label::new("Preview").into_any_element())
9623 .opacity(if has_completion { 1.0 } else { 0.4 }),
9624 )
9625 })
9626 .into_any(),
9627 )
9628 }
9629
9630 fn render_edit_prediction_cursor_popover_preview(
9631 &self,
9632 completion: &EditPredictionState,
9633 cursor_point: Point,
9634 style: &EditorStyle,
9635 cx: &mut Context<Editor>,
9636 ) -> Option<Div> {
9637 use text::ToPoint as _;
9638
9639 fn render_relative_row_jump(
9640 prefix: impl Into<String>,
9641 current_row: u32,
9642 target_row: u32,
9643 ) -> Div {
9644 let (row_diff, arrow) = if target_row < current_row {
9645 (current_row - target_row, IconName::ArrowUp)
9646 } else {
9647 (target_row - current_row, IconName::ArrowDown)
9648 };
9649
9650 h_flex()
9651 .child(
9652 Label::new(format!("{}{}", prefix.into(), row_diff))
9653 .color(Color::Muted)
9654 .size(LabelSize::Small),
9655 )
9656 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9657 }
9658
9659 let supports_jump = self
9660 .edit_prediction_provider
9661 .as_ref()
9662 .map(|provider| provider.provider.supports_jump_to_edit())
9663 .unwrap_or(true);
9664
9665 match &completion.completion {
9666 EditPrediction::MoveWithin {
9667 target, snapshot, ..
9668 } => {
9669 if !supports_jump {
9670 return None;
9671 }
9672
9673 Some(
9674 h_flex()
9675 .px_2()
9676 .gap_2()
9677 .flex_1()
9678 .child(
9679 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9680 Icon::new(IconName::ZedPredictDown)
9681 } else {
9682 Icon::new(IconName::ZedPredictUp)
9683 },
9684 )
9685 .child(Label::new("Jump to Edit")),
9686 )
9687 }
9688 EditPrediction::MoveOutside { snapshot, .. } => {
9689 let file_name = snapshot
9690 .file()
9691 .map(|file| file.file_name(cx))
9692 .unwrap_or("untitled");
9693 Some(
9694 h_flex()
9695 .px_2()
9696 .gap_2()
9697 .flex_1()
9698 .child(Icon::new(IconName::ZedPredict))
9699 .child(Label::new(format!("Jump to {file_name}"))),
9700 )
9701 }
9702 EditPrediction::Edit {
9703 edits,
9704 edit_preview,
9705 snapshot,
9706 display_mode: _,
9707 } => {
9708 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9709
9710 let (highlighted_edits, has_more_lines) =
9711 if let Some(edit_preview) = edit_preview.as_ref() {
9712 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9713 .first_line_preview()
9714 } else {
9715 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9716 };
9717
9718 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9719 .with_default_highlights(&style.text, highlighted_edits.highlights);
9720
9721 let preview = h_flex()
9722 .gap_1()
9723 .min_w_16()
9724 .child(styled_text)
9725 .when(has_more_lines, |parent| parent.child("…"));
9726
9727 let left = if supports_jump && first_edit_row != cursor_point.row {
9728 render_relative_row_jump("", cursor_point.row, first_edit_row)
9729 .into_any_element()
9730 } else {
9731 let icon_name =
9732 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9733 Icon::new(icon_name).into_any_element()
9734 };
9735
9736 Some(
9737 h_flex()
9738 .h_full()
9739 .flex_1()
9740 .gap_2()
9741 .pr_1()
9742 .overflow_x_hidden()
9743 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9744 .child(left)
9745 .child(preview),
9746 )
9747 }
9748 }
9749 }
9750
9751 pub fn render_context_menu(
9752 &self,
9753 style: &EditorStyle,
9754 max_height_in_lines: u32,
9755 window: &mut Window,
9756 cx: &mut Context<Editor>,
9757 ) -> Option<AnyElement> {
9758 let menu = self.context_menu.borrow();
9759 let menu = menu.as_ref()?;
9760 if !menu.visible() {
9761 return None;
9762 };
9763 Some(menu.render(style, max_height_in_lines, window, cx))
9764 }
9765
9766 fn render_context_menu_aside(
9767 &mut self,
9768 max_size: Size<Pixels>,
9769 window: &mut Window,
9770 cx: &mut Context<Editor>,
9771 ) -> Option<AnyElement> {
9772 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9773 if menu.visible() {
9774 menu.render_aside(max_size, window, cx)
9775 } else {
9776 None
9777 }
9778 })
9779 }
9780
9781 fn hide_context_menu(
9782 &mut self,
9783 window: &mut Window,
9784 cx: &mut Context<Self>,
9785 ) -> Option<CodeContextMenu> {
9786 cx.notify();
9787 self.completion_tasks.clear();
9788 let context_menu = self.context_menu.borrow_mut().take();
9789 self.stale_edit_prediction_in_menu.take();
9790 self.update_visible_edit_prediction(window, cx);
9791 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9792 && let Some(completion_provider) = &self.completion_provider
9793 {
9794 completion_provider.selection_changed(None, window, cx);
9795 }
9796 context_menu
9797 }
9798
9799 fn show_snippet_choices(
9800 &mut self,
9801 choices: &Vec<String>,
9802 selection: Range<Anchor>,
9803 cx: &mut Context<Self>,
9804 ) {
9805 let Some((_, buffer, _)) = self
9806 .buffer()
9807 .read(cx)
9808 .excerpt_containing(selection.start, cx)
9809 else {
9810 return;
9811 };
9812 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9813 else {
9814 return;
9815 };
9816 if buffer != end_buffer {
9817 log::error!("expected anchor range to have matching buffer IDs");
9818 return;
9819 }
9820
9821 let id = post_inc(&mut self.next_completion_id);
9822 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9823 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9824 CompletionsMenu::new_snippet_choices(
9825 id,
9826 true,
9827 choices,
9828 selection,
9829 buffer,
9830 snippet_sort_order,
9831 ),
9832 ));
9833 }
9834
9835 pub fn insert_snippet(
9836 &mut self,
9837 insertion_ranges: &[Range<usize>],
9838 snippet: Snippet,
9839 window: &mut Window,
9840 cx: &mut Context<Self>,
9841 ) -> Result<()> {
9842 struct Tabstop<T> {
9843 is_end_tabstop: bool,
9844 ranges: Vec<Range<T>>,
9845 choices: Option<Vec<String>>,
9846 }
9847
9848 let tabstops = self.buffer.update(cx, |buffer, cx| {
9849 let snippet_text: Arc<str> = snippet.text.clone().into();
9850 let edits = insertion_ranges
9851 .iter()
9852 .cloned()
9853 .map(|range| (range, snippet_text.clone()));
9854 let autoindent_mode = AutoindentMode::Block {
9855 original_indent_columns: Vec::new(),
9856 };
9857 buffer.edit(edits, Some(autoindent_mode), cx);
9858
9859 let snapshot = &*buffer.read(cx);
9860 let snippet = &snippet;
9861 snippet
9862 .tabstops
9863 .iter()
9864 .map(|tabstop| {
9865 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9866 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9867 });
9868 let mut tabstop_ranges = tabstop
9869 .ranges
9870 .iter()
9871 .flat_map(|tabstop_range| {
9872 let mut delta = 0_isize;
9873 insertion_ranges.iter().map(move |insertion_range| {
9874 let insertion_start = insertion_range.start as isize + delta;
9875 delta +=
9876 snippet.text.len() as isize - insertion_range.len() as isize;
9877
9878 let start = ((insertion_start + tabstop_range.start) as usize)
9879 .min(snapshot.len());
9880 let end = ((insertion_start + tabstop_range.end) as usize)
9881 .min(snapshot.len());
9882 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9883 })
9884 })
9885 .collect::<Vec<_>>();
9886 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9887
9888 Tabstop {
9889 is_end_tabstop,
9890 ranges: tabstop_ranges,
9891 choices: tabstop.choices.clone(),
9892 }
9893 })
9894 .collect::<Vec<_>>()
9895 });
9896 if let Some(tabstop) = tabstops.first() {
9897 self.change_selections(Default::default(), window, cx, |s| {
9898 // Reverse order so that the first range is the newest created selection.
9899 // Completions will use it and autoscroll will prioritize it.
9900 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9901 });
9902
9903 if let Some(choices) = &tabstop.choices
9904 && let Some(selection) = tabstop.ranges.first()
9905 {
9906 self.show_snippet_choices(choices, selection.clone(), cx)
9907 }
9908
9909 // If we're already at the last tabstop and it's at the end of the snippet,
9910 // we're done, we don't need to keep the state around.
9911 if !tabstop.is_end_tabstop {
9912 let choices = tabstops
9913 .iter()
9914 .map(|tabstop| tabstop.choices.clone())
9915 .collect();
9916
9917 let ranges = tabstops
9918 .into_iter()
9919 .map(|tabstop| tabstop.ranges)
9920 .collect::<Vec<_>>();
9921
9922 self.snippet_stack.push(SnippetState {
9923 active_index: 0,
9924 ranges,
9925 choices,
9926 });
9927 }
9928
9929 // Check whether the just-entered snippet ends with an auto-closable bracket.
9930 if self.autoclose_regions.is_empty() {
9931 let snapshot = self.buffer.read(cx).snapshot(cx);
9932 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9933 let selection_head = selection.head();
9934 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9935 continue;
9936 };
9937
9938 let mut bracket_pair = None;
9939 let max_lookup_length = scope
9940 .brackets()
9941 .map(|(pair, _)| {
9942 pair.start
9943 .as_str()
9944 .chars()
9945 .count()
9946 .max(pair.end.as_str().chars().count())
9947 })
9948 .max();
9949 if let Some(max_lookup_length) = max_lookup_length {
9950 let next_text = snapshot
9951 .chars_at(selection_head)
9952 .take(max_lookup_length)
9953 .collect::<String>();
9954 let prev_text = snapshot
9955 .reversed_chars_at(selection_head)
9956 .take(max_lookup_length)
9957 .collect::<String>();
9958
9959 for (pair, enabled) in scope.brackets() {
9960 if enabled
9961 && pair.close
9962 && prev_text.starts_with(pair.start.as_str())
9963 && next_text.starts_with(pair.end.as_str())
9964 {
9965 bracket_pair = Some(pair.clone());
9966 break;
9967 }
9968 }
9969 }
9970
9971 if let Some(pair) = bracket_pair {
9972 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9973 let autoclose_enabled =
9974 self.use_autoclose && snapshot_settings.use_autoclose;
9975 if autoclose_enabled {
9976 let start = snapshot.anchor_after(selection_head);
9977 let end = snapshot.anchor_after(selection_head);
9978 self.autoclose_regions.push(AutocloseRegion {
9979 selection_id: selection.id,
9980 range: start..end,
9981 pair,
9982 });
9983 }
9984 }
9985 }
9986 }
9987 }
9988 Ok(())
9989 }
9990
9991 pub fn move_to_next_snippet_tabstop(
9992 &mut self,
9993 window: &mut Window,
9994 cx: &mut Context<Self>,
9995 ) -> bool {
9996 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9997 }
9998
9999 pub fn move_to_prev_snippet_tabstop(
10000 &mut self,
10001 window: &mut Window,
10002 cx: &mut Context<Self>,
10003 ) -> bool {
10004 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10005 }
10006
10007 pub fn move_to_snippet_tabstop(
10008 &mut self,
10009 bias: Bias,
10010 window: &mut Window,
10011 cx: &mut Context<Self>,
10012 ) -> bool {
10013 if let Some(mut snippet) = self.snippet_stack.pop() {
10014 match bias {
10015 Bias::Left => {
10016 if snippet.active_index > 0 {
10017 snippet.active_index -= 1;
10018 } else {
10019 self.snippet_stack.push(snippet);
10020 return false;
10021 }
10022 }
10023 Bias::Right => {
10024 if snippet.active_index + 1 < snippet.ranges.len() {
10025 snippet.active_index += 1;
10026 } else {
10027 self.snippet_stack.push(snippet);
10028 return false;
10029 }
10030 }
10031 }
10032 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10033 self.change_selections(Default::default(), window, cx, |s| {
10034 // Reverse order so that the first range is the newest created selection.
10035 // Completions will use it and autoscroll will prioritize it.
10036 s.select_ranges(current_ranges.iter().rev().cloned())
10037 });
10038
10039 if let Some(choices) = &snippet.choices[snippet.active_index]
10040 && let Some(selection) = current_ranges.first()
10041 {
10042 self.show_snippet_choices(choices, selection.clone(), cx);
10043 }
10044
10045 // If snippet state is not at the last tabstop, push it back on the stack
10046 if snippet.active_index + 1 < snippet.ranges.len() {
10047 self.snippet_stack.push(snippet);
10048 }
10049 return true;
10050 }
10051 }
10052
10053 false
10054 }
10055
10056 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10057 self.transact(window, cx, |this, window, cx| {
10058 this.select_all(&SelectAll, window, cx);
10059 this.insert("", window, cx);
10060 });
10061 }
10062
10063 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10064 if self.read_only(cx) {
10065 return;
10066 }
10067 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10068 self.transact(window, cx, |this, window, cx| {
10069 this.select_autoclose_pair(window, cx);
10070
10071 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10072
10073 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10074 if !this.linked_edit_ranges.is_empty() {
10075 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10076 let snapshot = this.buffer.read(cx).snapshot(cx);
10077
10078 for selection in selections.iter() {
10079 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10080 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10081 if selection_start.buffer_id != selection_end.buffer_id {
10082 continue;
10083 }
10084 if let Some(ranges) =
10085 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10086 {
10087 for (buffer, entries) in ranges {
10088 linked_ranges.entry(buffer).or_default().extend(entries);
10089 }
10090 }
10091 }
10092 }
10093
10094 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10095 for selection in &mut selections {
10096 if selection.is_empty() {
10097 let old_head = selection.head();
10098 let mut new_head =
10099 movement::left(&display_map, old_head.to_display_point(&display_map))
10100 .to_point(&display_map);
10101 if let Some((buffer, line_buffer_range)) = display_map
10102 .buffer_snapshot()
10103 .buffer_line_for_row(MultiBufferRow(old_head.row))
10104 {
10105 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10106 let indent_len = match indent_size.kind {
10107 IndentKind::Space => {
10108 buffer.settings_at(line_buffer_range.start, cx).tab_size
10109 }
10110 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10111 };
10112 if old_head.column <= indent_size.len && old_head.column > 0 {
10113 let indent_len = indent_len.get();
10114 new_head = cmp::min(
10115 new_head,
10116 MultiBufferPoint::new(
10117 old_head.row,
10118 ((old_head.column - 1) / indent_len) * indent_len,
10119 ),
10120 );
10121 }
10122 }
10123
10124 selection.set_head(new_head, SelectionGoal::None);
10125 }
10126 }
10127
10128 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10129 this.insert("", window, cx);
10130 let empty_str: Arc<str> = Arc::from("");
10131 for (buffer, edits) in linked_ranges {
10132 let snapshot = buffer.read(cx).snapshot();
10133 use text::ToPoint as TP;
10134
10135 let edits = edits
10136 .into_iter()
10137 .map(|range| {
10138 let end_point = TP::to_point(&range.end, &snapshot);
10139 let mut start_point = TP::to_point(&range.start, &snapshot);
10140
10141 if end_point == start_point {
10142 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10143 .saturating_sub(1);
10144 start_point =
10145 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10146 };
10147
10148 (start_point..end_point, empty_str.clone())
10149 })
10150 .sorted_by_key(|(range, _)| range.start)
10151 .collect::<Vec<_>>();
10152 buffer.update(cx, |this, cx| {
10153 this.edit(edits, None, cx);
10154 })
10155 }
10156 this.refresh_edit_prediction(true, false, window, cx);
10157 refresh_linked_ranges(this, window, cx);
10158 });
10159 }
10160
10161 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10162 if self.read_only(cx) {
10163 return;
10164 }
10165 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10166 self.transact(window, cx, |this, window, cx| {
10167 this.change_selections(Default::default(), window, cx, |s| {
10168 s.move_with(|map, selection| {
10169 if selection.is_empty() {
10170 let cursor = movement::right(map, selection.head());
10171 selection.end = cursor;
10172 selection.reversed = true;
10173 selection.goal = SelectionGoal::None;
10174 }
10175 })
10176 });
10177 this.insert("", window, cx);
10178 this.refresh_edit_prediction(true, false, window, cx);
10179 });
10180 }
10181
10182 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10183 if self.mode.is_single_line() {
10184 cx.propagate();
10185 return;
10186 }
10187
10188 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10189 if self.move_to_prev_snippet_tabstop(window, cx) {
10190 return;
10191 }
10192 self.outdent(&Outdent, window, cx);
10193 }
10194
10195 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10196 if self.mode.is_single_line() {
10197 cx.propagate();
10198 return;
10199 }
10200
10201 if self.move_to_next_snippet_tabstop(window, cx) {
10202 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10203 return;
10204 }
10205 if self.read_only(cx) {
10206 return;
10207 }
10208 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10209 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10210 let buffer = self.buffer.read(cx);
10211 let snapshot = buffer.snapshot(cx);
10212 let rows_iter = selections.iter().map(|s| s.head().row);
10213 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10214
10215 let has_some_cursor_in_whitespace = selections
10216 .iter()
10217 .filter(|selection| selection.is_empty())
10218 .any(|selection| {
10219 let cursor = selection.head();
10220 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10221 cursor.column < current_indent.len
10222 });
10223
10224 let mut edits = Vec::new();
10225 let mut prev_edited_row = 0;
10226 let mut row_delta = 0;
10227 for selection in &mut selections {
10228 if selection.start.row != prev_edited_row {
10229 row_delta = 0;
10230 }
10231 prev_edited_row = selection.end.row;
10232
10233 // If the selection is non-empty, then increase the indentation of the selected lines.
10234 if !selection.is_empty() {
10235 row_delta =
10236 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10237 continue;
10238 }
10239
10240 let cursor = selection.head();
10241 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10242 if let Some(suggested_indent) =
10243 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10244 {
10245 // Don't do anything if already at suggested indent
10246 // and there is any other cursor which is not
10247 if has_some_cursor_in_whitespace
10248 && cursor.column == current_indent.len
10249 && current_indent.len == suggested_indent.len
10250 {
10251 continue;
10252 }
10253
10254 // Adjust line and move cursor to suggested indent
10255 // if cursor is not at suggested indent
10256 if cursor.column < suggested_indent.len
10257 && cursor.column <= current_indent.len
10258 && current_indent.len <= suggested_indent.len
10259 {
10260 selection.start = Point::new(cursor.row, suggested_indent.len);
10261 selection.end = selection.start;
10262 if row_delta == 0 {
10263 edits.extend(Buffer::edit_for_indent_size_adjustment(
10264 cursor.row,
10265 current_indent,
10266 suggested_indent,
10267 ));
10268 row_delta = suggested_indent.len - current_indent.len;
10269 }
10270 continue;
10271 }
10272
10273 // If current indent is more than suggested indent
10274 // only move cursor to current indent and skip indent
10275 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10276 selection.start = Point::new(cursor.row, current_indent.len);
10277 selection.end = selection.start;
10278 continue;
10279 }
10280 }
10281
10282 // Otherwise, insert a hard or soft tab.
10283 let settings = buffer.language_settings_at(cursor, cx);
10284 let tab_size = if settings.hard_tabs {
10285 IndentSize::tab()
10286 } else {
10287 let tab_size = settings.tab_size.get();
10288 let indent_remainder = snapshot
10289 .text_for_range(Point::new(cursor.row, 0)..cursor)
10290 .flat_map(str::chars)
10291 .fold(row_delta % tab_size, |counter: u32, c| {
10292 if c == '\t' {
10293 0
10294 } else {
10295 (counter + 1) % tab_size
10296 }
10297 });
10298
10299 let chars_to_next_tab_stop = tab_size - indent_remainder;
10300 IndentSize::spaces(chars_to_next_tab_stop)
10301 };
10302 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10303 selection.end = selection.start;
10304 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10305 row_delta += tab_size.len;
10306 }
10307
10308 self.transact(window, cx, |this, window, cx| {
10309 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10310 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10311 this.refresh_edit_prediction(true, false, window, cx);
10312 });
10313 }
10314
10315 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10316 if self.read_only(cx) {
10317 return;
10318 }
10319 if self.mode.is_single_line() {
10320 cx.propagate();
10321 return;
10322 }
10323
10324 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10325 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10326 let mut prev_edited_row = 0;
10327 let mut row_delta = 0;
10328 let mut edits = Vec::new();
10329 let buffer = self.buffer.read(cx);
10330 let snapshot = buffer.snapshot(cx);
10331 for selection in &mut selections {
10332 if selection.start.row != prev_edited_row {
10333 row_delta = 0;
10334 }
10335 prev_edited_row = selection.end.row;
10336
10337 row_delta =
10338 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10339 }
10340
10341 self.transact(window, cx, |this, window, cx| {
10342 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10343 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10344 });
10345 }
10346
10347 fn indent_selection(
10348 buffer: &MultiBuffer,
10349 snapshot: &MultiBufferSnapshot,
10350 selection: &mut Selection<Point>,
10351 edits: &mut Vec<(Range<Point>, String)>,
10352 delta_for_start_row: u32,
10353 cx: &App,
10354 ) -> u32 {
10355 let settings = buffer.language_settings_at(selection.start, cx);
10356 let tab_size = settings.tab_size.get();
10357 let indent_kind = if settings.hard_tabs {
10358 IndentKind::Tab
10359 } else {
10360 IndentKind::Space
10361 };
10362 let mut start_row = selection.start.row;
10363 let mut end_row = selection.end.row + 1;
10364
10365 // If a selection ends at the beginning of a line, don't indent
10366 // that last line.
10367 if selection.end.column == 0 && selection.end.row > selection.start.row {
10368 end_row -= 1;
10369 }
10370
10371 // Avoid re-indenting a row that has already been indented by a
10372 // previous selection, but still update this selection's column
10373 // to reflect that indentation.
10374 if delta_for_start_row > 0 {
10375 start_row += 1;
10376 selection.start.column += delta_for_start_row;
10377 if selection.end.row == selection.start.row {
10378 selection.end.column += delta_for_start_row;
10379 }
10380 }
10381
10382 let mut delta_for_end_row = 0;
10383 let has_multiple_rows = start_row + 1 != end_row;
10384 for row in start_row..end_row {
10385 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10386 let indent_delta = match (current_indent.kind, indent_kind) {
10387 (IndentKind::Space, IndentKind::Space) => {
10388 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10389 IndentSize::spaces(columns_to_next_tab_stop)
10390 }
10391 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10392 (_, IndentKind::Tab) => IndentSize::tab(),
10393 };
10394
10395 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10396 0
10397 } else {
10398 selection.start.column
10399 };
10400 let row_start = Point::new(row, start);
10401 edits.push((
10402 row_start..row_start,
10403 indent_delta.chars().collect::<String>(),
10404 ));
10405
10406 // Update this selection's endpoints to reflect the indentation.
10407 if row == selection.start.row {
10408 selection.start.column += indent_delta.len;
10409 }
10410 if row == selection.end.row {
10411 selection.end.column += indent_delta.len;
10412 delta_for_end_row = indent_delta.len;
10413 }
10414 }
10415
10416 if selection.start.row == selection.end.row {
10417 delta_for_start_row + delta_for_end_row
10418 } else {
10419 delta_for_end_row
10420 }
10421 }
10422
10423 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10424 if self.read_only(cx) {
10425 return;
10426 }
10427 if self.mode.is_single_line() {
10428 cx.propagate();
10429 return;
10430 }
10431
10432 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10433 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10434 let selections = self.selections.all::<Point>(&display_map);
10435 let mut deletion_ranges = Vec::new();
10436 let mut last_outdent = None;
10437 {
10438 let buffer = self.buffer.read(cx);
10439 let snapshot = buffer.snapshot(cx);
10440 for selection in &selections {
10441 let settings = buffer.language_settings_at(selection.start, cx);
10442 let tab_size = settings.tab_size.get();
10443 let mut rows = selection.spanned_rows(false, &display_map);
10444
10445 // Avoid re-outdenting a row that has already been outdented by a
10446 // previous selection.
10447 if let Some(last_row) = last_outdent
10448 && last_row == rows.start
10449 {
10450 rows.start = rows.start.next_row();
10451 }
10452 let has_multiple_rows = rows.len() > 1;
10453 for row in rows.iter_rows() {
10454 let indent_size = snapshot.indent_size_for_line(row);
10455 if indent_size.len > 0 {
10456 let deletion_len = match indent_size.kind {
10457 IndentKind::Space => {
10458 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10459 if columns_to_prev_tab_stop == 0 {
10460 tab_size
10461 } else {
10462 columns_to_prev_tab_stop
10463 }
10464 }
10465 IndentKind::Tab => 1,
10466 };
10467 let start = if has_multiple_rows
10468 || deletion_len > selection.start.column
10469 || indent_size.len < selection.start.column
10470 {
10471 0
10472 } else {
10473 selection.start.column - deletion_len
10474 };
10475 deletion_ranges.push(
10476 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10477 );
10478 last_outdent = Some(row);
10479 }
10480 }
10481 }
10482 }
10483
10484 self.transact(window, cx, |this, window, cx| {
10485 this.buffer.update(cx, |buffer, cx| {
10486 let empty_str: Arc<str> = Arc::default();
10487 buffer.edit(
10488 deletion_ranges
10489 .into_iter()
10490 .map(|range| (range, empty_str.clone())),
10491 None,
10492 cx,
10493 );
10494 });
10495 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10496 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10497 });
10498 }
10499
10500 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10501 if self.read_only(cx) {
10502 return;
10503 }
10504 if self.mode.is_single_line() {
10505 cx.propagate();
10506 return;
10507 }
10508
10509 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10510 let selections = self
10511 .selections
10512 .all::<usize>(&self.display_snapshot(cx))
10513 .into_iter()
10514 .map(|s| s.range());
10515
10516 self.transact(window, cx, |this, window, cx| {
10517 this.buffer.update(cx, |buffer, cx| {
10518 buffer.autoindent_ranges(selections, cx);
10519 });
10520 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10521 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10522 });
10523 }
10524
10525 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10527 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10528 let selections = self.selections.all::<Point>(&display_map);
10529
10530 let mut new_cursors = Vec::new();
10531 let mut edit_ranges = Vec::new();
10532 let mut selections = selections.iter().peekable();
10533 while let Some(selection) = selections.next() {
10534 let mut rows = selection.spanned_rows(false, &display_map);
10535
10536 // Accumulate contiguous regions of rows that we want to delete.
10537 while let Some(next_selection) = selections.peek() {
10538 let next_rows = next_selection.spanned_rows(false, &display_map);
10539 if next_rows.start <= rows.end {
10540 rows.end = next_rows.end;
10541 selections.next().unwrap();
10542 } else {
10543 break;
10544 }
10545 }
10546
10547 let buffer = display_map.buffer_snapshot();
10548 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10549 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10550 // If there's a line after the range, delete the \n from the end of the row range
10551 (
10552 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10553 rows.end,
10554 )
10555 } else {
10556 // If there isn't a line after the range, delete the \n from the line before the
10557 // start of the row range
10558 edit_start = edit_start.saturating_sub(1);
10559 (buffer.len(), rows.start.previous_row())
10560 };
10561
10562 let text_layout_details = self.text_layout_details(window);
10563 let x = display_map.x_for_display_point(
10564 selection.head().to_display_point(&display_map),
10565 &text_layout_details,
10566 );
10567 let row = Point::new(target_row.0, 0)
10568 .to_display_point(&display_map)
10569 .row();
10570 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10571
10572 new_cursors.push((
10573 selection.id,
10574 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10575 SelectionGoal::None,
10576 ));
10577 edit_ranges.push(edit_start..edit_end);
10578 }
10579
10580 self.transact(window, cx, |this, window, cx| {
10581 let buffer = this.buffer.update(cx, |buffer, cx| {
10582 let empty_str: Arc<str> = Arc::default();
10583 buffer.edit(
10584 edit_ranges
10585 .into_iter()
10586 .map(|range| (range, empty_str.clone())),
10587 None,
10588 cx,
10589 );
10590 buffer.snapshot(cx)
10591 });
10592 let new_selections = new_cursors
10593 .into_iter()
10594 .map(|(id, cursor, goal)| {
10595 let cursor = cursor.to_point(&buffer);
10596 Selection {
10597 id,
10598 start: cursor,
10599 end: cursor,
10600 reversed: false,
10601 goal,
10602 }
10603 })
10604 .collect();
10605
10606 this.change_selections(Default::default(), window, cx, |s| {
10607 s.select(new_selections);
10608 });
10609 });
10610 }
10611
10612 pub fn join_lines_impl(
10613 &mut self,
10614 insert_whitespace: bool,
10615 window: &mut Window,
10616 cx: &mut Context<Self>,
10617 ) {
10618 if self.read_only(cx) {
10619 return;
10620 }
10621 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10622 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10623 let start = MultiBufferRow(selection.start.row);
10624 // Treat single line selections as if they include the next line. Otherwise this action
10625 // would do nothing for single line selections individual cursors.
10626 let end = if selection.start.row == selection.end.row {
10627 MultiBufferRow(selection.start.row + 1)
10628 } else {
10629 MultiBufferRow(selection.end.row)
10630 };
10631
10632 if let Some(last_row_range) = row_ranges.last_mut()
10633 && start <= last_row_range.end
10634 {
10635 last_row_range.end = end;
10636 continue;
10637 }
10638 row_ranges.push(start..end);
10639 }
10640
10641 let snapshot = self.buffer.read(cx).snapshot(cx);
10642 let mut cursor_positions = Vec::new();
10643 for row_range in &row_ranges {
10644 let anchor = snapshot.anchor_before(Point::new(
10645 row_range.end.previous_row().0,
10646 snapshot.line_len(row_range.end.previous_row()),
10647 ));
10648 cursor_positions.push(anchor..anchor);
10649 }
10650
10651 self.transact(window, cx, |this, window, cx| {
10652 for row_range in row_ranges.into_iter().rev() {
10653 for row in row_range.iter_rows().rev() {
10654 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10655 let next_line_row = row.next_row();
10656 let indent = snapshot.indent_size_for_line(next_line_row);
10657 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10658
10659 let replace =
10660 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10661 " "
10662 } else {
10663 ""
10664 };
10665
10666 this.buffer.update(cx, |buffer, cx| {
10667 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10668 });
10669 }
10670 }
10671
10672 this.change_selections(Default::default(), window, cx, |s| {
10673 s.select_anchor_ranges(cursor_positions)
10674 });
10675 });
10676 }
10677
10678 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10679 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10680 self.join_lines_impl(true, window, cx);
10681 }
10682
10683 pub fn sort_lines_case_sensitive(
10684 &mut self,
10685 _: &SortLinesCaseSensitive,
10686 window: &mut Window,
10687 cx: &mut Context<Self>,
10688 ) {
10689 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10690 }
10691
10692 pub fn sort_lines_by_length(
10693 &mut self,
10694 _: &SortLinesByLength,
10695 window: &mut Window,
10696 cx: &mut Context<Self>,
10697 ) {
10698 self.manipulate_immutable_lines(window, cx, |lines| {
10699 lines.sort_by_key(|&line| line.chars().count())
10700 })
10701 }
10702
10703 pub fn sort_lines_case_insensitive(
10704 &mut self,
10705 _: &SortLinesCaseInsensitive,
10706 window: &mut Window,
10707 cx: &mut Context<Self>,
10708 ) {
10709 self.manipulate_immutable_lines(window, cx, |lines| {
10710 lines.sort_by_key(|line| line.to_lowercase())
10711 })
10712 }
10713
10714 pub fn unique_lines_case_insensitive(
10715 &mut self,
10716 _: &UniqueLinesCaseInsensitive,
10717 window: &mut Window,
10718 cx: &mut Context<Self>,
10719 ) {
10720 self.manipulate_immutable_lines(window, cx, |lines| {
10721 let mut seen = HashSet::default();
10722 lines.retain(|line| seen.insert(line.to_lowercase()));
10723 })
10724 }
10725
10726 pub fn unique_lines_case_sensitive(
10727 &mut self,
10728 _: &UniqueLinesCaseSensitive,
10729 window: &mut Window,
10730 cx: &mut Context<Self>,
10731 ) {
10732 self.manipulate_immutable_lines(window, cx, |lines| {
10733 let mut seen = HashSet::default();
10734 lines.retain(|line| seen.insert(*line));
10735 })
10736 }
10737
10738 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10739 let snapshot = self.buffer.read(cx).snapshot(cx);
10740 for selection in self.selections.disjoint_anchors_arc().iter() {
10741 if snapshot
10742 .language_at(selection.start)
10743 .and_then(|lang| lang.config().wrap_characters.as_ref())
10744 .is_some()
10745 {
10746 return true;
10747 }
10748 }
10749 false
10750 }
10751
10752 fn wrap_selections_in_tag(
10753 &mut self,
10754 _: &WrapSelectionsInTag,
10755 window: &mut Window,
10756 cx: &mut Context<Self>,
10757 ) {
10758 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10759
10760 let snapshot = self.buffer.read(cx).snapshot(cx);
10761
10762 let mut edits = Vec::new();
10763 let mut boundaries = Vec::new();
10764
10765 for selection in self
10766 .selections
10767 .all::<Point>(&self.display_snapshot(cx))
10768 .iter()
10769 {
10770 let Some(wrap_config) = snapshot
10771 .language_at(selection.start)
10772 .and_then(|lang| lang.config().wrap_characters.clone())
10773 else {
10774 continue;
10775 };
10776
10777 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10778 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10779
10780 let start_before = snapshot.anchor_before(selection.start);
10781 let end_after = snapshot.anchor_after(selection.end);
10782
10783 edits.push((start_before..start_before, open_tag));
10784 edits.push((end_after..end_after, close_tag));
10785
10786 boundaries.push((
10787 start_before,
10788 end_after,
10789 wrap_config.start_prefix.len(),
10790 wrap_config.end_suffix.len(),
10791 ));
10792 }
10793
10794 if edits.is_empty() {
10795 return;
10796 }
10797
10798 self.transact(window, cx, |this, window, cx| {
10799 let buffer = this.buffer.update(cx, |buffer, cx| {
10800 buffer.edit(edits, None, cx);
10801 buffer.snapshot(cx)
10802 });
10803
10804 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10805 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10806 boundaries.into_iter()
10807 {
10808 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10809 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10810 new_selections.push(open_offset..open_offset);
10811 new_selections.push(close_offset..close_offset);
10812 }
10813
10814 this.change_selections(Default::default(), window, cx, |s| {
10815 s.select_ranges(new_selections);
10816 });
10817
10818 this.request_autoscroll(Autoscroll::fit(), cx);
10819 });
10820 }
10821
10822 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10823 let Some(project) = self.project.clone() else {
10824 return;
10825 };
10826 self.reload(project, window, cx)
10827 .detach_and_notify_err(window, cx);
10828 }
10829
10830 pub fn restore_file(
10831 &mut self,
10832 _: &::git::RestoreFile,
10833 window: &mut Window,
10834 cx: &mut Context<Self>,
10835 ) {
10836 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10837 let mut buffer_ids = HashSet::default();
10838 let snapshot = self.buffer().read(cx).snapshot(cx);
10839 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10840 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10841 }
10842
10843 let buffer = self.buffer().read(cx);
10844 let ranges = buffer_ids
10845 .into_iter()
10846 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10847 .collect::<Vec<_>>();
10848
10849 self.restore_hunks_in_ranges(ranges, window, cx);
10850 }
10851
10852 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10854 let selections = self
10855 .selections
10856 .all(&self.display_snapshot(cx))
10857 .into_iter()
10858 .map(|s| s.range())
10859 .collect();
10860 self.restore_hunks_in_ranges(selections, window, cx);
10861 }
10862
10863 pub fn restore_hunks_in_ranges(
10864 &mut self,
10865 ranges: Vec<Range<Point>>,
10866 window: &mut Window,
10867 cx: &mut Context<Editor>,
10868 ) {
10869 let mut revert_changes = HashMap::default();
10870 let chunk_by = self
10871 .snapshot(window, cx)
10872 .hunks_for_ranges(ranges)
10873 .into_iter()
10874 .chunk_by(|hunk| hunk.buffer_id);
10875 for (buffer_id, hunks) in &chunk_by {
10876 let hunks = hunks.collect::<Vec<_>>();
10877 for hunk in &hunks {
10878 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10879 }
10880 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10881 }
10882 drop(chunk_by);
10883 if !revert_changes.is_empty() {
10884 self.transact(window, cx, |editor, window, cx| {
10885 editor.restore(revert_changes, window, cx);
10886 });
10887 }
10888 }
10889
10890 pub fn open_active_item_in_terminal(
10891 &mut self,
10892 _: &OpenInTerminal,
10893 window: &mut Window,
10894 cx: &mut Context<Self>,
10895 ) {
10896 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10897 let project_path = buffer.read(cx).project_path(cx)?;
10898 let project = self.project()?.read(cx);
10899 let entry = project.entry_for_path(&project_path, cx)?;
10900 let parent = match &entry.canonical_path {
10901 Some(canonical_path) => canonical_path.to_path_buf(),
10902 None => project.absolute_path(&project_path, cx)?,
10903 }
10904 .parent()?
10905 .to_path_buf();
10906 Some(parent)
10907 }) {
10908 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10909 }
10910 }
10911
10912 fn set_breakpoint_context_menu(
10913 &mut self,
10914 display_row: DisplayRow,
10915 position: Option<Anchor>,
10916 clicked_point: gpui::Point<Pixels>,
10917 window: &mut Window,
10918 cx: &mut Context<Self>,
10919 ) {
10920 let source = self
10921 .buffer
10922 .read(cx)
10923 .snapshot(cx)
10924 .anchor_before(Point::new(display_row.0, 0u32));
10925
10926 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10927
10928 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10929 self,
10930 source,
10931 clicked_point,
10932 context_menu,
10933 window,
10934 cx,
10935 );
10936 }
10937
10938 fn add_edit_breakpoint_block(
10939 &mut self,
10940 anchor: Anchor,
10941 breakpoint: &Breakpoint,
10942 edit_action: BreakpointPromptEditAction,
10943 window: &mut Window,
10944 cx: &mut Context<Self>,
10945 ) {
10946 let weak_editor = cx.weak_entity();
10947 let bp_prompt = cx.new(|cx| {
10948 BreakpointPromptEditor::new(
10949 weak_editor,
10950 anchor,
10951 breakpoint.clone(),
10952 edit_action,
10953 window,
10954 cx,
10955 )
10956 });
10957
10958 let height = bp_prompt.update(cx, |this, cx| {
10959 this.prompt
10960 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10961 });
10962 let cloned_prompt = bp_prompt.clone();
10963 let blocks = vec![BlockProperties {
10964 style: BlockStyle::Sticky,
10965 placement: BlockPlacement::Above(anchor),
10966 height: Some(height),
10967 render: Arc::new(move |cx| {
10968 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10969 cloned_prompt.clone().into_any_element()
10970 }),
10971 priority: 0,
10972 }];
10973
10974 let focus_handle = bp_prompt.focus_handle(cx);
10975 window.focus(&focus_handle);
10976
10977 let block_ids = self.insert_blocks(blocks, None, cx);
10978 bp_prompt.update(cx, |prompt, _| {
10979 prompt.add_block_ids(block_ids);
10980 });
10981 }
10982
10983 pub(crate) fn breakpoint_at_row(
10984 &self,
10985 row: u32,
10986 window: &mut Window,
10987 cx: &mut Context<Self>,
10988 ) -> Option<(Anchor, Breakpoint)> {
10989 let snapshot = self.snapshot(window, cx);
10990 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10991
10992 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10993 }
10994
10995 pub(crate) fn breakpoint_at_anchor(
10996 &self,
10997 breakpoint_position: Anchor,
10998 snapshot: &EditorSnapshot,
10999 cx: &mut Context<Self>,
11000 ) -> Option<(Anchor, Breakpoint)> {
11001 let buffer = self
11002 .buffer
11003 .read(cx)
11004 .buffer_for_anchor(breakpoint_position, cx)?;
11005
11006 let enclosing_excerpt = breakpoint_position.excerpt_id;
11007 let buffer_snapshot = buffer.read(cx).snapshot();
11008
11009 let row = buffer_snapshot
11010 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11011 .row;
11012
11013 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11014 let anchor_end = snapshot
11015 .buffer_snapshot()
11016 .anchor_after(Point::new(row, line_len));
11017
11018 self.breakpoint_store
11019 .as_ref()?
11020 .read_with(cx, |breakpoint_store, cx| {
11021 breakpoint_store
11022 .breakpoints(
11023 &buffer,
11024 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11025 &buffer_snapshot,
11026 cx,
11027 )
11028 .next()
11029 .and_then(|(bp, _)| {
11030 let breakpoint_row = buffer_snapshot
11031 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11032 .row;
11033
11034 if breakpoint_row == row {
11035 snapshot
11036 .buffer_snapshot()
11037 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11038 .map(|position| (position, bp.bp.clone()))
11039 } else {
11040 None
11041 }
11042 })
11043 })
11044 }
11045
11046 pub fn edit_log_breakpoint(
11047 &mut self,
11048 _: &EditLogBreakpoint,
11049 window: &mut Window,
11050 cx: &mut Context<Self>,
11051 ) {
11052 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11053 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11054 message: None,
11055 state: BreakpointState::Enabled,
11056 condition: None,
11057 hit_condition: None,
11058 });
11059
11060 self.add_edit_breakpoint_block(
11061 anchor,
11062 &breakpoint,
11063 BreakpointPromptEditAction::Log,
11064 window,
11065 cx,
11066 );
11067 }
11068 }
11069
11070 fn breakpoints_at_cursors(
11071 &self,
11072 window: &mut Window,
11073 cx: &mut Context<Self>,
11074 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11075 let snapshot = self.snapshot(window, cx);
11076 let cursors = self
11077 .selections
11078 .disjoint_anchors_arc()
11079 .iter()
11080 .map(|selection| {
11081 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11082
11083 let breakpoint_position = self
11084 .breakpoint_at_row(cursor_position.row, window, cx)
11085 .map(|bp| bp.0)
11086 .unwrap_or_else(|| {
11087 snapshot
11088 .display_snapshot
11089 .buffer_snapshot()
11090 .anchor_after(Point::new(cursor_position.row, 0))
11091 });
11092
11093 let breakpoint = self
11094 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11095 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11096
11097 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11098 })
11099 // 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.
11100 .collect::<HashMap<Anchor, _>>();
11101
11102 cursors.into_iter().collect()
11103 }
11104
11105 pub fn enable_breakpoint(
11106 &mut self,
11107 _: &crate::actions::EnableBreakpoint,
11108 window: &mut Window,
11109 cx: &mut Context<Self>,
11110 ) {
11111 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11112 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11113 continue;
11114 };
11115 self.edit_breakpoint_at_anchor(
11116 anchor,
11117 breakpoint,
11118 BreakpointEditAction::InvertState,
11119 cx,
11120 );
11121 }
11122 }
11123
11124 pub fn disable_breakpoint(
11125 &mut self,
11126 _: &crate::actions::DisableBreakpoint,
11127 window: &mut Window,
11128 cx: &mut Context<Self>,
11129 ) {
11130 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11131 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11132 continue;
11133 };
11134 self.edit_breakpoint_at_anchor(
11135 anchor,
11136 breakpoint,
11137 BreakpointEditAction::InvertState,
11138 cx,
11139 );
11140 }
11141 }
11142
11143 pub fn toggle_breakpoint(
11144 &mut self,
11145 _: &crate::actions::ToggleBreakpoint,
11146 window: &mut Window,
11147 cx: &mut Context<Self>,
11148 ) {
11149 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11150 if let Some(breakpoint) = breakpoint {
11151 self.edit_breakpoint_at_anchor(
11152 anchor,
11153 breakpoint,
11154 BreakpointEditAction::Toggle,
11155 cx,
11156 );
11157 } else {
11158 self.edit_breakpoint_at_anchor(
11159 anchor,
11160 Breakpoint::new_standard(),
11161 BreakpointEditAction::Toggle,
11162 cx,
11163 );
11164 }
11165 }
11166 }
11167
11168 pub fn edit_breakpoint_at_anchor(
11169 &mut self,
11170 breakpoint_position: Anchor,
11171 breakpoint: Breakpoint,
11172 edit_action: BreakpointEditAction,
11173 cx: &mut Context<Self>,
11174 ) {
11175 let Some(breakpoint_store) = &self.breakpoint_store else {
11176 return;
11177 };
11178
11179 let Some(buffer) = self
11180 .buffer
11181 .read(cx)
11182 .buffer_for_anchor(breakpoint_position, cx)
11183 else {
11184 return;
11185 };
11186
11187 breakpoint_store.update(cx, |breakpoint_store, cx| {
11188 breakpoint_store.toggle_breakpoint(
11189 buffer,
11190 BreakpointWithPosition {
11191 position: breakpoint_position.text_anchor,
11192 bp: breakpoint,
11193 },
11194 edit_action,
11195 cx,
11196 );
11197 });
11198
11199 cx.notify();
11200 }
11201
11202 #[cfg(any(test, feature = "test-support"))]
11203 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11204 self.breakpoint_store.clone()
11205 }
11206
11207 pub fn prepare_restore_change(
11208 &self,
11209 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11210 hunk: &MultiBufferDiffHunk,
11211 cx: &mut App,
11212 ) -> Option<()> {
11213 if hunk.is_created_file() {
11214 return None;
11215 }
11216 let buffer = self.buffer.read(cx);
11217 let diff = buffer.diff_for(hunk.buffer_id)?;
11218 let buffer = buffer.buffer(hunk.buffer_id)?;
11219 let buffer = buffer.read(cx);
11220 let original_text = diff
11221 .read(cx)
11222 .base_text()
11223 .as_rope()
11224 .slice(hunk.diff_base_byte_range.clone());
11225 let buffer_snapshot = buffer.snapshot();
11226 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11227 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11228 probe
11229 .0
11230 .start
11231 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11232 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11233 }) {
11234 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11235 Some(())
11236 } else {
11237 None
11238 }
11239 }
11240
11241 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11242 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11243 }
11244
11245 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11246 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11247 }
11248
11249 fn manipulate_lines<M>(
11250 &mut self,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 mut manipulate: M,
11254 ) where
11255 M: FnMut(&str) -> LineManipulationResult,
11256 {
11257 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11258
11259 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11260 let buffer = self.buffer.read(cx).snapshot(cx);
11261
11262 let mut edits = Vec::new();
11263
11264 let selections = self.selections.all::<Point>(&display_map);
11265 let mut selections = selections.iter().peekable();
11266 let mut contiguous_row_selections = Vec::new();
11267 let mut new_selections = Vec::new();
11268 let mut added_lines = 0;
11269 let mut removed_lines = 0;
11270
11271 while let Some(selection) = selections.next() {
11272 let (start_row, end_row) = consume_contiguous_rows(
11273 &mut contiguous_row_selections,
11274 selection,
11275 &display_map,
11276 &mut selections,
11277 );
11278
11279 let start_point = Point::new(start_row.0, 0);
11280 let end_point = Point::new(
11281 end_row.previous_row().0,
11282 buffer.line_len(end_row.previous_row()),
11283 );
11284 let text = buffer
11285 .text_for_range(start_point..end_point)
11286 .collect::<String>();
11287
11288 let LineManipulationResult {
11289 new_text,
11290 line_count_before,
11291 line_count_after,
11292 } = manipulate(&text);
11293
11294 edits.push((start_point..end_point, new_text));
11295
11296 // Selections must change based on added and removed line count
11297 let start_row =
11298 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11299 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11300 new_selections.push(Selection {
11301 id: selection.id,
11302 start: start_row,
11303 end: end_row,
11304 goal: SelectionGoal::None,
11305 reversed: selection.reversed,
11306 });
11307
11308 if line_count_after > line_count_before {
11309 added_lines += line_count_after - line_count_before;
11310 } else if line_count_before > line_count_after {
11311 removed_lines += line_count_before - line_count_after;
11312 }
11313 }
11314
11315 self.transact(window, cx, |this, window, cx| {
11316 let buffer = this.buffer.update(cx, |buffer, cx| {
11317 buffer.edit(edits, None, cx);
11318 buffer.snapshot(cx)
11319 });
11320
11321 // Recalculate offsets on newly edited buffer
11322 let new_selections = new_selections
11323 .iter()
11324 .map(|s| {
11325 let start_point = Point::new(s.start.0, 0);
11326 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11327 Selection {
11328 id: s.id,
11329 start: buffer.point_to_offset(start_point),
11330 end: buffer.point_to_offset(end_point),
11331 goal: s.goal,
11332 reversed: s.reversed,
11333 }
11334 })
11335 .collect();
11336
11337 this.change_selections(Default::default(), window, cx, |s| {
11338 s.select(new_selections);
11339 });
11340
11341 this.request_autoscroll(Autoscroll::fit(), cx);
11342 });
11343 }
11344
11345 fn manipulate_immutable_lines<Fn>(
11346 &mut self,
11347 window: &mut Window,
11348 cx: &mut Context<Self>,
11349 mut callback: Fn,
11350 ) where
11351 Fn: FnMut(&mut Vec<&str>),
11352 {
11353 self.manipulate_lines(window, cx, |text| {
11354 let mut lines: Vec<&str> = text.split('\n').collect();
11355 let line_count_before = lines.len();
11356
11357 callback(&mut lines);
11358
11359 LineManipulationResult {
11360 new_text: lines.join("\n"),
11361 line_count_before,
11362 line_count_after: lines.len(),
11363 }
11364 });
11365 }
11366
11367 fn manipulate_mutable_lines<Fn>(
11368 &mut self,
11369 window: &mut Window,
11370 cx: &mut Context<Self>,
11371 mut callback: Fn,
11372 ) where
11373 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11374 {
11375 self.manipulate_lines(window, cx, |text| {
11376 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11377 let line_count_before = lines.len();
11378
11379 callback(&mut lines);
11380
11381 LineManipulationResult {
11382 new_text: lines.join("\n"),
11383 line_count_before,
11384 line_count_after: lines.len(),
11385 }
11386 });
11387 }
11388
11389 pub fn convert_indentation_to_spaces(
11390 &mut self,
11391 _: &ConvertIndentationToSpaces,
11392 window: &mut Window,
11393 cx: &mut Context<Self>,
11394 ) {
11395 let settings = self.buffer.read(cx).language_settings(cx);
11396 let tab_size = settings.tab_size.get() as usize;
11397
11398 self.manipulate_mutable_lines(window, cx, |lines| {
11399 // Allocates a reasonably sized scratch buffer once for the whole loop
11400 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11401 // Avoids recomputing spaces that could be inserted many times
11402 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11403 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11404 .collect();
11405
11406 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11407 let mut chars = line.as_ref().chars();
11408 let mut col = 0;
11409 let mut changed = false;
11410
11411 for ch in chars.by_ref() {
11412 match ch {
11413 ' ' => {
11414 reindented_line.push(' ');
11415 col += 1;
11416 }
11417 '\t' => {
11418 // \t are converted to spaces depending on the current column
11419 let spaces_len = tab_size - (col % tab_size);
11420 reindented_line.extend(&space_cache[spaces_len - 1]);
11421 col += spaces_len;
11422 changed = true;
11423 }
11424 _ => {
11425 // If we dont append before break, the character is consumed
11426 reindented_line.push(ch);
11427 break;
11428 }
11429 }
11430 }
11431
11432 if !changed {
11433 reindented_line.clear();
11434 continue;
11435 }
11436 // Append the rest of the line and replace old reference with new one
11437 reindented_line.extend(chars);
11438 *line = Cow::Owned(reindented_line.clone());
11439 reindented_line.clear();
11440 }
11441 });
11442 }
11443
11444 pub fn convert_indentation_to_tabs(
11445 &mut self,
11446 _: &ConvertIndentationToTabs,
11447 window: &mut Window,
11448 cx: &mut Context<Self>,
11449 ) {
11450 let settings = self.buffer.read(cx).language_settings(cx);
11451 let tab_size = settings.tab_size.get() as usize;
11452
11453 self.manipulate_mutable_lines(window, cx, |lines| {
11454 // Allocates a reasonably sized buffer once for the whole loop
11455 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11456 // Avoids recomputing spaces that could be inserted many times
11457 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11458 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11459 .collect();
11460
11461 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11462 let mut chars = line.chars();
11463 let mut spaces_count = 0;
11464 let mut first_non_indent_char = None;
11465 let mut changed = false;
11466
11467 for ch in chars.by_ref() {
11468 match ch {
11469 ' ' => {
11470 // Keep track of spaces. Append \t when we reach tab_size
11471 spaces_count += 1;
11472 changed = true;
11473 if spaces_count == tab_size {
11474 reindented_line.push('\t');
11475 spaces_count = 0;
11476 }
11477 }
11478 '\t' => {
11479 reindented_line.push('\t');
11480 spaces_count = 0;
11481 }
11482 _ => {
11483 // Dont append it yet, we might have remaining spaces
11484 first_non_indent_char = Some(ch);
11485 break;
11486 }
11487 }
11488 }
11489
11490 if !changed {
11491 reindented_line.clear();
11492 continue;
11493 }
11494 // Remaining spaces that didn't make a full tab stop
11495 if spaces_count > 0 {
11496 reindented_line.extend(&space_cache[spaces_count - 1]);
11497 }
11498 // If we consume an extra character that was not indentation, add it back
11499 if let Some(extra_char) = first_non_indent_char {
11500 reindented_line.push(extra_char);
11501 }
11502 // Append the rest of the line and replace old reference with new one
11503 reindented_line.extend(chars);
11504 *line = Cow::Owned(reindented_line.clone());
11505 reindented_line.clear();
11506 }
11507 });
11508 }
11509
11510 pub fn convert_to_upper_case(
11511 &mut self,
11512 _: &ConvertToUpperCase,
11513 window: &mut Window,
11514 cx: &mut Context<Self>,
11515 ) {
11516 self.manipulate_text(window, cx, |text| text.to_uppercase())
11517 }
11518
11519 pub fn convert_to_lower_case(
11520 &mut self,
11521 _: &ConvertToLowerCase,
11522 window: &mut Window,
11523 cx: &mut Context<Self>,
11524 ) {
11525 self.manipulate_text(window, cx, |text| text.to_lowercase())
11526 }
11527
11528 pub fn convert_to_title_case(
11529 &mut self,
11530 _: &ConvertToTitleCase,
11531 window: &mut Window,
11532 cx: &mut Context<Self>,
11533 ) {
11534 self.manipulate_text(window, cx, |text| {
11535 text.split('\n')
11536 .map(|line| line.to_case(Case::Title))
11537 .join("\n")
11538 })
11539 }
11540
11541 pub fn convert_to_snake_case(
11542 &mut self,
11543 _: &ConvertToSnakeCase,
11544 window: &mut Window,
11545 cx: &mut Context<Self>,
11546 ) {
11547 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11548 }
11549
11550 pub fn convert_to_kebab_case(
11551 &mut self,
11552 _: &ConvertToKebabCase,
11553 window: &mut Window,
11554 cx: &mut Context<Self>,
11555 ) {
11556 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11557 }
11558
11559 pub fn convert_to_upper_camel_case(
11560 &mut self,
11561 _: &ConvertToUpperCamelCase,
11562 window: &mut Window,
11563 cx: &mut Context<Self>,
11564 ) {
11565 self.manipulate_text(window, cx, |text| {
11566 text.split('\n')
11567 .map(|line| line.to_case(Case::UpperCamel))
11568 .join("\n")
11569 })
11570 }
11571
11572 pub fn convert_to_lower_camel_case(
11573 &mut self,
11574 _: &ConvertToLowerCamelCase,
11575 window: &mut Window,
11576 cx: &mut Context<Self>,
11577 ) {
11578 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11579 }
11580
11581 pub fn convert_to_opposite_case(
11582 &mut self,
11583 _: &ConvertToOppositeCase,
11584 window: &mut Window,
11585 cx: &mut Context<Self>,
11586 ) {
11587 self.manipulate_text(window, cx, |text| {
11588 text.chars()
11589 .fold(String::with_capacity(text.len()), |mut t, c| {
11590 if c.is_uppercase() {
11591 t.extend(c.to_lowercase());
11592 } else {
11593 t.extend(c.to_uppercase());
11594 }
11595 t
11596 })
11597 })
11598 }
11599
11600 pub fn convert_to_sentence_case(
11601 &mut self,
11602 _: &ConvertToSentenceCase,
11603 window: &mut Window,
11604 cx: &mut Context<Self>,
11605 ) {
11606 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11607 }
11608
11609 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11610 self.manipulate_text(window, cx, |text| {
11611 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11612 if has_upper_case_characters {
11613 text.to_lowercase()
11614 } else {
11615 text.to_uppercase()
11616 }
11617 })
11618 }
11619
11620 pub fn convert_to_rot13(
11621 &mut self,
11622 _: &ConvertToRot13,
11623 window: &mut Window,
11624 cx: &mut Context<Self>,
11625 ) {
11626 self.manipulate_text(window, cx, |text| {
11627 text.chars()
11628 .map(|c| match c {
11629 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11630 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11631 _ => c,
11632 })
11633 .collect()
11634 })
11635 }
11636
11637 pub fn convert_to_rot47(
11638 &mut self,
11639 _: &ConvertToRot47,
11640 window: &mut Window,
11641 cx: &mut Context<Self>,
11642 ) {
11643 self.manipulate_text(window, cx, |text| {
11644 text.chars()
11645 .map(|c| {
11646 let code_point = c as u32;
11647 if code_point >= 33 && code_point <= 126 {
11648 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11649 }
11650 c
11651 })
11652 .collect()
11653 })
11654 }
11655
11656 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11657 where
11658 Fn: FnMut(&str) -> String,
11659 {
11660 let buffer = self.buffer.read(cx).snapshot(cx);
11661
11662 let mut new_selections = Vec::new();
11663 let mut edits = Vec::new();
11664 let mut selection_adjustment = 0i32;
11665
11666 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11667 let selection_is_empty = selection.is_empty();
11668
11669 let (start, end) = if selection_is_empty {
11670 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11671 (word_range.start, word_range.end)
11672 } else {
11673 (
11674 buffer.point_to_offset(selection.start),
11675 buffer.point_to_offset(selection.end),
11676 )
11677 };
11678
11679 let text = buffer.text_for_range(start..end).collect::<String>();
11680 let old_length = text.len() as i32;
11681 let text = callback(&text);
11682
11683 new_selections.push(Selection {
11684 start: (start as i32 - selection_adjustment) as usize,
11685 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11686 goal: SelectionGoal::None,
11687 id: selection.id,
11688 reversed: selection.reversed,
11689 });
11690
11691 selection_adjustment += old_length - text.len() as i32;
11692
11693 edits.push((start..end, text));
11694 }
11695
11696 self.transact(window, cx, |this, window, cx| {
11697 this.buffer.update(cx, |buffer, cx| {
11698 buffer.edit(edits, None, cx);
11699 });
11700
11701 this.change_selections(Default::default(), window, cx, |s| {
11702 s.select(new_selections);
11703 });
11704
11705 this.request_autoscroll(Autoscroll::fit(), cx);
11706 });
11707 }
11708
11709 pub fn move_selection_on_drop(
11710 &mut self,
11711 selection: &Selection<Anchor>,
11712 target: DisplayPoint,
11713 is_cut: bool,
11714 window: &mut Window,
11715 cx: &mut Context<Self>,
11716 ) {
11717 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11718 let buffer = display_map.buffer_snapshot();
11719 let mut edits = Vec::new();
11720 let insert_point = display_map
11721 .clip_point(target, Bias::Left)
11722 .to_point(&display_map);
11723 let text = buffer
11724 .text_for_range(selection.start..selection.end)
11725 .collect::<String>();
11726 if is_cut {
11727 edits.push(((selection.start..selection.end), String::new()));
11728 }
11729 let insert_anchor = buffer.anchor_before(insert_point);
11730 edits.push(((insert_anchor..insert_anchor), text));
11731 let last_edit_start = insert_anchor.bias_left(buffer);
11732 let last_edit_end = insert_anchor.bias_right(buffer);
11733 self.transact(window, cx, |this, window, cx| {
11734 this.buffer.update(cx, |buffer, cx| {
11735 buffer.edit(edits, None, cx);
11736 });
11737 this.change_selections(Default::default(), window, cx, |s| {
11738 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11739 });
11740 });
11741 }
11742
11743 pub fn clear_selection_drag_state(&mut self) {
11744 self.selection_drag_state = SelectionDragState::None;
11745 }
11746
11747 pub fn duplicate(
11748 &mut self,
11749 upwards: bool,
11750 whole_lines: bool,
11751 window: &mut Window,
11752 cx: &mut Context<Self>,
11753 ) {
11754 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11755
11756 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11757 let buffer = display_map.buffer_snapshot();
11758 let selections = self.selections.all::<Point>(&display_map);
11759
11760 let mut edits = Vec::new();
11761 let mut selections_iter = selections.iter().peekable();
11762 while let Some(selection) = selections_iter.next() {
11763 let mut rows = selection.spanned_rows(false, &display_map);
11764 // duplicate line-wise
11765 if whole_lines || selection.start == selection.end {
11766 // Avoid duplicating the same lines twice.
11767 while let Some(next_selection) = selections_iter.peek() {
11768 let next_rows = next_selection.spanned_rows(false, &display_map);
11769 if next_rows.start < rows.end {
11770 rows.end = next_rows.end;
11771 selections_iter.next().unwrap();
11772 } else {
11773 break;
11774 }
11775 }
11776
11777 // Copy the text from the selected row region and splice it either at the start
11778 // or end of the region.
11779 let start = Point::new(rows.start.0, 0);
11780 let end = Point::new(
11781 rows.end.previous_row().0,
11782 buffer.line_len(rows.end.previous_row()),
11783 );
11784
11785 let mut text = buffer.text_for_range(start..end).collect::<String>();
11786
11787 let insert_location = if upwards {
11788 // When duplicating upward, we need to insert before the current line.
11789 // If we're on the last line and it doesn't end with a newline,
11790 // we need to add a newline before the duplicated content.
11791 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11792 && buffer.max_point().column > 0
11793 && !text.ends_with('\n');
11794
11795 if needs_leading_newline {
11796 text.insert(0, '\n');
11797 end
11798 } else {
11799 text.push('\n');
11800 Point::new(rows.end.0, 0)
11801 }
11802 } else {
11803 text.push('\n');
11804 start
11805 };
11806 edits.push((insert_location..insert_location, text));
11807 } else {
11808 // duplicate character-wise
11809 let start = selection.start;
11810 let end = selection.end;
11811 let text = buffer.text_for_range(start..end).collect::<String>();
11812 edits.push((selection.end..selection.end, text));
11813 }
11814 }
11815
11816 self.transact(window, cx, |this, _, cx| {
11817 this.buffer.update(cx, |buffer, cx| {
11818 buffer.edit(edits, None, cx);
11819 });
11820
11821 this.request_autoscroll(Autoscroll::fit(), cx);
11822 });
11823 }
11824
11825 pub fn duplicate_line_up(
11826 &mut self,
11827 _: &DuplicateLineUp,
11828 window: &mut Window,
11829 cx: &mut Context<Self>,
11830 ) {
11831 self.duplicate(true, true, window, cx);
11832 }
11833
11834 pub fn duplicate_line_down(
11835 &mut self,
11836 _: &DuplicateLineDown,
11837 window: &mut Window,
11838 cx: &mut Context<Self>,
11839 ) {
11840 self.duplicate(false, true, window, cx);
11841 }
11842
11843 pub fn duplicate_selection(
11844 &mut self,
11845 _: &DuplicateSelection,
11846 window: &mut Window,
11847 cx: &mut Context<Self>,
11848 ) {
11849 self.duplicate(false, false, window, cx);
11850 }
11851
11852 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11853 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11854 if self.mode.is_single_line() {
11855 cx.propagate();
11856 return;
11857 }
11858
11859 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11860 let buffer = self.buffer.read(cx).snapshot(cx);
11861
11862 let mut edits = Vec::new();
11863 let mut unfold_ranges = Vec::new();
11864 let mut refold_creases = Vec::new();
11865
11866 let selections = self.selections.all::<Point>(&display_map);
11867 let mut selections = selections.iter().peekable();
11868 let mut contiguous_row_selections = Vec::new();
11869 let mut new_selections = Vec::new();
11870
11871 while let Some(selection) = selections.next() {
11872 // Find all the selections that span a contiguous row range
11873 let (start_row, end_row) = consume_contiguous_rows(
11874 &mut contiguous_row_selections,
11875 selection,
11876 &display_map,
11877 &mut selections,
11878 );
11879
11880 // Move the text spanned by the row range to be before the line preceding the row range
11881 if start_row.0 > 0 {
11882 let range_to_move = Point::new(
11883 start_row.previous_row().0,
11884 buffer.line_len(start_row.previous_row()),
11885 )
11886 ..Point::new(
11887 end_row.previous_row().0,
11888 buffer.line_len(end_row.previous_row()),
11889 );
11890 let insertion_point = display_map
11891 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11892 .0;
11893
11894 // Don't move lines across excerpts
11895 if buffer
11896 .excerpt_containing(insertion_point..range_to_move.end)
11897 .is_some()
11898 {
11899 let text = buffer
11900 .text_for_range(range_to_move.clone())
11901 .flat_map(|s| s.chars())
11902 .skip(1)
11903 .chain(['\n'])
11904 .collect::<String>();
11905
11906 edits.push((
11907 buffer.anchor_after(range_to_move.start)
11908 ..buffer.anchor_before(range_to_move.end),
11909 String::new(),
11910 ));
11911 let insertion_anchor = buffer.anchor_after(insertion_point);
11912 edits.push((insertion_anchor..insertion_anchor, text));
11913
11914 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11915
11916 // Move selections up
11917 new_selections.extend(contiguous_row_selections.drain(..).map(
11918 |mut selection| {
11919 selection.start.row -= row_delta;
11920 selection.end.row -= row_delta;
11921 selection
11922 },
11923 ));
11924
11925 // Move folds up
11926 unfold_ranges.push(range_to_move.clone());
11927 for fold in display_map.folds_in_range(
11928 buffer.anchor_before(range_to_move.start)
11929 ..buffer.anchor_after(range_to_move.end),
11930 ) {
11931 let mut start = fold.range.start.to_point(&buffer);
11932 let mut end = fold.range.end.to_point(&buffer);
11933 start.row -= row_delta;
11934 end.row -= row_delta;
11935 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11936 }
11937 }
11938 }
11939
11940 // If we didn't move line(s), preserve the existing selections
11941 new_selections.append(&mut contiguous_row_selections);
11942 }
11943
11944 self.transact(window, cx, |this, window, cx| {
11945 this.unfold_ranges(&unfold_ranges, true, true, cx);
11946 this.buffer.update(cx, |buffer, cx| {
11947 for (range, text) in edits {
11948 buffer.edit([(range, text)], None, cx);
11949 }
11950 });
11951 this.fold_creases(refold_creases, true, window, cx);
11952 this.change_selections(Default::default(), window, cx, |s| {
11953 s.select(new_selections);
11954 })
11955 });
11956 }
11957
11958 pub fn move_line_down(
11959 &mut self,
11960 _: &MoveLineDown,
11961 window: &mut Window,
11962 cx: &mut Context<Self>,
11963 ) {
11964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11965 if self.mode.is_single_line() {
11966 cx.propagate();
11967 return;
11968 }
11969
11970 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11971 let buffer = self.buffer.read(cx).snapshot(cx);
11972
11973 let mut edits = Vec::new();
11974 let mut unfold_ranges = Vec::new();
11975 let mut refold_creases = Vec::new();
11976
11977 let selections = self.selections.all::<Point>(&display_map);
11978 let mut selections = selections.iter().peekable();
11979 let mut contiguous_row_selections = Vec::new();
11980 let mut new_selections = Vec::new();
11981
11982 while let Some(selection) = selections.next() {
11983 // Find all the selections that span a contiguous row range
11984 let (start_row, end_row) = consume_contiguous_rows(
11985 &mut contiguous_row_selections,
11986 selection,
11987 &display_map,
11988 &mut selections,
11989 );
11990
11991 // Move the text spanned by the row range to be after the last line of the row range
11992 if end_row.0 <= buffer.max_point().row {
11993 let range_to_move =
11994 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11995 let insertion_point = display_map
11996 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11997 .0;
11998
11999 // Don't move lines across excerpt boundaries
12000 if buffer
12001 .excerpt_containing(range_to_move.start..insertion_point)
12002 .is_some()
12003 {
12004 let mut text = String::from("\n");
12005 text.extend(buffer.text_for_range(range_to_move.clone()));
12006 text.pop(); // Drop trailing newline
12007 edits.push((
12008 buffer.anchor_after(range_to_move.start)
12009 ..buffer.anchor_before(range_to_move.end),
12010 String::new(),
12011 ));
12012 let insertion_anchor = buffer.anchor_after(insertion_point);
12013 edits.push((insertion_anchor..insertion_anchor, text));
12014
12015 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12016
12017 // Move selections down
12018 new_selections.extend(contiguous_row_selections.drain(..).map(
12019 |mut selection| {
12020 selection.start.row += row_delta;
12021 selection.end.row += row_delta;
12022 selection
12023 },
12024 ));
12025
12026 // Move folds down
12027 unfold_ranges.push(range_to_move.clone());
12028 for fold in display_map.folds_in_range(
12029 buffer.anchor_before(range_to_move.start)
12030 ..buffer.anchor_after(range_to_move.end),
12031 ) {
12032 let mut start = fold.range.start.to_point(&buffer);
12033 let mut end = fold.range.end.to_point(&buffer);
12034 start.row += row_delta;
12035 end.row += row_delta;
12036 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12037 }
12038 }
12039 }
12040
12041 // If we didn't move line(s), preserve the existing selections
12042 new_selections.append(&mut contiguous_row_selections);
12043 }
12044
12045 self.transact(window, cx, |this, window, cx| {
12046 this.unfold_ranges(&unfold_ranges, true, true, cx);
12047 this.buffer.update(cx, |buffer, cx| {
12048 for (range, text) in edits {
12049 buffer.edit([(range, text)], None, cx);
12050 }
12051 });
12052 this.fold_creases(refold_creases, true, window, cx);
12053 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12054 });
12055 }
12056
12057 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12058 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12059 let text_layout_details = &self.text_layout_details(window);
12060 self.transact(window, cx, |this, window, cx| {
12061 let edits = this.change_selections(Default::default(), window, cx, |s| {
12062 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12063 s.move_with(|display_map, selection| {
12064 if !selection.is_empty() {
12065 return;
12066 }
12067
12068 let mut head = selection.head();
12069 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12070 if head.column() == display_map.line_len(head.row()) {
12071 transpose_offset = display_map
12072 .buffer_snapshot()
12073 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12074 }
12075
12076 if transpose_offset == 0 {
12077 return;
12078 }
12079
12080 *head.column_mut() += 1;
12081 head = display_map.clip_point(head, Bias::Right);
12082 let goal = SelectionGoal::HorizontalPosition(
12083 display_map
12084 .x_for_display_point(head, text_layout_details)
12085 .into(),
12086 );
12087 selection.collapse_to(head, goal);
12088
12089 let transpose_start = display_map
12090 .buffer_snapshot()
12091 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12092 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12093 let transpose_end = display_map
12094 .buffer_snapshot()
12095 .clip_offset(transpose_offset + 1, Bias::Right);
12096 if let Some(ch) = display_map
12097 .buffer_snapshot()
12098 .chars_at(transpose_start)
12099 .next()
12100 {
12101 edits.push((transpose_start..transpose_offset, String::new()));
12102 edits.push((transpose_end..transpose_end, ch.to_string()));
12103 }
12104 }
12105 });
12106 edits
12107 });
12108 this.buffer
12109 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12110 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12111 this.change_selections(Default::default(), window, cx, |s| {
12112 s.select(selections);
12113 });
12114 });
12115 }
12116
12117 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12119 if self.mode.is_single_line() {
12120 cx.propagate();
12121 return;
12122 }
12123
12124 self.rewrap_impl(RewrapOptions::default(), cx)
12125 }
12126
12127 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12128 let buffer = self.buffer.read(cx).snapshot(cx);
12129 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12130
12131 #[derive(Clone, Debug, PartialEq)]
12132 enum CommentFormat {
12133 /// single line comment, with prefix for line
12134 Line(String),
12135 /// single line within a block comment, with prefix for line
12136 BlockLine(String),
12137 /// a single line of a block comment that includes the initial delimiter
12138 BlockCommentWithStart(BlockCommentConfig),
12139 /// a single line of a block comment that includes the ending delimiter
12140 BlockCommentWithEnd(BlockCommentConfig),
12141 }
12142
12143 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12144 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12145 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12146 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12147 .peekable();
12148
12149 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12150 row
12151 } else {
12152 return Vec::new();
12153 };
12154
12155 let language_settings = buffer.language_settings_at(selection.head(), cx);
12156 let language_scope = buffer.language_scope_at(selection.head());
12157
12158 let indent_and_prefix_for_row =
12159 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12160 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12161 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12162 &language_scope
12163 {
12164 let indent_end = Point::new(row, indent.len);
12165 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12166 let line_text_after_indent = buffer
12167 .text_for_range(indent_end..line_end)
12168 .collect::<String>();
12169
12170 let is_within_comment_override = buffer
12171 .language_scope_at(indent_end)
12172 .is_some_and(|scope| scope.override_name() == Some("comment"));
12173 let comment_delimiters = if is_within_comment_override {
12174 // we are within a comment syntax node, but we don't
12175 // yet know what kind of comment: block, doc or line
12176 match (
12177 language_scope.documentation_comment(),
12178 language_scope.block_comment(),
12179 ) {
12180 (Some(config), _) | (_, Some(config))
12181 if buffer.contains_str_at(indent_end, &config.start) =>
12182 {
12183 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12184 }
12185 (Some(config), _) | (_, Some(config))
12186 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12187 {
12188 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12189 }
12190 (Some(config), _) | (_, Some(config))
12191 if buffer.contains_str_at(indent_end, &config.prefix) =>
12192 {
12193 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12194 }
12195 (_, _) => language_scope
12196 .line_comment_prefixes()
12197 .iter()
12198 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12199 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12200 }
12201 } else {
12202 // we not in an overridden comment node, but we may
12203 // be within a non-overridden line comment node
12204 language_scope
12205 .line_comment_prefixes()
12206 .iter()
12207 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12208 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12209 };
12210
12211 let rewrap_prefix = language_scope
12212 .rewrap_prefixes()
12213 .iter()
12214 .find_map(|prefix_regex| {
12215 prefix_regex.find(&line_text_after_indent).map(|mat| {
12216 if mat.start() == 0 {
12217 Some(mat.as_str().to_string())
12218 } else {
12219 None
12220 }
12221 })
12222 })
12223 .flatten();
12224 (comment_delimiters, rewrap_prefix)
12225 } else {
12226 (None, None)
12227 };
12228 (indent, comment_prefix, rewrap_prefix)
12229 };
12230
12231 let mut ranges = Vec::new();
12232 let from_empty_selection = selection.is_empty();
12233
12234 let mut current_range_start = first_row;
12235 let mut prev_row = first_row;
12236 let (
12237 mut current_range_indent,
12238 mut current_range_comment_delimiters,
12239 mut current_range_rewrap_prefix,
12240 ) = indent_and_prefix_for_row(first_row);
12241
12242 for row in non_blank_rows_iter.skip(1) {
12243 let has_paragraph_break = row > prev_row + 1;
12244
12245 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12246 indent_and_prefix_for_row(row);
12247
12248 let has_indent_change = row_indent != current_range_indent;
12249 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12250
12251 let has_boundary_change = has_comment_change
12252 || row_rewrap_prefix.is_some()
12253 || (has_indent_change && current_range_comment_delimiters.is_some());
12254
12255 if has_paragraph_break || has_boundary_change {
12256 ranges.push((
12257 language_settings.clone(),
12258 Point::new(current_range_start, 0)
12259 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12260 current_range_indent,
12261 current_range_comment_delimiters.clone(),
12262 current_range_rewrap_prefix.clone(),
12263 from_empty_selection,
12264 ));
12265 current_range_start = row;
12266 current_range_indent = row_indent;
12267 current_range_comment_delimiters = row_comment_delimiters;
12268 current_range_rewrap_prefix = row_rewrap_prefix;
12269 }
12270 prev_row = row;
12271 }
12272
12273 ranges.push((
12274 language_settings.clone(),
12275 Point::new(current_range_start, 0)
12276 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12277 current_range_indent,
12278 current_range_comment_delimiters,
12279 current_range_rewrap_prefix,
12280 from_empty_selection,
12281 ));
12282
12283 ranges
12284 });
12285
12286 let mut edits = Vec::new();
12287 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12288
12289 for (
12290 language_settings,
12291 wrap_range,
12292 mut indent_size,
12293 comment_prefix,
12294 rewrap_prefix,
12295 from_empty_selection,
12296 ) in wrap_ranges
12297 {
12298 let mut start_row = wrap_range.start.row;
12299 let mut end_row = wrap_range.end.row;
12300
12301 // Skip selections that overlap with a range that has already been rewrapped.
12302 let selection_range = start_row..end_row;
12303 if rewrapped_row_ranges
12304 .iter()
12305 .any(|range| range.overlaps(&selection_range))
12306 {
12307 continue;
12308 }
12309
12310 let tab_size = language_settings.tab_size;
12311
12312 let (line_prefix, inside_comment) = match &comment_prefix {
12313 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12314 (Some(prefix.as_str()), true)
12315 }
12316 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12317 (Some(prefix.as_ref()), true)
12318 }
12319 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12320 start: _,
12321 end: _,
12322 prefix,
12323 tab_size,
12324 })) => {
12325 indent_size.len += tab_size;
12326 (Some(prefix.as_ref()), true)
12327 }
12328 None => (None, false),
12329 };
12330 let indent_prefix = indent_size.chars().collect::<String>();
12331 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12332
12333 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12334 RewrapBehavior::InComments => inside_comment,
12335 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12336 RewrapBehavior::Anywhere => true,
12337 };
12338
12339 let should_rewrap = options.override_language_settings
12340 || allow_rewrap_based_on_language
12341 || self.hard_wrap.is_some();
12342 if !should_rewrap {
12343 continue;
12344 }
12345
12346 if from_empty_selection {
12347 'expand_upwards: while start_row > 0 {
12348 let prev_row = start_row - 1;
12349 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12350 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12351 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12352 {
12353 start_row = prev_row;
12354 } else {
12355 break 'expand_upwards;
12356 }
12357 }
12358
12359 'expand_downwards: while end_row < buffer.max_point().row {
12360 let next_row = end_row + 1;
12361 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12362 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12363 && !buffer.is_line_blank(MultiBufferRow(next_row))
12364 {
12365 end_row = next_row;
12366 } else {
12367 break 'expand_downwards;
12368 }
12369 }
12370 }
12371
12372 let start = Point::new(start_row, 0);
12373 let start_offset = ToOffset::to_offset(&start, &buffer);
12374 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12375 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12376 let mut first_line_delimiter = None;
12377 let mut last_line_delimiter = None;
12378 let Some(lines_without_prefixes) = selection_text
12379 .lines()
12380 .enumerate()
12381 .map(|(ix, line)| {
12382 let line_trimmed = line.trim_start();
12383 if rewrap_prefix.is_some() && ix > 0 {
12384 Ok(line_trimmed)
12385 } else if let Some(
12386 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12387 start,
12388 prefix,
12389 end,
12390 tab_size,
12391 })
12392 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12393 start,
12394 prefix,
12395 end,
12396 tab_size,
12397 }),
12398 ) = &comment_prefix
12399 {
12400 let line_trimmed = line_trimmed
12401 .strip_prefix(start.as_ref())
12402 .map(|s| {
12403 let mut indent_size = indent_size;
12404 indent_size.len -= tab_size;
12405 let indent_prefix: String = indent_size.chars().collect();
12406 first_line_delimiter = Some((indent_prefix, start));
12407 s.trim_start()
12408 })
12409 .unwrap_or(line_trimmed);
12410 let line_trimmed = line_trimmed
12411 .strip_suffix(end.as_ref())
12412 .map(|s| {
12413 last_line_delimiter = Some(end);
12414 s.trim_end()
12415 })
12416 .unwrap_or(line_trimmed);
12417 let line_trimmed = line_trimmed
12418 .strip_prefix(prefix.as_ref())
12419 .unwrap_or(line_trimmed);
12420 Ok(line_trimmed)
12421 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12422 line_trimmed.strip_prefix(prefix).with_context(|| {
12423 format!("line did not start with prefix {prefix:?}: {line:?}")
12424 })
12425 } else {
12426 line_trimmed
12427 .strip_prefix(&line_prefix.trim_start())
12428 .with_context(|| {
12429 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12430 })
12431 }
12432 })
12433 .collect::<Result<Vec<_>, _>>()
12434 .log_err()
12435 else {
12436 continue;
12437 };
12438
12439 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12440 buffer
12441 .language_settings_at(Point::new(start_row, 0), cx)
12442 .preferred_line_length as usize
12443 });
12444
12445 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12446 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12447 } else {
12448 line_prefix.clone()
12449 };
12450
12451 let wrapped_text = {
12452 let mut wrapped_text = wrap_with_prefix(
12453 line_prefix,
12454 subsequent_lines_prefix,
12455 lines_without_prefixes.join("\n"),
12456 wrap_column,
12457 tab_size,
12458 options.preserve_existing_whitespace,
12459 );
12460
12461 if let Some((indent, delimiter)) = first_line_delimiter {
12462 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12463 }
12464 if let Some(last_line) = last_line_delimiter {
12465 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12466 }
12467
12468 wrapped_text
12469 };
12470
12471 // TODO: should always use char-based diff while still supporting cursor behavior that
12472 // matches vim.
12473 let mut diff_options = DiffOptions::default();
12474 if options.override_language_settings {
12475 diff_options.max_word_diff_len = 0;
12476 diff_options.max_word_diff_line_count = 0;
12477 } else {
12478 diff_options.max_word_diff_len = usize::MAX;
12479 diff_options.max_word_diff_line_count = usize::MAX;
12480 }
12481
12482 for (old_range, new_text) in
12483 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12484 {
12485 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12486 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12487 edits.push((edit_start..edit_end, new_text));
12488 }
12489
12490 rewrapped_row_ranges.push(start_row..=end_row);
12491 }
12492
12493 self.buffer
12494 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12495 }
12496
12497 pub fn cut_common(
12498 &mut self,
12499 cut_no_selection_line: bool,
12500 window: &mut Window,
12501 cx: &mut Context<Self>,
12502 ) -> ClipboardItem {
12503 let mut text = String::new();
12504 let buffer = self.buffer.read(cx).snapshot(cx);
12505 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12506 let mut clipboard_selections = Vec::with_capacity(selections.len());
12507 {
12508 let max_point = buffer.max_point();
12509 let mut is_first = true;
12510 for selection in &mut selections {
12511 let is_entire_line =
12512 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12513 if is_entire_line {
12514 selection.start = Point::new(selection.start.row, 0);
12515 if !selection.is_empty() && selection.end.column == 0 {
12516 selection.end = cmp::min(max_point, selection.end);
12517 } else {
12518 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12519 }
12520 selection.goal = SelectionGoal::None;
12521 }
12522 if is_first {
12523 is_first = false;
12524 } else {
12525 text += "\n";
12526 }
12527 let mut len = 0;
12528 for chunk in buffer.text_for_range(selection.start..selection.end) {
12529 text.push_str(chunk);
12530 len += chunk.len();
12531 }
12532 clipboard_selections.push(ClipboardSelection {
12533 len,
12534 is_entire_line,
12535 first_line_indent: buffer
12536 .indent_size_for_line(MultiBufferRow(selection.start.row))
12537 .len,
12538 });
12539 }
12540 }
12541
12542 self.transact(window, cx, |this, window, cx| {
12543 this.change_selections(Default::default(), window, cx, |s| {
12544 s.select(selections);
12545 });
12546 this.insert("", window, cx);
12547 });
12548 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12549 }
12550
12551 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12552 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12553 let item = self.cut_common(true, window, cx);
12554 cx.write_to_clipboard(item);
12555 }
12556
12557 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12558 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12559 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12560 s.move_with(|snapshot, sel| {
12561 if sel.is_empty() {
12562 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12563 }
12564 if sel.is_empty() {
12565 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12566 }
12567 });
12568 });
12569 let item = self.cut_common(false, window, cx);
12570 cx.set_global(KillRing(item))
12571 }
12572
12573 pub fn kill_ring_yank(
12574 &mut self,
12575 _: &KillRingYank,
12576 window: &mut Window,
12577 cx: &mut Context<Self>,
12578 ) {
12579 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12580 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12581 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12582 (kill_ring.text().to_string(), kill_ring.metadata_json())
12583 } else {
12584 return;
12585 }
12586 } else {
12587 return;
12588 };
12589 self.do_paste(&text, metadata, false, window, cx);
12590 }
12591
12592 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12593 self.do_copy(true, cx);
12594 }
12595
12596 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12597 self.do_copy(false, cx);
12598 }
12599
12600 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12601 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12602 let buffer = self.buffer.read(cx).read(cx);
12603 let mut text = String::new();
12604
12605 let mut clipboard_selections = Vec::with_capacity(selections.len());
12606 {
12607 let max_point = buffer.max_point();
12608 let mut is_first = true;
12609 for selection in &selections {
12610 let mut start = selection.start;
12611 let mut end = selection.end;
12612 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12613 let mut add_trailing_newline = false;
12614 if is_entire_line {
12615 start = Point::new(start.row, 0);
12616 let next_line_start = Point::new(end.row + 1, 0);
12617 if next_line_start <= max_point {
12618 end = next_line_start;
12619 } else {
12620 // We're on the last line without a trailing newline.
12621 // Copy to the end of the line and add a newline afterwards.
12622 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12623 add_trailing_newline = true;
12624 }
12625 }
12626
12627 let mut trimmed_selections = Vec::new();
12628 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12629 let row = MultiBufferRow(start.row);
12630 let first_indent = buffer.indent_size_for_line(row);
12631 if first_indent.len == 0 || start.column > first_indent.len {
12632 trimmed_selections.push(start..end);
12633 } else {
12634 trimmed_selections.push(
12635 Point::new(row.0, first_indent.len)
12636 ..Point::new(row.0, buffer.line_len(row)),
12637 );
12638 for row in start.row + 1..=end.row {
12639 let mut line_len = buffer.line_len(MultiBufferRow(row));
12640 if row == end.row {
12641 line_len = end.column;
12642 }
12643 if line_len == 0 {
12644 trimmed_selections
12645 .push(Point::new(row, 0)..Point::new(row, line_len));
12646 continue;
12647 }
12648 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12649 if row_indent_size.len >= first_indent.len {
12650 trimmed_selections.push(
12651 Point::new(row, first_indent.len)..Point::new(row, line_len),
12652 );
12653 } else {
12654 trimmed_selections.clear();
12655 trimmed_selections.push(start..end);
12656 break;
12657 }
12658 }
12659 }
12660 } else {
12661 trimmed_selections.push(start..end);
12662 }
12663
12664 for trimmed_range in trimmed_selections {
12665 if is_first {
12666 is_first = false;
12667 } else {
12668 text += "\n";
12669 }
12670 let mut len = 0;
12671 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12672 text.push_str(chunk);
12673 len += chunk.len();
12674 }
12675 if add_trailing_newline {
12676 text.push('\n');
12677 len += 1;
12678 }
12679 clipboard_selections.push(ClipboardSelection {
12680 len,
12681 is_entire_line,
12682 first_line_indent: buffer
12683 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12684 .len,
12685 });
12686 }
12687 }
12688 }
12689
12690 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12691 text,
12692 clipboard_selections,
12693 ));
12694 }
12695
12696 pub fn do_paste(
12697 &mut self,
12698 text: &String,
12699 clipboard_selections: Option<Vec<ClipboardSelection>>,
12700 handle_entire_lines: bool,
12701 window: &mut Window,
12702 cx: &mut Context<Self>,
12703 ) {
12704 if self.read_only(cx) {
12705 return;
12706 }
12707
12708 let clipboard_text = Cow::Borrowed(text.as_str());
12709
12710 self.transact(window, cx, |this, window, cx| {
12711 let had_active_edit_prediction = this.has_active_edit_prediction();
12712 let display_map = this.display_snapshot(cx);
12713 let old_selections = this.selections.all::<usize>(&display_map);
12714 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12715
12716 if let Some(mut clipboard_selections) = clipboard_selections {
12717 let all_selections_were_entire_line =
12718 clipboard_selections.iter().all(|s| s.is_entire_line);
12719 let first_selection_indent_column =
12720 clipboard_selections.first().map(|s| s.first_line_indent);
12721 if clipboard_selections.len() != old_selections.len() {
12722 clipboard_selections.drain(..);
12723 }
12724 let mut auto_indent_on_paste = true;
12725
12726 this.buffer.update(cx, |buffer, cx| {
12727 let snapshot = buffer.read(cx);
12728 auto_indent_on_paste = snapshot
12729 .language_settings_at(cursor_offset, cx)
12730 .auto_indent_on_paste;
12731
12732 let mut start_offset = 0;
12733 let mut edits = Vec::new();
12734 let mut original_indent_columns = Vec::new();
12735 for (ix, selection) in old_selections.iter().enumerate() {
12736 let to_insert;
12737 let entire_line;
12738 let original_indent_column;
12739 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12740 let end_offset = start_offset + clipboard_selection.len;
12741 to_insert = &clipboard_text[start_offset..end_offset];
12742 entire_line = clipboard_selection.is_entire_line;
12743 start_offset = end_offset + 1;
12744 original_indent_column = Some(clipboard_selection.first_line_indent);
12745 } else {
12746 to_insert = &*clipboard_text;
12747 entire_line = all_selections_were_entire_line;
12748 original_indent_column = first_selection_indent_column
12749 }
12750
12751 let (range, to_insert) =
12752 if selection.is_empty() && handle_entire_lines && entire_line {
12753 // If the corresponding selection was empty when this slice of the
12754 // clipboard text was written, then the entire line containing the
12755 // selection was copied. If this selection is also currently empty,
12756 // then paste the line before the current line of the buffer.
12757 let column = selection.start.to_point(&snapshot).column as usize;
12758 let line_start = selection.start - column;
12759 (line_start..line_start, Cow::Borrowed(to_insert))
12760 } else {
12761 let language = snapshot.language_at(selection.head());
12762 let range = selection.range();
12763 if let Some(language) = language
12764 && language.name() == "Markdown".into()
12765 {
12766 edit_for_markdown_paste(
12767 &snapshot,
12768 range,
12769 to_insert,
12770 url::Url::parse(to_insert).ok(),
12771 )
12772 } else {
12773 (range, Cow::Borrowed(to_insert))
12774 }
12775 };
12776
12777 edits.push((range, to_insert));
12778 original_indent_columns.push(original_indent_column);
12779 }
12780 drop(snapshot);
12781
12782 buffer.edit(
12783 edits,
12784 if auto_indent_on_paste {
12785 Some(AutoindentMode::Block {
12786 original_indent_columns,
12787 })
12788 } else {
12789 None
12790 },
12791 cx,
12792 );
12793 });
12794
12795 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12796 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12797 } else {
12798 let url = url::Url::parse(&clipboard_text).ok();
12799
12800 let auto_indent_mode = if !clipboard_text.is_empty() {
12801 Some(AutoindentMode::Block {
12802 original_indent_columns: Vec::new(),
12803 })
12804 } else {
12805 None
12806 };
12807
12808 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12809 let snapshot = buffer.snapshot(cx);
12810
12811 let anchors = old_selections
12812 .iter()
12813 .map(|s| {
12814 let anchor = snapshot.anchor_after(s.head());
12815 s.map(|_| anchor)
12816 })
12817 .collect::<Vec<_>>();
12818
12819 let mut edits = Vec::new();
12820
12821 for selection in old_selections.iter() {
12822 let language = snapshot.language_at(selection.head());
12823 let range = selection.range();
12824
12825 let (edit_range, edit_text) = if let Some(language) = language
12826 && language.name() == "Markdown".into()
12827 {
12828 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12829 } else {
12830 (range, clipboard_text.clone())
12831 };
12832
12833 edits.push((edit_range, edit_text));
12834 }
12835
12836 drop(snapshot);
12837 buffer.edit(edits, auto_indent_mode, cx);
12838
12839 anchors
12840 });
12841
12842 this.change_selections(Default::default(), window, cx, |s| {
12843 s.select_anchors(selection_anchors);
12844 });
12845 }
12846
12847 let trigger_in_words =
12848 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12849
12850 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12851 });
12852 }
12853
12854 pub fn diff_clipboard_with_selection(
12855 &mut self,
12856 _: &DiffClipboardWithSelection,
12857 window: &mut Window,
12858 cx: &mut Context<Self>,
12859 ) {
12860 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12861
12862 if selections.is_empty() {
12863 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12864 return;
12865 };
12866
12867 let clipboard_text = match cx.read_from_clipboard() {
12868 Some(item) => match item.entries().first() {
12869 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12870 _ => None,
12871 },
12872 None => None,
12873 };
12874
12875 let Some(clipboard_text) = clipboard_text else {
12876 log::warn!("Clipboard doesn't contain text.");
12877 return;
12878 };
12879
12880 window.dispatch_action(
12881 Box::new(DiffClipboardWithSelectionData {
12882 clipboard_text,
12883 editor: cx.entity(),
12884 }),
12885 cx,
12886 );
12887 }
12888
12889 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12890 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12891 if let Some(item) = cx.read_from_clipboard() {
12892 let entries = item.entries();
12893
12894 match entries.first() {
12895 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12896 // of all the pasted entries.
12897 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12898 .do_paste(
12899 clipboard_string.text(),
12900 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12901 true,
12902 window,
12903 cx,
12904 ),
12905 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12906 }
12907 }
12908 }
12909
12910 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12911 if self.read_only(cx) {
12912 return;
12913 }
12914
12915 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12916
12917 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12918 if let Some((selections, _)) =
12919 self.selection_history.transaction(transaction_id).cloned()
12920 {
12921 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12922 s.select_anchors(selections.to_vec());
12923 });
12924 } else {
12925 log::error!(
12926 "No entry in selection_history found for undo. \
12927 This may correspond to a bug where undo does not update the selection. \
12928 If this is occurring, please add details to \
12929 https://github.com/zed-industries/zed/issues/22692"
12930 );
12931 }
12932 self.request_autoscroll(Autoscroll::fit(), cx);
12933 self.unmark_text(window, cx);
12934 self.refresh_edit_prediction(true, false, window, cx);
12935 cx.emit(EditorEvent::Edited { transaction_id });
12936 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12937 }
12938 }
12939
12940 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12941 if self.read_only(cx) {
12942 return;
12943 }
12944
12945 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12946
12947 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12948 if let Some((_, Some(selections))) =
12949 self.selection_history.transaction(transaction_id).cloned()
12950 {
12951 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12952 s.select_anchors(selections.to_vec());
12953 });
12954 } else {
12955 log::error!(
12956 "No entry in selection_history found for redo. \
12957 This may correspond to a bug where undo does not update the selection. \
12958 If this is occurring, please add details to \
12959 https://github.com/zed-industries/zed/issues/22692"
12960 );
12961 }
12962 self.request_autoscroll(Autoscroll::fit(), cx);
12963 self.unmark_text(window, cx);
12964 self.refresh_edit_prediction(true, false, window, cx);
12965 cx.emit(EditorEvent::Edited { transaction_id });
12966 }
12967 }
12968
12969 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12970 self.buffer
12971 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12972 }
12973
12974 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12975 self.buffer
12976 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12977 }
12978
12979 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12980 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12981 self.change_selections(Default::default(), window, cx, |s| {
12982 s.move_with(|map, selection| {
12983 let cursor = if selection.is_empty() {
12984 movement::left(map, selection.start)
12985 } else {
12986 selection.start
12987 };
12988 selection.collapse_to(cursor, SelectionGoal::None);
12989 });
12990 })
12991 }
12992
12993 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12994 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12995 self.change_selections(Default::default(), window, cx, |s| {
12996 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12997 })
12998 }
12999
13000 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13001 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13002 self.change_selections(Default::default(), window, cx, |s| {
13003 s.move_with(|map, selection| {
13004 let cursor = if selection.is_empty() {
13005 movement::right(map, selection.end)
13006 } else {
13007 selection.end
13008 };
13009 selection.collapse_to(cursor, SelectionGoal::None)
13010 });
13011 })
13012 }
13013
13014 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13016 self.change_selections(Default::default(), window, cx, |s| {
13017 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13018 });
13019 }
13020
13021 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13022 if self.take_rename(true, window, cx).is_some() {
13023 return;
13024 }
13025
13026 if self.mode.is_single_line() {
13027 cx.propagate();
13028 return;
13029 }
13030
13031 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13032
13033 let text_layout_details = &self.text_layout_details(window);
13034 let selection_count = self.selections.count();
13035 let first_selection = self.selections.first_anchor();
13036
13037 self.change_selections(Default::default(), window, cx, |s| {
13038 s.move_with(|map, selection| {
13039 if !selection.is_empty() {
13040 selection.goal = SelectionGoal::None;
13041 }
13042 let (cursor, goal) = movement::up(
13043 map,
13044 selection.start,
13045 selection.goal,
13046 false,
13047 text_layout_details,
13048 );
13049 selection.collapse_to(cursor, goal);
13050 });
13051 });
13052
13053 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13054 {
13055 cx.propagate();
13056 }
13057 }
13058
13059 pub fn move_up_by_lines(
13060 &mut self,
13061 action: &MoveUpByLines,
13062 window: &mut Window,
13063 cx: &mut Context<Self>,
13064 ) {
13065 if self.take_rename(true, window, cx).is_some() {
13066 return;
13067 }
13068
13069 if self.mode.is_single_line() {
13070 cx.propagate();
13071 return;
13072 }
13073
13074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13075
13076 let text_layout_details = &self.text_layout_details(window);
13077
13078 self.change_selections(Default::default(), window, cx, |s| {
13079 s.move_with(|map, selection| {
13080 if !selection.is_empty() {
13081 selection.goal = SelectionGoal::None;
13082 }
13083 let (cursor, goal) = movement::up_by_rows(
13084 map,
13085 selection.start,
13086 action.lines,
13087 selection.goal,
13088 false,
13089 text_layout_details,
13090 );
13091 selection.collapse_to(cursor, goal);
13092 });
13093 })
13094 }
13095
13096 pub fn move_down_by_lines(
13097 &mut self,
13098 action: &MoveDownByLines,
13099 window: &mut Window,
13100 cx: &mut Context<Self>,
13101 ) {
13102 if self.take_rename(true, window, cx).is_some() {
13103 return;
13104 }
13105
13106 if self.mode.is_single_line() {
13107 cx.propagate();
13108 return;
13109 }
13110
13111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13112
13113 let text_layout_details = &self.text_layout_details(window);
13114
13115 self.change_selections(Default::default(), window, cx, |s| {
13116 s.move_with(|map, selection| {
13117 if !selection.is_empty() {
13118 selection.goal = SelectionGoal::None;
13119 }
13120 let (cursor, goal) = movement::down_by_rows(
13121 map,
13122 selection.start,
13123 action.lines,
13124 selection.goal,
13125 false,
13126 text_layout_details,
13127 );
13128 selection.collapse_to(cursor, goal);
13129 });
13130 })
13131 }
13132
13133 pub fn select_down_by_lines(
13134 &mut self,
13135 action: &SelectDownByLines,
13136 window: &mut Window,
13137 cx: &mut Context<Self>,
13138 ) {
13139 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13140 let text_layout_details = &self.text_layout_details(window);
13141 self.change_selections(Default::default(), window, cx, |s| {
13142 s.move_heads_with(|map, head, goal| {
13143 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13144 })
13145 })
13146 }
13147
13148 pub fn select_up_by_lines(
13149 &mut self,
13150 action: &SelectUpByLines,
13151 window: &mut Window,
13152 cx: &mut Context<Self>,
13153 ) {
13154 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13155 let text_layout_details = &self.text_layout_details(window);
13156 self.change_selections(Default::default(), window, cx, |s| {
13157 s.move_heads_with(|map, head, goal| {
13158 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13159 })
13160 })
13161 }
13162
13163 pub fn select_page_up(
13164 &mut self,
13165 _: &SelectPageUp,
13166 window: &mut Window,
13167 cx: &mut Context<Self>,
13168 ) {
13169 let Some(row_count) = self.visible_row_count() else {
13170 return;
13171 };
13172
13173 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13174
13175 let text_layout_details = &self.text_layout_details(window);
13176
13177 self.change_selections(Default::default(), window, cx, |s| {
13178 s.move_heads_with(|map, head, goal| {
13179 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13180 })
13181 })
13182 }
13183
13184 pub fn move_page_up(
13185 &mut self,
13186 action: &MovePageUp,
13187 window: &mut Window,
13188 cx: &mut Context<Self>,
13189 ) {
13190 if self.take_rename(true, window, cx).is_some() {
13191 return;
13192 }
13193
13194 if self
13195 .context_menu
13196 .borrow_mut()
13197 .as_mut()
13198 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13199 .unwrap_or(false)
13200 {
13201 return;
13202 }
13203
13204 if matches!(self.mode, EditorMode::SingleLine) {
13205 cx.propagate();
13206 return;
13207 }
13208
13209 let Some(row_count) = self.visible_row_count() else {
13210 return;
13211 };
13212
13213 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13214
13215 let effects = if action.center_cursor {
13216 SelectionEffects::scroll(Autoscroll::center())
13217 } else {
13218 SelectionEffects::default()
13219 };
13220
13221 let text_layout_details = &self.text_layout_details(window);
13222
13223 self.change_selections(effects, window, cx, |s| {
13224 s.move_with(|map, selection| {
13225 if !selection.is_empty() {
13226 selection.goal = SelectionGoal::None;
13227 }
13228 let (cursor, goal) = movement::up_by_rows(
13229 map,
13230 selection.end,
13231 row_count,
13232 selection.goal,
13233 false,
13234 text_layout_details,
13235 );
13236 selection.collapse_to(cursor, goal);
13237 });
13238 });
13239 }
13240
13241 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13243 let text_layout_details = &self.text_layout_details(window);
13244 self.change_selections(Default::default(), window, cx, |s| {
13245 s.move_heads_with(|map, head, goal| {
13246 movement::up(map, head, goal, false, text_layout_details)
13247 })
13248 })
13249 }
13250
13251 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13252 self.take_rename(true, window, cx);
13253
13254 if self.mode.is_single_line() {
13255 cx.propagate();
13256 return;
13257 }
13258
13259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13260
13261 let text_layout_details = &self.text_layout_details(window);
13262 let selection_count = self.selections.count();
13263 let first_selection = self.selections.first_anchor();
13264
13265 self.change_selections(Default::default(), window, cx, |s| {
13266 s.move_with(|map, selection| {
13267 if !selection.is_empty() {
13268 selection.goal = SelectionGoal::None;
13269 }
13270 let (cursor, goal) = movement::down(
13271 map,
13272 selection.end,
13273 selection.goal,
13274 false,
13275 text_layout_details,
13276 );
13277 selection.collapse_to(cursor, goal);
13278 });
13279 });
13280
13281 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13282 {
13283 cx.propagate();
13284 }
13285 }
13286
13287 pub fn select_page_down(
13288 &mut self,
13289 _: &SelectPageDown,
13290 window: &mut Window,
13291 cx: &mut Context<Self>,
13292 ) {
13293 let Some(row_count) = self.visible_row_count() else {
13294 return;
13295 };
13296
13297 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13298
13299 let text_layout_details = &self.text_layout_details(window);
13300
13301 self.change_selections(Default::default(), window, cx, |s| {
13302 s.move_heads_with(|map, head, goal| {
13303 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13304 })
13305 })
13306 }
13307
13308 pub fn move_page_down(
13309 &mut self,
13310 action: &MovePageDown,
13311 window: &mut Window,
13312 cx: &mut Context<Self>,
13313 ) {
13314 if self.take_rename(true, window, cx).is_some() {
13315 return;
13316 }
13317
13318 if self
13319 .context_menu
13320 .borrow_mut()
13321 .as_mut()
13322 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13323 .unwrap_or(false)
13324 {
13325 return;
13326 }
13327
13328 if matches!(self.mode, EditorMode::SingleLine) {
13329 cx.propagate();
13330 return;
13331 }
13332
13333 let Some(row_count) = self.visible_row_count() else {
13334 return;
13335 };
13336
13337 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13338
13339 let effects = if action.center_cursor {
13340 SelectionEffects::scroll(Autoscroll::center())
13341 } else {
13342 SelectionEffects::default()
13343 };
13344
13345 let text_layout_details = &self.text_layout_details(window);
13346 self.change_selections(effects, window, cx, |s| {
13347 s.move_with(|map, selection| {
13348 if !selection.is_empty() {
13349 selection.goal = SelectionGoal::None;
13350 }
13351 let (cursor, goal) = movement::down_by_rows(
13352 map,
13353 selection.end,
13354 row_count,
13355 selection.goal,
13356 false,
13357 text_layout_details,
13358 );
13359 selection.collapse_to(cursor, goal);
13360 });
13361 });
13362 }
13363
13364 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13365 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13366 let text_layout_details = &self.text_layout_details(window);
13367 self.change_selections(Default::default(), window, cx, |s| {
13368 s.move_heads_with(|map, head, goal| {
13369 movement::down(map, head, goal, false, text_layout_details)
13370 })
13371 });
13372 }
13373
13374 pub fn context_menu_first(
13375 &mut self,
13376 _: &ContextMenuFirst,
13377 window: &mut Window,
13378 cx: &mut Context<Self>,
13379 ) {
13380 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13381 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13382 }
13383 }
13384
13385 pub fn context_menu_prev(
13386 &mut self,
13387 _: &ContextMenuPrevious,
13388 window: &mut Window,
13389 cx: &mut Context<Self>,
13390 ) {
13391 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13392 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13393 }
13394 }
13395
13396 pub fn context_menu_next(
13397 &mut self,
13398 _: &ContextMenuNext,
13399 window: &mut Window,
13400 cx: &mut Context<Self>,
13401 ) {
13402 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13403 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13404 }
13405 }
13406
13407 pub fn context_menu_last(
13408 &mut self,
13409 _: &ContextMenuLast,
13410 window: &mut Window,
13411 cx: &mut Context<Self>,
13412 ) {
13413 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13414 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13415 }
13416 }
13417
13418 pub fn signature_help_prev(
13419 &mut self,
13420 _: &SignatureHelpPrevious,
13421 _: &mut Window,
13422 cx: &mut Context<Self>,
13423 ) {
13424 if let Some(popover) = self.signature_help_state.popover_mut() {
13425 if popover.current_signature == 0 {
13426 popover.current_signature = popover.signatures.len() - 1;
13427 } else {
13428 popover.current_signature -= 1;
13429 }
13430 cx.notify();
13431 }
13432 }
13433
13434 pub fn signature_help_next(
13435 &mut self,
13436 _: &SignatureHelpNext,
13437 _: &mut Window,
13438 cx: &mut Context<Self>,
13439 ) {
13440 if let Some(popover) = self.signature_help_state.popover_mut() {
13441 if popover.current_signature + 1 == popover.signatures.len() {
13442 popover.current_signature = 0;
13443 } else {
13444 popover.current_signature += 1;
13445 }
13446 cx.notify();
13447 }
13448 }
13449
13450 pub fn move_to_previous_word_start(
13451 &mut self,
13452 _: &MoveToPreviousWordStart,
13453 window: &mut Window,
13454 cx: &mut Context<Self>,
13455 ) {
13456 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13457 self.change_selections(Default::default(), window, cx, |s| {
13458 s.move_cursors_with(|map, head, _| {
13459 (
13460 movement::previous_word_start(map, head),
13461 SelectionGoal::None,
13462 )
13463 });
13464 })
13465 }
13466
13467 pub fn move_to_previous_subword_start(
13468 &mut self,
13469 _: &MoveToPreviousSubwordStart,
13470 window: &mut Window,
13471 cx: &mut Context<Self>,
13472 ) {
13473 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13474 self.change_selections(Default::default(), window, cx, |s| {
13475 s.move_cursors_with(|map, head, _| {
13476 (
13477 movement::previous_subword_start(map, head),
13478 SelectionGoal::None,
13479 )
13480 });
13481 })
13482 }
13483
13484 pub fn select_to_previous_word_start(
13485 &mut self,
13486 _: &SelectToPreviousWordStart,
13487 window: &mut Window,
13488 cx: &mut Context<Self>,
13489 ) {
13490 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13491 self.change_selections(Default::default(), window, cx, |s| {
13492 s.move_heads_with(|map, head, _| {
13493 (
13494 movement::previous_word_start(map, head),
13495 SelectionGoal::None,
13496 )
13497 });
13498 })
13499 }
13500
13501 pub fn select_to_previous_subword_start(
13502 &mut self,
13503 _: &SelectToPreviousSubwordStart,
13504 window: &mut Window,
13505 cx: &mut Context<Self>,
13506 ) {
13507 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13508 self.change_selections(Default::default(), window, cx, |s| {
13509 s.move_heads_with(|map, head, _| {
13510 (
13511 movement::previous_subword_start(map, head),
13512 SelectionGoal::None,
13513 )
13514 });
13515 })
13516 }
13517
13518 pub fn delete_to_previous_word_start(
13519 &mut self,
13520 action: &DeleteToPreviousWordStart,
13521 window: &mut Window,
13522 cx: &mut Context<Self>,
13523 ) {
13524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13525 self.transact(window, cx, |this, window, cx| {
13526 this.select_autoclose_pair(window, cx);
13527 this.change_selections(Default::default(), window, cx, |s| {
13528 s.move_with(|map, selection| {
13529 if selection.is_empty() {
13530 let mut cursor = if action.ignore_newlines {
13531 movement::previous_word_start(map, selection.head())
13532 } else {
13533 movement::previous_word_start_or_newline(map, selection.head())
13534 };
13535 cursor = movement::adjust_greedy_deletion(
13536 map,
13537 selection.head(),
13538 cursor,
13539 action.ignore_brackets,
13540 );
13541 selection.set_head(cursor, SelectionGoal::None);
13542 }
13543 });
13544 });
13545 this.insert("", window, cx);
13546 });
13547 }
13548
13549 pub fn delete_to_previous_subword_start(
13550 &mut self,
13551 _: &DeleteToPreviousSubwordStart,
13552 window: &mut Window,
13553 cx: &mut Context<Self>,
13554 ) {
13555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13556 self.transact(window, cx, |this, window, cx| {
13557 this.select_autoclose_pair(window, cx);
13558 this.change_selections(Default::default(), window, cx, |s| {
13559 s.move_with(|map, selection| {
13560 if selection.is_empty() {
13561 let mut cursor = movement::previous_subword_start(map, selection.head());
13562 cursor =
13563 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13564 selection.set_head(cursor, SelectionGoal::None);
13565 }
13566 });
13567 });
13568 this.insert("", window, cx);
13569 });
13570 }
13571
13572 pub fn move_to_next_word_end(
13573 &mut self,
13574 _: &MoveToNextWordEnd,
13575 window: &mut Window,
13576 cx: &mut Context<Self>,
13577 ) {
13578 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13579 self.change_selections(Default::default(), window, cx, |s| {
13580 s.move_cursors_with(|map, head, _| {
13581 (movement::next_word_end(map, head), SelectionGoal::None)
13582 });
13583 })
13584 }
13585
13586 pub fn move_to_next_subword_end(
13587 &mut self,
13588 _: &MoveToNextSubwordEnd,
13589 window: &mut Window,
13590 cx: &mut Context<Self>,
13591 ) {
13592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13593 self.change_selections(Default::default(), window, cx, |s| {
13594 s.move_cursors_with(|map, head, _| {
13595 (movement::next_subword_end(map, head), SelectionGoal::None)
13596 });
13597 })
13598 }
13599
13600 pub fn select_to_next_word_end(
13601 &mut self,
13602 _: &SelectToNextWordEnd,
13603 window: &mut Window,
13604 cx: &mut Context<Self>,
13605 ) {
13606 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13607 self.change_selections(Default::default(), window, cx, |s| {
13608 s.move_heads_with(|map, head, _| {
13609 (movement::next_word_end(map, head), SelectionGoal::None)
13610 });
13611 })
13612 }
13613
13614 pub fn select_to_next_subword_end(
13615 &mut self,
13616 _: &SelectToNextSubwordEnd,
13617 window: &mut Window,
13618 cx: &mut Context<Self>,
13619 ) {
13620 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13621 self.change_selections(Default::default(), window, cx, |s| {
13622 s.move_heads_with(|map, head, _| {
13623 (movement::next_subword_end(map, head), SelectionGoal::None)
13624 });
13625 })
13626 }
13627
13628 pub fn delete_to_next_word_end(
13629 &mut self,
13630 action: &DeleteToNextWordEnd,
13631 window: &mut Window,
13632 cx: &mut Context<Self>,
13633 ) {
13634 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13635 self.transact(window, cx, |this, window, cx| {
13636 this.change_selections(Default::default(), window, cx, |s| {
13637 s.move_with(|map, selection| {
13638 if selection.is_empty() {
13639 let mut cursor = if action.ignore_newlines {
13640 movement::next_word_end(map, selection.head())
13641 } else {
13642 movement::next_word_end_or_newline(map, selection.head())
13643 };
13644 cursor = movement::adjust_greedy_deletion(
13645 map,
13646 selection.head(),
13647 cursor,
13648 action.ignore_brackets,
13649 );
13650 selection.set_head(cursor, SelectionGoal::None);
13651 }
13652 });
13653 });
13654 this.insert("", window, cx);
13655 });
13656 }
13657
13658 pub fn delete_to_next_subword_end(
13659 &mut self,
13660 _: &DeleteToNextSubwordEnd,
13661 window: &mut Window,
13662 cx: &mut Context<Self>,
13663 ) {
13664 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13665 self.transact(window, cx, |this, window, cx| {
13666 this.change_selections(Default::default(), window, cx, |s| {
13667 s.move_with(|map, selection| {
13668 if selection.is_empty() {
13669 let mut cursor = movement::next_subword_end(map, selection.head());
13670 cursor =
13671 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13672 selection.set_head(cursor, SelectionGoal::None);
13673 }
13674 });
13675 });
13676 this.insert("", window, cx);
13677 });
13678 }
13679
13680 pub fn move_to_beginning_of_line(
13681 &mut self,
13682 action: &MoveToBeginningOfLine,
13683 window: &mut Window,
13684 cx: &mut Context<Self>,
13685 ) {
13686 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13687 self.change_selections(Default::default(), window, cx, |s| {
13688 s.move_cursors_with(|map, head, _| {
13689 (
13690 movement::indented_line_beginning(
13691 map,
13692 head,
13693 action.stop_at_soft_wraps,
13694 action.stop_at_indent,
13695 ),
13696 SelectionGoal::None,
13697 )
13698 });
13699 })
13700 }
13701
13702 pub fn select_to_beginning_of_line(
13703 &mut self,
13704 action: &SelectToBeginningOfLine,
13705 window: &mut Window,
13706 cx: &mut Context<Self>,
13707 ) {
13708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13709 self.change_selections(Default::default(), window, cx, |s| {
13710 s.move_heads_with(|map, head, _| {
13711 (
13712 movement::indented_line_beginning(
13713 map,
13714 head,
13715 action.stop_at_soft_wraps,
13716 action.stop_at_indent,
13717 ),
13718 SelectionGoal::None,
13719 )
13720 });
13721 });
13722 }
13723
13724 pub fn delete_to_beginning_of_line(
13725 &mut self,
13726 action: &DeleteToBeginningOfLine,
13727 window: &mut Window,
13728 cx: &mut Context<Self>,
13729 ) {
13730 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13731 self.transact(window, cx, |this, window, cx| {
13732 this.change_selections(Default::default(), window, cx, |s| {
13733 s.move_with(|_, selection| {
13734 selection.reversed = true;
13735 });
13736 });
13737
13738 this.select_to_beginning_of_line(
13739 &SelectToBeginningOfLine {
13740 stop_at_soft_wraps: false,
13741 stop_at_indent: action.stop_at_indent,
13742 },
13743 window,
13744 cx,
13745 );
13746 this.backspace(&Backspace, window, cx);
13747 });
13748 }
13749
13750 pub fn move_to_end_of_line(
13751 &mut self,
13752 action: &MoveToEndOfLine,
13753 window: &mut Window,
13754 cx: &mut Context<Self>,
13755 ) {
13756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13757 self.change_selections(Default::default(), window, cx, |s| {
13758 s.move_cursors_with(|map, head, _| {
13759 (
13760 movement::line_end(map, head, action.stop_at_soft_wraps),
13761 SelectionGoal::None,
13762 )
13763 });
13764 })
13765 }
13766
13767 pub fn select_to_end_of_line(
13768 &mut self,
13769 action: &SelectToEndOfLine,
13770 window: &mut Window,
13771 cx: &mut Context<Self>,
13772 ) {
13773 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13774 self.change_selections(Default::default(), window, cx, |s| {
13775 s.move_heads_with(|map, head, _| {
13776 (
13777 movement::line_end(map, head, action.stop_at_soft_wraps),
13778 SelectionGoal::None,
13779 )
13780 });
13781 })
13782 }
13783
13784 pub fn delete_to_end_of_line(
13785 &mut self,
13786 _: &DeleteToEndOfLine,
13787 window: &mut Window,
13788 cx: &mut Context<Self>,
13789 ) {
13790 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13791 self.transact(window, cx, |this, window, cx| {
13792 this.select_to_end_of_line(
13793 &SelectToEndOfLine {
13794 stop_at_soft_wraps: false,
13795 },
13796 window,
13797 cx,
13798 );
13799 this.delete(&Delete, window, cx);
13800 });
13801 }
13802
13803 pub fn cut_to_end_of_line(
13804 &mut self,
13805 action: &CutToEndOfLine,
13806 window: &mut Window,
13807 cx: &mut Context<Self>,
13808 ) {
13809 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13810 self.transact(window, cx, |this, window, cx| {
13811 this.select_to_end_of_line(
13812 &SelectToEndOfLine {
13813 stop_at_soft_wraps: false,
13814 },
13815 window,
13816 cx,
13817 );
13818 if !action.stop_at_newlines {
13819 this.change_selections(Default::default(), window, cx, |s| {
13820 s.move_with(|_, sel| {
13821 if sel.is_empty() {
13822 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13823 }
13824 });
13825 });
13826 }
13827 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13828 let item = this.cut_common(false, window, cx);
13829 cx.write_to_clipboard(item);
13830 });
13831 }
13832
13833 pub fn move_to_start_of_paragraph(
13834 &mut self,
13835 _: &MoveToStartOfParagraph,
13836 window: &mut Window,
13837 cx: &mut Context<Self>,
13838 ) {
13839 if matches!(self.mode, EditorMode::SingleLine) {
13840 cx.propagate();
13841 return;
13842 }
13843 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13844 self.change_selections(Default::default(), window, cx, |s| {
13845 s.move_with(|map, selection| {
13846 selection.collapse_to(
13847 movement::start_of_paragraph(map, selection.head(), 1),
13848 SelectionGoal::None,
13849 )
13850 });
13851 })
13852 }
13853
13854 pub fn move_to_end_of_paragraph(
13855 &mut self,
13856 _: &MoveToEndOfParagraph,
13857 window: &mut Window,
13858 cx: &mut Context<Self>,
13859 ) {
13860 if matches!(self.mode, EditorMode::SingleLine) {
13861 cx.propagate();
13862 return;
13863 }
13864 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13865 self.change_selections(Default::default(), window, cx, |s| {
13866 s.move_with(|map, selection| {
13867 selection.collapse_to(
13868 movement::end_of_paragraph(map, selection.head(), 1),
13869 SelectionGoal::None,
13870 )
13871 });
13872 })
13873 }
13874
13875 pub fn select_to_start_of_paragraph(
13876 &mut self,
13877 _: &SelectToStartOfParagraph,
13878 window: &mut Window,
13879 cx: &mut Context<Self>,
13880 ) {
13881 if matches!(self.mode, EditorMode::SingleLine) {
13882 cx.propagate();
13883 return;
13884 }
13885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13886 self.change_selections(Default::default(), window, cx, |s| {
13887 s.move_heads_with(|map, head, _| {
13888 (
13889 movement::start_of_paragraph(map, head, 1),
13890 SelectionGoal::None,
13891 )
13892 });
13893 })
13894 }
13895
13896 pub fn select_to_end_of_paragraph(
13897 &mut self,
13898 _: &SelectToEndOfParagraph,
13899 window: &mut Window,
13900 cx: &mut Context<Self>,
13901 ) {
13902 if matches!(self.mode, EditorMode::SingleLine) {
13903 cx.propagate();
13904 return;
13905 }
13906 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13907 self.change_selections(Default::default(), window, cx, |s| {
13908 s.move_heads_with(|map, head, _| {
13909 (
13910 movement::end_of_paragraph(map, head, 1),
13911 SelectionGoal::None,
13912 )
13913 });
13914 })
13915 }
13916
13917 pub fn move_to_start_of_excerpt(
13918 &mut self,
13919 _: &MoveToStartOfExcerpt,
13920 window: &mut Window,
13921 cx: &mut Context<Self>,
13922 ) {
13923 if matches!(self.mode, EditorMode::SingleLine) {
13924 cx.propagate();
13925 return;
13926 }
13927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13928 self.change_selections(Default::default(), window, cx, |s| {
13929 s.move_with(|map, selection| {
13930 selection.collapse_to(
13931 movement::start_of_excerpt(
13932 map,
13933 selection.head(),
13934 workspace::searchable::Direction::Prev,
13935 ),
13936 SelectionGoal::None,
13937 )
13938 });
13939 })
13940 }
13941
13942 pub fn move_to_start_of_next_excerpt(
13943 &mut self,
13944 _: &MoveToStartOfNextExcerpt,
13945 window: &mut Window,
13946 cx: &mut Context<Self>,
13947 ) {
13948 if matches!(self.mode, EditorMode::SingleLine) {
13949 cx.propagate();
13950 return;
13951 }
13952
13953 self.change_selections(Default::default(), window, cx, |s| {
13954 s.move_with(|map, selection| {
13955 selection.collapse_to(
13956 movement::start_of_excerpt(
13957 map,
13958 selection.head(),
13959 workspace::searchable::Direction::Next,
13960 ),
13961 SelectionGoal::None,
13962 )
13963 });
13964 })
13965 }
13966
13967 pub fn move_to_end_of_excerpt(
13968 &mut self,
13969 _: &MoveToEndOfExcerpt,
13970 window: &mut Window,
13971 cx: &mut Context<Self>,
13972 ) {
13973 if matches!(self.mode, EditorMode::SingleLine) {
13974 cx.propagate();
13975 return;
13976 }
13977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13978 self.change_selections(Default::default(), window, cx, |s| {
13979 s.move_with(|map, selection| {
13980 selection.collapse_to(
13981 movement::end_of_excerpt(
13982 map,
13983 selection.head(),
13984 workspace::searchable::Direction::Next,
13985 ),
13986 SelectionGoal::None,
13987 )
13988 });
13989 })
13990 }
13991
13992 pub fn move_to_end_of_previous_excerpt(
13993 &mut self,
13994 _: &MoveToEndOfPreviousExcerpt,
13995 window: &mut Window,
13996 cx: &mut Context<Self>,
13997 ) {
13998 if matches!(self.mode, EditorMode::SingleLine) {
13999 cx.propagate();
14000 return;
14001 }
14002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14003 self.change_selections(Default::default(), window, cx, |s| {
14004 s.move_with(|map, selection| {
14005 selection.collapse_to(
14006 movement::end_of_excerpt(
14007 map,
14008 selection.head(),
14009 workspace::searchable::Direction::Prev,
14010 ),
14011 SelectionGoal::None,
14012 )
14013 });
14014 })
14015 }
14016
14017 pub fn select_to_start_of_excerpt(
14018 &mut self,
14019 _: &SelectToStartOfExcerpt,
14020 window: &mut Window,
14021 cx: &mut Context<Self>,
14022 ) {
14023 if matches!(self.mode, EditorMode::SingleLine) {
14024 cx.propagate();
14025 return;
14026 }
14027 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14028 self.change_selections(Default::default(), window, cx, |s| {
14029 s.move_heads_with(|map, head, _| {
14030 (
14031 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14032 SelectionGoal::None,
14033 )
14034 });
14035 })
14036 }
14037
14038 pub fn select_to_start_of_next_excerpt(
14039 &mut self,
14040 _: &SelectToStartOfNextExcerpt,
14041 window: &mut Window,
14042 cx: &mut Context<Self>,
14043 ) {
14044 if matches!(self.mode, EditorMode::SingleLine) {
14045 cx.propagate();
14046 return;
14047 }
14048 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14049 self.change_selections(Default::default(), window, cx, |s| {
14050 s.move_heads_with(|map, head, _| {
14051 (
14052 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14053 SelectionGoal::None,
14054 )
14055 });
14056 })
14057 }
14058
14059 pub fn select_to_end_of_excerpt(
14060 &mut self,
14061 _: &SelectToEndOfExcerpt,
14062 window: &mut Window,
14063 cx: &mut Context<Self>,
14064 ) {
14065 if matches!(self.mode, EditorMode::SingleLine) {
14066 cx.propagate();
14067 return;
14068 }
14069 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14070 self.change_selections(Default::default(), window, cx, |s| {
14071 s.move_heads_with(|map, head, _| {
14072 (
14073 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14074 SelectionGoal::None,
14075 )
14076 });
14077 })
14078 }
14079
14080 pub fn select_to_end_of_previous_excerpt(
14081 &mut self,
14082 _: &SelectToEndOfPreviousExcerpt,
14083 window: &mut Window,
14084 cx: &mut Context<Self>,
14085 ) {
14086 if matches!(self.mode, EditorMode::SingleLine) {
14087 cx.propagate();
14088 return;
14089 }
14090 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14091 self.change_selections(Default::default(), window, cx, |s| {
14092 s.move_heads_with(|map, head, _| {
14093 (
14094 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14095 SelectionGoal::None,
14096 )
14097 });
14098 })
14099 }
14100
14101 pub fn move_to_beginning(
14102 &mut self,
14103 _: &MoveToBeginning,
14104 window: &mut Window,
14105 cx: &mut Context<Self>,
14106 ) {
14107 if matches!(self.mode, EditorMode::SingleLine) {
14108 cx.propagate();
14109 return;
14110 }
14111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14112 self.change_selections(Default::default(), window, cx, |s| {
14113 s.select_ranges(vec![0..0]);
14114 });
14115 }
14116
14117 pub fn select_to_beginning(
14118 &mut self,
14119 _: &SelectToBeginning,
14120 window: &mut Window,
14121 cx: &mut Context<Self>,
14122 ) {
14123 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14124 selection.set_head(Point::zero(), SelectionGoal::None);
14125 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14126 self.change_selections(Default::default(), window, cx, |s| {
14127 s.select(vec![selection]);
14128 });
14129 }
14130
14131 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14132 if matches!(self.mode, EditorMode::SingleLine) {
14133 cx.propagate();
14134 return;
14135 }
14136 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14137 let cursor = self.buffer.read(cx).read(cx).len();
14138 self.change_selections(Default::default(), window, cx, |s| {
14139 s.select_ranges(vec![cursor..cursor])
14140 });
14141 }
14142
14143 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14144 self.nav_history = nav_history;
14145 }
14146
14147 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14148 self.nav_history.as_ref()
14149 }
14150
14151 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14152 self.push_to_nav_history(
14153 self.selections.newest_anchor().head(),
14154 None,
14155 false,
14156 true,
14157 cx,
14158 );
14159 }
14160
14161 fn push_to_nav_history(
14162 &mut self,
14163 cursor_anchor: Anchor,
14164 new_position: Option<Point>,
14165 is_deactivate: bool,
14166 always: bool,
14167 cx: &mut Context<Self>,
14168 ) {
14169 if let Some(nav_history) = self.nav_history.as_mut() {
14170 let buffer = self.buffer.read(cx).read(cx);
14171 let cursor_position = cursor_anchor.to_point(&buffer);
14172 let scroll_state = self.scroll_manager.anchor();
14173 let scroll_top_row = scroll_state.top_row(&buffer);
14174 drop(buffer);
14175
14176 if let Some(new_position) = new_position {
14177 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14178 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14179 return;
14180 }
14181 }
14182
14183 nav_history.push(
14184 Some(NavigationData {
14185 cursor_anchor,
14186 cursor_position,
14187 scroll_anchor: scroll_state,
14188 scroll_top_row,
14189 }),
14190 cx,
14191 );
14192 cx.emit(EditorEvent::PushedToNavHistory {
14193 anchor: cursor_anchor,
14194 is_deactivate,
14195 })
14196 }
14197 }
14198
14199 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14200 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14201 let buffer = self.buffer.read(cx).snapshot(cx);
14202 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14203 selection.set_head(buffer.len(), SelectionGoal::None);
14204 self.change_selections(Default::default(), window, cx, |s| {
14205 s.select(vec![selection]);
14206 });
14207 }
14208
14209 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14210 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14211 let end = self.buffer.read(cx).read(cx).len();
14212 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14213 s.select_ranges(vec![0..end]);
14214 });
14215 }
14216
14217 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14218 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14219 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14220 let mut selections = self.selections.all::<Point>(&display_map);
14221 let max_point = display_map.buffer_snapshot().max_point();
14222 for selection in &mut selections {
14223 let rows = selection.spanned_rows(true, &display_map);
14224 selection.start = Point::new(rows.start.0, 0);
14225 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14226 selection.reversed = false;
14227 }
14228 self.change_selections(Default::default(), window, cx, |s| {
14229 s.select(selections);
14230 });
14231 }
14232
14233 pub fn split_selection_into_lines(
14234 &mut self,
14235 action: &SplitSelectionIntoLines,
14236 window: &mut Window,
14237 cx: &mut Context<Self>,
14238 ) {
14239 let selections = self
14240 .selections
14241 .all::<Point>(&self.display_snapshot(cx))
14242 .into_iter()
14243 .map(|selection| selection.start..selection.end)
14244 .collect::<Vec<_>>();
14245 self.unfold_ranges(&selections, true, true, cx);
14246
14247 let mut new_selection_ranges = Vec::new();
14248 {
14249 let buffer = self.buffer.read(cx).read(cx);
14250 for selection in selections {
14251 for row in selection.start.row..selection.end.row {
14252 let line_start = Point::new(row, 0);
14253 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14254
14255 if action.keep_selections {
14256 // Keep the selection range for each line
14257 let selection_start = if row == selection.start.row {
14258 selection.start
14259 } else {
14260 line_start
14261 };
14262 new_selection_ranges.push(selection_start..line_end);
14263 } else {
14264 // Collapse to cursor at end of line
14265 new_selection_ranges.push(line_end..line_end);
14266 }
14267 }
14268
14269 let is_multiline_selection = selection.start.row != selection.end.row;
14270 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14271 // so this action feels more ergonomic when paired with other selection operations
14272 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14273 if !should_skip_last {
14274 if action.keep_selections {
14275 if is_multiline_selection {
14276 let line_start = Point::new(selection.end.row, 0);
14277 new_selection_ranges.push(line_start..selection.end);
14278 } else {
14279 new_selection_ranges.push(selection.start..selection.end);
14280 }
14281 } else {
14282 new_selection_ranges.push(selection.end..selection.end);
14283 }
14284 }
14285 }
14286 }
14287 self.change_selections(Default::default(), window, cx, |s| {
14288 s.select_ranges(new_selection_ranges);
14289 });
14290 }
14291
14292 pub fn add_selection_above(
14293 &mut self,
14294 action: &AddSelectionAbove,
14295 window: &mut Window,
14296 cx: &mut Context<Self>,
14297 ) {
14298 self.add_selection(true, action.skip_soft_wrap, window, cx);
14299 }
14300
14301 pub fn add_selection_below(
14302 &mut self,
14303 action: &AddSelectionBelow,
14304 window: &mut Window,
14305 cx: &mut Context<Self>,
14306 ) {
14307 self.add_selection(false, action.skip_soft_wrap, window, cx);
14308 }
14309
14310 fn add_selection(
14311 &mut self,
14312 above: bool,
14313 skip_soft_wrap: bool,
14314 window: &mut Window,
14315 cx: &mut Context<Self>,
14316 ) {
14317 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14318
14319 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14320 let all_selections = self.selections.all::<Point>(&display_map);
14321 let text_layout_details = self.text_layout_details(window);
14322
14323 let (mut columnar_selections, new_selections_to_columnarize) = {
14324 if let Some(state) = self.add_selections_state.as_ref() {
14325 let columnar_selection_ids: HashSet<_> = state
14326 .groups
14327 .iter()
14328 .flat_map(|group| group.stack.iter())
14329 .copied()
14330 .collect();
14331
14332 all_selections
14333 .into_iter()
14334 .partition(|s| columnar_selection_ids.contains(&s.id))
14335 } else {
14336 (Vec::new(), all_selections)
14337 }
14338 };
14339
14340 let mut state = self
14341 .add_selections_state
14342 .take()
14343 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14344
14345 for selection in new_selections_to_columnarize {
14346 let range = selection.display_range(&display_map).sorted();
14347 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14348 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14349 let positions = start_x.min(end_x)..start_x.max(end_x);
14350 let mut stack = Vec::new();
14351 for row in range.start.row().0..=range.end.row().0 {
14352 if let Some(selection) = self.selections.build_columnar_selection(
14353 &display_map,
14354 DisplayRow(row),
14355 &positions,
14356 selection.reversed,
14357 &text_layout_details,
14358 ) {
14359 stack.push(selection.id);
14360 columnar_selections.push(selection);
14361 }
14362 }
14363 if !stack.is_empty() {
14364 if above {
14365 stack.reverse();
14366 }
14367 state.groups.push(AddSelectionsGroup { above, stack });
14368 }
14369 }
14370
14371 let mut final_selections = Vec::new();
14372 let end_row = if above {
14373 DisplayRow(0)
14374 } else {
14375 display_map.max_point().row()
14376 };
14377
14378 let mut last_added_item_per_group = HashMap::default();
14379 for group in state.groups.iter_mut() {
14380 if let Some(last_id) = group.stack.last() {
14381 last_added_item_per_group.insert(*last_id, group);
14382 }
14383 }
14384
14385 for selection in columnar_selections {
14386 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14387 if above == group.above {
14388 let range = selection.display_range(&display_map).sorted();
14389 debug_assert_eq!(range.start.row(), range.end.row());
14390 let mut row = range.start.row();
14391 let positions =
14392 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14393 Pixels::from(start)..Pixels::from(end)
14394 } else {
14395 let start_x =
14396 display_map.x_for_display_point(range.start, &text_layout_details);
14397 let end_x =
14398 display_map.x_for_display_point(range.end, &text_layout_details);
14399 start_x.min(end_x)..start_x.max(end_x)
14400 };
14401
14402 let mut maybe_new_selection = None;
14403 let direction = if above { -1 } else { 1 };
14404
14405 while row != end_row {
14406 if skip_soft_wrap {
14407 row = display_map
14408 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14409 .row();
14410 } else if above {
14411 row.0 -= 1;
14412 } else {
14413 row.0 += 1;
14414 }
14415
14416 if let Some(new_selection) = self.selections.build_columnar_selection(
14417 &display_map,
14418 row,
14419 &positions,
14420 selection.reversed,
14421 &text_layout_details,
14422 ) {
14423 maybe_new_selection = Some(new_selection);
14424 break;
14425 }
14426 }
14427
14428 if let Some(new_selection) = maybe_new_selection {
14429 group.stack.push(new_selection.id);
14430 if above {
14431 final_selections.push(new_selection);
14432 final_selections.push(selection);
14433 } else {
14434 final_selections.push(selection);
14435 final_selections.push(new_selection);
14436 }
14437 } else {
14438 final_selections.push(selection);
14439 }
14440 } else {
14441 group.stack.pop();
14442 }
14443 } else {
14444 final_selections.push(selection);
14445 }
14446 }
14447
14448 self.change_selections(Default::default(), window, cx, |s| {
14449 s.select(final_selections);
14450 });
14451
14452 let final_selection_ids: HashSet<_> = self
14453 .selections
14454 .all::<Point>(&display_map)
14455 .iter()
14456 .map(|s| s.id)
14457 .collect();
14458 state.groups.retain_mut(|group| {
14459 // selections might get merged above so we remove invalid items from stacks
14460 group.stack.retain(|id| final_selection_ids.contains(id));
14461
14462 // single selection in stack can be treated as initial state
14463 group.stack.len() > 1
14464 });
14465
14466 if !state.groups.is_empty() {
14467 self.add_selections_state = Some(state);
14468 }
14469 }
14470
14471 fn select_match_ranges(
14472 &mut self,
14473 range: Range<usize>,
14474 reversed: bool,
14475 replace_newest: bool,
14476 auto_scroll: Option<Autoscroll>,
14477 window: &mut Window,
14478 cx: &mut Context<Editor>,
14479 ) {
14480 self.unfold_ranges(
14481 std::slice::from_ref(&range),
14482 false,
14483 auto_scroll.is_some(),
14484 cx,
14485 );
14486 let effects = if let Some(scroll) = auto_scroll {
14487 SelectionEffects::scroll(scroll)
14488 } else {
14489 SelectionEffects::no_scroll()
14490 };
14491 self.change_selections(effects, window, cx, |s| {
14492 if replace_newest {
14493 s.delete(s.newest_anchor().id);
14494 }
14495 if reversed {
14496 s.insert_range(range.end..range.start);
14497 } else {
14498 s.insert_range(range);
14499 }
14500 });
14501 }
14502
14503 pub fn select_next_match_internal(
14504 &mut self,
14505 display_map: &DisplaySnapshot,
14506 replace_newest: bool,
14507 autoscroll: Option<Autoscroll>,
14508 window: &mut Window,
14509 cx: &mut Context<Self>,
14510 ) -> Result<()> {
14511 let buffer = display_map.buffer_snapshot();
14512 let mut selections = self.selections.all::<usize>(&display_map);
14513 if let Some(mut select_next_state) = self.select_next_state.take() {
14514 let query = &select_next_state.query;
14515 if !select_next_state.done {
14516 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14517 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14518 let mut next_selected_range = None;
14519
14520 // Collect and sort selection ranges for efficient overlap checking
14521 let mut selection_ranges: Vec<_> = selections.iter().map(|s| s.range()).collect();
14522 selection_ranges.sort_by_key(|r| r.start);
14523
14524 let bytes_after_last_selection =
14525 buffer.bytes_in_range(last_selection.end..buffer.len());
14526 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14527 let query_matches = query
14528 .stream_find_iter(bytes_after_last_selection)
14529 .map(|result| (last_selection.end, result))
14530 .chain(
14531 query
14532 .stream_find_iter(bytes_before_first_selection)
14533 .map(|result| (0, result)),
14534 );
14535
14536 for (start_offset, query_match) in query_matches {
14537 let query_match = query_match.unwrap(); // can only fail due to I/O
14538 let offset_range =
14539 start_offset + query_match.start()..start_offset + query_match.end();
14540
14541 if !select_next_state.wordwise
14542 || (!buffer.is_inside_word(offset_range.start, None)
14543 && !buffer.is_inside_word(offset_range.end, None))
14544 {
14545 // Use binary search to check for overlap (O(log n))
14546 let overlaps = selection_ranges
14547 .binary_search_by(|range| {
14548 if range.end <= offset_range.start {
14549 std::cmp::Ordering::Less
14550 } else if range.start >= offset_range.end {
14551 std::cmp::Ordering::Greater
14552 } else {
14553 std::cmp::Ordering::Equal
14554 }
14555 })
14556 .is_ok();
14557
14558 if !overlaps {
14559 next_selected_range = Some(offset_range);
14560 break;
14561 }
14562 }
14563 }
14564
14565 if let Some(next_selected_range) = next_selected_range {
14566 self.select_match_ranges(
14567 next_selected_range,
14568 last_selection.reversed,
14569 replace_newest,
14570 autoscroll,
14571 window,
14572 cx,
14573 );
14574 } else {
14575 select_next_state.done = true;
14576 }
14577 }
14578
14579 self.select_next_state = Some(select_next_state);
14580 } else {
14581 let mut only_carets = true;
14582 let mut same_text_selected = true;
14583 let mut selected_text = None;
14584
14585 let mut selections_iter = selections.iter().peekable();
14586 while let Some(selection) = selections_iter.next() {
14587 if selection.start != selection.end {
14588 only_carets = false;
14589 }
14590
14591 if same_text_selected {
14592 if selected_text.is_none() {
14593 selected_text =
14594 Some(buffer.text_for_range(selection.range()).collect::<String>());
14595 }
14596
14597 if let Some(next_selection) = selections_iter.peek() {
14598 if next_selection.range().len() == selection.range().len() {
14599 let next_selected_text = buffer
14600 .text_for_range(next_selection.range())
14601 .collect::<String>();
14602 if Some(next_selected_text) != selected_text {
14603 same_text_selected = false;
14604 selected_text = None;
14605 }
14606 } else {
14607 same_text_selected = false;
14608 selected_text = None;
14609 }
14610 }
14611 }
14612 }
14613
14614 if only_carets {
14615 for selection in &mut selections {
14616 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14617 selection.start = word_range.start;
14618 selection.end = word_range.end;
14619 selection.goal = SelectionGoal::None;
14620 selection.reversed = false;
14621 self.select_match_ranges(
14622 selection.start..selection.end,
14623 selection.reversed,
14624 replace_newest,
14625 autoscroll,
14626 window,
14627 cx,
14628 );
14629 }
14630
14631 if selections.len() == 1 {
14632 let selection = selections
14633 .last()
14634 .expect("ensured that there's only one selection");
14635 let query = buffer
14636 .text_for_range(selection.start..selection.end)
14637 .collect::<String>();
14638 let is_empty = query.is_empty();
14639 let select_state = SelectNextState {
14640 query: AhoCorasick::new(&[query])?,
14641 wordwise: true,
14642 done: is_empty,
14643 };
14644 self.select_next_state = Some(select_state);
14645 } else {
14646 self.select_next_state = None;
14647 }
14648 } else if let Some(selected_text) = selected_text {
14649 self.select_next_state = Some(SelectNextState {
14650 query: AhoCorasick::new(&[selected_text])?,
14651 wordwise: false,
14652 done: false,
14653 });
14654 self.select_next_match_internal(
14655 display_map,
14656 replace_newest,
14657 autoscroll,
14658 window,
14659 cx,
14660 )?;
14661 }
14662 }
14663 Ok(())
14664 }
14665
14666 pub fn select_all_matches(
14667 &mut self,
14668 _action: &SelectAllMatches,
14669 window: &mut Window,
14670 cx: &mut Context<Self>,
14671 ) -> Result<()> {
14672 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14673
14674 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14675
14676 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14677 let Some(select_next_state) = self.select_next_state.as_mut() else {
14678 return Ok(());
14679 };
14680 if select_next_state.done {
14681 return Ok(());
14682 }
14683
14684 let mut new_selections = Vec::new();
14685
14686 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14687 let buffer = display_map.buffer_snapshot();
14688 let query_matches = select_next_state
14689 .query
14690 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14691
14692 for query_match in query_matches.into_iter() {
14693 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14694 let offset_range = if reversed {
14695 query_match.end()..query_match.start()
14696 } else {
14697 query_match.start()..query_match.end()
14698 };
14699
14700 if !select_next_state.wordwise
14701 || (!buffer.is_inside_word(offset_range.start, None)
14702 && !buffer.is_inside_word(offset_range.end, None))
14703 {
14704 new_selections.push(offset_range.start..offset_range.end);
14705 }
14706 }
14707
14708 select_next_state.done = true;
14709
14710 if new_selections.is_empty() {
14711 log::error!("bug: new_selections is empty in select_all_matches");
14712 return Ok(());
14713 }
14714
14715 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14716 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14717 selections.select_ranges(new_selections)
14718 });
14719
14720 Ok(())
14721 }
14722
14723 pub fn select_next(
14724 &mut self,
14725 action: &SelectNext,
14726 window: &mut Window,
14727 cx: &mut Context<Self>,
14728 ) -> Result<()> {
14729 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14730 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14731 self.select_next_match_internal(
14732 &display_map,
14733 action.replace_newest,
14734 Some(Autoscroll::newest()),
14735 window,
14736 cx,
14737 )?;
14738 Ok(())
14739 }
14740
14741 pub fn select_previous(
14742 &mut self,
14743 action: &SelectPrevious,
14744 window: &mut Window,
14745 cx: &mut Context<Self>,
14746 ) -> Result<()> {
14747 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14748 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14749 let buffer = display_map.buffer_snapshot();
14750 let mut selections = self.selections.all::<usize>(&display_map);
14751 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14752 let query = &select_prev_state.query;
14753 if !select_prev_state.done {
14754 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14755 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14756 let mut next_selected_range = None;
14757 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14758 let bytes_before_last_selection =
14759 buffer.reversed_bytes_in_range(0..last_selection.start);
14760 let bytes_after_first_selection =
14761 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14762 let query_matches = query
14763 .stream_find_iter(bytes_before_last_selection)
14764 .map(|result| (last_selection.start, result))
14765 .chain(
14766 query
14767 .stream_find_iter(bytes_after_first_selection)
14768 .map(|result| (buffer.len(), result)),
14769 );
14770 for (end_offset, query_match) in query_matches {
14771 let query_match = query_match.unwrap(); // can only fail due to I/O
14772 let offset_range =
14773 end_offset - query_match.end()..end_offset - query_match.start();
14774
14775 if !select_prev_state.wordwise
14776 || (!buffer.is_inside_word(offset_range.start, None)
14777 && !buffer.is_inside_word(offset_range.end, None))
14778 {
14779 next_selected_range = Some(offset_range);
14780 break;
14781 }
14782 }
14783
14784 if let Some(next_selected_range) = next_selected_range {
14785 self.select_match_ranges(
14786 next_selected_range,
14787 last_selection.reversed,
14788 action.replace_newest,
14789 Some(Autoscroll::newest()),
14790 window,
14791 cx,
14792 );
14793 } else {
14794 select_prev_state.done = true;
14795 }
14796 }
14797
14798 self.select_prev_state = Some(select_prev_state);
14799 } else {
14800 let mut only_carets = true;
14801 let mut same_text_selected = true;
14802 let mut selected_text = None;
14803
14804 let mut selections_iter = selections.iter().peekable();
14805 while let Some(selection) = selections_iter.next() {
14806 if selection.start != selection.end {
14807 only_carets = false;
14808 }
14809
14810 if same_text_selected {
14811 if selected_text.is_none() {
14812 selected_text =
14813 Some(buffer.text_for_range(selection.range()).collect::<String>());
14814 }
14815
14816 if let Some(next_selection) = selections_iter.peek() {
14817 if next_selection.range().len() == selection.range().len() {
14818 let next_selected_text = buffer
14819 .text_for_range(next_selection.range())
14820 .collect::<String>();
14821 if Some(next_selected_text) != selected_text {
14822 same_text_selected = false;
14823 selected_text = None;
14824 }
14825 } else {
14826 same_text_selected = false;
14827 selected_text = None;
14828 }
14829 }
14830 }
14831 }
14832
14833 if only_carets {
14834 for selection in &mut selections {
14835 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14836 selection.start = word_range.start;
14837 selection.end = word_range.end;
14838 selection.goal = SelectionGoal::None;
14839 selection.reversed = false;
14840 self.select_match_ranges(
14841 selection.start..selection.end,
14842 selection.reversed,
14843 action.replace_newest,
14844 Some(Autoscroll::newest()),
14845 window,
14846 cx,
14847 );
14848 }
14849 if selections.len() == 1 {
14850 let selection = selections
14851 .last()
14852 .expect("ensured that there's only one selection");
14853 let query = buffer
14854 .text_for_range(selection.start..selection.end)
14855 .collect::<String>();
14856 let is_empty = query.is_empty();
14857 let select_state = SelectNextState {
14858 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14859 wordwise: true,
14860 done: is_empty,
14861 };
14862 self.select_prev_state = Some(select_state);
14863 } else {
14864 self.select_prev_state = None;
14865 }
14866 } else if let Some(selected_text) = selected_text {
14867 self.select_prev_state = Some(SelectNextState {
14868 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14869 wordwise: false,
14870 done: false,
14871 });
14872 self.select_previous(action, window, cx)?;
14873 }
14874 }
14875 Ok(())
14876 }
14877
14878 pub fn find_next_match(
14879 &mut self,
14880 _: &FindNextMatch,
14881 window: &mut Window,
14882 cx: &mut Context<Self>,
14883 ) -> Result<()> {
14884 let selections = self.selections.disjoint_anchors_arc();
14885 match selections.first() {
14886 Some(first) if selections.len() >= 2 => {
14887 self.change_selections(Default::default(), window, cx, |s| {
14888 s.select_ranges([first.range()]);
14889 });
14890 }
14891 _ => self.select_next(
14892 &SelectNext {
14893 replace_newest: true,
14894 },
14895 window,
14896 cx,
14897 )?,
14898 }
14899 Ok(())
14900 }
14901
14902 pub fn find_previous_match(
14903 &mut self,
14904 _: &FindPreviousMatch,
14905 window: &mut Window,
14906 cx: &mut Context<Self>,
14907 ) -> Result<()> {
14908 let selections = self.selections.disjoint_anchors_arc();
14909 match selections.last() {
14910 Some(last) if selections.len() >= 2 => {
14911 self.change_selections(Default::default(), window, cx, |s| {
14912 s.select_ranges([last.range()]);
14913 });
14914 }
14915 _ => self.select_previous(
14916 &SelectPrevious {
14917 replace_newest: true,
14918 },
14919 window,
14920 cx,
14921 )?,
14922 }
14923 Ok(())
14924 }
14925
14926 pub fn toggle_comments(
14927 &mut self,
14928 action: &ToggleComments,
14929 window: &mut Window,
14930 cx: &mut Context<Self>,
14931 ) {
14932 if self.read_only(cx) {
14933 return;
14934 }
14935 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14936 let text_layout_details = &self.text_layout_details(window);
14937 self.transact(window, cx, |this, window, cx| {
14938 let mut selections = this
14939 .selections
14940 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14941 let mut edits = Vec::new();
14942 let mut selection_edit_ranges = Vec::new();
14943 let mut last_toggled_row = None;
14944 let snapshot = this.buffer.read(cx).read(cx);
14945 let empty_str: Arc<str> = Arc::default();
14946 let mut suffixes_inserted = Vec::new();
14947 let ignore_indent = action.ignore_indent;
14948
14949 fn comment_prefix_range(
14950 snapshot: &MultiBufferSnapshot,
14951 row: MultiBufferRow,
14952 comment_prefix: &str,
14953 comment_prefix_whitespace: &str,
14954 ignore_indent: bool,
14955 ) -> Range<Point> {
14956 let indent_size = if ignore_indent {
14957 0
14958 } else {
14959 snapshot.indent_size_for_line(row).len
14960 };
14961
14962 let start = Point::new(row.0, indent_size);
14963
14964 let mut line_bytes = snapshot
14965 .bytes_in_range(start..snapshot.max_point())
14966 .flatten()
14967 .copied();
14968
14969 // If this line currently begins with the line comment prefix, then record
14970 // the range containing the prefix.
14971 if line_bytes
14972 .by_ref()
14973 .take(comment_prefix.len())
14974 .eq(comment_prefix.bytes())
14975 {
14976 // Include any whitespace that matches the comment prefix.
14977 let matching_whitespace_len = line_bytes
14978 .zip(comment_prefix_whitespace.bytes())
14979 .take_while(|(a, b)| a == b)
14980 .count() as u32;
14981 let end = Point::new(
14982 start.row,
14983 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14984 );
14985 start..end
14986 } else {
14987 start..start
14988 }
14989 }
14990
14991 fn comment_suffix_range(
14992 snapshot: &MultiBufferSnapshot,
14993 row: MultiBufferRow,
14994 comment_suffix: &str,
14995 comment_suffix_has_leading_space: bool,
14996 ) -> Range<Point> {
14997 let end = Point::new(row.0, snapshot.line_len(row));
14998 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14999
15000 let mut line_end_bytes = snapshot
15001 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15002 .flatten()
15003 .copied();
15004
15005 let leading_space_len = if suffix_start_column > 0
15006 && line_end_bytes.next() == Some(b' ')
15007 && comment_suffix_has_leading_space
15008 {
15009 1
15010 } else {
15011 0
15012 };
15013
15014 // If this line currently begins with the line comment prefix, then record
15015 // the range containing the prefix.
15016 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15017 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15018 start..end
15019 } else {
15020 end..end
15021 }
15022 }
15023
15024 // TODO: Handle selections that cross excerpts
15025 for selection in &mut selections {
15026 let start_column = snapshot
15027 .indent_size_for_line(MultiBufferRow(selection.start.row))
15028 .len;
15029 let language = if let Some(language) =
15030 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15031 {
15032 language
15033 } else {
15034 continue;
15035 };
15036
15037 selection_edit_ranges.clear();
15038
15039 // If multiple selections contain a given row, avoid processing that
15040 // row more than once.
15041 let mut start_row = MultiBufferRow(selection.start.row);
15042 if last_toggled_row == Some(start_row) {
15043 start_row = start_row.next_row();
15044 }
15045 let end_row =
15046 if selection.end.row > selection.start.row && selection.end.column == 0 {
15047 MultiBufferRow(selection.end.row - 1)
15048 } else {
15049 MultiBufferRow(selection.end.row)
15050 };
15051 last_toggled_row = Some(end_row);
15052
15053 if start_row > end_row {
15054 continue;
15055 }
15056
15057 // If the language has line comments, toggle those.
15058 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15059
15060 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15061 if ignore_indent {
15062 full_comment_prefixes = full_comment_prefixes
15063 .into_iter()
15064 .map(|s| Arc::from(s.trim_end()))
15065 .collect();
15066 }
15067
15068 if !full_comment_prefixes.is_empty() {
15069 let first_prefix = full_comment_prefixes
15070 .first()
15071 .expect("prefixes is non-empty");
15072 let prefix_trimmed_lengths = full_comment_prefixes
15073 .iter()
15074 .map(|p| p.trim_end_matches(' ').len())
15075 .collect::<SmallVec<[usize; 4]>>();
15076
15077 let mut all_selection_lines_are_comments = true;
15078
15079 for row in start_row.0..=end_row.0 {
15080 let row = MultiBufferRow(row);
15081 if start_row < end_row && snapshot.is_line_blank(row) {
15082 continue;
15083 }
15084
15085 let prefix_range = full_comment_prefixes
15086 .iter()
15087 .zip(prefix_trimmed_lengths.iter().copied())
15088 .map(|(prefix, trimmed_prefix_len)| {
15089 comment_prefix_range(
15090 snapshot.deref(),
15091 row,
15092 &prefix[..trimmed_prefix_len],
15093 &prefix[trimmed_prefix_len..],
15094 ignore_indent,
15095 )
15096 })
15097 .max_by_key(|range| range.end.column - range.start.column)
15098 .expect("prefixes is non-empty");
15099
15100 if prefix_range.is_empty() {
15101 all_selection_lines_are_comments = false;
15102 }
15103
15104 selection_edit_ranges.push(prefix_range);
15105 }
15106
15107 if all_selection_lines_are_comments {
15108 edits.extend(
15109 selection_edit_ranges
15110 .iter()
15111 .cloned()
15112 .map(|range| (range, empty_str.clone())),
15113 );
15114 } else {
15115 let min_column = selection_edit_ranges
15116 .iter()
15117 .map(|range| range.start.column)
15118 .min()
15119 .unwrap_or(0);
15120 edits.extend(selection_edit_ranges.iter().map(|range| {
15121 let position = Point::new(range.start.row, min_column);
15122 (position..position, first_prefix.clone())
15123 }));
15124 }
15125 } else if let Some(BlockCommentConfig {
15126 start: full_comment_prefix,
15127 end: comment_suffix,
15128 ..
15129 }) = language.block_comment()
15130 {
15131 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15132 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15133 let prefix_range = comment_prefix_range(
15134 snapshot.deref(),
15135 start_row,
15136 comment_prefix,
15137 comment_prefix_whitespace,
15138 ignore_indent,
15139 );
15140 let suffix_range = comment_suffix_range(
15141 snapshot.deref(),
15142 end_row,
15143 comment_suffix.trim_start_matches(' '),
15144 comment_suffix.starts_with(' '),
15145 );
15146
15147 if prefix_range.is_empty() || suffix_range.is_empty() {
15148 edits.push((
15149 prefix_range.start..prefix_range.start,
15150 full_comment_prefix.clone(),
15151 ));
15152 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15153 suffixes_inserted.push((end_row, comment_suffix.len()));
15154 } else {
15155 edits.push((prefix_range, empty_str.clone()));
15156 edits.push((suffix_range, empty_str.clone()));
15157 }
15158 } else {
15159 continue;
15160 }
15161 }
15162
15163 drop(snapshot);
15164 this.buffer.update(cx, |buffer, cx| {
15165 buffer.edit(edits, None, cx);
15166 });
15167
15168 // Adjust selections so that they end before any comment suffixes that
15169 // were inserted.
15170 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15171 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15172 let snapshot = this.buffer.read(cx).read(cx);
15173 for selection in &mut selections {
15174 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15175 match row.cmp(&MultiBufferRow(selection.end.row)) {
15176 Ordering::Less => {
15177 suffixes_inserted.next();
15178 continue;
15179 }
15180 Ordering::Greater => break,
15181 Ordering::Equal => {
15182 if selection.end.column == snapshot.line_len(row) {
15183 if selection.is_empty() {
15184 selection.start.column -= suffix_len as u32;
15185 }
15186 selection.end.column -= suffix_len as u32;
15187 }
15188 break;
15189 }
15190 }
15191 }
15192 }
15193
15194 drop(snapshot);
15195 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15196
15197 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15198 let selections_on_single_row = selections.windows(2).all(|selections| {
15199 selections[0].start.row == selections[1].start.row
15200 && selections[0].end.row == selections[1].end.row
15201 && selections[0].start.row == selections[0].end.row
15202 });
15203 let selections_selecting = selections
15204 .iter()
15205 .any(|selection| selection.start != selection.end);
15206 let advance_downwards = action.advance_downwards
15207 && selections_on_single_row
15208 && !selections_selecting
15209 && !matches!(this.mode, EditorMode::SingleLine);
15210
15211 if advance_downwards {
15212 let snapshot = this.buffer.read(cx).snapshot(cx);
15213
15214 this.change_selections(Default::default(), window, cx, |s| {
15215 s.move_cursors_with(|display_snapshot, display_point, _| {
15216 let mut point = display_point.to_point(display_snapshot);
15217 point.row += 1;
15218 point = snapshot.clip_point(point, Bias::Left);
15219 let display_point = point.to_display_point(display_snapshot);
15220 let goal = SelectionGoal::HorizontalPosition(
15221 display_snapshot
15222 .x_for_display_point(display_point, text_layout_details)
15223 .into(),
15224 );
15225 (display_point, goal)
15226 })
15227 });
15228 }
15229 });
15230 }
15231
15232 pub fn select_enclosing_symbol(
15233 &mut self,
15234 _: &SelectEnclosingSymbol,
15235 window: &mut Window,
15236 cx: &mut Context<Self>,
15237 ) {
15238 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15239
15240 let buffer = self.buffer.read(cx).snapshot(cx);
15241 let old_selections = self
15242 .selections
15243 .all::<usize>(&self.display_snapshot(cx))
15244 .into_boxed_slice();
15245
15246 fn update_selection(
15247 selection: &Selection<usize>,
15248 buffer_snap: &MultiBufferSnapshot,
15249 ) -> Option<Selection<usize>> {
15250 let cursor = selection.head();
15251 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15252 for symbol in symbols.iter().rev() {
15253 let start = symbol.range.start.to_offset(buffer_snap);
15254 let end = symbol.range.end.to_offset(buffer_snap);
15255 let new_range = start..end;
15256 if start < selection.start || end > selection.end {
15257 return Some(Selection {
15258 id: selection.id,
15259 start: new_range.start,
15260 end: new_range.end,
15261 goal: SelectionGoal::None,
15262 reversed: selection.reversed,
15263 });
15264 }
15265 }
15266 None
15267 }
15268
15269 let mut selected_larger_symbol = false;
15270 let new_selections = old_selections
15271 .iter()
15272 .map(|selection| match update_selection(selection, &buffer) {
15273 Some(new_selection) => {
15274 if new_selection.range() != selection.range() {
15275 selected_larger_symbol = true;
15276 }
15277 new_selection
15278 }
15279 None => selection.clone(),
15280 })
15281 .collect::<Vec<_>>();
15282
15283 if selected_larger_symbol {
15284 self.change_selections(Default::default(), window, cx, |s| {
15285 s.select(new_selections);
15286 });
15287 }
15288 }
15289
15290 pub fn select_larger_syntax_node(
15291 &mut self,
15292 _: &SelectLargerSyntaxNode,
15293 window: &mut Window,
15294 cx: &mut Context<Self>,
15295 ) {
15296 let Some(visible_row_count) = self.visible_row_count() else {
15297 return;
15298 };
15299 let old_selections: Box<[_]> = self
15300 .selections
15301 .all::<usize>(&self.display_snapshot(cx))
15302 .into();
15303 if old_selections.is_empty() {
15304 return;
15305 }
15306
15307 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15308
15309 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15310 let buffer = self.buffer.read(cx).snapshot(cx);
15311
15312 let mut selected_larger_node = false;
15313 let mut new_selections = old_selections
15314 .iter()
15315 .map(|selection| {
15316 let old_range = selection.start..selection.end;
15317
15318 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15319 // manually select word at selection
15320 if ["string_content", "inline"].contains(&node.kind()) {
15321 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15322 // ignore if word is already selected
15323 if !word_range.is_empty() && old_range != word_range {
15324 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15325 // only select word if start and end point belongs to same word
15326 if word_range == last_word_range {
15327 selected_larger_node = true;
15328 return Selection {
15329 id: selection.id,
15330 start: word_range.start,
15331 end: word_range.end,
15332 goal: SelectionGoal::None,
15333 reversed: selection.reversed,
15334 };
15335 }
15336 }
15337 }
15338 }
15339
15340 let mut new_range = old_range.clone();
15341 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15342 new_range = range;
15343 if !node.is_named() {
15344 continue;
15345 }
15346 if !display_map.intersects_fold(new_range.start)
15347 && !display_map.intersects_fold(new_range.end)
15348 {
15349 break;
15350 }
15351 }
15352
15353 selected_larger_node |= new_range != old_range;
15354 Selection {
15355 id: selection.id,
15356 start: new_range.start,
15357 end: new_range.end,
15358 goal: SelectionGoal::None,
15359 reversed: selection.reversed,
15360 }
15361 })
15362 .collect::<Vec<_>>();
15363
15364 if !selected_larger_node {
15365 return; // don't put this call in the history
15366 }
15367
15368 // scroll based on transformation done to the last selection created by the user
15369 let (last_old, last_new) = old_selections
15370 .last()
15371 .zip(new_selections.last().cloned())
15372 .expect("old_selections isn't empty");
15373
15374 // revert selection
15375 let is_selection_reversed = {
15376 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15377 new_selections.last_mut().expect("checked above").reversed =
15378 should_newest_selection_be_reversed;
15379 should_newest_selection_be_reversed
15380 };
15381
15382 if selected_larger_node {
15383 self.select_syntax_node_history.disable_clearing = true;
15384 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15385 s.select(new_selections.clone());
15386 });
15387 self.select_syntax_node_history.disable_clearing = false;
15388 }
15389
15390 let start_row = last_new.start.to_display_point(&display_map).row().0;
15391 let end_row = last_new.end.to_display_point(&display_map).row().0;
15392 let selection_height = end_row - start_row + 1;
15393 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15394
15395 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15396 let scroll_behavior = if fits_on_the_screen {
15397 self.request_autoscroll(Autoscroll::fit(), cx);
15398 SelectSyntaxNodeScrollBehavior::FitSelection
15399 } else if is_selection_reversed {
15400 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15401 SelectSyntaxNodeScrollBehavior::CursorTop
15402 } else {
15403 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15404 SelectSyntaxNodeScrollBehavior::CursorBottom
15405 };
15406
15407 self.select_syntax_node_history.push((
15408 old_selections,
15409 scroll_behavior,
15410 is_selection_reversed,
15411 ));
15412 }
15413
15414 pub fn select_smaller_syntax_node(
15415 &mut self,
15416 _: &SelectSmallerSyntaxNode,
15417 window: &mut Window,
15418 cx: &mut Context<Self>,
15419 ) {
15420 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15421
15422 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15423 self.select_syntax_node_history.pop()
15424 {
15425 if let Some(selection) = selections.last_mut() {
15426 selection.reversed = is_selection_reversed;
15427 }
15428
15429 self.select_syntax_node_history.disable_clearing = true;
15430 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15431 s.select(selections.to_vec());
15432 });
15433 self.select_syntax_node_history.disable_clearing = false;
15434
15435 match scroll_behavior {
15436 SelectSyntaxNodeScrollBehavior::CursorTop => {
15437 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15438 }
15439 SelectSyntaxNodeScrollBehavior::FitSelection => {
15440 self.request_autoscroll(Autoscroll::fit(), cx);
15441 }
15442 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15443 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15444 }
15445 }
15446 }
15447 }
15448
15449 pub fn unwrap_syntax_node(
15450 &mut self,
15451 _: &UnwrapSyntaxNode,
15452 window: &mut Window,
15453 cx: &mut Context<Self>,
15454 ) {
15455 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15456
15457 let buffer = self.buffer.read(cx).snapshot(cx);
15458 let selections = self
15459 .selections
15460 .all::<usize>(&self.display_snapshot(cx))
15461 .into_iter()
15462 // subtracting the offset requires sorting
15463 .sorted_by_key(|i| i.start);
15464
15465 let full_edits = selections
15466 .into_iter()
15467 .filter_map(|selection| {
15468 let child = if selection.is_empty()
15469 && let Some((_, ancestor_range)) =
15470 buffer.syntax_ancestor(selection.start..selection.end)
15471 {
15472 ancestor_range
15473 } else {
15474 selection.range()
15475 };
15476
15477 let mut parent = child.clone();
15478 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15479 parent = ancestor_range;
15480 if parent.start < child.start || parent.end > child.end {
15481 break;
15482 }
15483 }
15484
15485 if parent == child {
15486 return None;
15487 }
15488 let text = buffer.text_for_range(child).collect::<String>();
15489 Some((selection.id, parent, text))
15490 })
15491 .collect::<Vec<_>>();
15492 if full_edits.is_empty() {
15493 return;
15494 }
15495
15496 self.transact(window, cx, |this, window, cx| {
15497 this.buffer.update(cx, |buffer, cx| {
15498 buffer.edit(
15499 full_edits
15500 .iter()
15501 .map(|(_, p, t)| (p.clone(), t.clone()))
15502 .collect::<Vec<_>>(),
15503 None,
15504 cx,
15505 );
15506 });
15507 this.change_selections(Default::default(), window, cx, |s| {
15508 let mut offset = 0;
15509 let mut selections = vec![];
15510 for (id, parent, text) in full_edits {
15511 let start = parent.start - offset;
15512 offset += parent.len() - text.len();
15513 selections.push(Selection {
15514 id,
15515 start,
15516 end: start + text.len(),
15517 reversed: false,
15518 goal: Default::default(),
15519 });
15520 }
15521 s.select(selections);
15522 });
15523 });
15524 }
15525
15526 pub fn select_next_syntax_node(
15527 &mut self,
15528 _: &SelectNextSyntaxNode,
15529 window: &mut Window,
15530 cx: &mut Context<Self>,
15531 ) {
15532 let old_selections: Box<[_]> = self
15533 .selections
15534 .all::<usize>(&self.display_snapshot(cx))
15535 .into();
15536 if old_selections.is_empty() {
15537 return;
15538 }
15539
15540 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15541
15542 let buffer = self.buffer.read(cx).snapshot(cx);
15543 let mut selected_sibling = false;
15544
15545 let new_selections = old_selections
15546 .iter()
15547 .map(|selection| {
15548 let old_range = selection.start..selection.end;
15549
15550 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15551 let new_range = node.byte_range();
15552 selected_sibling = true;
15553 Selection {
15554 id: selection.id,
15555 start: new_range.start,
15556 end: new_range.end,
15557 goal: SelectionGoal::None,
15558 reversed: selection.reversed,
15559 }
15560 } else {
15561 selection.clone()
15562 }
15563 })
15564 .collect::<Vec<_>>();
15565
15566 if selected_sibling {
15567 self.change_selections(
15568 SelectionEffects::scroll(Autoscroll::fit()),
15569 window,
15570 cx,
15571 |s| {
15572 s.select(new_selections);
15573 },
15574 );
15575 }
15576 }
15577
15578 pub fn select_prev_syntax_node(
15579 &mut self,
15580 _: &SelectPreviousSyntaxNode,
15581 window: &mut Window,
15582 cx: &mut Context<Self>,
15583 ) {
15584 let old_selections: Box<[_]> = self
15585 .selections
15586 .all::<usize>(&self.display_snapshot(cx))
15587 .into();
15588 if old_selections.is_empty() {
15589 return;
15590 }
15591
15592 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15593
15594 let buffer = self.buffer.read(cx).snapshot(cx);
15595 let mut selected_sibling = false;
15596
15597 let new_selections = old_selections
15598 .iter()
15599 .map(|selection| {
15600 let old_range = selection.start..selection.end;
15601
15602 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15603 let new_range = node.byte_range();
15604 selected_sibling = true;
15605 Selection {
15606 id: selection.id,
15607 start: new_range.start,
15608 end: new_range.end,
15609 goal: SelectionGoal::None,
15610 reversed: selection.reversed,
15611 }
15612 } else {
15613 selection.clone()
15614 }
15615 })
15616 .collect::<Vec<_>>();
15617
15618 if selected_sibling {
15619 self.change_selections(
15620 SelectionEffects::scroll(Autoscroll::fit()),
15621 window,
15622 cx,
15623 |s| {
15624 s.select(new_selections);
15625 },
15626 );
15627 }
15628 }
15629
15630 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15631 if !EditorSettings::get_global(cx).gutter.runnables {
15632 self.clear_tasks();
15633 return Task::ready(());
15634 }
15635 let project = self.project().map(Entity::downgrade);
15636 let task_sources = self.lsp_task_sources(cx);
15637 let multi_buffer = self.buffer.downgrade();
15638 cx.spawn_in(window, async move |editor, cx| {
15639 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15640 let Some(project) = project.and_then(|p| p.upgrade()) else {
15641 return;
15642 };
15643 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15644 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15645 }) else {
15646 return;
15647 };
15648
15649 let hide_runnables = project
15650 .update(cx, |project, _| project.is_via_collab())
15651 .unwrap_or(true);
15652 if hide_runnables {
15653 return;
15654 }
15655 let new_rows =
15656 cx.background_spawn({
15657 let snapshot = display_snapshot.clone();
15658 async move {
15659 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15660 }
15661 })
15662 .await;
15663 let Ok(lsp_tasks) =
15664 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15665 else {
15666 return;
15667 };
15668 let lsp_tasks = lsp_tasks.await;
15669
15670 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15671 lsp_tasks
15672 .into_iter()
15673 .flat_map(|(kind, tasks)| {
15674 tasks.into_iter().filter_map(move |(location, task)| {
15675 Some((kind.clone(), location?, task))
15676 })
15677 })
15678 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15679 let buffer = location.target.buffer;
15680 let buffer_snapshot = buffer.read(cx).snapshot();
15681 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15682 |(excerpt_id, snapshot, _)| {
15683 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15684 display_snapshot
15685 .buffer_snapshot()
15686 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15687 } else {
15688 None
15689 }
15690 },
15691 );
15692 if let Some(offset) = offset {
15693 let task_buffer_range =
15694 location.target.range.to_point(&buffer_snapshot);
15695 let context_buffer_range =
15696 task_buffer_range.to_offset(&buffer_snapshot);
15697 let context_range = BufferOffset(context_buffer_range.start)
15698 ..BufferOffset(context_buffer_range.end);
15699
15700 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15701 .or_insert_with(|| RunnableTasks {
15702 templates: Vec::new(),
15703 offset,
15704 column: task_buffer_range.start.column,
15705 extra_variables: HashMap::default(),
15706 context_range,
15707 })
15708 .templates
15709 .push((kind, task.original_task().clone()));
15710 }
15711
15712 acc
15713 })
15714 }) else {
15715 return;
15716 };
15717
15718 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15719 buffer.language_settings(cx).tasks.prefer_lsp
15720 }) else {
15721 return;
15722 };
15723
15724 let rows = Self::runnable_rows(
15725 project,
15726 display_snapshot,
15727 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15728 new_rows,
15729 cx.clone(),
15730 )
15731 .await;
15732 editor
15733 .update(cx, |editor, _| {
15734 editor.clear_tasks();
15735 for (key, mut value) in rows {
15736 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15737 value.templates.extend(lsp_tasks.templates);
15738 }
15739
15740 editor.insert_tasks(key, value);
15741 }
15742 for (key, value) in lsp_tasks_by_rows {
15743 editor.insert_tasks(key, value);
15744 }
15745 })
15746 .ok();
15747 })
15748 }
15749 fn fetch_runnable_ranges(
15750 snapshot: &DisplaySnapshot,
15751 range: Range<Anchor>,
15752 ) -> Vec<language::RunnableRange> {
15753 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15754 }
15755
15756 fn runnable_rows(
15757 project: Entity<Project>,
15758 snapshot: DisplaySnapshot,
15759 prefer_lsp: bool,
15760 runnable_ranges: Vec<RunnableRange>,
15761 cx: AsyncWindowContext,
15762 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15763 cx.spawn(async move |cx| {
15764 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15765 for mut runnable in runnable_ranges {
15766 let Some(tasks) = cx
15767 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15768 .ok()
15769 else {
15770 continue;
15771 };
15772 let mut tasks = tasks.await;
15773
15774 if prefer_lsp {
15775 tasks.retain(|(task_kind, _)| {
15776 !matches!(task_kind, TaskSourceKind::Language { .. })
15777 });
15778 }
15779 if tasks.is_empty() {
15780 continue;
15781 }
15782
15783 let point = runnable
15784 .run_range
15785 .start
15786 .to_point(&snapshot.buffer_snapshot());
15787 let Some(row) = snapshot
15788 .buffer_snapshot()
15789 .buffer_line_for_row(MultiBufferRow(point.row))
15790 .map(|(_, range)| range.start.row)
15791 else {
15792 continue;
15793 };
15794
15795 let context_range =
15796 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15797 runnable_rows.push((
15798 (runnable.buffer_id, row),
15799 RunnableTasks {
15800 templates: tasks,
15801 offset: snapshot
15802 .buffer_snapshot()
15803 .anchor_before(runnable.run_range.start),
15804 context_range,
15805 column: point.column,
15806 extra_variables: runnable.extra_captures,
15807 },
15808 ));
15809 }
15810 runnable_rows
15811 })
15812 }
15813
15814 fn templates_with_tags(
15815 project: &Entity<Project>,
15816 runnable: &mut Runnable,
15817 cx: &mut App,
15818 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15819 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15820 let (worktree_id, file) = project
15821 .buffer_for_id(runnable.buffer, cx)
15822 .and_then(|buffer| buffer.read(cx).file())
15823 .map(|file| (file.worktree_id(cx), file.clone()))
15824 .unzip();
15825
15826 (
15827 project.task_store().read(cx).task_inventory().cloned(),
15828 worktree_id,
15829 file,
15830 )
15831 });
15832
15833 let tags = mem::take(&mut runnable.tags);
15834 let language = runnable.language.clone();
15835 cx.spawn(async move |cx| {
15836 let mut templates_with_tags = Vec::new();
15837 if let Some(inventory) = inventory {
15838 for RunnableTag(tag) in tags {
15839 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15840 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15841 }) else {
15842 return templates_with_tags;
15843 };
15844 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15845 move |(_, template)| {
15846 template.tags.iter().any(|source_tag| source_tag == &tag)
15847 },
15848 ));
15849 }
15850 }
15851 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15852
15853 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15854 // Strongest source wins; if we have worktree tag binding, prefer that to
15855 // global and language bindings;
15856 // if we have a global binding, prefer that to language binding.
15857 let first_mismatch = templates_with_tags
15858 .iter()
15859 .position(|(tag_source, _)| tag_source != leading_tag_source);
15860 if let Some(index) = first_mismatch {
15861 templates_with_tags.truncate(index);
15862 }
15863 }
15864
15865 templates_with_tags
15866 })
15867 }
15868
15869 pub fn move_to_enclosing_bracket(
15870 &mut self,
15871 _: &MoveToEnclosingBracket,
15872 window: &mut Window,
15873 cx: &mut Context<Self>,
15874 ) {
15875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15876 self.change_selections(Default::default(), window, cx, |s| {
15877 s.move_offsets_with(|snapshot, selection| {
15878 let Some(enclosing_bracket_ranges) =
15879 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15880 else {
15881 return;
15882 };
15883
15884 let mut best_length = usize::MAX;
15885 let mut best_inside = false;
15886 let mut best_in_bracket_range = false;
15887 let mut best_destination = None;
15888 for (open, close) in enclosing_bracket_ranges {
15889 let close = close.to_inclusive();
15890 let length = close.end() - open.start;
15891 let inside = selection.start >= open.end && selection.end <= *close.start();
15892 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15893 || close.contains(&selection.head());
15894
15895 // If best is next to a bracket and current isn't, skip
15896 if !in_bracket_range && best_in_bracket_range {
15897 continue;
15898 }
15899
15900 // Prefer smaller lengths unless best is inside and current isn't
15901 if length > best_length && (best_inside || !inside) {
15902 continue;
15903 }
15904
15905 best_length = length;
15906 best_inside = inside;
15907 best_in_bracket_range = in_bracket_range;
15908 best_destination = Some(
15909 if close.contains(&selection.start) && close.contains(&selection.end) {
15910 if inside { open.end } else { open.start }
15911 } else if inside {
15912 *close.start()
15913 } else {
15914 *close.end()
15915 },
15916 );
15917 }
15918
15919 if let Some(destination) = best_destination {
15920 selection.collapse_to(destination, SelectionGoal::None);
15921 }
15922 })
15923 });
15924 }
15925
15926 pub fn undo_selection(
15927 &mut self,
15928 _: &UndoSelection,
15929 window: &mut Window,
15930 cx: &mut Context<Self>,
15931 ) {
15932 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15933 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15934 self.selection_history.mode = SelectionHistoryMode::Undoing;
15935 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15936 this.end_selection(window, cx);
15937 this.change_selections(
15938 SelectionEffects::scroll(Autoscroll::newest()),
15939 window,
15940 cx,
15941 |s| s.select_anchors(entry.selections.to_vec()),
15942 );
15943 });
15944 self.selection_history.mode = SelectionHistoryMode::Normal;
15945
15946 self.select_next_state = entry.select_next_state;
15947 self.select_prev_state = entry.select_prev_state;
15948 self.add_selections_state = entry.add_selections_state;
15949 }
15950 }
15951
15952 pub fn redo_selection(
15953 &mut self,
15954 _: &RedoSelection,
15955 window: &mut Window,
15956 cx: &mut Context<Self>,
15957 ) {
15958 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15959 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15960 self.selection_history.mode = SelectionHistoryMode::Redoing;
15961 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15962 this.end_selection(window, cx);
15963 this.change_selections(
15964 SelectionEffects::scroll(Autoscroll::newest()),
15965 window,
15966 cx,
15967 |s| s.select_anchors(entry.selections.to_vec()),
15968 );
15969 });
15970 self.selection_history.mode = SelectionHistoryMode::Normal;
15971
15972 self.select_next_state = entry.select_next_state;
15973 self.select_prev_state = entry.select_prev_state;
15974 self.add_selections_state = entry.add_selections_state;
15975 }
15976 }
15977
15978 pub fn expand_excerpts(
15979 &mut self,
15980 action: &ExpandExcerpts,
15981 _: &mut Window,
15982 cx: &mut Context<Self>,
15983 ) {
15984 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15985 }
15986
15987 pub fn expand_excerpts_down(
15988 &mut self,
15989 action: &ExpandExcerptsDown,
15990 _: &mut Window,
15991 cx: &mut Context<Self>,
15992 ) {
15993 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15994 }
15995
15996 pub fn expand_excerpts_up(
15997 &mut self,
15998 action: &ExpandExcerptsUp,
15999 _: &mut Window,
16000 cx: &mut Context<Self>,
16001 ) {
16002 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16003 }
16004
16005 pub fn expand_excerpts_for_direction(
16006 &mut self,
16007 lines: u32,
16008 direction: ExpandExcerptDirection,
16009
16010 cx: &mut Context<Self>,
16011 ) {
16012 let selections = self.selections.disjoint_anchors_arc();
16013
16014 let lines = if lines == 0 {
16015 EditorSettings::get_global(cx).expand_excerpt_lines
16016 } else {
16017 lines
16018 };
16019
16020 self.buffer.update(cx, |buffer, cx| {
16021 let snapshot = buffer.snapshot(cx);
16022 let mut excerpt_ids = selections
16023 .iter()
16024 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16025 .collect::<Vec<_>>();
16026 excerpt_ids.sort();
16027 excerpt_ids.dedup();
16028 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16029 })
16030 }
16031
16032 pub fn expand_excerpt(
16033 &mut self,
16034 excerpt: ExcerptId,
16035 direction: ExpandExcerptDirection,
16036 window: &mut Window,
16037 cx: &mut Context<Self>,
16038 ) {
16039 let current_scroll_position = self.scroll_position(cx);
16040 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16041 let mut should_scroll_up = false;
16042
16043 if direction == ExpandExcerptDirection::Down {
16044 let multi_buffer = self.buffer.read(cx);
16045 let snapshot = multi_buffer.snapshot(cx);
16046 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16047 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16048 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16049 {
16050 let buffer_snapshot = buffer.read(cx).snapshot();
16051 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16052 let last_row = buffer_snapshot.max_point().row;
16053 let lines_below = last_row.saturating_sub(excerpt_end_row);
16054 should_scroll_up = lines_below >= lines_to_expand;
16055 }
16056 }
16057
16058 self.buffer.update(cx, |buffer, cx| {
16059 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16060 });
16061
16062 if should_scroll_up {
16063 let new_scroll_position =
16064 current_scroll_position + gpui::Point::new(0.0, lines_to_expand as ScrollOffset);
16065 self.set_scroll_position(new_scroll_position, window, cx);
16066 }
16067 }
16068
16069 pub fn go_to_singleton_buffer_point(
16070 &mut self,
16071 point: Point,
16072 window: &mut Window,
16073 cx: &mut Context<Self>,
16074 ) {
16075 self.go_to_singleton_buffer_range(point..point, window, cx);
16076 }
16077
16078 pub fn go_to_singleton_buffer_range(
16079 &mut self,
16080 range: Range<Point>,
16081 window: &mut Window,
16082 cx: &mut Context<Self>,
16083 ) {
16084 let multibuffer = self.buffer().read(cx);
16085 let Some(buffer) = multibuffer.as_singleton() else {
16086 return;
16087 };
16088 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16089 return;
16090 };
16091 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16092 return;
16093 };
16094 self.change_selections(
16095 SelectionEffects::default().nav_history(true),
16096 window,
16097 cx,
16098 |s| s.select_anchor_ranges([start..end]),
16099 );
16100 }
16101
16102 pub fn go_to_diagnostic(
16103 &mut self,
16104 action: &GoToDiagnostic,
16105 window: &mut Window,
16106 cx: &mut Context<Self>,
16107 ) {
16108 if !self.diagnostics_enabled() {
16109 return;
16110 }
16111 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16112 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16113 }
16114
16115 pub fn go_to_prev_diagnostic(
16116 &mut self,
16117 action: &GoToPreviousDiagnostic,
16118 window: &mut Window,
16119 cx: &mut Context<Self>,
16120 ) {
16121 if !self.diagnostics_enabled() {
16122 return;
16123 }
16124 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16125 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16126 }
16127
16128 pub fn go_to_diagnostic_impl(
16129 &mut self,
16130 direction: Direction,
16131 severity: GoToDiagnosticSeverityFilter,
16132 window: &mut Window,
16133 cx: &mut Context<Self>,
16134 ) {
16135 let buffer = self.buffer.read(cx).snapshot(cx);
16136 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16137
16138 let mut active_group_id = None;
16139 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16140 && active_group.active_range.start.to_offset(&buffer) == selection.start
16141 {
16142 active_group_id = Some(active_group.group_id);
16143 }
16144
16145 fn filtered<'a>(
16146 snapshot: EditorSnapshot,
16147 severity: GoToDiagnosticSeverityFilter,
16148 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16149 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16150 diagnostics
16151 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16152 .filter(|entry| entry.range.start != entry.range.end)
16153 .filter(|entry| !entry.diagnostic.is_unnecessary)
16154 .filter(move |entry| !snapshot.intersects_fold(entry.range.start))
16155 }
16156
16157 let snapshot = self.snapshot(window, cx);
16158 let before = filtered(
16159 snapshot.clone(),
16160 severity,
16161 buffer
16162 .diagnostics_in_range(0..selection.start)
16163 .filter(|entry| entry.range.start <= selection.start),
16164 );
16165 let after = filtered(
16166 snapshot,
16167 severity,
16168 buffer
16169 .diagnostics_in_range(selection.start..buffer.len())
16170 .filter(|entry| entry.range.start >= selection.start),
16171 );
16172
16173 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16174 if direction == Direction::Prev {
16175 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16176 {
16177 for diagnostic in prev_diagnostics.into_iter().rev() {
16178 if diagnostic.range.start != selection.start
16179 || active_group_id
16180 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16181 {
16182 found = Some(diagnostic);
16183 break 'outer;
16184 }
16185 }
16186 }
16187 } else {
16188 for diagnostic in after.chain(before) {
16189 if diagnostic.range.start != selection.start
16190 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16191 {
16192 found = Some(diagnostic);
16193 break;
16194 }
16195 }
16196 }
16197 let Some(next_diagnostic) = found else {
16198 return;
16199 };
16200
16201 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16202 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16203 return;
16204 };
16205 self.change_selections(Default::default(), window, cx, |s| {
16206 s.select_ranges(vec![
16207 next_diagnostic.range.start..next_diagnostic.range.start,
16208 ])
16209 });
16210 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16211 self.refresh_edit_prediction(false, true, window, cx);
16212 }
16213
16214 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16215 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16216 let snapshot = self.snapshot(window, cx);
16217 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16218 self.go_to_hunk_before_or_after_position(
16219 &snapshot,
16220 selection.head(),
16221 Direction::Next,
16222 window,
16223 cx,
16224 );
16225 }
16226
16227 pub fn go_to_hunk_before_or_after_position(
16228 &mut self,
16229 snapshot: &EditorSnapshot,
16230 position: Point,
16231 direction: Direction,
16232 window: &mut Window,
16233 cx: &mut Context<Editor>,
16234 ) {
16235 let row = if direction == Direction::Next {
16236 self.hunk_after_position(snapshot, position)
16237 .map(|hunk| hunk.row_range.start)
16238 } else {
16239 self.hunk_before_position(snapshot, position)
16240 };
16241
16242 if let Some(row) = row {
16243 let destination = Point::new(row.0, 0);
16244 let autoscroll = Autoscroll::center();
16245
16246 self.unfold_ranges(&[destination..destination], false, false, cx);
16247 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16248 s.select_ranges([destination..destination]);
16249 });
16250 }
16251 }
16252
16253 fn hunk_after_position(
16254 &mut self,
16255 snapshot: &EditorSnapshot,
16256 position: Point,
16257 ) -> Option<MultiBufferDiffHunk> {
16258 snapshot
16259 .buffer_snapshot()
16260 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16261 .find(|hunk| hunk.row_range.start.0 > position.row)
16262 .or_else(|| {
16263 snapshot
16264 .buffer_snapshot()
16265 .diff_hunks_in_range(Point::zero()..position)
16266 .find(|hunk| hunk.row_range.end.0 < position.row)
16267 })
16268 }
16269
16270 fn go_to_prev_hunk(
16271 &mut self,
16272 _: &GoToPreviousHunk,
16273 window: &mut Window,
16274 cx: &mut Context<Self>,
16275 ) {
16276 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16277 let snapshot = self.snapshot(window, cx);
16278 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16279 self.go_to_hunk_before_or_after_position(
16280 &snapshot,
16281 selection.head(),
16282 Direction::Prev,
16283 window,
16284 cx,
16285 );
16286 }
16287
16288 fn hunk_before_position(
16289 &mut self,
16290 snapshot: &EditorSnapshot,
16291 position: Point,
16292 ) -> Option<MultiBufferRow> {
16293 snapshot
16294 .buffer_snapshot()
16295 .diff_hunk_before(position)
16296 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16297 }
16298
16299 fn go_to_next_change(
16300 &mut self,
16301 _: &GoToNextChange,
16302 window: &mut Window,
16303 cx: &mut Context<Self>,
16304 ) {
16305 if let Some(selections) = self
16306 .change_list
16307 .next_change(1, Direction::Next)
16308 .map(|s| s.to_vec())
16309 {
16310 self.change_selections(Default::default(), window, cx, |s| {
16311 let map = s.display_map();
16312 s.select_display_ranges(selections.iter().map(|a| {
16313 let point = a.to_display_point(&map);
16314 point..point
16315 }))
16316 })
16317 }
16318 }
16319
16320 fn go_to_previous_change(
16321 &mut self,
16322 _: &GoToPreviousChange,
16323 window: &mut Window,
16324 cx: &mut Context<Self>,
16325 ) {
16326 if let Some(selections) = self
16327 .change_list
16328 .next_change(1, Direction::Prev)
16329 .map(|s| s.to_vec())
16330 {
16331 self.change_selections(Default::default(), window, cx, |s| {
16332 let map = s.display_map();
16333 s.select_display_ranges(selections.iter().map(|a| {
16334 let point = a.to_display_point(&map);
16335 point..point
16336 }))
16337 })
16338 }
16339 }
16340
16341 pub fn go_to_next_document_highlight(
16342 &mut self,
16343 _: &GoToNextDocumentHighlight,
16344 window: &mut Window,
16345 cx: &mut Context<Self>,
16346 ) {
16347 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16348 }
16349
16350 pub fn go_to_prev_document_highlight(
16351 &mut self,
16352 _: &GoToPreviousDocumentHighlight,
16353 window: &mut Window,
16354 cx: &mut Context<Self>,
16355 ) {
16356 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16357 }
16358
16359 pub fn go_to_document_highlight_before_or_after_position(
16360 &mut self,
16361 direction: Direction,
16362 window: &mut Window,
16363 cx: &mut Context<Editor>,
16364 ) {
16365 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16366 let snapshot = self.snapshot(window, cx);
16367 let buffer = &snapshot.buffer_snapshot();
16368 let position = self
16369 .selections
16370 .newest::<Point>(&snapshot.display_snapshot)
16371 .head();
16372 let anchor_position = buffer.anchor_after(position);
16373
16374 // Get all document highlights (both read and write)
16375 let mut all_highlights = Vec::new();
16376
16377 if let Some((_, read_highlights)) = self
16378 .background_highlights
16379 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16380 {
16381 all_highlights.extend(read_highlights.iter());
16382 }
16383
16384 if let Some((_, write_highlights)) = self
16385 .background_highlights
16386 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16387 {
16388 all_highlights.extend(write_highlights.iter());
16389 }
16390
16391 if all_highlights.is_empty() {
16392 return;
16393 }
16394
16395 // Sort highlights by position
16396 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16397
16398 let target_highlight = match direction {
16399 Direction::Next => {
16400 // Find the first highlight after the current position
16401 all_highlights
16402 .iter()
16403 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16404 }
16405 Direction::Prev => {
16406 // Find the last highlight before the current position
16407 all_highlights
16408 .iter()
16409 .rev()
16410 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16411 }
16412 };
16413
16414 if let Some(highlight) = target_highlight {
16415 let destination = highlight.start.to_point(buffer);
16416 let autoscroll = Autoscroll::center();
16417
16418 self.unfold_ranges(&[destination..destination], false, false, cx);
16419 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16420 s.select_ranges([destination..destination]);
16421 });
16422 }
16423 }
16424
16425 fn go_to_line<T: 'static>(
16426 &mut self,
16427 position: Anchor,
16428 highlight_color: Option<Hsla>,
16429 window: &mut Window,
16430 cx: &mut Context<Self>,
16431 ) {
16432 let snapshot = self.snapshot(window, cx).display_snapshot;
16433 let position = position.to_point(&snapshot.buffer_snapshot());
16434 let start = snapshot
16435 .buffer_snapshot()
16436 .clip_point(Point::new(position.row, 0), Bias::Left);
16437 let end = start + Point::new(1, 0);
16438 let start = snapshot.buffer_snapshot().anchor_before(start);
16439 let end = snapshot.buffer_snapshot().anchor_before(end);
16440
16441 self.highlight_rows::<T>(
16442 start..end,
16443 highlight_color
16444 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16445 Default::default(),
16446 cx,
16447 );
16448
16449 if self.buffer.read(cx).is_singleton() {
16450 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16451 }
16452 }
16453
16454 pub fn go_to_definition(
16455 &mut self,
16456 _: &GoToDefinition,
16457 window: &mut Window,
16458 cx: &mut Context<Self>,
16459 ) -> Task<Result<Navigated>> {
16460 let definition =
16461 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16462 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16463 cx.spawn_in(window, async move |editor, cx| {
16464 if definition.await? == Navigated::Yes {
16465 return Ok(Navigated::Yes);
16466 }
16467 match fallback_strategy {
16468 GoToDefinitionFallback::None => Ok(Navigated::No),
16469 GoToDefinitionFallback::FindAllReferences => {
16470 match editor.update_in(cx, |editor, window, cx| {
16471 editor.find_all_references(&FindAllReferences, window, cx)
16472 })? {
16473 Some(references) => references.await,
16474 None => Ok(Navigated::No),
16475 }
16476 }
16477 }
16478 })
16479 }
16480
16481 pub fn go_to_declaration(
16482 &mut self,
16483 _: &GoToDeclaration,
16484 window: &mut Window,
16485 cx: &mut Context<Self>,
16486 ) -> Task<Result<Navigated>> {
16487 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16488 }
16489
16490 pub fn go_to_declaration_split(
16491 &mut self,
16492 _: &GoToDeclaration,
16493 window: &mut Window,
16494 cx: &mut Context<Self>,
16495 ) -> Task<Result<Navigated>> {
16496 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16497 }
16498
16499 pub fn go_to_implementation(
16500 &mut self,
16501 _: &GoToImplementation,
16502 window: &mut Window,
16503 cx: &mut Context<Self>,
16504 ) -> Task<Result<Navigated>> {
16505 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16506 }
16507
16508 pub fn go_to_implementation_split(
16509 &mut self,
16510 _: &GoToImplementationSplit,
16511 window: &mut Window,
16512 cx: &mut Context<Self>,
16513 ) -> Task<Result<Navigated>> {
16514 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16515 }
16516
16517 pub fn go_to_type_definition(
16518 &mut self,
16519 _: &GoToTypeDefinition,
16520 window: &mut Window,
16521 cx: &mut Context<Self>,
16522 ) -> Task<Result<Navigated>> {
16523 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16524 }
16525
16526 pub fn go_to_definition_split(
16527 &mut self,
16528 _: &GoToDefinitionSplit,
16529 window: &mut Window,
16530 cx: &mut Context<Self>,
16531 ) -> Task<Result<Navigated>> {
16532 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16533 }
16534
16535 pub fn go_to_type_definition_split(
16536 &mut self,
16537 _: &GoToTypeDefinitionSplit,
16538 window: &mut Window,
16539 cx: &mut Context<Self>,
16540 ) -> Task<Result<Navigated>> {
16541 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16542 }
16543
16544 fn go_to_definition_of_kind(
16545 &mut self,
16546 kind: GotoDefinitionKind,
16547 split: bool,
16548 window: &mut Window,
16549 cx: &mut Context<Self>,
16550 ) -> Task<Result<Navigated>> {
16551 let Some(provider) = self.semantics_provider.clone() else {
16552 return Task::ready(Ok(Navigated::No));
16553 };
16554 let head = self
16555 .selections
16556 .newest::<usize>(&self.display_snapshot(cx))
16557 .head();
16558 let buffer = self.buffer.read(cx);
16559 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16560 return Task::ready(Ok(Navigated::No));
16561 };
16562 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16563 return Task::ready(Ok(Navigated::No));
16564 };
16565
16566 cx.spawn_in(window, async move |editor, cx| {
16567 let Some(definitions) = definitions.await? else {
16568 return Ok(Navigated::No);
16569 };
16570 let navigated = editor
16571 .update_in(cx, |editor, window, cx| {
16572 editor.navigate_to_hover_links(
16573 Some(kind),
16574 definitions
16575 .into_iter()
16576 .filter(|location| {
16577 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16578 })
16579 .map(HoverLink::Text)
16580 .collect::<Vec<_>>(),
16581 split,
16582 window,
16583 cx,
16584 )
16585 })?
16586 .await?;
16587 anyhow::Ok(navigated)
16588 })
16589 }
16590
16591 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16592 let selection = self.selections.newest_anchor();
16593 let head = selection.head();
16594 let tail = selection.tail();
16595
16596 let Some((buffer, start_position)) =
16597 self.buffer.read(cx).text_anchor_for_position(head, cx)
16598 else {
16599 return;
16600 };
16601
16602 let end_position = if head != tail {
16603 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16604 return;
16605 };
16606 Some(pos)
16607 } else {
16608 None
16609 };
16610
16611 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16612 let url = if let Some(end_pos) = end_position {
16613 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16614 } else {
16615 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16616 };
16617
16618 if let Some(url) = url {
16619 cx.update(|window, cx| {
16620 if parse_zed_link(&url, cx).is_some() {
16621 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16622 } else {
16623 cx.open_url(&url);
16624 }
16625 })?;
16626 }
16627
16628 anyhow::Ok(())
16629 });
16630
16631 url_finder.detach();
16632 }
16633
16634 pub fn open_selected_filename(
16635 &mut self,
16636 _: &OpenSelectedFilename,
16637 window: &mut Window,
16638 cx: &mut Context<Self>,
16639 ) {
16640 let Some(workspace) = self.workspace() else {
16641 return;
16642 };
16643
16644 let position = self.selections.newest_anchor().head();
16645
16646 let Some((buffer, buffer_position)) =
16647 self.buffer.read(cx).text_anchor_for_position(position, cx)
16648 else {
16649 return;
16650 };
16651
16652 let project = self.project.clone();
16653
16654 cx.spawn_in(window, async move |_, cx| {
16655 let result = find_file(&buffer, project, buffer_position, cx).await;
16656
16657 if let Some((_, path)) = result {
16658 workspace
16659 .update_in(cx, |workspace, window, cx| {
16660 workspace.open_resolved_path(path, window, cx)
16661 })?
16662 .await?;
16663 }
16664 anyhow::Ok(())
16665 })
16666 .detach();
16667 }
16668
16669 pub(crate) fn navigate_to_hover_links(
16670 &mut self,
16671 kind: Option<GotoDefinitionKind>,
16672 definitions: Vec<HoverLink>,
16673 split: bool,
16674 window: &mut Window,
16675 cx: &mut Context<Editor>,
16676 ) -> Task<Result<Navigated>> {
16677 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16678 let mut first_url_or_file = None;
16679 let definitions: Vec<_> = definitions
16680 .into_iter()
16681 .filter_map(|def| match def {
16682 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16683 HoverLink::InlayHint(lsp_location, server_id) => {
16684 let computation =
16685 self.compute_target_location(lsp_location, server_id, window, cx);
16686 Some(cx.background_spawn(computation))
16687 }
16688 HoverLink::Url(url) => {
16689 first_url_or_file = Some(Either::Left(url));
16690 None
16691 }
16692 HoverLink::File(path) => {
16693 first_url_or_file = Some(Either::Right(path));
16694 None
16695 }
16696 })
16697 .collect();
16698
16699 let workspace = self.workspace();
16700
16701 cx.spawn_in(window, async move |editor, cx| {
16702 let locations: Vec<Location> = future::join_all(definitions)
16703 .await
16704 .into_iter()
16705 .filter_map(|location| location.transpose())
16706 .collect::<Result<_>>()
16707 .context("location tasks")?;
16708 let mut locations = cx.update(|_, cx| {
16709 locations
16710 .into_iter()
16711 .map(|location| {
16712 let buffer = location.buffer.read(cx);
16713 (location.buffer, location.range.to_point(buffer))
16714 })
16715 .into_group_map()
16716 })?;
16717 let mut num_locations = 0;
16718 for ranges in locations.values_mut() {
16719 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16720 ranges.dedup();
16721 num_locations += ranges.len();
16722 }
16723
16724 if num_locations > 1 {
16725 let Some(workspace) = workspace else {
16726 return Ok(Navigated::No);
16727 };
16728
16729 let tab_kind = match kind {
16730 Some(GotoDefinitionKind::Implementation) => "Implementations",
16731 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16732 Some(GotoDefinitionKind::Declaration) => "Declarations",
16733 Some(GotoDefinitionKind::Type) => "Types",
16734 };
16735 let title = editor
16736 .update_in(cx, |_, _, cx| {
16737 let target = locations
16738 .iter()
16739 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16740 .map(|(buffer, location)| {
16741 buffer
16742 .read(cx)
16743 .text_for_range(location.clone())
16744 .collect::<String>()
16745 })
16746 .filter(|text| !text.contains('\n'))
16747 .unique()
16748 .take(3)
16749 .join(", ");
16750 if target.is_empty() {
16751 tab_kind.to_owned()
16752 } else {
16753 format!("{tab_kind} for {target}")
16754 }
16755 })
16756 .context("buffer title")?;
16757
16758 let opened = workspace
16759 .update_in(cx, |workspace, window, cx| {
16760 Self::open_locations_in_multibuffer(
16761 workspace,
16762 locations,
16763 title,
16764 split,
16765 MultibufferSelectionMode::First,
16766 window,
16767 cx,
16768 )
16769 })
16770 .is_ok();
16771
16772 anyhow::Ok(Navigated::from_bool(opened))
16773 } else if num_locations == 0 {
16774 // If there is one url or file, open it directly
16775 match first_url_or_file {
16776 Some(Either::Left(url)) => {
16777 cx.update(|_, cx| cx.open_url(&url))?;
16778 Ok(Navigated::Yes)
16779 }
16780 Some(Either::Right(path)) => {
16781 let Some(workspace) = workspace else {
16782 return Ok(Navigated::No);
16783 };
16784
16785 workspace
16786 .update_in(cx, |workspace, window, cx| {
16787 workspace.open_resolved_path(path, window, cx)
16788 })?
16789 .await?;
16790 Ok(Navigated::Yes)
16791 }
16792 None => Ok(Navigated::No),
16793 }
16794 } else {
16795 let Some(workspace) = workspace else {
16796 return Ok(Navigated::No);
16797 };
16798
16799 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16800 let target_range = target_ranges.first().unwrap().clone();
16801
16802 editor.update_in(cx, |editor, window, cx| {
16803 let range = target_range.to_point(target_buffer.read(cx));
16804 let range = editor.range_for_match(&range);
16805 let range = collapse_multiline_range(range);
16806
16807 if !split
16808 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16809 {
16810 editor.go_to_singleton_buffer_range(range, window, cx);
16811 } else {
16812 let pane = workspace.read(cx).active_pane().clone();
16813 window.defer(cx, move |window, cx| {
16814 let target_editor: Entity<Self> =
16815 workspace.update(cx, |workspace, cx| {
16816 let pane = if split {
16817 workspace.adjacent_pane(window, cx)
16818 } else {
16819 workspace.active_pane().clone()
16820 };
16821
16822 workspace.open_project_item(
16823 pane,
16824 target_buffer.clone(),
16825 true,
16826 true,
16827 window,
16828 cx,
16829 )
16830 });
16831 target_editor.update(cx, |target_editor, cx| {
16832 // When selecting a definition in a different buffer, disable the nav history
16833 // to avoid creating a history entry at the previous cursor location.
16834 pane.update(cx, |pane, _| pane.disable_history());
16835 target_editor.go_to_singleton_buffer_range(range, window, cx);
16836 pane.update(cx, |pane, _| pane.enable_history());
16837 });
16838 });
16839 }
16840 Navigated::Yes
16841 })
16842 }
16843 })
16844 }
16845
16846 fn compute_target_location(
16847 &self,
16848 lsp_location: lsp::Location,
16849 server_id: LanguageServerId,
16850 window: &mut Window,
16851 cx: &mut Context<Self>,
16852 ) -> Task<anyhow::Result<Option<Location>>> {
16853 let Some(project) = self.project.clone() else {
16854 return Task::ready(Ok(None));
16855 };
16856
16857 cx.spawn_in(window, async move |editor, cx| {
16858 let location_task = editor.update(cx, |_, cx| {
16859 project.update(cx, |project, cx| {
16860 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16861 })
16862 })?;
16863 let location = Some({
16864 let target_buffer_handle = location_task.await.context("open local buffer")?;
16865 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16866 let target_start = target_buffer
16867 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16868 let target_end = target_buffer
16869 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16870 target_buffer.anchor_after(target_start)
16871 ..target_buffer.anchor_before(target_end)
16872 })?;
16873 Location {
16874 buffer: target_buffer_handle,
16875 range,
16876 }
16877 });
16878 Ok(location)
16879 })
16880 }
16881
16882 pub fn find_all_references(
16883 &mut self,
16884 _: &FindAllReferences,
16885 window: &mut Window,
16886 cx: &mut Context<Self>,
16887 ) -> Option<Task<Result<Navigated>>> {
16888 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16889 let multi_buffer = self.buffer.read(cx);
16890 let head = selection.head();
16891
16892 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
16893 let head_anchor = multi_buffer_snapshot.anchor_at(
16894 head,
16895 if head < selection.tail() {
16896 Bias::Right
16897 } else {
16898 Bias::Left
16899 },
16900 );
16901
16902 match self
16903 .find_all_references_task_sources
16904 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16905 {
16906 Ok(_) => {
16907 log::info!(
16908 "Ignoring repeated FindAllReferences invocation with the position of already running task"
16909 );
16910 return None;
16911 }
16912 Err(i) => {
16913 self.find_all_references_task_sources.insert(i, head_anchor);
16914 }
16915 }
16916
16917 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
16918 let workspace = self.workspace()?;
16919 let project = workspace.read(cx).project().clone();
16920 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
16921 Some(cx.spawn_in(window, async move |editor, cx| {
16922 let _cleanup = cx.on_drop(&editor, move |editor, _| {
16923 if let Ok(i) = editor
16924 .find_all_references_task_sources
16925 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
16926 {
16927 editor.find_all_references_task_sources.remove(i);
16928 }
16929 });
16930
16931 let Some(locations) = references.await? else {
16932 return anyhow::Ok(Navigated::No);
16933 };
16934 let mut locations = cx.update(|_, cx| {
16935 locations
16936 .into_iter()
16937 .map(|location| {
16938 let buffer = location.buffer.read(cx);
16939 (location.buffer, location.range.to_point(buffer))
16940 })
16941 .into_group_map()
16942 })?;
16943 if locations.is_empty() {
16944 return anyhow::Ok(Navigated::No);
16945 }
16946 for ranges in locations.values_mut() {
16947 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16948 ranges.dedup();
16949 }
16950
16951 workspace.update_in(cx, |workspace, window, cx| {
16952 let target = locations
16953 .iter()
16954 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16955 .map(|(buffer, location)| {
16956 buffer
16957 .read(cx)
16958 .text_for_range(location.clone())
16959 .collect::<String>()
16960 })
16961 .filter(|text| !text.contains('\n'))
16962 .unique()
16963 .take(3)
16964 .join(", ");
16965 let title = if target.is_empty() {
16966 "References".to_owned()
16967 } else {
16968 format!("References to {target}")
16969 };
16970 Self::open_locations_in_multibuffer(
16971 workspace,
16972 locations,
16973 title,
16974 false,
16975 MultibufferSelectionMode::First,
16976 window,
16977 cx,
16978 );
16979 Navigated::Yes
16980 })
16981 }))
16982 }
16983
16984 /// Opens a multibuffer with the given project locations in it
16985 pub fn open_locations_in_multibuffer(
16986 workspace: &mut Workspace,
16987 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
16988 title: String,
16989 split: bool,
16990 multibuffer_selection_mode: MultibufferSelectionMode,
16991 window: &mut Window,
16992 cx: &mut Context<Workspace>,
16993 ) {
16994 if locations.is_empty() {
16995 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
16996 return;
16997 }
16998
16999 let capability = workspace.project().read(cx).capability();
17000 let mut ranges = <Vec<Range<Anchor>>>::new();
17001
17002 // a key to find existing multibuffer editors with the same set of locations
17003 // to prevent us from opening more and more multibuffer tabs for searches and the like
17004 let mut key = (title.clone(), vec![]);
17005 let excerpt_buffer = cx.new(|cx| {
17006 let key = &mut key.1;
17007 let mut multibuffer = MultiBuffer::new(capability);
17008 for (buffer, mut ranges_for_buffer) in locations {
17009 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17010 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17011 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17012 PathKey::for_buffer(&buffer, cx),
17013 buffer.clone(),
17014 ranges_for_buffer,
17015 multibuffer_context_lines(cx),
17016 cx,
17017 );
17018 ranges.extend(new_ranges)
17019 }
17020
17021 multibuffer.with_title(title)
17022 });
17023 let existing = workspace.active_pane().update(cx, |pane, cx| {
17024 pane.items()
17025 .filter_map(|item| item.downcast::<Editor>())
17026 .find(|editor| {
17027 editor
17028 .read(cx)
17029 .lookup_key
17030 .as_ref()
17031 .and_then(|it| {
17032 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17033 })
17034 .is_some_and(|it| *it == key)
17035 })
17036 });
17037 let editor = existing.unwrap_or_else(|| {
17038 cx.new(|cx| {
17039 let mut editor = Editor::for_multibuffer(
17040 excerpt_buffer,
17041 Some(workspace.project().clone()),
17042 window,
17043 cx,
17044 );
17045 editor.lookup_key = Some(Box::new(key));
17046 editor
17047 })
17048 });
17049 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17050 MultibufferSelectionMode::First => {
17051 if let Some(first_range) = ranges.first() {
17052 editor.change_selections(
17053 SelectionEffects::no_scroll(),
17054 window,
17055 cx,
17056 |selections| {
17057 selections.clear_disjoint();
17058 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17059 },
17060 );
17061 }
17062 editor.highlight_background::<Self>(
17063 &ranges,
17064 |theme| theme.colors().editor_highlighted_line_background,
17065 cx,
17066 );
17067 }
17068 MultibufferSelectionMode::All => {
17069 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17070 selections.clear_disjoint();
17071 selections.select_anchor_ranges(ranges);
17072 });
17073 }
17074 });
17075
17076 let item = Box::new(editor);
17077 let item_id = item.item_id();
17078
17079 if split {
17080 let pane = workspace.adjacent_pane(window, cx);
17081 workspace.add_item(pane, item, None, true, true, window, cx);
17082 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17083 let (preview_item_id, preview_item_idx) =
17084 workspace.active_pane().read_with(cx, |pane, _| {
17085 (pane.preview_item_id(), pane.preview_item_idx())
17086 });
17087
17088 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17089
17090 if let Some(preview_item_id) = preview_item_id {
17091 workspace.active_pane().update(cx, |pane, cx| {
17092 pane.remove_item(preview_item_id, false, false, window, cx);
17093 });
17094 }
17095 } else {
17096 workspace.add_item_to_active_pane(item, None, true, window, cx);
17097 }
17098 workspace.active_pane().update(cx, |pane, cx| {
17099 pane.set_preview_item_id(Some(item_id), cx);
17100 });
17101 }
17102
17103 pub fn rename(
17104 &mut self,
17105 _: &Rename,
17106 window: &mut Window,
17107 cx: &mut Context<Self>,
17108 ) -> Option<Task<Result<()>>> {
17109 use language::ToOffset as _;
17110
17111 let provider = self.semantics_provider.clone()?;
17112 let selection = self.selections.newest_anchor().clone();
17113 let (cursor_buffer, cursor_buffer_position) = self
17114 .buffer
17115 .read(cx)
17116 .text_anchor_for_position(selection.head(), cx)?;
17117 let (tail_buffer, cursor_buffer_position_end) = self
17118 .buffer
17119 .read(cx)
17120 .text_anchor_for_position(selection.tail(), cx)?;
17121 if tail_buffer != cursor_buffer {
17122 return None;
17123 }
17124
17125 let snapshot = cursor_buffer.read(cx).snapshot();
17126 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17127 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17128 let prepare_rename = provider
17129 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17130 .unwrap_or_else(|| Task::ready(Ok(None)));
17131 drop(snapshot);
17132
17133 Some(cx.spawn_in(window, async move |this, cx| {
17134 let rename_range = if let Some(range) = prepare_rename.await? {
17135 Some(range)
17136 } else {
17137 this.update(cx, |this, cx| {
17138 let buffer = this.buffer.read(cx).snapshot(cx);
17139 let mut buffer_highlights = this
17140 .document_highlights_for_position(selection.head(), &buffer)
17141 .filter(|highlight| {
17142 highlight.start.excerpt_id == selection.head().excerpt_id
17143 && highlight.end.excerpt_id == selection.head().excerpt_id
17144 });
17145 buffer_highlights
17146 .next()
17147 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17148 })?
17149 };
17150 if let Some(rename_range) = rename_range {
17151 this.update_in(cx, |this, window, cx| {
17152 let snapshot = cursor_buffer.read(cx).snapshot();
17153 let rename_buffer_range = rename_range.to_offset(&snapshot);
17154 let cursor_offset_in_rename_range =
17155 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17156 let cursor_offset_in_rename_range_end =
17157 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17158
17159 this.take_rename(false, window, cx);
17160 let buffer = this.buffer.read(cx).read(cx);
17161 let cursor_offset = selection.head().to_offset(&buffer);
17162 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17163 let rename_end = rename_start + rename_buffer_range.len();
17164 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17165 let mut old_highlight_id = None;
17166 let old_name: Arc<str> = buffer
17167 .chunks(rename_start..rename_end, true)
17168 .map(|chunk| {
17169 if old_highlight_id.is_none() {
17170 old_highlight_id = chunk.syntax_highlight_id;
17171 }
17172 chunk.text
17173 })
17174 .collect::<String>()
17175 .into();
17176
17177 drop(buffer);
17178
17179 // Position the selection in the rename editor so that it matches the current selection.
17180 this.show_local_selections = false;
17181 let rename_editor = cx.new(|cx| {
17182 let mut editor = Editor::single_line(window, cx);
17183 editor.buffer.update(cx, |buffer, cx| {
17184 buffer.edit([(0..0, old_name.clone())], None, cx)
17185 });
17186 let rename_selection_range = match cursor_offset_in_rename_range
17187 .cmp(&cursor_offset_in_rename_range_end)
17188 {
17189 Ordering::Equal => {
17190 editor.select_all(&SelectAll, window, cx);
17191 return editor;
17192 }
17193 Ordering::Less => {
17194 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17195 }
17196 Ordering::Greater => {
17197 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17198 }
17199 };
17200 if rename_selection_range.end > old_name.len() {
17201 editor.select_all(&SelectAll, window, cx);
17202 } else {
17203 editor.change_selections(Default::default(), window, cx, |s| {
17204 s.select_ranges([rename_selection_range]);
17205 });
17206 }
17207 editor
17208 });
17209 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17210 if e == &EditorEvent::Focused {
17211 cx.emit(EditorEvent::FocusedIn)
17212 }
17213 })
17214 .detach();
17215
17216 let write_highlights =
17217 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17218 let read_highlights =
17219 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17220 let ranges = write_highlights
17221 .iter()
17222 .flat_map(|(_, ranges)| ranges.iter())
17223 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17224 .cloned()
17225 .collect();
17226
17227 this.highlight_text::<Rename>(
17228 ranges,
17229 HighlightStyle {
17230 fade_out: Some(0.6),
17231 ..Default::default()
17232 },
17233 cx,
17234 );
17235 let rename_focus_handle = rename_editor.focus_handle(cx);
17236 window.focus(&rename_focus_handle);
17237 let block_id = this.insert_blocks(
17238 [BlockProperties {
17239 style: BlockStyle::Flex,
17240 placement: BlockPlacement::Below(range.start),
17241 height: Some(1),
17242 render: Arc::new({
17243 let rename_editor = rename_editor.clone();
17244 move |cx: &mut BlockContext| {
17245 let mut text_style = cx.editor_style.text.clone();
17246 if let Some(highlight_style) = old_highlight_id
17247 .and_then(|h| h.style(&cx.editor_style.syntax))
17248 {
17249 text_style = text_style.highlight(highlight_style);
17250 }
17251 div()
17252 .block_mouse_except_scroll()
17253 .pl(cx.anchor_x)
17254 .child(EditorElement::new(
17255 &rename_editor,
17256 EditorStyle {
17257 background: cx.theme().system().transparent,
17258 local_player: cx.editor_style.local_player,
17259 text: text_style,
17260 scrollbar_width: cx.editor_style.scrollbar_width,
17261 syntax: cx.editor_style.syntax.clone(),
17262 status: cx.editor_style.status.clone(),
17263 inlay_hints_style: HighlightStyle {
17264 font_weight: Some(FontWeight::BOLD),
17265 ..make_inlay_hints_style(cx.app)
17266 },
17267 edit_prediction_styles: make_suggestion_styles(
17268 cx.app,
17269 ),
17270 ..EditorStyle::default()
17271 },
17272 ))
17273 .into_any_element()
17274 }
17275 }),
17276 priority: 0,
17277 }],
17278 Some(Autoscroll::fit()),
17279 cx,
17280 )[0];
17281 this.pending_rename = Some(RenameState {
17282 range,
17283 old_name,
17284 editor: rename_editor,
17285 block_id,
17286 });
17287 })?;
17288 }
17289
17290 Ok(())
17291 }))
17292 }
17293
17294 pub fn confirm_rename(
17295 &mut self,
17296 _: &ConfirmRename,
17297 window: &mut Window,
17298 cx: &mut Context<Self>,
17299 ) -> Option<Task<Result<()>>> {
17300 let rename = self.take_rename(false, window, cx)?;
17301 let workspace = self.workspace()?.downgrade();
17302 let (buffer, start) = self
17303 .buffer
17304 .read(cx)
17305 .text_anchor_for_position(rename.range.start, cx)?;
17306 let (end_buffer, _) = self
17307 .buffer
17308 .read(cx)
17309 .text_anchor_for_position(rename.range.end, cx)?;
17310 if buffer != end_buffer {
17311 return None;
17312 }
17313
17314 let old_name = rename.old_name;
17315 let new_name = rename.editor.read(cx).text(cx);
17316
17317 let rename = self.semantics_provider.as_ref()?.perform_rename(
17318 &buffer,
17319 start,
17320 new_name.clone(),
17321 cx,
17322 )?;
17323
17324 Some(cx.spawn_in(window, async move |editor, cx| {
17325 let project_transaction = rename.await?;
17326 Self::open_project_transaction(
17327 &editor,
17328 workspace,
17329 project_transaction,
17330 format!("Rename: {} → {}", old_name, new_name),
17331 cx,
17332 )
17333 .await?;
17334
17335 editor.update(cx, |editor, cx| {
17336 editor.refresh_document_highlights(cx);
17337 })?;
17338 Ok(())
17339 }))
17340 }
17341
17342 fn take_rename(
17343 &mut self,
17344 moving_cursor: bool,
17345 window: &mut Window,
17346 cx: &mut Context<Self>,
17347 ) -> Option<RenameState> {
17348 let rename = self.pending_rename.take()?;
17349 if rename.editor.focus_handle(cx).is_focused(window) {
17350 window.focus(&self.focus_handle);
17351 }
17352
17353 self.remove_blocks(
17354 [rename.block_id].into_iter().collect(),
17355 Some(Autoscroll::fit()),
17356 cx,
17357 );
17358 self.clear_highlights::<Rename>(cx);
17359 self.show_local_selections = true;
17360
17361 if moving_cursor {
17362 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17363 editor
17364 .selections
17365 .newest::<usize>(&editor.display_snapshot(cx))
17366 .head()
17367 });
17368
17369 // Update the selection to match the position of the selection inside
17370 // the rename editor.
17371 let snapshot = self.buffer.read(cx).read(cx);
17372 let rename_range = rename.range.to_offset(&snapshot);
17373 let cursor_in_editor = snapshot
17374 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17375 .min(rename_range.end);
17376 drop(snapshot);
17377
17378 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17379 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17380 });
17381 } else {
17382 self.refresh_document_highlights(cx);
17383 }
17384
17385 Some(rename)
17386 }
17387
17388 pub fn pending_rename(&self) -> Option<&RenameState> {
17389 self.pending_rename.as_ref()
17390 }
17391
17392 fn format(
17393 &mut self,
17394 _: &Format,
17395 window: &mut Window,
17396 cx: &mut Context<Self>,
17397 ) -> Option<Task<Result<()>>> {
17398 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17399
17400 let project = match &self.project {
17401 Some(project) => project.clone(),
17402 None => return None,
17403 };
17404
17405 Some(self.perform_format(
17406 project,
17407 FormatTrigger::Manual,
17408 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17409 window,
17410 cx,
17411 ))
17412 }
17413
17414 fn format_selections(
17415 &mut self,
17416 _: &FormatSelections,
17417 window: &mut Window,
17418 cx: &mut Context<Self>,
17419 ) -> Option<Task<Result<()>>> {
17420 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17421
17422 let project = match &self.project {
17423 Some(project) => project.clone(),
17424 None => return None,
17425 };
17426
17427 let ranges = self
17428 .selections
17429 .all_adjusted(&self.display_snapshot(cx))
17430 .into_iter()
17431 .map(|selection| selection.range())
17432 .collect_vec();
17433
17434 Some(self.perform_format(
17435 project,
17436 FormatTrigger::Manual,
17437 FormatTarget::Ranges(ranges),
17438 window,
17439 cx,
17440 ))
17441 }
17442
17443 fn perform_format(
17444 &mut self,
17445 project: Entity<Project>,
17446 trigger: FormatTrigger,
17447 target: FormatTarget,
17448 window: &mut Window,
17449 cx: &mut Context<Self>,
17450 ) -> Task<Result<()>> {
17451 let buffer = self.buffer.clone();
17452 let (buffers, target) = match target {
17453 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17454 FormatTarget::Ranges(selection_ranges) => {
17455 let multi_buffer = buffer.read(cx);
17456 let snapshot = multi_buffer.read(cx);
17457 let mut buffers = HashSet::default();
17458 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17459 BTreeMap::new();
17460 for selection_range in selection_ranges {
17461 for (buffer, buffer_range, _) in
17462 snapshot.range_to_buffer_ranges(selection_range)
17463 {
17464 let buffer_id = buffer.remote_id();
17465 let start = buffer.anchor_before(buffer_range.start);
17466 let end = buffer.anchor_after(buffer_range.end);
17467 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17468 buffer_id_to_ranges
17469 .entry(buffer_id)
17470 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17471 .or_insert_with(|| vec![start..end]);
17472 }
17473 }
17474 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17475 }
17476 };
17477
17478 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17479 let selections_prev = transaction_id_prev
17480 .and_then(|transaction_id_prev| {
17481 // default to selections as they were after the last edit, if we have them,
17482 // instead of how they are now.
17483 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17484 // will take you back to where you made the last edit, instead of staying where you scrolled
17485 self.selection_history
17486 .transaction(transaction_id_prev)
17487 .map(|t| t.0.clone())
17488 })
17489 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17490
17491 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17492 let format = project.update(cx, |project, cx| {
17493 project.format(buffers, target, true, trigger, cx)
17494 });
17495
17496 cx.spawn_in(window, async move |editor, cx| {
17497 let transaction = futures::select_biased! {
17498 transaction = format.log_err().fuse() => transaction,
17499 () = timeout => {
17500 log::warn!("timed out waiting for formatting");
17501 None
17502 }
17503 };
17504
17505 buffer
17506 .update(cx, |buffer, cx| {
17507 if let Some(transaction) = transaction
17508 && !buffer.is_singleton()
17509 {
17510 buffer.push_transaction(&transaction.0, cx);
17511 }
17512 cx.notify();
17513 })
17514 .ok();
17515
17516 if let Some(transaction_id_now) =
17517 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17518 {
17519 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17520 if has_new_transaction {
17521 _ = editor.update(cx, |editor, _| {
17522 editor
17523 .selection_history
17524 .insert_transaction(transaction_id_now, selections_prev);
17525 });
17526 }
17527 }
17528
17529 Ok(())
17530 })
17531 }
17532
17533 fn organize_imports(
17534 &mut self,
17535 _: &OrganizeImports,
17536 window: &mut Window,
17537 cx: &mut Context<Self>,
17538 ) -> Option<Task<Result<()>>> {
17539 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17540 let project = match &self.project {
17541 Some(project) => project.clone(),
17542 None => return None,
17543 };
17544 Some(self.perform_code_action_kind(
17545 project,
17546 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17547 window,
17548 cx,
17549 ))
17550 }
17551
17552 fn perform_code_action_kind(
17553 &mut self,
17554 project: Entity<Project>,
17555 kind: CodeActionKind,
17556 window: &mut Window,
17557 cx: &mut Context<Self>,
17558 ) -> Task<Result<()>> {
17559 let buffer = self.buffer.clone();
17560 let buffers = buffer.read(cx).all_buffers();
17561 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17562 let apply_action = project.update(cx, |project, cx| {
17563 project.apply_code_action_kind(buffers, kind, true, cx)
17564 });
17565 cx.spawn_in(window, async move |_, cx| {
17566 let transaction = futures::select_biased! {
17567 () = timeout => {
17568 log::warn!("timed out waiting for executing code action");
17569 None
17570 }
17571 transaction = apply_action.log_err().fuse() => transaction,
17572 };
17573 buffer
17574 .update(cx, |buffer, cx| {
17575 // check if we need this
17576 if let Some(transaction) = transaction
17577 && !buffer.is_singleton()
17578 {
17579 buffer.push_transaction(&transaction.0, cx);
17580 }
17581 cx.notify();
17582 })
17583 .ok();
17584 Ok(())
17585 })
17586 }
17587
17588 pub fn restart_language_server(
17589 &mut self,
17590 _: &RestartLanguageServer,
17591 _: &mut Window,
17592 cx: &mut Context<Self>,
17593 ) {
17594 if let Some(project) = self.project.clone() {
17595 self.buffer.update(cx, |multi_buffer, cx| {
17596 project.update(cx, |project, cx| {
17597 project.restart_language_servers_for_buffers(
17598 multi_buffer.all_buffers().into_iter().collect(),
17599 HashSet::default(),
17600 cx,
17601 );
17602 });
17603 })
17604 }
17605 }
17606
17607 pub fn stop_language_server(
17608 &mut self,
17609 _: &StopLanguageServer,
17610 _: &mut Window,
17611 cx: &mut Context<Self>,
17612 ) {
17613 if let Some(project) = self.project.clone() {
17614 self.buffer.update(cx, |multi_buffer, cx| {
17615 project.update(cx, |project, cx| {
17616 project.stop_language_servers_for_buffers(
17617 multi_buffer.all_buffers().into_iter().collect(),
17618 HashSet::default(),
17619 cx,
17620 );
17621 cx.emit(project::Event::RefreshInlayHints);
17622 });
17623 });
17624 }
17625 }
17626
17627 fn cancel_language_server_work(
17628 workspace: &mut Workspace,
17629 _: &actions::CancelLanguageServerWork,
17630 _: &mut Window,
17631 cx: &mut Context<Workspace>,
17632 ) {
17633 let project = workspace.project();
17634 let buffers = workspace
17635 .active_item(cx)
17636 .and_then(|item| item.act_as::<Editor>(cx))
17637 .map_or(HashSet::default(), |editor| {
17638 editor.read(cx).buffer.read(cx).all_buffers()
17639 });
17640 project.update(cx, |project, cx| {
17641 project.cancel_language_server_work_for_buffers(buffers, cx);
17642 });
17643 }
17644
17645 fn show_character_palette(
17646 &mut self,
17647 _: &ShowCharacterPalette,
17648 window: &mut Window,
17649 _: &mut Context<Self>,
17650 ) {
17651 window.show_character_palette();
17652 }
17653
17654 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17655 if !self.diagnostics_enabled() {
17656 return;
17657 }
17658
17659 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17660 let buffer = self.buffer.read(cx).snapshot(cx);
17661 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17662 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17663 let is_valid = buffer
17664 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17665 .any(|entry| {
17666 entry.diagnostic.is_primary
17667 && !entry.range.is_empty()
17668 && entry.range.start == primary_range_start
17669 && entry.diagnostic.message == active_diagnostics.active_message
17670 });
17671
17672 if !is_valid {
17673 self.dismiss_diagnostics(cx);
17674 }
17675 }
17676 }
17677
17678 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17679 match &self.active_diagnostics {
17680 ActiveDiagnostic::Group(group) => Some(group),
17681 _ => None,
17682 }
17683 }
17684
17685 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17686 if !self.diagnostics_enabled() {
17687 return;
17688 }
17689 self.dismiss_diagnostics(cx);
17690 self.active_diagnostics = ActiveDiagnostic::All;
17691 }
17692
17693 fn activate_diagnostics(
17694 &mut self,
17695 buffer_id: BufferId,
17696 diagnostic: DiagnosticEntryRef<'_, usize>,
17697 window: &mut Window,
17698 cx: &mut Context<Self>,
17699 ) {
17700 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17701 return;
17702 }
17703 self.dismiss_diagnostics(cx);
17704 let snapshot = self.snapshot(window, cx);
17705 let buffer = self.buffer.read(cx).snapshot(cx);
17706 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17707 return;
17708 };
17709
17710 let diagnostic_group = buffer
17711 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17712 .collect::<Vec<_>>();
17713
17714 let blocks =
17715 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17716
17717 let blocks = self.display_map.update(cx, |display_map, cx| {
17718 display_map.insert_blocks(blocks, cx).into_iter().collect()
17719 });
17720 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17721 active_range: buffer.anchor_before(diagnostic.range.start)
17722 ..buffer.anchor_after(diagnostic.range.end),
17723 active_message: diagnostic.diagnostic.message.clone(),
17724 group_id: diagnostic.diagnostic.group_id,
17725 blocks,
17726 });
17727 cx.notify();
17728 }
17729
17730 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17731 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17732 return;
17733 };
17734
17735 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17736 if let ActiveDiagnostic::Group(group) = prev {
17737 self.display_map.update(cx, |display_map, cx| {
17738 display_map.remove_blocks(group.blocks, cx);
17739 });
17740 cx.notify();
17741 }
17742 }
17743
17744 /// Disable inline diagnostics rendering for this editor.
17745 pub fn disable_inline_diagnostics(&mut self) {
17746 self.inline_diagnostics_enabled = false;
17747 self.inline_diagnostics_update = Task::ready(());
17748 self.inline_diagnostics.clear();
17749 }
17750
17751 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17752 self.diagnostics_enabled = false;
17753 self.dismiss_diagnostics(cx);
17754 self.inline_diagnostics_update = Task::ready(());
17755 self.inline_diagnostics.clear();
17756 }
17757
17758 pub fn disable_word_completions(&mut self) {
17759 self.word_completions_enabled = false;
17760 }
17761
17762 pub fn diagnostics_enabled(&self) -> bool {
17763 self.diagnostics_enabled && self.mode.is_full()
17764 }
17765
17766 pub fn inline_diagnostics_enabled(&self) -> bool {
17767 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17768 }
17769
17770 pub fn show_inline_diagnostics(&self) -> bool {
17771 self.show_inline_diagnostics
17772 }
17773
17774 pub fn toggle_inline_diagnostics(
17775 &mut self,
17776 _: &ToggleInlineDiagnostics,
17777 window: &mut Window,
17778 cx: &mut Context<Editor>,
17779 ) {
17780 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17781 self.refresh_inline_diagnostics(false, window, cx);
17782 }
17783
17784 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17785 self.diagnostics_max_severity = severity;
17786 self.display_map.update(cx, |display_map, _| {
17787 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17788 });
17789 }
17790
17791 pub fn toggle_diagnostics(
17792 &mut self,
17793 _: &ToggleDiagnostics,
17794 window: &mut Window,
17795 cx: &mut Context<Editor>,
17796 ) {
17797 if !self.diagnostics_enabled() {
17798 return;
17799 }
17800
17801 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17802 EditorSettings::get_global(cx)
17803 .diagnostics_max_severity
17804 .filter(|severity| severity != &DiagnosticSeverity::Off)
17805 .unwrap_or(DiagnosticSeverity::Hint)
17806 } else {
17807 DiagnosticSeverity::Off
17808 };
17809 self.set_max_diagnostics_severity(new_severity, cx);
17810 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17811 self.active_diagnostics = ActiveDiagnostic::None;
17812 self.inline_diagnostics_update = Task::ready(());
17813 self.inline_diagnostics.clear();
17814 } else {
17815 self.refresh_inline_diagnostics(false, window, cx);
17816 }
17817
17818 cx.notify();
17819 }
17820
17821 pub fn toggle_minimap(
17822 &mut self,
17823 _: &ToggleMinimap,
17824 window: &mut Window,
17825 cx: &mut Context<Editor>,
17826 ) {
17827 if self.supports_minimap(cx) {
17828 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17829 }
17830 }
17831
17832 fn refresh_inline_diagnostics(
17833 &mut self,
17834 debounce: bool,
17835 window: &mut Window,
17836 cx: &mut Context<Self>,
17837 ) {
17838 let max_severity = ProjectSettings::get_global(cx)
17839 .diagnostics
17840 .inline
17841 .max_severity
17842 .unwrap_or(self.diagnostics_max_severity);
17843
17844 if !self.inline_diagnostics_enabled()
17845 || !self.show_inline_diagnostics
17846 || max_severity == DiagnosticSeverity::Off
17847 {
17848 self.inline_diagnostics_update = Task::ready(());
17849 self.inline_diagnostics.clear();
17850 return;
17851 }
17852
17853 let debounce_ms = ProjectSettings::get_global(cx)
17854 .diagnostics
17855 .inline
17856 .update_debounce_ms;
17857 let debounce = if debounce && debounce_ms > 0 {
17858 Some(Duration::from_millis(debounce_ms))
17859 } else {
17860 None
17861 };
17862 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17863 if let Some(debounce) = debounce {
17864 cx.background_executor().timer(debounce).await;
17865 }
17866 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17867 editor
17868 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17869 .ok()
17870 }) else {
17871 return;
17872 };
17873
17874 let new_inline_diagnostics = cx
17875 .background_spawn(async move {
17876 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
17877 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
17878 let message = diagnostic_entry
17879 .diagnostic
17880 .message
17881 .split_once('\n')
17882 .map(|(line, _)| line)
17883 .map(SharedString::new)
17884 .unwrap_or_else(|| {
17885 SharedString::new(&*diagnostic_entry.diagnostic.message)
17886 });
17887 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
17888 let (Ok(i) | Err(i)) = inline_diagnostics
17889 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
17890 inline_diagnostics.insert(
17891 i,
17892 (
17893 start_anchor,
17894 InlineDiagnostic {
17895 message,
17896 group_id: diagnostic_entry.diagnostic.group_id,
17897 start: diagnostic_entry.range.start.to_point(&snapshot),
17898 is_primary: diagnostic_entry.diagnostic.is_primary,
17899 severity: diagnostic_entry.diagnostic.severity,
17900 },
17901 ),
17902 );
17903 }
17904 inline_diagnostics
17905 })
17906 .await;
17907
17908 editor
17909 .update(cx, |editor, cx| {
17910 editor.inline_diagnostics = new_inline_diagnostics;
17911 cx.notify();
17912 })
17913 .ok();
17914 });
17915 }
17916
17917 fn pull_diagnostics(
17918 &mut self,
17919 buffer_id: Option<BufferId>,
17920 window: &Window,
17921 cx: &mut Context<Self>,
17922 ) -> Option<()> {
17923 if self.ignore_lsp_data() {
17924 return None;
17925 }
17926 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
17927 .diagnostics
17928 .lsp_pull_diagnostics;
17929 if !pull_diagnostics_settings.enabled {
17930 return None;
17931 }
17932 let project = self.project()?.downgrade();
17933 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
17934 let mut buffers = self.buffer.read(cx).all_buffers();
17935 buffers.retain(|buffer| {
17936 let buffer_id_to_retain = buffer.read(cx).remote_id();
17937 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
17938 && self.registered_buffers.contains_key(&buffer_id_to_retain)
17939 });
17940 if buffers.is_empty() {
17941 self.pull_diagnostics_task = Task::ready(());
17942 return None;
17943 }
17944
17945 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
17946 cx.background_executor().timer(debounce).await;
17947
17948 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
17949 buffers
17950 .into_iter()
17951 .filter_map(|buffer| {
17952 project
17953 .update(cx, |project, cx| {
17954 project.lsp_store().update(cx, |lsp_store, cx| {
17955 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
17956 })
17957 })
17958 .ok()
17959 })
17960 .collect::<FuturesUnordered<_>>()
17961 }) else {
17962 return;
17963 };
17964
17965 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
17966 match pull_task {
17967 Ok(()) => {
17968 if editor
17969 .update_in(cx, |editor, window, cx| {
17970 editor.update_diagnostics_state(window, cx);
17971 })
17972 .is_err()
17973 {
17974 return;
17975 }
17976 }
17977 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
17978 }
17979 }
17980 });
17981
17982 Some(())
17983 }
17984
17985 pub fn set_selections_from_remote(
17986 &mut self,
17987 selections: Vec<Selection<Anchor>>,
17988 pending_selection: Option<Selection<Anchor>>,
17989 window: &mut Window,
17990 cx: &mut Context<Self>,
17991 ) {
17992 let old_cursor_position = self.selections.newest_anchor().head();
17993 self.selections.change_with(cx, |s| {
17994 s.select_anchors(selections);
17995 if let Some(pending_selection) = pending_selection {
17996 s.set_pending(pending_selection, SelectMode::Character);
17997 } else {
17998 s.clear_pending();
17999 }
18000 });
18001 self.selections_did_change(
18002 false,
18003 &old_cursor_position,
18004 SelectionEffects::default(),
18005 window,
18006 cx,
18007 );
18008 }
18009
18010 pub fn transact(
18011 &mut self,
18012 window: &mut Window,
18013 cx: &mut Context<Self>,
18014 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18015 ) -> Option<TransactionId> {
18016 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18017 this.start_transaction_at(Instant::now(), window, cx);
18018 update(this, window, cx);
18019 this.end_transaction_at(Instant::now(), cx)
18020 })
18021 }
18022
18023 pub fn start_transaction_at(
18024 &mut self,
18025 now: Instant,
18026 window: &mut Window,
18027 cx: &mut Context<Self>,
18028 ) -> Option<TransactionId> {
18029 self.end_selection(window, cx);
18030 if let Some(tx_id) = self
18031 .buffer
18032 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18033 {
18034 self.selection_history
18035 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18036 cx.emit(EditorEvent::TransactionBegun {
18037 transaction_id: tx_id,
18038 });
18039 Some(tx_id)
18040 } else {
18041 None
18042 }
18043 }
18044
18045 pub fn end_transaction_at(
18046 &mut self,
18047 now: Instant,
18048 cx: &mut Context<Self>,
18049 ) -> Option<TransactionId> {
18050 if let Some(transaction_id) = self
18051 .buffer
18052 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18053 {
18054 if let Some((_, end_selections)) =
18055 self.selection_history.transaction_mut(transaction_id)
18056 {
18057 *end_selections = Some(self.selections.disjoint_anchors_arc());
18058 } else {
18059 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18060 }
18061
18062 cx.emit(EditorEvent::Edited { transaction_id });
18063 Some(transaction_id)
18064 } else {
18065 None
18066 }
18067 }
18068
18069 pub fn modify_transaction_selection_history(
18070 &mut self,
18071 transaction_id: TransactionId,
18072 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18073 ) -> bool {
18074 self.selection_history
18075 .transaction_mut(transaction_id)
18076 .map(modify)
18077 .is_some()
18078 }
18079
18080 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18081 if self.selection_mark_mode {
18082 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18083 s.move_with(|_, sel| {
18084 sel.collapse_to(sel.head(), SelectionGoal::None);
18085 });
18086 })
18087 }
18088 self.selection_mark_mode = true;
18089 cx.notify();
18090 }
18091
18092 pub fn swap_selection_ends(
18093 &mut self,
18094 _: &actions::SwapSelectionEnds,
18095 window: &mut Window,
18096 cx: &mut Context<Self>,
18097 ) {
18098 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18099 s.move_with(|_, sel| {
18100 if sel.start != sel.end {
18101 sel.reversed = !sel.reversed
18102 }
18103 });
18104 });
18105 self.request_autoscroll(Autoscroll::newest(), cx);
18106 cx.notify();
18107 }
18108
18109 pub fn toggle_focus(
18110 workspace: &mut Workspace,
18111 _: &actions::ToggleFocus,
18112 window: &mut Window,
18113 cx: &mut Context<Workspace>,
18114 ) {
18115 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18116 return;
18117 };
18118 workspace.activate_item(&item, true, true, window, cx);
18119 }
18120
18121 pub fn toggle_fold(
18122 &mut self,
18123 _: &actions::ToggleFold,
18124 window: &mut Window,
18125 cx: &mut Context<Self>,
18126 ) {
18127 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18128 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18129 let selection = self.selections.newest::<Point>(&display_map);
18130
18131 let range = if selection.is_empty() {
18132 let point = selection.head().to_display_point(&display_map);
18133 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18134 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18135 .to_point(&display_map);
18136 start..end
18137 } else {
18138 selection.range()
18139 };
18140 if display_map.folds_in_range(range).next().is_some() {
18141 self.unfold_lines(&Default::default(), window, cx)
18142 } else {
18143 self.fold(&Default::default(), window, cx)
18144 }
18145 } else {
18146 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18147 let buffer_ids: HashSet<_> = self
18148 .selections
18149 .disjoint_anchor_ranges()
18150 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18151 .collect();
18152
18153 let should_unfold = buffer_ids
18154 .iter()
18155 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18156
18157 for buffer_id in buffer_ids {
18158 if should_unfold {
18159 self.unfold_buffer(buffer_id, cx);
18160 } else {
18161 self.fold_buffer(buffer_id, cx);
18162 }
18163 }
18164 }
18165 }
18166
18167 pub fn toggle_fold_recursive(
18168 &mut self,
18169 _: &actions::ToggleFoldRecursive,
18170 window: &mut Window,
18171 cx: &mut Context<Self>,
18172 ) {
18173 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18174
18175 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18176 let range = if selection.is_empty() {
18177 let point = selection.head().to_display_point(&display_map);
18178 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18179 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18180 .to_point(&display_map);
18181 start..end
18182 } else {
18183 selection.range()
18184 };
18185 if display_map.folds_in_range(range).next().is_some() {
18186 self.unfold_recursive(&Default::default(), window, cx)
18187 } else {
18188 self.fold_recursive(&Default::default(), window, cx)
18189 }
18190 }
18191
18192 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18193 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18194 let mut to_fold = Vec::new();
18195 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18196 let selections = self.selections.all_adjusted(&display_map);
18197
18198 for selection in selections {
18199 let range = selection.range().sorted();
18200 let buffer_start_row = range.start.row;
18201
18202 if range.start.row != range.end.row {
18203 let mut found = false;
18204 let mut row = range.start.row;
18205 while row <= range.end.row {
18206 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18207 {
18208 found = true;
18209 row = crease.range().end.row + 1;
18210 to_fold.push(crease);
18211 } else {
18212 row += 1
18213 }
18214 }
18215 if found {
18216 continue;
18217 }
18218 }
18219
18220 for row in (0..=range.start.row).rev() {
18221 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18222 && crease.range().end.row >= buffer_start_row
18223 {
18224 to_fold.push(crease);
18225 if row <= range.start.row {
18226 break;
18227 }
18228 }
18229 }
18230 }
18231
18232 self.fold_creases(to_fold, true, window, cx);
18233 } else {
18234 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18235 let buffer_ids = self
18236 .selections
18237 .disjoint_anchor_ranges()
18238 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18239 .collect::<HashSet<_>>();
18240 for buffer_id in buffer_ids {
18241 self.fold_buffer(buffer_id, cx);
18242 }
18243 }
18244 }
18245
18246 pub fn toggle_fold_all(
18247 &mut self,
18248 _: &actions::ToggleFoldAll,
18249 window: &mut Window,
18250 cx: &mut Context<Self>,
18251 ) {
18252 if self.buffer.read(cx).is_singleton() {
18253 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18254 let has_folds = display_map
18255 .folds_in_range(0..display_map.buffer_snapshot().len())
18256 .next()
18257 .is_some();
18258
18259 if has_folds {
18260 self.unfold_all(&actions::UnfoldAll, window, cx);
18261 } else {
18262 self.fold_all(&actions::FoldAll, window, cx);
18263 }
18264 } else {
18265 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18266 let should_unfold = buffer_ids
18267 .iter()
18268 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18269
18270 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18271 editor
18272 .update_in(cx, |editor, _, cx| {
18273 for buffer_id in buffer_ids {
18274 if should_unfold {
18275 editor.unfold_buffer(buffer_id, cx);
18276 } else {
18277 editor.fold_buffer(buffer_id, cx);
18278 }
18279 }
18280 })
18281 .ok();
18282 });
18283 }
18284 }
18285
18286 fn fold_at_level(
18287 &mut self,
18288 fold_at: &FoldAtLevel,
18289 window: &mut Window,
18290 cx: &mut Context<Self>,
18291 ) {
18292 if !self.buffer.read(cx).is_singleton() {
18293 return;
18294 }
18295
18296 let fold_at_level = fold_at.0;
18297 let snapshot = self.buffer.read(cx).snapshot(cx);
18298 let mut to_fold = Vec::new();
18299 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18300
18301 let row_ranges_to_keep: Vec<Range<u32>> = self
18302 .selections
18303 .all::<Point>(&self.display_snapshot(cx))
18304 .into_iter()
18305 .map(|sel| sel.start.row..sel.end.row)
18306 .collect();
18307
18308 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18309 while start_row < end_row {
18310 match self
18311 .snapshot(window, cx)
18312 .crease_for_buffer_row(MultiBufferRow(start_row))
18313 {
18314 Some(crease) => {
18315 let nested_start_row = crease.range().start.row + 1;
18316 let nested_end_row = crease.range().end.row;
18317
18318 if current_level < fold_at_level {
18319 stack.push((nested_start_row, nested_end_row, current_level + 1));
18320 } else if current_level == fold_at_level {
18321 // Fold iff there is no selection completely contained within the fold region
18322 if !row_ranges_to_keep.iter().any(|selection| {
18323 selection.end >= nested_start_row
18324 && selection.start <= nested_end_row
18325 }) {
18326 to_fold.push(crease);
18327 }
18328 }
18329
18330 start_row = nested_end_row + 1;
18331 }
18332 None => start_row += 1,
18333 }
18334 }
18335 }
18336
18337 self.fold_creases(to_fold, true, window, cx);
18338 }
18339
18340 pub fn fold_at_level_1(
18341 &mut self,
18342 _: &actions::FoldAtLevel1,
18343 window: &mut Window,
18344 cx: &mut Context<Self>,
18345 ) {
18346 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18347 }
18348
18349 pub fn fold_at_level_2(
18350 &mut self,
18351 _: &actions::FoldAtLevel2,
18352 window: &mut Window,
18353 cx: &mut Context<Self>,
18354 ) {
18355 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18356 }
18357
18358 pub fn fold_at_level_3(
18359 &mut self,
18360 _: &actions::FoldAtLevel3,
18361 window: &mut Window,
18362 cx: &mut Context<Self>,
18363 ) {
18364 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18365 }
18366
18367 pub fn fold_at_level_4(
18368 &mut self,
18369 _: &actions::FoldAtLevel4,
18370 window: &mut Window,
18371 cx: &mut Context<Self>,
18372 ) {
18373 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18374 }
18375
18376 pub fn fold_at_level_5(
18377 &mut self,
18378 _: &actions::FoldAtLevel5,
18379 window: &mut Window,
18380 cx: &mut Context<Self>,
18381 ) {
18382 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18383 }
18384
18385 pub fn fold_at_level_6(
18386 &mut self,
18387 _: &actions::FoldAtLevel6,
18388 window: &mut Window,
18389 cx: &mut Context<Self>,
18390 ) {
18391 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18392 }
18393
18394 pub fn fold_at_level_7(
18395 &mut self,
18396 _: &actions::FoldAtLevel7,
18397 window: &mut Window,
18398 cx: &mut Context<Self>,
18399 ) {
18400 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18401 }
18402
18403 pub fn fold_at_level_8(
18404 &mut self,
18405 _: &actions::FoldAtLevel8,
18406 window: &mut Window,
18407 cx: &mut Context<Self>,
18408 ) {
18409 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18410 }
18411
18412 pub fn fold_at_level_9(
18413 &mut self,
18414 _: &actions::FoldAtLevel9,
18415 window: &mut Window,
18416 cx: &mut Context<Self>,
18417 ) {
18418 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18419 }
18420
18421 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18422 if self.buffer.read(cx).is_singleton() {
18423 let mut fold_ranges = Vec::new();
18424 let snapshot = self.buffer.read(cx).snapshot(cx);
18425
18426 for row in 0..snapshot.max_row().0 {
18427 if let Some(foldable_range) = self
18428 .snapshot(window, cx)
18429 .crease_for_buffer_row(MultiBufferRow(row))
18430 {
18431 fold_ranges.push(foldable_range);
18432 }
18433 }
18434
18435 self.fold_creases(fold_ranges, true, window, cx);
18436 } else {
18437 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18438 editor
18439 .update_in(cx, |editor, _, cx| {
18440 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18441 editor.fold_buffer(buffer_id, cx);
18442 }
18443 })
18444 .ok();
18445 });
18446 }
18447 }
18448
18449 pub fn fold_function_bodies(
18450 &mut self,
18451 _: &actions::FoldFunctionBodies,
18452 window: &mut Window,
18453 cx: &mut Context<Self>,
18454 ) {
18455 let snapshot = self.buffer.read(cx).snapshot(cx);
18456
18457 let ranges = snapshot
18458 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18459 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18460 .collect::<Vec<_>>();
18461
18462 let creases = ranges
18463 .into_iter()
18464 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18465 .collect();
18466
18467 self.fold_creases(creases, true, window, cx);
18468 }
18469
18470 pub fn fold_recursive(
18471 &mut self,
18472 _: &actions::FoldRecursive,
18473 window: &mut Window,
18474 cx: &mut Context<Self>,
18475 ) {
18476 let mut to_fold = Vec::new();
18477 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18478 let selections = self.selections.all_adjusted(&display_map);
18479
18480 for selection in selections {
18481 let range = selection.range().sorted();
18482 let buffer_start_row = range.start.row;
18483
18484 if range.start.row != range.end.row {
18485 let mut found = false;
18486 for row in range.start.row..=range.end.row {
18487 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18488 found = true;
18489 to_fold.push(crease);
18490 }
18491 }
18492 if found {
18493 continue;
18494 }
18495 }
18496
18497 for row in (0..=range.start.row).rev() {
18498 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18499 if crease.range().end.row >= buffer_start_row {
18500 to_fold.push(crease);
18501 } else {
18502 break;
18503 }
18504 }
18505 }
18506 }
18507
18508 self.fold_creases(to_fold, true, window, cx);
18509 }
18510
18511 pub fn fold_at(
18512 &mut self,
18513 buffer_row: MultiBufferRow,
18514 window: &mut Window,
18515 cx: &mut Context<Self>,
18516 ) {
18517 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18518
18519 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18520 let autoscroll = self
18521 .selections
18522 .all::<Point>(&display_map)
18523 .iter()
18524 .any(|selection| crease.range().overlaps(&selection.range()));
18525
18526 self.fold_creases(vec![crease], autoscroll, window, cx);
18527 }
18528 }
18529
18530 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18531 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18532 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18533 let buffer = display_map.buffer_snapshot();
18534 let selections = self.selections.all::<Point>(&display_map);
18535 let ranges = selections
18536 .iter()
18537 .map(|s| {
18538 let range = s.display_range(&display_map).sorted();
18539 let mut start = range.start.to_point(&display_map);
18540 let mut end = range.end.to_point(&display_map);
18541 start.column = 0;
18542 end.column = buffer.line_len(MultiBufferRow(end.row));
18543 start..end
18544 })
18545 .collect::<Vec<_>>();
18546
18547 self.unfold_ranges(&ranges, true, true, cx);
18548 } else {
18549 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18550 let buffer_ids = self
18551 .selections
18552 .disjoint_anchor_ranges()
18553 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18554 .collect::<HashSet<_>>();
18555 for buffer_id in buffer_ids {
18556 self.unfold_buffer(buffer_id, cx);
18557 }
18558 }
18559 }
18560
18561 pub fn unfold_recursive(
18562 &mut self,
18563 _: &UnfoldRecursive,
18564 _window: &mut Window,
18565 cx: &mut Context<Self>,
18566 ) {
18567 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18568 let selections = self.selections.all::<Point>(&display_map);
18569 let ranges = selections
18570 .iter()
18571 .map(|s| {
18572 let mut range = s.display_range(&display_map).sorted();
18573 *range.start.column_mut() = 0;
18574 *range.end.column_mut() = display_map.line_len(range.end.row());
18575 let start = range.start.to_point(&display_map);
18576 let end = range.end.to_point(&display_map);
18577 start..end
18578 })
18579 .collect::<Vec<_>>();
18580
18581 self.unfold_ranges(&ranges, true, true, cx);
18582 }
18583
18584 pub fn unfold_at(
18585 &mut self,
18586 buffer_row: MultiBufferRow,
18587 _window: &mut Window,
18588 cx: &mut Context<Self>,
18589 ) {
18590 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18591
18592 let intersection_range = Point::new(buffer_row.0, 0)
18593 ..Point::new(
18594 buffer_row.0,
18595 display_map.buffer_snapshot().line_len(buffer_row),
18596 );
18597
18598 let autoscroll = self
18599 .selections
18600 .all::<Point>(&display_map)
18601 .iter()
18602 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18603
18604 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18605 }
18606
18607 pub fn unfold_all(
18608 &mut self,
18609 _: &actions::UnfoldAll,
18610 _window: &mut Window,
18611 cx: &mut Context<Self>,
18612 ) {
18613 if self.buffer.read(cx).is_singleton() {
18614 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18615 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18616 } else {
18617 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18618 editor
18619 .update(cx, |editor, cx| {
18620 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18621 editor.unfold_buffer(buffer_id, cx);
18622 }
18623 })
18624 .ok();
18625 });
18626 }
18627 }
18628
18629 pub fn fold_selected_ranges(
18630 &mut self,
18631 _: &FoldSelectedRanges,
18632 window: &mut Window,
18633 cx: &mut Context<Self>,
18634 ) {
18635 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18636 let selections = self.selections.all_adjusted(&display_map);
18637 let ranges = selections
18638 .into_iter()
18639 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18640 .collect::<Vec<_>>();
18641 self.fold_creases(ranges, true, window, cx);
18642 }
18643
18644 pub fn fold_ranges<T: ToOffset + Clone>(
18645 &mut self,
18646 ranges: Vec<Range<T>>,
18647 auto_scroll: bool,
18648 window: &mut Window,
18649 cx: &mut Context<Self>,
18650 ) {
18651 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18652 let ranges = ranges
18653 .into_iter()
18654 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18655 .collect::<Vec<_>>();
18656 self.fold_creases(ranges, auto_scroll, window, cx);
18657 }
18658
18659 pub fn fold_creases<T: ToOffset + Clone>(
18660 &mut self,
18661 creases: Vec<Crease<T>>,
18662 auto_scroll: bool,
18663 _window: &mut Window,
18664 cx: &mut Context<Self>,
18665 ) {
18666 if creases.is_empty() {
18667 return;
18668 }
18669
18670 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18671
18672 if auto_scroll {
18673 self.request_autoscroll(Autoscroll::fit(), cx);
18674 }
18675
18676 cx.notify();
18677
18678 self.scrollbar_marker_state.dirty = true;
18679 self.folds_did_change(cx);
18680 }
18681
18682 /// Removes any folds whose ranges intersect any of the given ranges.
18683 pub fn unfold_ranges<T: ToOffset + Clone>(
18684 &mut self,
18685 ranges: &[Range<T>],
18686 inclusive: bool,
18687 auto_scroll: bool,
18688 cx: &mut Context<Self>,
18689 ) {
18690 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18691 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18692 });
18693 self.folds_did_change(cx);
18694 }
18695
18696 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18697 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18698 return;
18699 }
18700 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18701 self.display_map.update(cx, |display_map, cx| {
18702 display_map.fold_buffers([buffer_id], cx)
18703 });
18704 cx.emit(EditorEvent::BufferFoldToggled {
18705 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18706 folded: true,
18707 });
18708 cx.notify();
18709 }
18710
18711 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18712 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18713 return;
18714 }
18715 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18716 self.display_map.update(cx, |display_map, cx| {
18717 display_map.unfold_buffers([buffer_id], cx);
18718 });
18719 cx.emit(EditorEvent::BufferFoldToggled {
18720 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18721 folded: false,
18722 });
18723 cx.notify();
18724 }
18725
18726 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18727 self.display_map.read(cx).is_buffer_folded(buffer)
18728 }
18729
18730 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18731 self.display_map.read(cx).folded_buffers()
18732 }
18733
18734 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18735 self.display_map.update(cx, |display_map, cx| {
18736 display_map.disable_header_for_buffer(buffer_id, cx);
18737 });
18738 cx.notify();
18739 }
18740
18741 /// Removes any folds with the given ranges.
18742 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18743 &mut self,
18744 ranges: &[Range<T>],
18745 type_id: TypeId,
18746 auto_scroll: bool,
18747 cx: &mut Context<Self>,
18748 ) {
18749 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18750 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18751 });
18752 self.folds_did_change(cx);
18753 }
18754
18755 fn remove_folds_with<T: ToOffset + Clone>(
18756 &mut self,
18757 ranges: &[Range<T>],
18758 auto_scroll: bool,
18759 cx: &mut Context<Self>,
18760 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18761 ) {
18762 if ranges.is_empty() {
18763 return;
18764 }
18765
18766 let mut buffers_affected = HashSet::default();
18767 let multi_buffer = self.buffer().read(cx);
18768 for range in ranges {
18769 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18770 buffers_affected.insert(buffer.read(cx).remote_id());
18771 };
18772 }
18773
18774 self.display_map.update(cx, update);
18775
18776 if auto_scroll {
18777 self.request_autoscroll(Autoscroll::fit(), cx);
18778 }
18779
18780 cx.notify();
18781 self.scrollbar_marker_state.dirty = true;
18782 self.active_indent_guides_state.dirty = true;
18783 }
18784
18785 pub fn update_renderer_widths(
18786 &mut self,
18787 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18788 cx: &mut Context<Self>,
18789 ) -> bool {
18790 self.display_map
18791 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18792 }
18793
18794 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18795 self.display_map.read(cx).fold_placeholder.clone()
18796 }
18797
18798 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18799 self.buffer.update(cx, |buffer, cx| {
18800 buffer.set_all_diff_hunks_expanded(cx);
18801 });
18802 }
18803
18804 pub fn expand_all_diff_hunks(
18805 &mut self,
18806 _: &ExpandAllDiffHunks,
18807 _window: &mut Window,
18808 cx: &mut Context<Self>,
18809 ) {
18810 self.buffer.update(cx, |buffer, cx| {
18811 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18812 });
18813 }
18814
18815 pub fn collapse_all_diff_hunks(
18816 &mut self,
18817 _: &CollapseAllDiffHunks,
18818 _window: &mut Window,
18819 cx: &mut Context<Self>,
18820 ) {
18821 self.buffer.update(cx, |buffer, cx| {
18822 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18823 });
18824 }
18825
18826 pub fn toggle_selected_diff_hunks(
18827 &mut self,
18828 _: &ToggleSelectedDiffHunks,
18829 _window: &mut Window,
18830 cx: &mut Context<Self>,
18831 ) {
18832 let ranges: Vec<_> = self
18833 .selections
18834 .disjoint_anchors()
18835 .iter()
18836 .map(|s| s.range())
18837 .collect();
18838 self.toggle_diff_hunks_in_ranges(ranges, cx);
18839 }
18840
18841 pub fn diff_hunks_in_ranges<'a>(
18842 &'a self,
18843 ranges: &'a [Range<Anchor>],
18844 buffer: &'a MultiBufferSnapshot,
18845 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18846 ranges.iter().flat_map(move |range| {
18847 let end_excerpt_id = range.end.excerpt_id;
18848 let range = range.to_point(buffer);
18849 let mut peek_end = range.end;
18850 if range.end.row < buffer.max_row().0 {
18851 peek_end = Point::new(range.end.row + 1, 0);
18852 }
18853 buffer
18854 .diff_hunks_in_range(range.start..peek_end)
18855 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18856 })
18857 }
18858
18859 pub fn has_stageable_diff_hunks_in_ranges(
18860 &self,
18861 ranges: &[Range<Anchor>],
18862 snapshot: &MultiBufferSnapshot,
18863 ) -> bool {
18864 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18865 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18866 }
18867
18868 pub fn toggle_staged_selected_diff_hunks(
18869 &mut self,
18870 _: &::git::ToggleStaged,
18871 _: &mut Window,
18872 cx: &mut Context<Self>,
18873 ) {
18874 let snapshot = self.buffer.read(cx).snapshot(cx);
18875 let ranges: Vec<_> = self
18876 .selections
18877 .disjoint_anchors()
18878 .iter()
18879 .map(|s| s.range())
18880 .collect();
18881 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
18882 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18883 }
18884
18885 pub fn set_render_diff_hunk_controls(
18886 &mut self,
18887 render_diff_hunk_controls: RenderDiffHunkControlsFn,
18888 cx: &mut Context<Self>,
18889 ) {
18890 self.render_diff_hunk_controls = render_diff_hunk_controls;
18891 cx.notify();
18892 }
18893
18894 pub fn stage_and_next(
18895 &mut self,
18896 _: &::git::StageAndNext,
18897 window: &mut Window,
18898 cx: &mut Context<Self>,
18899 ) {
18900 self.do_stage_or_unstage_and_next(true, window, cx);
18901 }
18902
18903 pub fn unstage_and_next(
18904 &mut self,
18905 _: &::git::UnstageAndNext,
18906 window: &mut Window,
18907 cx: &mut Context<Self>,
18908 ) {
18909 self.do_stage_or_unstage_and_next(false, window, cx);
18910 }
18911
18912 pub fn stage_or_unstage_diff_hunks(
18913 &mut self,
18914 stage: bool,
18915 ranges: Vec<Range<Anchor>>,
18916 cx: &mut Context<Self>,
18917 ) {
18918 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
18919 cx.spawn(async move |this, cx| {
18920 task.await?;
18921 this.update(cx, |this, cx| {
18922 let snapshot = this.buffer.read(cx).snapshot(cx);
18923 let chunk_by = this
18924 .diff_hunks_in_ranges(&ranges, &snapshot)
18925 .chunk_by(|hunk| hunk.buffer_id);
18926 for (buffer_id, hunks) in &chunk_by {
18927 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
18928 }
18929 })
18930 })
18931 .detach_and_log_err(cx);
18932 }
18933
18934 fn save_buffers_for_ranges_if_needed(
18935 &mut self,
18936 ranges: &[Range<Anchor>],
18937 cx: &mut Context<Editor>,
18938 ) -> Task<Result<()>> {
18939 let multibuffer = self.buffer.read(cx);
18940 let snapshot = multibuffer.read(cx);
18941 let buffer_ids: HashSet<_> = ranges
18942 .iter()
18943 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
18944 .collect();
18945 drop(snapshot);
18946
18947 let mut buffers = HashSet::default();
18948 for buffer_id in buffer_ids {
18949 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
18950 let buffer = buffer_entity.read(cx);
18951 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
18952 {
18953 buffers.insert(buffer_entity);
18954 }
18955 }
18956 }
18957
18958 if let Some(project) = &self.project {
18959 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
18960 } else {
18961 Task::ready(Ok(()))
18962 }
18963 }
18964
18965 fn do_stage_or_unstage_and_next(
18966 &mut self,
18967 stage: bool,
18968 window: &mut Window,
18969 cx: &mut Context<Self>,
18970 ) {
18971 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
18972
18973 if ranges.iter().any(|range| range.start != range.end) {
18974 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18975 return;
18976 }
18977
18978 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
18979 let snapshot = self.snapshot(window, cx);
18980 let position = self
18981 .selections
18982 .newest::<Point>(&snapshot.display_snapshot)
18983 .head();
18984 let mut row = snapshot
18985 .buffer_snapshot()
18986 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
18987 .find(|hunk| hunk.row_range.start.0 > position.row)
18988 .map(|hunk| hunk.row_range.start);
18989
18990 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
18991 // Outside of the project diff editor, wrap around to the beginning.
18992 if !all_diff_hunks_expanded {
18993 row = row.or_else(|| {
18994 snapshot
18995 .buffer_snapshot()
18996 .diff_hunks_in_range(Point::zero()..position)
18997 .find(|hunk| hunk.row_range.end.0 < position.row)
18998 .map(|hunk| hunk.row_range.start)
18999 });
19000 }
19001
19002 if let Some(row) = row {
19003 let destination = Point::new(row.0, 0);
19004 let autoscroll = Autoscroll::center();
19005
19006 self.unfold_ranges(&[destination..destination], false, false, cx);
19007 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19008 s.select_ranges([destination..destination]);
19009 });
19010 }
19011 }
19012
19013 fn do_stage_or_unstage(
19014 &self,
19015 stage: bool,
19016 buffer_id: BufferId,
19017 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19018 cx: &mut App,
19019 ) -> Option<()> {
19020 let project = self.project()?;
19021 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19022 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19023 let buffer_snapshot = buffer.read(cx).snapshot();
19024 let file_exists = buffer_snapshot
19025 .file()
19026 .is_some_and(|file| file.disk_state().exists());
19027 diff.update(cx, |diff, cx| {
19028 diff.stage_or_unstage_hunks(
19029 stage,
19030 &hunks
19031 .map(|hunk| buffer_diff::DiffHunk {
19032 buffer_range: hunk.buffer_range,
19033 diff_base_byte_range: hunk.diff_base_byte_range,
19034 secondary_status: hunk.secondary_status,
19035 range: Point::zero()..Point::zero(), // unused
19036 })
19037 .collect::<Vec<_>>(),
19038 &buffer_snapshot,
19039 file_exists,
19040 cx,
19041 )
19042 });
19043 None
19044 }
19045
19046 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19047 let ranges: Vec<_> = self
19048 .selections
19049 .disjoint_anchors()
19050 .iter()
19051 .map(|s| s.range())
19052 .collect();
19053 self.buffer
19054 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19055 }
19056
19057 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19058 self.buffer.update(cx, |buffer, cx| {
19059 let ranges = vec![Anchor::min()..Anchor::max()];
19060 if !buffer.all_diff_hunks_expanded()
19061 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19062 {
19063 buffer.collapse_diff_hunks(ranges, cx);
19064 true
19065 } else {
19066 false
19067 }
19068 })
19069 }
19070
19071 fn toggle_diff_hunks_in_ranges(
19072 &mut self,
19073 ranges: Vec<Range<Anchor>>,
19074 cx: &mut Context<Editor>,
19075 ) {
19076 self.buffer.update(cx, |buffer, cx| {
19077 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19078 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19079 })
19080 }
19081
19082 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19083 self.buffer.update(cx, |buffer, cx| {
19084 let snapshot = buffer.snapshot(cx);
19085 let excerpt_id = range.end.excerpt_id;
19086 let point_range = range.to_point(&snapshot);
19087 let expand = !buffer.single_hunk_is_expanded(range, cx);
19088 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19089 })
19090 }
19091
19092 pub(crate) fn apply_all_diff_hunks(
19093 &mut self,
19094 _: &ApplyAllDiffHunks,
19095 window: &mut Window,
19096 cx: &mut Context<Self>,
19097 ) {
19098 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19099
19100 let buffers = self.buffer.read(cx).all_buffers();
19101 for branch_buffer in buffers {
19102 branch_buffer.update(cx, |branch_buffer, cx| {
19103 branch_buffer.merge_into_base(Vec::new(), cx);
19104 });
19105 }
19106
19107 if let Some(project) = self.project.clone() {
19108 self.save(
19109 SaveOptions {
19110 format: true,
19111 autosave: false,
19112 },
19113 project,
19114 window,
19115 cx,
19116 )
19117 .detach_and_log_err(cx);
19118 }
19119 }
19120
19121 pub(crate) fn apply_selected_diff_hunks(
19122 &mut self,
19123 _: &ApplyDiffHunk,
19124 window: &mut Window,
19125 cx: &mut Context<Self>,
19126 ) {
19127 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19128 let snapshot = self.snapshot(window, cx);
19129 let hunks = snapshot.hunks_for_ranges(
19130 self.selections
19131 .all(&snapshot.display_snapshot)
19132 .into_iter()
19133 .map(|selection| selection.range()),
19134 );
19135 let mut ranges_by_buffer = HashMap::default();
19136 self.transact(window, cx, |editor, _window, cx| {
19137 for hunk in hunks {
19138 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19139 ranges_by_buffer
19140 .entry(buffer.clone())
19141 .or_insert_with(Vec::new)
19142 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19143 }
19144 }
19145
19146 for (buffer, ranges) in ranges_by_buffer {
19147 buffer.update(cx, |buffer, cx| {
19148 buffer.merge_into_base(ranges, cx);
19149 });
19150 }
19151 });
19152
19153 if let Some(project) = self.project.clone() {
19154 self.save(
19155 SaveOptions {
19156 format: true,
19157 autosave: false,
19158 },
19159 project,
19160 window,
19161 cx,
19162 )
19163 .detach_and_log_err(cx);
19164 }
19165 }
19166
19167 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19168 if hovered != self.gutter_hovered {
19169 self.gutter_hovered = hovered;
19170 cx.notify();
19171 }
19172 }
19173
19174 pub fn insert_blocks(
19175 &mut self,
19176 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19177 autoscroll: Option<Autoscroll>,
19178 cx: &mut Context<Self>,
19179 ) -> Vec<CustomBlockId> {
19180 let blocks = self
19181 .display_map
19182 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19183 if let Some(autoscroll) = autoscroll {
19184 self.request_autoscroll(autoscroll, cx);
19185 }
19186 cx.notify();
19187 blocks
19188 }
19189
19190 pub fn resize_blocks(
19191 &mut self,
19192 heights: HashMap<CustomBlockId, u32>,
19193 autoscroll: Option<Autoscroll>,
19194 cx: &mut Context<Self>,
19195 ) {
19196 self.display_map
19197 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19198 if let Some(autoscroll) = autoscroll {
19199 self.request_autoscroll(autoscroll, cx);
19200 }
19201 cx.notify();
19202 }
19203
19204 pub fn replace_blocks(
19205 &mut self,
19206 renderers: HashMap<CustomBlockId, RenderBlock>,
19207 autoscroll: Option<Autoscroll>,
19208 cx: &mut Context<Self>,
19209 ) {
19210 self.display_map
19211 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19212 if let Some(autoscroll) = autoscroll {
19213 self.request_autoscroll(autoscroll, cx);
19214 }
19215 cx.notify();
19216 }
19217
19218 pub fn remove_blocks(
19219 &mut self,
19220 block_ids: HashSet<CustomBlockId>,
19221 autoscroll: Option<Autoscroll>,
19222 cx: &mut Context<Self>,
19223 ) {
19224 self.display_map.update(cx, |display_map, cx| {
19225 display_map.remove_blocks(block_ids, cx)
19226 });
19227 if let Some(autoscroll) = autoscroll {
19228 self.request_autoscroll(autoscroll, cx);
19229 }
19230 cx.notify();
19231 }
19232
19233 pub fn row_for_block(
19234 &self,
19235 block_id: CustomBlockId,
19236 cx: &mut Context<Self>,
19237 ) -> Option<DisplayRow> {
19238 self.display_map
19239 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19240 }
19241
19242 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19243 self.focused_block = Some(focused_block);
19244 }
19245
19246 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19247 self.focused_block.take()
19248 }
19249
19250 pub fn insert_creases(
19251 &mut self,
19252 creases: impl IntoIterator<Item = Crease<Anchor>>,
19253 cx: &mut Context<Self>,
19254 ) -> Vec<CreaseId> {
19255 self.display_map
19256 .update(cx, |map, cx| map.insert_creases(creases, cx))
19257 }
19258
19259 pub fn remove_creases(
19260 &mut self,
19261 ids: impl IntoIterator<Item = CreaseId>,
19262 cx: &mut Context<Self>,
19263 ) -> Vec<(CreaseId, Range<Anchor>)> {
19264 self.display_map
19265 .update(cx, |map, cx| map.remove_creases(ids, cx))
19266 }
19267
19268 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19269 self.display_map
19270 .update(cx, |map, cx| map.snapshot(cx))
19271 .longest_row()
19272 }
19273
19274 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19275 self.display_map
19276 .update(cx, |map, cx| map.snapshot(cx))
19277 .max_point()
19278 }
19279
19280 pub fn text(&self, cx: &App) -> String {
19281 self.buffer.read(cx).read(cx).text()
19282 }
19283
19284 pub fn is_empty(&self, cx: &App) -> bool {
19285 self.buffer.read(cx).read(cx).is_empty()
19286 }
19287
19288 pub fn text_option(&self, cx: &App) -> Option<String> {
19289 let text = self.text(cx);
19290 let text = text.trim();
19291
19292 if text.is_empty() {
19293 return None;
19294 }
19295
19296 Some(text.to_string())
19297 }
19298
19299 pub fn set_text(
19300 &mut self,
19301 text: impl Into<Arc<str>>,
19302 window: &mut Window,
19303 cx: &mut Context<Self>,
19304 ) {
19305 self.transact(window, cx, |this, _, cx| {
19306 this.buffer
19307 .read(cx)
19308 .as_singleton()
19309 .expect("you can only call set_text on editors for singleton buffers")
19310 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19311 });
19312 }
19313
19314 pub fn display_text(&self, cx: &mut App) -> String {
19315 self.display_map
19316 .update(cx, |map, cx| map.snapshot(cx))
19317 .text()
19318 }
19319
19320 fn create_minimap(
19321 &self,
19322 minimap_settings: MinimapSettings,
19323 window: &mut Window,
19324 cx: &mut Context<Self>,
19325 ) -> Option<Entity<Self>> {
19326 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19327 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19328 }
19329
19330 fn initialize_new_minimap(
19331 &self,
19332 minimap_settings: MinimapSettings,
19333 window: &mut Window,
19334 cx: &mut Context<Self>,
19335 ) -> Entity<Self> {
19336 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19337
19338 let mut minimap = Editor::new_internal(
19339 EditorMode::Minimap {
19340 parent: cx.weak_entity(),
19341 },
19342 self.buffer.clone(),
19343 None,
19344 Some(self.display_map.clone()),
19345 window,
19346 cx,
19347 );
19348 minimap.scroll_manager.clone_state(&self.scroll_manager);
19349 minimap.set_text_style_refinement(TextStyleRefinement {
19350 font_size: Some(MINIMAP_FONT_SIZE),
19351 font_weight: Some(MINIMAP_FONT_WEIGHT),
19352 ..Default::default()
19353 });
19354 minimap.update_minimap_configuration(minimap_settings, cx);
19355 cx.new(|_| minimap)
19356 }
19357
19358 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19359 let current_line_highlight = minimap_settings
19360 .current_line_highlight
19361 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19362 self.set_current_line_highlight(Some(current_line_highlight));
19363 }
19364
19365 pub fn minimap(&self) -> Option<&Entity<Self>> {
19366 self.minimap
19367 .as_ref()
19368 .filter(|_| self.minimap_visibility.visible())
19369 }
19370
19371 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19372 let mut wrap_guides = smallvec![];
19373
19374 if self.show_wrap_guides == Some(false) {
19375 return wrap_guides;
19376 }
19377
19378 let settings = self.buffer.read(cx).language_settings(cx);
19379 if settings.show_wrap_guides {
19380 match self.soft_wrap_mode(cx) {
19381 SoftWrap::Column(soft_wrap) => {
19382 wrap_guides.push((soft_wrap as usize, true));
19383 }
19384 SoftWrap::Bounded(soft_wrap) => {
19385 wrap_guides.push((soft_wrap as usize, true));
19386 }
19387 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19388 }
19389 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19390 }
19391
19392 wrap_guides
19393 }
19394
19395 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19396 let settings = self.buffer.read(cx).language_settings(cx);
19397 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19398 match mode {
19399 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19400 SoftWrap::None
19401 }
19402 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19403 language_settings::SoftWrap::PreferredLineLength => {
19404 SoftWrap::Column(settings.preferred_line_length)
19405 }
19406 language_settings::SoftWrap::Bounded => {
19407 SoftWrap::Bounded(settings.preferred_line_length)
19408 }
19409 }
19410 }
19411
19412 pub fn set_soft_wrap_mode(
19413 &mut self,
19414 mode: language_settings::SoftWrap,
19415
19416 cx: &mut Context<Self>,
19417 ) {
19418 self.soft_wrap_mode_override = Some(mode);
19419 cx.notify();
19420 }
19421
19422 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19423 self.hard_wrap = hard_wrap;
19424 cx.notify();
19425 }
19426
19427 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19428 self.text_style_refinement = Some(style);
19429 }
19430
19431 /// called by the Element so we know what style we were most recently rendered with.
19432 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19433 // We intentionally do not inform the display map about the minimap style
19434 // so that wrapping is not recalculated and stays consistent for the editor
19435 // and its linked minimap.
19436 if !self.mode.is_minimap() {
19437 let font = style.text.font();
19438 let font_size = style.text.font_size.to_pixels(window.rem_size());
19439 let display_map = self
19440 .placeholder_display_map
19441 .as_ref()
19442 .filter(|_| self.is_empty(cx))
19443 .unwrap_or(&self.display_map);
19444
19445 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19446 }
19447 self.style = Some(style);
19448 }
19449
19450 pub fn style(&self) -> Option<&EditorStyle> {
19451 self.style.as_ref()
19452 }
19453
19454 // Called by the element. This method is not designed to be called outside of the editor
19455 // element's layout code because it does not notify when rewrapping is computed synchronously.
19456 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19457 if self.is_empty(cx) {
19458 self.placeholder_display_map
19459 .as_ref()
19460 .map_or(false, |display_map| {
19461 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19462 })
19463 } else {
19464 self.display_map
19465 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19466 }
19467 }
19468
19469 pub fn set_soft_wrap(&mut self) {
19470 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19471 }
19472
19473 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19474 if self.soft_wrap_mode_override.is_some() {
19475 self.soft_wrap_mode_override.take();
19476 } else {
19477 let soft_wrap = match self.soft_wrap_mode(cx) {
19478 SoftWrap::GitDiff => return,
19479 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19480 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19481 language_settings::SoftWrap::None
19482 }
19483 };
19484 self.soft_wrap_mode_override = Some(soft_wrap);
19485 }
19486 cx.notify();
19487 }
19488
19489 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19490 let Some(workspace) = self.workspace() else {
19491 return;
19492 };
19493 let fs = workspace.read(cx).app_state().fs.clone();
19494 let current_show = TabBarSettings::get_global(cx).show;
19495 update_settings_file(fs, cx, move |setting, _| {
19496 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19497 });
19498 }
19499
19500 pub fn toggle_indent_guides(
19501 &mut self,
19502 _: &ToggleIndentGuides,
19503 _: &mut Window,
19504 cx: &mut Context<Self>,
19505 ) {
19506 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19507 self.buffer
19508 .read(cx)
19509 .language_settings(cx)
19510 .indent_guides
19511 .enabled
19512 });
19513 self.show_indent_guides = Some(!currently_enabled);
19514 cx.notify();
19515 }
19516
19517 fn should_show_indent_guides(&self) -> Option<bool> {
19518 self.show_indent_guides
19519 }
19520
19521 pub fn toggle_line_numbers(
19522 &mut self,
19523 _: &ToggleLineNumbers,
19524 _: &mut Window,
19525 cx: &mut Context<Self>,
19526 ) {
19527 let mut editor_settings = EditorSettings::get_global(cx).clone();
19528 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19529 EditorSettings::override_global(editor_settings, cx);
19530 }
19531
19532 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19533 if let Some(show_line_numbers) = self.show_line_numbers {
19534 return show_line_numbers;
19535 }
19536 EditorSettings::get_global(cx).gutter.line_numbers
19537 }
19538
19539 pub fn should_use_relative_line_numbers(&self, cx: &mut App) -> bool {
19540 self.use_relative_line_numbers
19541 .unwrap_or(EditorSettings::get_global(cx).relative_line_numbers)
19542 }
19543
19544 pub fn toggle_relative_line_numbers(
19545 &mut self,
19546 _: &ToggleRelativeLineNumbers,
19547 _: &mut Window,
19548 cx: &mut Context<Self>,
19549 ) {
19550 let is_relative = self.should_use_relative_line_numbers(cx);
19551 self.set_relative_line_number(Some(!is_relative), cx)
19552 }
19553
19554 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19555 self.use_relative_line_numbers = is_relative;
19556 cx.notify();
19557 }
19558
19559 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19560 self.show_gutter = show_gutter;
19561 cx.notify();
19562 }
19563
19564 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19565 self.show_scrollbars = ScrollbarAxes {
19566 horizontal: show,
19567 vertical: show,
19568 };
19569 cx.notify();
19570 }
19571
19572 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19573 self.show_scrollbars.vertical = show;
19574 cx.notify();
19575 }
19576
19577 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19578 self.show_scrollbars.horizontal = show;
19579 cx.notify();
19580 }
19581
19582 pub fn set_minimap_visibility(
19583 &mut self,
19584 minimap_visibility: MinimapVisibility,
19585 window: &mut Window,
19586 cx: &mut Context<Self>,
19587 ) {
19588 if self.minimap_visibility != minimap_visibility {
19589 if minimap_visibility.visible() && self.minimap.is_none() {
19590 let minimap_settings = EditorSettings::get_global(cx).minimap;
19591 self.minimap =
19592 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19593 }
19594 self.minimap_visibility = minimap_visibility;
19595 cx.notify();
19596 }
19597 }
19598
19599 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19600 self.set_show_scrollbars(false, cx);
19601 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19602 }
19603
19604 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19605 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19606 }
19607
19608 /// Normally the text in full mode and auto height editors is padded on the
19609 /// left side by roughly half a character width for improved hit testing.
19610 ///
19611 /// Use this method to disable this for cases where this is not wanted (e.g.
19612 /// if you want to align the editor text with some other text above or below)
19613 /// or if you want to add this padding to single-line editors.
19614 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19615 self.offset_content = offset_content;
19616 cx.notify();
19617 }
19618
19619 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19620 self.show_line_numbers = Some(show_line_numbers);
19621 cx.notify();
19622 }
19623
19624 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19625 self.disable_expand_excerpt_buttons = true;
19626 cx.notify();
19627 }
19628
19629 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19630 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19631 cx.notify();
19632 }
19633
19634 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19635 self.show_code_actions = Some(show_code_actions);
19636 cx.notify();
19637 }
19638
19639 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19640 self.show_runnables = Some(show_runnables);
19641 cx.notify();
19642 }
19643
19644 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19645 self.show_breakpoints = Some(show_breakpoints);
19646 cx.notify();
19647 }
19648
19649 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19650 if self.display_map.read(cx).masked != masked {
19651 self.display_map.update(cx, |map, _| map.masked = masked);
19652 }
19653 cx.notify()
19654 }
19655
19656 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19657 self.show_wrap_guides = Some(show_wrap_guides);
19658 cx.notify();
19659 }
19660
19661 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19662 self.show_indent_guides = Some(show_indent_guides);
19663 cx.notify();
19664 }
19665
19666 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19667 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19668 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19669 && let Some(dir) = file.abs_path(cx).parent()
19670 {
19671 return Some(dir.to_owned());
19672 }
19673 }
19674
19675 None
19676 }
19677
19678 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19679 self.active_excerpt(cx)?
19680 .1
19681 .read(cx)
19682 .file()
19683 .and_then(|f| f.as_local())
19684 }
19685
19686 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19687 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19688 let buffer = buffer.read(cx);
19689 if let Some(project_path) = buffer.project_path(cx) {
19690 let project = self.project()?.read(cx);
19691 project.absolute_path(&project_path, cx)
19692 } else {
19693 buffer
19694 .file()
19695 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19696 }
19697 })
19698 }
19699
19700 pub fn reveal_in_finder(
19701 &mut self,
19702 _: &RevealInFileManager,
19703 _window: &mut Window,
19704 cx: &mut Context<Self>,
19705 ) {
19706 if let Some(target) = self.target_file(cx) {
19707 cx.reveal_path(&target.abs_path(cx));
19708 }
19709 }
19710
19711 pub fn copy_path(
19712 &mut self,
19713 _: &zed_actions::workspace::CopyPath,
19714 _window: &mut Window,
19715 cx: &mut Context<Self>,
19716 ) {
19717 if let Some(path) = self.target_file_abs_path(cx)
19718 && let Some(path) = path.to_str()
19719 {
19720 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19721 } else {
19722 cx.propagate();
19723 }
19724 }
19725
19726 pub fn copy_relative_path(
19727 &mut self,
19728 _: &zed_actions::workspace::CopyRelativePath,
19729 _window: &mut Window,
19730 cx: &mut Context<Self>,
19731 ) {
19732 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19733 let project = self.project()?.read(cx);
19734 let path = buffer.read(cx).file()?.path();
19735 let path = path.display(project.path_style(cx));
19736 Some(path)
19737 }) {
19738 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19739 } else {
19740 cx.propagate();
19741 }
19742 }
19743
19744 /// Returns the project path for the editor's buffer, if any buffer is
19745 /// opened in the editor.
19746 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19747 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19748 buffer.read(cx).project_path(cx)
19749 } else {
19750 None
19751 }
19752 }
19753
19754 // Returns true if the editor handled a go-to-line request
19755 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19756 maybe!({
19757 let breakpoint_store = self.breakpoint_store.as_ref()?;
19758
19759 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19760 else {
19761 self.clear_row_highlights::<ActiveDebugLine>();
19762 return None;
19763 };
19764
19765 let position = active_stack_frame.position;
19766 let buffer_id = position.buffer_id?;
19767 let snapshot = self
19768 .project
19769 .as_ref()?
19770 .read(cx)
19771 .buffer_for_id(buffer_id, cx)?
19772 .read(cx)
19773 .snapshot();
19774
19775 let mut handled = false;
19776 for (id, ExcerptRange { context, .. }) in
19777 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19778 {
19779 if context.start.cmp(&position, &snapshot).is_ge()
19780 || context.end.cmp(&position, &snapshot).is_lt()
19781 {
19782 continue;
19783 }
19784 let snapshot = self.buffer.read(cx).snapshot(cx);
19785 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19786
19787 handled = true;
19788 self.clear_row_highlights::<ActiveDebugLine>();
19789
19790 self.go_to_line::<ActiveDebugLine>(
19791 multibuffer_anchor,
19792 Some(cx.theme().colors().editor_debugger_active_line_background),
19793 window,
19794 cx,
19795 );
19796
19797 cx.notify();
19798 }
19799
19800 handled.then_some(())
19801 })
19802 .is_some()
19803 }
19804
19805 pub fn copy_file_name_without_extension(
19806 &mut self,
19807 _: &CopyFileNameWithoutExtension,
19808 _: &mut Window,
19809 cx: &mut Context<Self>,
19810 ) {
19811 if let Some(file) = self.target_file(cx)
19812 && let Some(file_stem) = file.path().file_stem()
19813 {
19814 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19815 }
19816 }
19817
19818 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19819 if let Some(file) = self.target_file(cx)
19820 && let Some(name) = file.path().file_name()
19821 {
19822 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19823 }
19824 }
19825
19826 pub fn toggle_git_blame(
19827 &mut self,
19828 _: &::git::Blame,
19829 window: &mut Window,
19830 cx: &mut Context<Self>,
19831 ) {
19832 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19833
19834 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19835 self.start_git_blame(true, window, cx);
19836 }
19837
19838 cx.notify();
19839 }
19840
19841 pub fn toggle_git_blame_inline(
19842 &mut self,
19843 _: &ToggleGitBlameInline,
19844 window: &mut Window,
19845 cx: &mut Context<Self>,
19846 ) {
19847 self.toggle_git_blame_inline_internal(true, window, cx);
19848 cx.notify();
19849 }
19850
19851 pub fn open_git_blame_commit(
19852 &mut self,
19853 _: &OpenGitBlameCommit,
19854 window: &mut Window,
19855 cx: &mut Context<Self>,
19856 ) {
19857 self.open_git_blame_commit_internal(window, cx);
19858 }
19859
19860 fn open_git_blame_commit_internal(
19861 &mut self,
19862 window: &mut Window,
19863 cx: &mut Context<Self>,
19864 ) -> Option<()> {
19865 let blame = self.blame.as_ref()?;
19866 let snapshot = self.snapshot(window, cx);
19867 let cursor = self
19868 .selections
19869 .newest::<Point>(&snapshot.display_snapshot)
19870 .head();
19871 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
19872 let (_, blame_entry) = blame
19873 .update(cx, |blame, cx| {
19874 blame
19875 .blame_for_rows(
19876 &[RowInfo {
19877 buffer_id: Some(buffer.remote_id()),
19878 buffer_row: Some(point.row),
19879 ..Default::default()
19880 }],
19881 cx,
19882 )
19883 .next()
19884 })
19885 .flatten()?;
19886 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
19887 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
19888 let workspace = self.workspace()?.downgrade();
19889 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
19890 None
19891 }
19892
19893 pub fn git_blame_inline_enabled(&self) -> bool {
19894 self.git_blame_inline_enabled
19895 }
19896
19897 pub fn toggle_selection_menu(
19898 &mut self,
19899 _: &ToggleSelectionMenu,
19900 _: &mut Window,
19901 cx: &mut Context<Self>,
19902 ) {
19903 self.show_selection_menu = self
19904 .show_selection_menu
19905 .map(|show_selections_menu| !show_selections_menu)
19906 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
19907
19908 cx.notify();
19909 }
19910
19911 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
19912 self.show_selection_menu
19913 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
19914 }
19915
19916 fn start_git_blame(
19917 &mut self,
19918 user_triggered: bool,
19919 window: &mut Window,
19920 cx: &mut Context<Self>,
19921 ) {
19922 if let Some(project) = self.project() {
19923 if let Some(buffer) = self.buffer().read(cx).as_singleton()
19924 && buffer.read(cx).file().is_none()
19925 {
19926 return;
19927 }
19928
19929 let focused = self.focus_handle(cx).contains_focused(window, cx);
19930
19931 let project = project.clone();
19932 let blame = cx
19933 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
19934 self.blame_subscription =
19935 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
19936 self.blame = Some(blame);
19937 }
19938 }
19939
19940 fn toggle_git_blame_inline_internal(
19941 &mut self,
19942 user_triggered: bool,
19943 window: &mut Window,
19944 cx: &mut Context<Self>,
19945 ) {
19946 if self.git_blame_inline_enabled {
19947 self.git_blame_inline_enabled = false;
19948 self.show_git_blame_inline = false;
19949 self.show_git_blame_inline_delay_task.take();
19950 } else {
19951 self.git_blame_inline_enabled = true;
19952 self.start_git_blame_inline(user_triggered, window, cx);
19953 }
19954
19955 cx.notify();
19956 }
19957
19958 fn start_git_blame_inline(
19959 &mut self,
19960 user_triggered: bool,
19961 window: &mut Window,
19962 cx: &mut Context<Self>,
19963 ) {
19964 self.start_git_blame(user_triggered, window, cx);
19965
19966 if ProjectSettings::get_global(cx)
19967 .git
19968 .inline_blame_delay()
19969 .is_some()
19970 {
19971 self.start_inline_blame_timer(window, cx);
19972 } else {
19973 self.show_git_blame_inline = true
19974 }
19975 }
19976
19977 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
19978 self.blame.as_ref()
19979 }
19980
19981 pub fn show_git_blame_gutter(&self) -> bool {
19982 self.show_git_blame_gutter
19983 }
19984
19985 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
19986 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
19987 }
19988
19989 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
19990 self.show_git_blame_inline
19991 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
19992 && !self.newest_selection_head_on_empty_line(cx)
19993 && self.has_blame_entries(cx)
19994 }
19995
19996 fn has_blame_entries(&self, cx: &App) -> bool {
19997 self.blame()
19998 .is_some_and(|blame| blame.read(cx).has_generated_entries())
19999 }
20000
20001 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20002 let cursor_anchor = self.selections.newest_anchor().head();
20003
20004 let snapshot = self.buffer.read(cx).snapshot(cx);
20005 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20006
20007 snapshot.line_len(buffer_row) == 0
20008 }
20009
20010 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20011 let buffer_and_selection = maybe!({
20012 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20013 let selection_range = selection.range();
20014
20015 let multi_buffer = self.buffer().read(cx);
20016 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20017 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20018
20019 let (buffer, range, _) = if selection.reversed {
20020 buffer_ranges.first()
20021 } else {
20022 buffer_ranges.last()
20023 }?;
20024
20025 let selection = text::ToPoint::to_point(&range.start, buffer).row
20026 ..text::ToPoint::to_point(&range.end, buffer).row;
20027 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20028 });
20029
20030 let Some((buffer, selection)) = buffer_and_selection else {
20031 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20032 };
20033
20034 let Some(project) = self.project() else {
20035 return Task::ready(Err(anyhow!("editor does not have project")));
20036 };
20037
20038 project.update(cx, |project, cx| {
20039 project.get_permalink_to_line(&buffer, selection, cx)
20040 })
20041 }
20042
20043 pub fn copy_permalink_to_line(
20044 &mut self,
20045 _: &CopyPermalinkToLine,
20046 window: &mut Window,
20047 cx: &mut Context<Self>,
20048 ) {
20049 let permalink_task = self.get_permalink_to_line(cx);
20050 let workspace = self.workspace();
20051
20052 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20053 Ok(permalink) => {
20054 cx.update(|_, cx| {
20055 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20056 })
20057 .ok();
20058 }
20059 Err(err) => {
20060 let message = format!("Failed to copy permalink: {err}");
20061
20062 anyhow::Result::<()>::Err(err).log_err();
20063
20064 if let Some(workspace) = workspace {
20065 workspace
20066 .update_in(cx, |workspace, _, cx| {
20067 struct CopyPermalinkToLine;
20068
20069 workspace.show_toast(
20070 Toast::new(
20071 NotificationId::unique::<CopyPermalinkToLine>(),
20072 message,
20073 ),
20074 cx,
20075 )
20076 })
20077 .ok();
20078 }
20079 }
20080 })
20081 .detach();
20082 }
20083
20084 pub fn copy_file_location(
20085 &mut self,
20086 _: &CopyFileLocation,
20087 _: &mut Window,
20088 cx: &mut Context<Self>,
20089 ) {
20090 let selection = self
20091 .selections
20092 .newest::<Point>(&self.display_snapshot(cx))
20093 .start
20094 .row
20095 + 1;
20096 if let Some(file) = self.target_file(cx) {
20097 let path = file.path().display(file.path_style(cx));
20098 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20099 }
20100 }
20101
20102 pub fn open_permalink_to_line(
20103 &mut self,
20104 _: &OpenPermalinkToLine,
20105 window: &mut Window,
20106 cx: &mut Context<Self>,
20107 ) {
20108 let permalink_task = self.get_permalink_to_line(cx);
20109 let workspace = self.workspace();
20110
20111 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20112 Ok(permalink) => {
20113 cx.update(|_, cx| {
20114 cx.open_url(permalink.as_ref());
20115 })
20116 .ok();
20117 }
20118 Err(err) => {
20119 let message = format!("Failed to open permalink: {err}");
20120
20121 anyhow::Result::<()>::Err(err).log_err();
20122
20123 if let Some(workspace) = workspace {
20124 workspace
20125 .update(cx, |workspace, cx| {
20126 struct OpenPermalinkToLine;
20127
20128 workspace.show_toast(
20129 Toast::new(
20130 NotificationId::unique::<OpenPermalinkToLine>(),
20131 message,
20132 ),
20133 cx,
20134 )
20135 })
20136 .ok();
20137 }
20138 }
20139 })
20140 .detach();
20141 }
20142
20143 pub fn insert_uuid_v4(
20144 &mut self,
20145 _: &InsertUuidV4,
20146 window: &mut Window,
20147 cx: &mut Context<Self>,
20148 ) {
20149 self.insert_uuid(UuidVersion::V4, window, cx);
20150 }
20151
20152 pub fn insert_uuid_v7(
20153 &mut self,
20154 _: &InsertUuidV7,
20155 window: &mut Window,
20156 cx: &mut Context<Self>,
20157 ) {
20158 self.insert_uuid(UuidVersion::V7, window, cx);
20159 }
20160
20161 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20162 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20163 self.transact(window, cx, |this, window, cx| {
20164 let edits = this
20165 .selections
20166 .all::<Point>(&this.display_snapshot(cx))
20167 .into_iter()
20168 .map(|selection| {
20169 let uuid = match version {
20170 UuidVersion::V4 => uuid::Uuid::new_v4(),
20171 UuidVersion::V7 => uuid::Uuid::now_v7(),
20172 };
20173
20174 (selection.range(), uuid.to_string())
20175 });
20176 this.edit(edits, cx);
20177 this.refresh_edit_prediction(true, false, window, cx);
20178 });
20179 }
20180
20181 pub fn open_selections_in_multibuffer(
20182 &mut self,
20183 _: &OpenSelectionsInMultibuffer,
20184 window: &mut Window,
20185 cx: &mut Context<Self>,
20186 ) {
20187 let multibuffer = self.buffer.read(cx);
20188
20189 let Some(buffer) = multibuffer.as_singleton() else {
20190 return;
20191 };
20192
20193 let Some(workspace) = self.workspace() else {
20194 return;
20195 };
20196
20197 let title = multibuffer.title(cx).to_string();
20198
20199 let locations = self
20200 .selections
20201 .all_anchors(cx)
20202 .iter()
20203 .map(|selection| {
20204 (
20205 buffer.clone(),
20206 (selection.start.text_anchor..selection.end.text_anchor)
20207 .to_point(buffer.read(cx)),
20208 )
20209 })
20210 .into_group_map();
20211
20212 cx.spawn_in(window, async move |_, cx| {
20213 workspace.update_in(cx, |workspace, window, cx| {
20214 Self::open_locations_in_multibuffer(
20215 workspace,
20216 locations,
20217 format!("Selections for '{title}'"),
20218 false,
20219 MultibufferSelectionMode::All,
20220 window,
20221 cx,
20222 );
20223 })
20224 })
20225 .detach();
20226 }
20227
20228 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20229 /// last highlight added will be used.
20230 ///
20231 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20232 pub fn highlight_rows<T: 'static>(
20233 &mut self,
20234 range: Range<Anchor>,
20235 color: Hsla,
20236 options: RowHighlightOptions,
20237 cx: &mut Context<Self>,
20238 ) {
20239 let snapshot = self.buffer().read(cx).snapshot(cx);
20240 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20241 let ix = row_highlights.binary_search_by(|highlight| {
20242 Ordering::Equal
20243 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20244 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20245 });
20246
20247 if let Err(mut ix) = ix {
20248 let index = post_inc(&mut self.highlight_order);
20249
20250 // If this range intersects with the preceding highlight, then merge it with
20251 // the preceding highlight. Otherwise insert a new highlight.
20252 let mut merged = false;
20253 if ix > 0 {
20254 let prev_highlight = &mut row_highlights[ix - 1];
20255 if prev_highlight
20256 .range
20257 .end
20258 .cmp(&range.start, &snapshot)
20259 .is_ge()
20260 {
20261 ix -= 1;
20262 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20263 prev_highlight.range.end = range.end;
20264 }
20265 merged = true;
20266 prev_highlight.index = index;
20267 prev_highlight.color = color;
20268 prev_highlight.options = options;
20269 }
20270 }
20271
20272 if !merged {
20273 row_highlights.insert(
20274 ix,
20275 RowHighlight {
20276 range,
20277 index,
20278 color,
20279 options,
20280 type_id: TypeId::of::<T>(),
20281 },
20282 );
20283 }
20284
20285 // If any of the following highlights intersect with this one, merge them.
20286 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20287 let highlight = &row_highlights[ix];
20288 if next_highlight
20289 .range
20290 .start
20291 .cmp(&highlight.range.end, &snapshot)
20292 .is_le()
20293 {
20294 if next_highlight
20295 .range
20296 .end
20297 .cmp(&highlight.range.end, &snapshot)
20298 .is_gt()
20299 {
20300 row_highlights[ix].range.end = next_highlight.range.end;
20301 }
20302 row_highlights.remove(ix + 1);
20303 } else {
20304 break;
20305 }
20306 }
20307 }
20308 }
20309
20310 /// Remove any highlighted row ranges of the given type that intersect the
20311 /// given ranges.
20312 pub fn remove_highlighted_rows<T: 'static>(
20313 &mut self,
20314 ranges_to_remove: Vec<Range<Anchor>>,
20315 cx: &mut Context<Self>,
20316 ) {
20317 let snapshot = self.buffer().read(cx).snapshot(cx);
20318 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20319 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20320 row_highlights.retain(|highlight| {
20321 while let Some(range_to_remove) = ranges_to_remove.peek() {
20322 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20323 Ordering::Less | Ordering::Equal => {
20324 ranges_to_remove.next();
20325 }
20326 Ordering::Greater => {
20327 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20328 Ordering::Less | Ordering::Equal => {
20329 return false;
20330 }
20331 Ordering::Greater => break,
20332 }
20333 }
20334 }
20335 }
20336
20337 true
20338 })
20339 }
20340
20341 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20342 pub fn clear_row_highlights<T: 'static>(&mut self) {
20343 self.highlighted_rows.remove(&TypeId::of::<T>());
20344 }
20345
20346 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20347 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20348 self.highlighted_rows
20349 .get(&TypeId::of::<T>())
20350 .map_or(&[] as &[_], |vec| vec.as_slice())
20351 .iter()
20352 .map(|highlight| (highlight.range.clone(), highlight.color))
20353 }
20354
20355 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20356 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20357 /// Allows to ignore certain kinds of highlights.
20358 pub fn highlighted_display_rows(
20359 &self,
20360 window: &mut Window,
20361 cx: &mut App,
20362 ) -> BTreeMap<DisplayRow, LineHighlight> {
20363 let snapshot = self.snapshot(window, cx);
20364 let mut used_highlight_orders = HashMap::default();
20365 self.highlighted_rows
20366 .iter()
20367 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20368 .fold(
20369 BTreeMap::<DisplayRow, LineHighlight>::new(),
20370 |mut unique_rows, highlight| {
20371 let start = highlight.range.start.to_display_point(&snapshot);
20372 let end = highlight.range.end.to_display_point(&snapshot);
20373 let start_row = start.row().0;
20374 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20375 && end.column() == 0
20376 {
20377 end.row().0.saturating_sub(1)
20378 } else {
20379 end.row().0
20380 };
20381 for row in start_row..=end_row {
20382 let used_index =
20383 used_highlight_orders.entry(row).or_insert(highlight.index);
20384 if highlight.index >= *used_index {
20385 *used_index = highlight.index;
20386 unique_rows.insert(
20387 DisplayRow(row),
20388 LineHighlight {
20389 include_gutter: highlight.options.include_gutter,
20390 border: None,
20391 background: highlight.color.into(),
20392 type_id: Some(highlight.type_id),
20393 },
20394 );
20395 }
20396 }
20397 unique_rows
20398 },
20399 )
20400 }
20401
20402 pub fn highlighted_display_row_for_autoscroll(
20403 &self,
20404 snapshot: &DisplaySnapshot,
20405 ) -> Option<DisplayRow> {
20406 self.highlighted_rows
20407 .values()
20408 .flat_map(|highlighted_rows| highlighted_rows.iter())
20409 .filter_map(|highlight| {
20410 if highlight.options.autoscroll {
20411 Some(highlight.range.start.to_display_point(snapshot).row())
20412 } else {
20413 None
20414 }
20415 })
20416 .min()
20417 }
20418
20419 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20420 self.highlight_background::<SearchWithinRange>(
20421 ranges,
20422 |colors| colors.colors().editor_document_highlight_read_background,
20423 cx,
20424 )
20425 }
20426
20427 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20428 self.breadcrumb_header = Some(new_header);
20429 }
20430
20431 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20432 self.clear_background_highlights::<SearchWithinRange>(cx);
20433 }
20434
20435 pub fn highlight_background<T: 'static>(
20436 &mut self,
20437 ranges: &[Range<Anchor>],
20438 color_fetcher: fn(&Theme) -> Hsla,
20439 cx: &mut Context<Self>,
20440 ) {
20441 self.background_highlights.insert(
20442 HighlightKey::Type(TypeId::of::<T>()),
20443 (color_fetcher, Arc::from(ranges)),
20444 );
20445 self.scrollbar_marker_state.dirty = true;
20446 cx.notify();
20447 }
20448
20449 pub fn highlight_background_key<T: 'static>(
20450 &mut self,
20451 key: usize,
20452 ranges: &[Range<Anchor>],
20453 color_fetcher: fn(&Theme) -> Hsla,
20454 cx: &mut Context<Self>,
20455 ) {
20456 self.background_highlights.insert(
20457 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20458 (color_fetcher, Arc::from(ranges)),
20459 );
20460 self.scrollbar_marker_state.dirty = true;
20461 cx.notify();
20462 }
20463
20464 pub fn clear_background_highlights<T: 'static>(
20465 &mut self,
20466 cx: &mut Context<Self>,
20467 ) -> Option<BackgroundHighlight> {
20468 let text_highlights = self
20469 .background_highlights
20470 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20471 if !text_highlights.1.is_empty() {
20472 self.scrollbar_marker_state.dirty = true;
20473 cx.notify();
20474 }
20475 Some(text_highlights)
20476 }
20477
20478 pub fn highlight_gutter<T: 'static>(
20479 &mut self,
20480 ranges: impl Into<Vec<Range<Anchor>>>,
20481 color_fetcher: fn(&App) -> Hsla,
20482 cx: &mut Context<Self>,
20483 ) {
20484 self.gutter_highlights
20485 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20486 cx.notify();
20487 }
20488
20489 pub fn clear_gutter_highlights<T: 'static>(
20490 &mut self,
20491 cx: &mut Context<Self>,
20492 ) -> Option<GutterHighlight> {
20493 cx.notify();
20494 self.gutter_highlights.remove(&TypeId::of::<T>())
20495 }
20496
20497 pub fn insert_gutter_highlight<T: 'static>(
20498 &mut self,
20499 range: Range<Anchor>,
20500 color_fetcher: fn(&App) -> Hsla,
20501 cx: &mut Context<Self>,
20502 ) {
20503 let snapshot = self.buffer().read(cx).snapshot(cx);
20504 let mut highlights = self
20505 .gutter_highlights
20506 .remove(&TypeId::of::<T>())
20507 .map(|(_, highlights)| highlights)
20508 .unwrap_or_default();
20509 let ix = highlights.binary_search_by(|highlight| {
20510 Ordering::Equal
20511 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20512 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20513 });
20514 if let Err(ix) = ix {
20515 highlights.insert(ix, range);
20516 }
20517 self.gutter_highlights
20518 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20519 }
20520
20521 pub fn remove_gutter_highlights<T: 'static>(
20522 &mut self,
20523 ranges_to_remove: Vec<Range<Anchor>>,
20524 cx: &mut Context<Self>,
20525 ) {
20526 let snapshot = self.buffer().read(cx).snapshot(cx);
20527 let Some((color_fetcher, mut gutter_highlights)) =
20528 self.gutter_highlights.remove(&TypeId::of::<T>())
20529 else {
20530 return;
20531 };
20532 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20533 gutter_highlights.retain(|highlight| {
20534 while let Some(range_to_remove) = ranges_to_remove.peek() {
20535 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20536 Ordering::Less | Ordering::Equal => {
20537 ranges_to_remove.next();
20538 }
20539 Ordering::Greater => {
20540 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20541 Ordering::Less | Ordering::Equal => {
20542 return false;
20543 }
20544 Ordering::Greater => break,
20545 }
20546 }
20547 }
20548 }
20549
20550 true
20551 });
20552 self.gutter_highlights
20553 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20554 }
20555
20556 #[cfg(feature = "test-support")]
20557 pub fn all_text_highlights(
20558 &self,
20559 window: &mut Window,
20560 cx: &mut Context<Self>,
20561 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20562 let snapshot = self.snapshot(window, cx);
20563 self.display_map.update(cx, |display_map, _| {
20564 display_map
20565 .all_text_highlights()
20566 .map(|highlight| {
20567 let (style, ranges) = highlight.as_ref();
20568 (
20569 *style,
20570 ranges
20571 .iter()
20572 .map(|range| range.clone().to_display_points(&snapshot))
20573 .collect(),
20574 )
20575 })
20576 .collect()
20577 })
20578 }
20579
20580 #[cfg(feature = "test-support")]
20581 pub fn all_text_background_highlights(
20582 &self,
20583 window: &mut Window,
20584 cx: &mut Context<Self>,
20585 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20586 let snapshot = self.snapshot(window, cx);
20587 let buffer = &snapshot.buffer_snapshot();
20588 let start = buffer.anchor_before(0);
20589 let end = buffer.anchor_after(buffer.len());
20590 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20591 }
20592
20593 #[cfg(any(test, feature = "test-support"))]
20594 pub fn sorted_background_highlights_in_range(
20595 &self,
20596 search_range: Range<Anchor>,
20597 display_snapshot: &DisplaySnapshot,
20598 theme: &Theme,
20599 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20600 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20601 res.sort_by(|a, b| {
20602 a.0.start
20603 .cmp(&b.0.start)
20604 .then_with(|| a.0.end.cmp(&b.0.end))
20605 .then_with(|| a.1.cmp(&b.1))
20606 });
20607 res
20608 }
20609
20610 #[cfg(feature = "test-support")]
20611 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20612 let snapshot = self.buffer().read(cx).snapshot(cx);
20613
20614 let highlights = self
20615 .background_highlights
20616 .get(&HighlightKey::Type(TypeId::of::<
20617 items::BufferSearchHighlights,
20618 >()));
20619
20620 if let Some((_color, ranges)) = highlights {
20621 ranges
20622 .iter()
20623 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20624 .collect_vec()
20625 } else {
20626 vec![]
20627 }
20628 }
20629
20630 fn document_highlights_for_position<'a>(
20631 &'a self,
20632 position: Anchor,
20633 buffer: &'a MultiBufferSnapshot,
20634 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20635 let read_highlights = self
20636 .background_highlights
20637 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20638 .map(|h| &h.1);
20639 let write_highlights = self
20640 .background_highlights
20641 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20642 .map(|h| &h.1);
20643 let left_position = position.bias_left(buffer);
20644 let right_position = position.bias_right(buffer);
20645 read_highlights
20646 .into_iter()
20647 .chain(write_highlights)
20648 .flat_map(move |ranges| {
20649 let start_ix = match ranges.binary_search_by(|probe| {
20650 let cmp = probe.end.cmp(&left_position, buffer);
20651 if cmp.is_ge() {
20652 Ordering::Greater
20653 } else {
20654 Ordering::Less
20655 }
20656 }) {
20657 Ok(i) | Err(i) => i,
20658 };
20659
20660 ranges[start_ix..]
20661 .iter()
20662 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20663 })
20664 }
20665
20666 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20667 self.background_highlights
20668 .get(&HighlightKey::Type(TypeId::of::<T>()))
20669 .is_some_and(|(_, highlights)| !highlights.is_empty())
20670 }
20671
20672 /// Returns all background highlights for a given range.
20673 ///
20674 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20675 pub fn background_highlights_in_range(
20676 &self,
20677 search_range: Range<Anchor>,
20678 display_snapshot: &DisplaySnapshot,
20679 theme: &Theme,
20680 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20681 let mut results = Vec::new();
20682 for (color_fetcher, ranges) in self.background_highlights.values() {
20683 let color = color_fetcher(theme);
20684 let start_ix = match ranges.binary_search_by(|probe| {
20685 let cmp = probe
20686 .end
20687 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20688 if cmp.is_gt() {
20689 Ordering::Greater
20690 } else {
20691 Ordering::Less
20692 }
20693 }) {
20694 Ok(i) | Err(i) => i,
20695 };
20696 for range in &ranges[start_ix..] {
20697 if range
20698 .start
20699 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20700 .is_ge()
20701 {
20702 break;
20703 }
20704
20705 let start = range.start.to_display_point(display_snapshot);
20706 let end = range.end.to_display_point(display_snapshot);
20707 results.push((start..end, color))
20708 }
20709 }
20710 results
20711 }
20712
20713 pub fn gutter_highlights_in_range(
20714 &self,
20715 search_range: Range<Anchor>,
20716 display_snapshot: &DisplaySnapshot,
20717 cx: &App,
20718 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20719 let mut results = Vec::new();
20720 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20721 let color = color_fetcher(cx);
20722 let start_ix = match ranges.binary_search_by(|probe| {
20723 let cmp = probe
20724 .end
20725 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20726 if cmp.is_gt() {
20727 Ordering::Greater
20728 } else {
20729 Ordering::Less
20730 }
20731 }) {
20732 Ok(i) | Err(i) => i,
20733 };
20734 for range in &ranges[start_ix..] {
20735 if range
20736 .start
20737 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20738 .is_ge()
20739 {
20740 break;
20741 }
20742
20743 let start = range.start.to_display_point(display_snapshot);
20744 let end = range.end.to_display_point(display_snapshot);
20745 results.push((start..end, color))
20746 }
20747 }
20748 results
20749 }
20750
20751 /// Get the text ranges corresponding to the redaction query
20752 pub fn redacted_ranges(
20753 &self,
20754 search_range: Range<Anchor>,
20755 display_snapshot: &DisplaySnapshot,
20756 cx: &App,
20757 ) -> Vec<Range<DisplayPoint>> {
20758 display_snapshot
20759 .buffer_snapshot()
20760 .redacted_ranges(search_range, |file| {
20761 if let Some(file) = file {
20762 file.is_private()
20763 && EditorSettings::get(
20764 Some(SettingsLocation {
20765 worktree_id: file.worktree_id(cx),
20766 path: file.path().as_ref(),
20767 }),
20768 cx,
20769 )
20770 .redact_private_values
20771 } else {
20772 false
20773 }
20774 })
20775 .map(|range| {
20776 range.start.to_display_point(display_snapshot)
20777 ..range.end.to_display_point(display_snapshot)
20778 })
20779 .collect()
20780 }
20781
20782 pub fn highlight_text_key<T: 'static>(
20783 &mut self,
20784 key: usize,
20785 ranges: Vec<Range<Anchor>>,
20786 style: HighlightStyle,
20787 cx: &mut Context<Self>,
20788 ) {
20789 self.display_map.update(cx, |map, _| {
20790 map.highlight_text(
20791 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20792 ranges,
20793 style,
20794 );
20795 });
20796 cx.notify();
20797 }
20798
20799 pub fn highlight_text<T: 'static>(
20800 &mut self,
20801 ranges: Vec<Range<Anchor>>,
20802 style: HighlightStyle,
20803 cx: &mut Context<Self>,
20804 ) {
20805 self.display_map.update(cx, |map, _| {
20806 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20807 });
20808 cx.notify();
20809 }
20810
20811 pub(crate) fn highlight_inlays<T: 'static>(
20812 &mut self,
20813 highlights: Vec<InlayHighlight>,
20814 style: HighlightStyle,
20815 cx: &mut Context<Self>,
20816 ) {
20817 self.display_map.update(cx, |map, _| {
20818 map.highlight_inlays(TypeId::of::<T>(), highlights, style)
20819 });
20820 cx.notify();
20821 }
20822
20823 pub fn text_highlights<'a, T: 'static>(
20824 &'a self,
20825 cx: &'a App,
20826 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20827 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20828 }
20829
20830 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20831 let cleared = self
20832 .display_map
20833 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20834 if cleared {
20835 cx.notify();
20836 }
20837 }
20838
20839 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20840 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20841 && self.focus_handle.is_focused(window)
20842 }
20843
20844 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20845 self.show_cursor_when_unfocused = is_enabled;
20846 cx.notify();
20847 }
20848
20849 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20850 cx.notify();
20851 }
20852
20853 fn on_debug_session_event(
20854 &mut self,
20855 _session: Entity<Session>,
20856 event: &SessionEvent,
20857 cx: &mut Context<Self>,
20858 ) {
20859 if let SessionEvent::InvalidateInlineValue = event {
20860 self.refresh_inline_values(cx);
20861 }
20862 }
20863
20864 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20865 let Some(project) = self.project.clone() else {
20866 return;
20867 };
20868
20869 if !self.inline_value_cache.enabled {
20870 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20871 self.splice_inlays(&inlays, Vec::new(), cx);
20872 return;
20873 }
20874
20875 let current_execution_position = self
20876 .highlighted_rows
20877 .get(&TypeId::of::<ActiveDebugLine>())
20878 .and_then(|lines| lines.last().map(|line| line.range.end));
20879
20880 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
20881 let inline_values = editor
20882 .update(cx, |editor, cx| {
20883 let Some(current_execution_position) = current_execution_position else {
20884 return Some(Task::ready(Ok(Vec::new())));
20885 };
20886
20887 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
20888 let snapshot = buffer.snapshot(cx);
20889
20890 let excerpt = snapshot.excerpt_containing(
20891 current_execution_position..current_execution_position,
20892 )?;
20893
20894 editor.buffer.read(cx).buffer(excerpt.buffer_id())
20895 })?;
20896
20897 let range =
20898 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
20899
20900 project.inline_values(buffer, range, cx)
20901 })
20902 .ok()
20903 .flatten()?
20904 .await
20905 .context("refreshing debugger inlays")
20906 .log_err()?;
20907
20908 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
20909
20910 for (buffer_id, inline_value) in inline_values
20911 .into_iter()
20912 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
20913 {
20914 buffer_inline_values
20915 .entry(buffer_id)
20916 .or_default()
20917 .push(inline_value);
20918 }
20919
20920 editor
20921 .update(cx, |editor, cx| {
20922 let snapshot = editor.buffer.read(cx).snapshot(cx);
20923 let mut new_inlays = Vec::default();
20924
20925 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
20926 let buffer_id = buffer_snapshot.remote_id();
20927 buffer_inline_values
20928 .get(&buffer_id)
20929 .into_iter()
20930 .flatten()
20931 .for_each(|hint| {
20932 let inlay = Inlay::debugger(
20933 post_inc(&mut editor.next_inlay_id),
20934 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
20935 hint.text(),
20936 );
20937 if !inlay.text().chars().contains(&'\n') {
20938 new_inlays.push(inlay);
20939 }
20940 });
20941 }
20942
20943 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
20944 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
20945
20946 editor.splice_inlays(&inlay_ids, new_inlays, cx);
20947 })
20948 .ok()?;
20949 Some(())
20950 });
20951 }
20952
20953 fn on_buffer_event(
20954 &mut self,
20955 multibuffer: &Entity<MultiBuffer>,
20956 event: &multi_buffer::Event,
20957 window: &mut Window,
20958 cx: &mut Context<Self>,
20959 ) {
20960 match event {
20961 multi_buffer::Event::Edited { edited_buffer } => {
20962 self.scrollbar_marker_state.dirty = true;
20963 self.active_indent_guides_state.dirty = true;
20964 self.refresh_active_diagnostics(cx);
20965 self.refresh_code_actions(window, cx);
20966 self.refresh_selected_text_highlights(true, window, cx);
20967 self.refresh_single_line_folds(window, cx);
20968 self.refresh_matching_bracket_highlights(window, cx);
20969 if self.has_active_edit_prediction() {
20970 self.update_visible_edit_prediction(window, cx);
20971 }
20972
20973 if let Some(edited_buffer) = edited_buffer {
20974 if edited_buffer.read(cx).file().is_none() {
20975 cx.emit(EditorEvent::TitleChanged);
20976 }
20977
20978 let buffer_id = edited_buffer.read(cx).remote_id();
20979 if let Some(project) = self.project.clone() {
20980 self.register_buffer(buffer_id, cx);
20981 self.update_lsp_data(Some(buffer_id), window, cx);
20982 #[allow(clippy::mutable_key_type)]
20983 let languages_affected = multibuffer.update(cx, |multibuffer, cx| {
20984 multibuffer
20985 .all_buffers()
20986 .into_iter()
20987 .filter_map(|buffer| {
20988 buffer.update(cx, |buffer, cx| {
20989 let language = buffer.language()?;
20990 let should_discard = project.update(cx, |project, cx| {
20991 project.is_local()
20992 && !project.has_language_servers_for(buffer, cx)
20993 });
20994 should_discard.not().then_some(language.clone())
20995 })
20996 })
20997 .collect::<HashSet<_>>()
20998 });
20999 if !languages_affected.is_empty() {
21000 self.refresh_inlay_hints(
21001 InlayHintRefreshReason::BufferEdited(languages_affected),
21002 cx,
21003 );
21004 }
21005 }
21006 }
21007
21008 cx.emit(EditorEvent::BufferEdited);
21009 cx.emit(SearchEvent::MatchesInvalidated);
21010
21011 let Some(project) = &self.project else { return };
21012 let (telemetry, is_via_ssh) = {
21013 let project = project.read(cx);
21014 let telemetry = project.client().telemetry().clone();
21015 let is_via_ssh = project.is_via_remote_server();
21016 (telemetry, is_via_ssh)
21017 };
21018 telemetry.log_edit_event("editor", is_via_ssh);
21019 }
21020 multi_buffer::Event::ExcerptsAdded {
21021 buffer,
21022 predecessor,
21023 excerpts,
21024 } => {
21025 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21026 let buffer_id = buffer.read(cx).remote_id();
21027 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21028 && let Some(project) = &self.project
21029 {
21030 update_uncommitted_diff_for_buffer(
21031 cx.entity(),
21032 project,
21033 [buffer.clone()],
21034 self.buffer.clone(),
21035 cx,
21036 )
21037 .detach();
21038 }
21039 self.update_lsp_data(Some(buffer_id), window, cx);
21040 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21041 cx.emit(EditorEvent::ExcerptsAdded {
21042 buffer: buffer.clone(),
21043 predecessor: *predecessor,
21044 excerpts: excerpts.clone(),
21045 });
21046 }
21047 multi_buffer::Event::ExcerptsRemoved {
21048 ids,
21049 removed_buffer_ids,
21050 } => {
21051 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21052 for buffer_id in removed_buffer_ids {
21053 self.registered_buffers.remove(buffer_id);
21054 }
21055 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21056 cx.emit(EditorEvent::ExcerptsRemoved {
21057 ids: ids.clone(),
21058 removed_buffer_ids: removed_buffer_ids.clone(),
21059 });
21060 }
21061 multi_buffer::Event::ExcerptsEdited {
21062 excerpt_ids,
21063 buffer_ids,
21064 } => {
21065 self.display_map.update(cx, |map, cx| {
21066 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21067 });
21068 cx.emit(EditorEvent::ExcerptsEdited {
21069 ids: excerpt_ids.clone(),
21070 });
21071 }
21072 multi_buffer::Event::ExcerptsExpanded { ids } => {
21073 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21074 self.refresh_document_highlights(cx);
21075 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21076 }
21077 multi_buffer::Event::Reparsed(buffer_id) => {
21078 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21079 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21080
21081 cx.emit(EditorEvent::Reparsed(*buffer_id));
21082 }
21083 multi_buffer::Event::DiffHunksToggled => {
21084 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21085 }
21086 multi_buffer::Event::LanguageChanged(buffer_id) => {
21087 self.registered_buffers.remove(&buffer_id);
21088 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21089 cx.emit(EditorEvent::Reparsed(*buffer_id));
21090 cx.notify();
21091 }
21092 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21093 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21094 multi_buffer::Event::FileHandleChanged
21095 | multi_buffer::Event::Reloaded
21096 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21097 multi_buffer::Event::DiagnosticsUpdated => {
21098 self.update_diagnostics_state(window, cx);
21099 }
21100 _ => {}
21101 };
21102 }
21103
21104 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21105 if !self.diagnostics_enabled() {
21106 return;
21107 }
21108 self.refresh_active_diagnostics(cx);
21109 self.refresh_inline_diagnostics(true, window, cx);
21110 self.scrollbar_marker_state.dirty = true;
21111 cx.notify();
21112 }
21113
21114 pub fn start_temporary_diff_override(&mut self) {
21115 self.load_diff_task.take();
21116 self.temporary_diff_override = true;
21117 }
21118
21119 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21120 self.temporary_diff_override = false;
21121 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21122 self.buffer.update(cx, |buffer, cx| {
21123 buffer.set_all_diff_hunks_collapsed(cx);
21124 });
21125
21126 if let Some(project) = self.project.clone() {
21127 self.load_diff_task = Some(
21128 update_uncommitted_diff_for_buffer(
21129 cx.entity(),
21130 &project,
21131 self.buffer.read(cx).all_buffers(),
21132 self.buffer.clone(),
21133 cx,
21134 )
21135 .shared(),
21136 );
21137 }
21138 }
21139
21140 fn on_display_map_changed(
21141 &mut self,
21142 _: Entity<DisplayMap>,
21143 _: &mut Window,
21144 cx: &mut Context<Self>,
21145 ) {
21146 cx.notify();
21147 }
21148
21149 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21150 if self.diagnostics_enabled() {
21151 let new_severity = EditorSettings::get_global(cx)
21152 .diagnostics_max_severity
21153 .unwrap_or(DiagnosticSeverity::Hint);
21154 self.set_max_diagnostics_severity(new_severity, cx);
21155 }
21156 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21157 self.update_edit_prediction_settings(cx);
21158 self.refresh_edit_prediction(true, false, window, cx);
21159 self.refresh_inline_values(cx);
21160 self.refresh_inlay_hints(
21161 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21162 self.selections.newest_anchor().head(),
21163 &self.buffer.read(cx).snapshot(cx),
21164 cx,
21165 )),
21166 cx,
21167 );
21168
21169 let old_cursor_shape = self.cursor_shape;
21170 let old_show_breadcrumbs = self.show_breadcrumbs;
21171
21172 {
21173 let editor_settings = EditorSettings::get_global(cx);
21174 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21175 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21176 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21177 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21178 }
21179
21180 if old_cursor_shape != self.cursor_shape {
21181 cx.emit(EditorEvent::CursorShapeChanged);
21182 }
21183
21184 if old_show_breadcrumbs != self.show_breadcrumbs {
21185 cx.emit(EditorEvent::BreadcrumbsChanged);
21186 }
21187
21188 let project_settings = ProjectSettings::get_global(cx);
21189 self.serialize_dirty_buffers =
21190 !self.mode.is_minimap() && project_settings.session.restore_unsaved_buffers;
21191
21192 if self.mode.is_full() {
21193 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21194 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21195 if self.show_inline_diagnostics != show_inline_diagnostics {
21196 self.show_inline_diagnostics = show_inline_diagnostics;
21197 self.refresh_inline_diagnostics(false, window, cx);
21198 }
21199
21200 if self.git_blame_inline_enabled != inline_blame_enabled {
21201 self.toggle_git_blame_inline_internal(false, window, cx);
21202 }
21203
21204 let minimap_settings = EditorSettings::get_global(cx).minimap;
21205 if self.minimap_visibility != MinimapVisibility::Disabled {
21206 if self.minimap_visibility.settings_visibility()
21207 != minimap_settings.minimap_enabled()
21208 {
21209 self.set_minimap_visibility(
21210 MinimapVisibility::for_mode(self.mode(), cx),
21211 window,
21212 cx,
21213 );
21214 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21215 minimap_entity.update(cx, |minimap_editor, cx| {
21216 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21217 })
21218 }
21219 }
21220 }
21221
21222 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21223 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21224 }) {
21225 if !inlay_splice.to_insert.is_empty() || !inlay_splice.to_remove.is_empty() {
21226 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21227 }
21228 self.refresh_colors_for_visible_range(None, window, cx);
21229 }
21230
21231 cx.notify();
21232 }
21233
21234 pub fn set_searchable(&mut self, searchable: bool) {
21235 self.searchable = searchable;
21236 }
21237
21238 pub fn searchable(&self) -> bool {
21239 self.searchable
21240 }
21241
21242 fn open_proposed_changes_editor(
21243 &mut self,
21244 _: &OpenProposedChangesEditor,
21245 window: &mut Window,
21246 cx: &mut Context<Self>,
21247 ) {
21248 let Some(workspace) = self.workspace() else {
21249 cx.propagate();
21250 return;
21251 };
21252
21253 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21254 let multi_buffer = self.buffer.read(cx);
21255 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21256 let mut new_selections_by_buffer = HashMap::default();
21257 for selection in selections {
21258 for (buffer, range, _) in
21259 multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end)
21260 {
21261 let mut range = range.to_point(buffer);
21262 range.start.column = 0;
21263 range.end.column = buffer.line_len(range.end.row);
21264 new_selections_by_buffer
21265 .entry(multi_buffer.buffer(buffer.remote_id()).unwrap())
21266 .or_insert(Vec::new())
21267 .push(range)
21268 }
21269 }
21270
21271 let proposed_changes_buffers = new_selections_by_buffer
21272 .into_iter()
21273 .map(|(buffer, ranges)| ProposedChangeLocation { buffer, ranges })
21274 .collect::<Vec<_>>();
21275 let proposed_changes_editor = cx.new(|cx| {
21276 ProposedChangesEditor::new(
21277 "Proposed changes",
21278 proposed_changes_buffers,
21279 self.project.clone(),
21280 window,
21281 cx,
21282 )
21283 });
21284
21285 window.defer(cx, move |window, cx| {
21286 workspace.update(cx, |workspace, cx| {
21287 workspace.active_pane().update(cx, |pane, cx| {
21288 pane.add_item(
21289 Box::new(proposed_changes_editor),
21290 true,
21291 true,
21292 None,
21293 window,
21294 cx,
21295 );
21296 });
21297 });
21298 });
21299 }
21300
21301 pub fn open_excerpts_in_split(
21302 &mut self,
21303 _: &OpenExcerptsSplit,
21304 window: &mut Window,
21305 cx: &mut Context<Self>,
21306 ) {
21307 self.open_excerpts_common(None, true, window, cx)
21308 }
21309
21310 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21311 self.open_excerpts_common(None, false, window, cx)
21312 }
21313
21314 fn open_excerpts_common(
21315 &mut self,
21316 jump_data: Option<JumpData>,
21317 split: bool,
21318 window: &mut Window,
21319 cx: &mut Context<Self>,
21320 ) {
21321 let Some(workspace) = self.workspace() else {
21322 cx.propagate();
21323 return;
21324 };
21325
21326 if self.buffer.read(cx).is_singleton() {
21327 cx.propagate();
21328 return;
21329 }
21330
21331 let mut new_selections_by_buffer = HashMap::default();
21332 match &jump_data {
21333 Some(JumpData::MultiBufferPoint {
21334 excerpt_id,
21335 position,
21336 anchor,
21337 line_offset_from_top,
21338 }) => {
21339 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21340 if let Some(buffer) = multi_buffer_snapshot
21341 .buffer_id_for_excerpt(*excerpt_id)
21342 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21343 {
21344 let buffer_snapshot = buffer.read(cx).snapshot();
21345 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21346 language::ToPoint::to_point(anchor, &buffer_snapshot)
21347 } else {
21348 buffer_snapshot.clip_point(*position, Bias::Left)
21349 };
21350 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21351 new_selections_by_buffer.insert(
21352 buffer,
21353 (
21354 vec![jump_to_offset..jump_to_offset],
21355 Some(*line_offset_from_top),
21356 ),
21357 );
21358 }
21359 }
21360 Some(JumpData::MultiBufferRow {
21361 row,
21362 line_offset_from_top,
21363 }) => {
21364 let point = MultiBufferPoint::new(row.0, 0);
21365 if let Some((buffer, buffer_point, _)) =
21366 self.buffer.read(cx).point_to_buffer_point(point, cx)
21367 {
21368 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21369 new_selections_by_buffer
21370 .entry(buffer)
21371 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21372 .0
21373 .push(buffer_offset..buffer_offset)
21374 }
21375 }
21376 None => {
21377 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21378 let multi_buffer = self.buffer.read(cx);
21379 for selection in selections {
21380 for (snapshot, range, _, anchor) in multi_buffer
21381 .snapshot(cx)
21382 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21383 {
21384 if let Some(anchor) = anchor {
21385 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21386 else {
21387 continue;
21388 };
21389 let offset = text::ToOffset::to_offset(
21390 &anchor.text_anchor,
21391 &buffer_handle.read(cx).snapshot(),
21392 );
21393 let range = offset..offset;
21394 new_selections_by_buffer
21395 .entry(buffer_handle)
21396 .or_insert((Vec::new(), None))
21397 .0
21398 .push(range)
21399 } else {
21400 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21401 else {
21402 continue;
21403 };
21404 new_selections_by_buffer
21405 .entry(buffer_handle)
21406 .or_insert((Vec::new(), None))
21407 .0
21408 .push(range)
21409 }
21410 }
21411 }
21412 }
21413 }
21414
21415 new_selections_by_buffer
21416 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21417
21418 if new_selections_by_buffer.is_empty() {
21419 return;
21420 }
21421
21422 // We defer the pane interaction because we ourselves are a workspace item
21423 // and activating a new item causes the pane to call a method on us reentrantly,
21424 // which panics if we're on the stack.
21425 window.defer(cx, move |window, cx| {
21426 workspace.update(cx, |workspace, cx| {
21427 let pane = if split {
21428 workspace.adjacent_pane(window, cx)
21429 } else {
21430 workspace.active_pane().clone()
21431 };
21432
21433 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21434 let editor = buffer
21435 .read(cx)
21436 .file()
21437 .is_none()
21438 .then(|| {
21439 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21440 // so `workspace.open_project_item` will never find them, always opening a new editor.
21441 // Instead, we try to activate the existing editor in the pane first.
21442 let (editor, pane_item_index) =
21443 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21444 let editor = item.downcast::<Editor>()?;
21445 let singleton_buffer =
21446 editor.read(cx).buffer().read(cx).as_singleton()?;
21447 if singleton_buffer == buffer {
21448 Some((editor, i))
21449 } else {
21450 None
21451 }
21452 })?;
21453 pane.update(cx, |pane, cx| {
21454 pane.activate_item(pane_item_index, true, true, window, cx)
21455 });
21456 Some(editor)
21457 })
21458 .flatten()
21459 .unwrap_or_else(|| {
21460 workspace.open_project_item::<Self>(
21461 pane.clone(),
21462 buffer,
21463 true,
21464 true,
21465 window,
21466 cx,
21467 )
21468 });
21469
21470 editor.update(cx, |editor, cx| {
21471 let autoscroll = match scroll_offset {
21472 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21473 None => Autoscroll::newest(),
21474 };
21475 let nav_history = editor.nav_history.take();
21476 editor.change_selections(
21477 SelectionEffects::scroll(autoscroll),
21478 window,
21479 cx,
21480 |s| {
21481 s.select_ranges(ranges);
21482 },
21483 );
21484 editor.nav_history = nav_history;
21485 });
21486 }
21487 })
21488 });
21489 }
21490
21491 // For now, don't allow opening excerpts in buffers that aren't backed by
21492 // regular project files.
21493 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21494 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21495 }
21496
21497 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21498 let snapshot = self.buffer.read(cx).read(cx);
21499 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21500 Some(
21501 ranges
21502 .iter()
21503 .map(move |range| {
21504 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21505 })
21506 .collect(),
21507 )
21508 }
21509
21510 fn selection_replacement_ranges(
21511 &self,
21512 range: Range<OffsetUtf16>,
21513 cx: &mut App,
21514 ) -> Vec<Range<OffsetUtf16>> {
21515 let selections = self
21516 .selections
21517 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21518 let newest_selection = selections
21519 .iter()
21520 .max_by_key(|selection| selection.id)
21521 .unwrap();
21522 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21523 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21524 let snapshot = self.buffer.read(cx).read(cx);
21525 selections
21526 .into_iter()
21527 .map(|mut selection| {
21528 selection.start.0 =
21529 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21530 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21531 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21532 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21533 })
21534 .collect()
21535 }
21536
21537 fn report_editor_event(
21538 &self,
21539 reported_event: ReportEditorEvent,
21540 file_extension: Option<String>,
21541 cx: &App,
21542 ) {
21543 if cfg!(any(test, feature = "test-support")) {
21544 return;
21545 }
21546
21547 let Some(project) = &self.project else { return };
21548
21549 // If None, we are in a file without an extension
21550 let file = self
21551 .buffer
21552 .read(cx)
21553 .as_singleton()
21554 .and_then(|b| b.read(cx).file());
21555 let file_extension = file_extension.or(file
21556 .as_ref()
21557 .and_then(|file| Path::new(file.file_name(cx)).extension())
21558 .and_then(|e| e.to_str())
21559 .map(|a| a.to_string()));
21560
21561 let vim_mode = vim_enabled(cx);
21562
21563 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21564 let copilot_enabled = edit_predictions_provider
21565 == language::language_settings::EditPredictionProvider::Copilot;
21566 let copilot_enabled_for_language = self
21567 .buffer
21568 .read(cx)
21569 .language_settings(cx)
21570 .show_edit_predictions;
21571
21572 let project = project.read(cx);
21573 let event_type = reported_event.event_type();
21574
21575 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21576 telemetry::event!(
21577 event_type,
21578 type = if auto_saved {"autosave"} else {"manual"},
21579 file_extension,
21580 vim_mode,
21581 copilot_enabled,
21582 copilot_enabled_for_language,
21583 edit_predictions_provider,
21584 is_via_ssh = project.is_via_remote_server(),
21585 );
21586 } else {
21587 telemetry::event!(
21588 event_type,
21589 file_extension,
21590 vim_mode,
21591 copilot_enabled,
21592 copilot_enabled_for_language,
21593 edit_predictions_provider,
21594 is_via_ssh = project.is_via_remote_server(),
21595 );
21596 };
21597 }
21598
21599 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21600 /// with each line being an array of {text, highlight} objects.
21601 fn copy_highlight_json(
21602 &mut self,
21603 _: &CopyHighlightJson,
21604 window: &mut Window,
21605 cx: &mut Context<Self>,
21606 ) {
21607 #[derive(Serialize)]
21608 struct Chunk<'a> {
21609 text: String,
21610 highlight: Option<&'a str>,
21611 }
21612
21613 let snapshot = self.buffer.read(cx).snapshot(cx);
21614 let range = self
21615 .selected_text_range(false, window, cx)
21616 .and_then(|selection| {
21617 if selection.range.is_empty() {
21618 None
21619 } else {
21620 Some(selection.range)
21621 }
21622 })
21623 .unwrap_or_else(|| 0..snapshot.len());
21624
21625 let chunks = snapshot.chunks(range, true);
21626 let mut lines = Vec::new();
21627 let mut line: VecDeque<Chunk> = VecDeque::new();
21628
21629 let Some(style) = self.style.as_ref() else {
21630 return;
21631 };
21632
21633 for chunk in chunks {
21634 let highlight = chunk
21635 .syntax_highlight_id
21636 .and_then(|id| id.name(&style.syntax));
21637 let mut chunk_lines = chunk.text.split('\n').peekable();
21638 while let Some(text) = chunk_lines.next() {
21639 let mut merged_with_last_token = false;
21640 if let Some(last_token) = line.back_mut()
21641 && last_token.highlight == highlight
21642 {
21643 last_token.text.push_str(text);
21644 merged_with_last_token = true;
21645 }
21646
21647 if !merged_with_last_token {
21648 line.push_back(Chunk {
21649 text: text.into(),
21650 highlight,
21651 });
21652 }
21653
21654 if chunk_lines.peek().is_some() {
21655 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21656 line.pop_front();
21657 }
21658 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21659 line.pop_back();
21660 }
21661
21662 lines.push(mem::take(&mut line));
21663 }
21664 }
21665 }
21666
21667 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21668 return;
21669 };
21670 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21671 }
21672
21673 pub fn open_context_menu(
21674 &mut self,
21675 _: &OpenContextMenu,
21676 window: &mut Window,
21677 cx: &mut Context<Self>,
21678 ) {
21679 self.request_autoscroll(Autoscroll::newest(), cx);
21680 let position = self
21681 .selections
21682 .newest_display(&self.display_snapshot(cx))
21683 .start;
21684 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21685 }
21686
21687 pub fn inlay_hint_cache(&self) -> &InlayHintCache {
21688 &self.inlay_hint_cache
21689 }
21690
21691 pub fn replay_insert_event(
21692 &mut self,
21693 text: &str,
21694 relative_utf16_range: Option<Range<isize>>,
21695 window: &mut Window,
21696 cx: &mut Context<Self>,
21697 ) {
21698 if !self.input_enabled {
21699 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21700 return;
21701 }
21702 if let Some(relative_utf16_range) = relative_utf16_range {
21703 let selections = self
21704 .selections
21705 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21706 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21707 let new_ranges = selections.into_iter().map(|range| {
21708 let start = OffsetUtf16(
21709 range
21710 .head()
21711 .0
21712 .saturating_add_signed(relative_utf16_range.start),
21713 );
21714 let end = OffsetUtf16(
21715 range
21716 .head()
21717 .0
21718 .saturating_add_signed(relative_utf16_range.end),
21719 );
21720 start..end
21721 });
21722 s.select_ranges(new_ranges);
21723 });
21724 }
21725
21726 self.handle_input(text, window, cx);
21727 }
21728
21729 pub fn supports_inlay_hints(&self, cx: &mut App) -> bool {
21730 let Some(provider) = self.semantics_provider.as_ref() else {
21731 return false;
21732 };
21733
21734 let mut supports = false;
21735 self.buffer().update(cx, |this, cx| {
21736 this.for_each_buffer(|buffer| {
21737 supports |= provider.supports_inlay_hints(buffer, cx);
21738 });
21739 });
21740
21741 supports
21742 }
21743
21744 pub fn is_focused(&self, window: &Window) -> bool {
21745 self.focus_handle.is_focused(window)
21746 }
21747
21748 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21749 cx.emit(EditorEvent::Focused);
21750
21751 if let Some(descendant) = self
21752 .last_focused_descendant
21753 .take()
21754 .and_then(|descendant| descendant.upgrade())
21755 {
21756 window.focus(&descendant);
21757 } else {
21758 if let Some(blame) = self.blame.as_ref() {
21759 blame.update(cx, GitBlame::focus)
21760 }
21761
21762 self.blink_manager.update(cx, BlinkManager::enable);
21763 self.show_cursor_names(window, cx);
21764 self.buffer.update(cx, |buffer, cx| {
21765 buffer.finalize_last_transaction(cx);
21766 if self.leader_id.is_none() {
21767 buffer.set_active_selections(
21768 &self.selections.disjoint_anchors_arc(),
21769 self.selections.line_mode(),
21770 self.cursor_shape,
21771 cx,
21772 );
21773 }
21774 });
21775 }
21776 }
21777
21778 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21779 cx.emit(EditorEvent::FocusedIn)
21780 }
21781
21782 fn handle_focus_out(
21783 &mut self,
21784 event: FocusOutEvent,
21785 _window: &mut Window,
21786 cx: &mut Context<Self>,
21787 ) {
21788 if event.blurred != self.focus_handle {
21789 self.last_focused_descendant = Some(event.blurred);
21790 }
21791 self.selection_drag_state = SelectionDragState::None;
21792 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21793 }
21794
21795 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21796 self.blink_manager.update(cx, BlinkManager::disable);
21797 self.buffer
21798 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21799
21800 if let Some(blame) = self.blame.as_ref() {
21801 blame.update(cx, GitBlame::blur)
21802 }
21803 if !self.hover_state.focused(window, cx) {
21804 hide_hover(self, cx);
21805 }
21806 if !self
21807 .context_menu
21808 .borrow()
21809 .as_ref()
21810 .is_some_and(|context_menu| context_menu.focused(window, cx))
21811 {
21812 self.hide_context_menu(window, cx);
21813 }
21814 self.take_active_edit_prediction(cx);
21815 cx.emit(EditorEvent::Blurred);
21816 cx.notify();
21817 }
21818
21819 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21820 let mut pending: String = window
21821 .pending_input_keystrokes()
21822 .into_iter()
21823 .flatten()
21824 .filter_map(|keystroke| {
21825 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21826 keystroke.key_char.clone()
21827 } else {
21828 None
21829 }
21830 })
21831 .collect();
21832
21833 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21834 pending = "".to_string();
21835 }
21836
21837 let existing_pending = self
21838 .text_highlights::<PendingInput>(cx)
21839 .map(|(_, ranges)| ranges.to_vec());
21840 if existing_pending.is_none() && pending.is_empty() {
21841 return;
21842 }
21843 let transaction =
21844 self.transact(window, cx, |this, window, cx| {
21845 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21846 let edits = selections
21847 .iter()
21848 .map(|selection| (selection.end..selection.end, pending.clone()));
21849 this.edit(edits, cx);
21850 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21851 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21852 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21853 }));
21854 });
21855 if let Some(existing_ranges) = existing_pending {
21856 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21857 this.edit(edits, cx);
21858 }
21859 });
21860
21861 let snapshot = self.snapshot(window, cx);
21862 let ranges = self
21863 .selections
21864 .all::<usize>(&snapshot.display_snapshot)
21865 .into_iter()
21866 .map(|selection| {
21867 snapshot.buffer_snapshot().anchor_after(selection.end)
21868 ..snapshot
21869 .buffer_snapshot()
21870 .anchor_before(selection.end + pending.len())
21871 })
21872 .collect();
21873
21874 if pending.is_empty() {
21875 self.clear_highlights::<PendingInput>(cx);
21876 } else {
21877 self.highlight_text::<PendingInput>(
21878 ranges,
21879 HighlightStyle {
21880 underline: Some(UnderlineStyle {
21881 thickness: px(1.),
21882 color: None,
21883 wavy: false,
21884 }),
21885 ..Default::default()
21886 },
21887 cx,
21888 );
21889 }
21890
21891 self.ime_transaction = self.ime_transaction.or(transaction);
21892 if let Some(transaction) = self.ime_transaction {
21893 self.buffer.update(cx, |buffer, cx| {
21894 buffer.group_until_transaction(transaction, cx);
21895 });
21896 }
21897
21898 if self.text_highlights::<PendingInput>(cx).is_none() {
21899 self.ime_transaction.take();
21900 }
21901 }
21902
21903 pub fn register_action_renderer(
21904 &mut self,
21905 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21906 ) -> Subscription {
21907 let id = self.next_editor_action_id.post_inc();
21908 self.editor_actions
21909 .borrow_mut()
21910 .insert(id, Box::new(listener));
21911
21912 let editor_actions = self.editor_actions.clone();
21913 Subscription::new(move || {
21914 editor_actions.borrow_mut().remove(&id);
21915 })
21916 }
21917
21918 pub fn register_action<A: Action>(
21919 &mut self,
21920 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21921 ) -> Subscription {
21922 let id = self.next_editor_action_id.post_inc();
21923 let listener = Arc::new(listener);
21924 self.editor_actions.borrow_mut().insert(
21925 id,
21926 Box::new(move |_, window, _| {
21927 let listener = listener.clone();
21928 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21929 let action = action.downcast_ref().unwrap();
21930 if phase == DispatchPhase::Bubble {
21931 listener(action, window, cx)
21932 }
21933 })
21934 }),
21935 );
21936
21937 let editor_actions = self.editor_actions.clone();
21938 Subscription::new(move || {
21939 editor_actions.borrow_mut().remove(&id);
21940 })
21941 }
21942
21943 pub fn file_header_size(&self) -> u32 {
21944 FILE_HEADER_HEIGHT
21945 }
21946
21947 pub fn restore(
21948 &mut self,
21949 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
21950 window: &mut Window,
21951 cx: &mut Context<Self>,
21952 ) {
21953 let workspace = self.workspace();
21954 let project = self.project();
21955 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
21956 let mut tasks = Vec::new();
21957 for (buffer_id, changes) in revert_changes {
21958 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
21959 buffer.update(cx, |buffer, cx| {
21960 buffer.edit(
21961 changes
21962 .into_iter()
21963 .map(|(range, text)| (range, text.to_string())),
21964 None,
21965 cx,
21966 );
21967 });
21968
21969 if let Some(project) =
21970 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
21971 {
21972 project.update(cx, |project, cx| {
21973 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
21974 })
21975 }
21976 }
21977 }
21978 tasks
21979 });
21980 cx.spawn_in(window, async move |_, cx| {
21981 for (buffer, task) in save_tasks {
21982 let result = task.await;
21983 if result.is_err() {
21984 let Some(path) = buffer
21985 .read_with(cx, |buffer, cx| buffer.project_path(cx))
21986 .ok()
21987 else {
21988 continue;
21989 };
21990 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
21991 let Some(task) = cx
21992 .update_window_entity(workspace, |workspace, window, cx| {
21993 workspace
21994 .open_path_preview(path, None, false, false, false, window, cx)
21995 })
21996 .ok()
21997 else {
21998 continue;
21999 };
22000 task.await.log_err();
22001 }
22002 }
22003 }
22004 })
22005 .detach();
22006 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22007 selections.refresh()
22008 });
22009 }
22010
22011 pub fn to_pixel_point(
22012 &self,
22013 source: multi_buffer::Anchor,
22014 editor_snapshot: &EditorSnapshot,
22015 window: &mut Window,
22016 ) -> Option<gpui::Point<Pixels>> {
22017 let source_point = source.to_display_point(editor_snapshot);
22018 self.display_to_pixel_point(source_point, editor_snapshot, window)
22019 }
22020
22021 pub fn display_to_pixel_point(
22022 &self,
22023 source: DisplayPoint,
22024 editor_snapshot: &EditorSnapshot,
22025 window: &mut Window,
22026 ) -> Option<gpui::Point<Pixels>> {
22027 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22028 let text_layout_details = self.text_layout_details(window);
22029 let scroll_top = text_layout_details
22030 .scroll_anchor
22031 .scroll_position(editor_snapshot)
22032 .y;
22033
22034 if source.row().as_f64() < scroll_top.floor() {
22035 return None;
22036 }
22037 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22038 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22039 Some(gpui::Point::new(source_x, source_y))
22040 }
22041
22042 pub fn has_visible_completions_menu(&self) -> bool {
22043 !self.edit_prediction_preview_is_active()
22044 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22045 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22046 })
22047 }
22048
22049 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22050 if self.mode.is_minimap() {
22051 return;
22052 }
22053 self.addons
22054 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22055 }
22056
22057 pub fn unregister_addon<T: Addon>(&mut self) {
22058 self.addons.remove(&std::any::TypeId::of::<T>());
22059 }
22060
22061 pub fn addon<T: Addon>(&self) -> Option<&T> {
22062 let type_id = std::any::TypeId::of::<T>();
22063 self.addons
22064 .get(&type_id)
22065 .and_then(|item| item.to_any().downcast_ref::<T>())
22066 }
22067
22068 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22069 let type_id = std::any::TypeId::of::<T>();
22070 self.addons
22071 .get_mut(&type_id)
22072 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22073 }
22074
22075 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22076 let text_layout_details = self.text_layout_details(window);
22077 let style = &text_layout_details.editor_style;
22078 let font_id = window.text_system().resolve_font(&style.text.font());
22079 let font_size = style.text.font_size.to_pixels(window.rem_size());
22080 let line_height = style.text.line_height_in_pixels(window.rem_size());
22081 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22082 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22083
22084 CharacterDimensions {
22085 em_width,
22086 em_advance,
22087 line_height,
22088 }
22089 }
22090
22091 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22092 self.load_diff_task.clone()
22093 }
22094
22095 fn read_metadata_from_db(
22096 &mut self,
22097 item_id: u64,
22098 workspace_id: WorkspaceId,
22099 window: &mut Window,
22100 cx: &mut Context<Editor>,
22101 ) {
22102 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22103 && !self.mode.is_minimap()
22104 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22105 {
22106 let buffer_snapshot = OnceCell::new();
22107
22108 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22109 && !folds.is_empty()
22110 {
22111 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22112 self.fold_ranges(
22113 folds
22114 .into_iter()
22115 .map(|(start, end)| {
22116 snapshot.clip_offset(start, Bias::Left)
22117 ..snapshot.clip_offset(end, Bias::Right)
22118 })
22119 .collect(),
22120 false,
22121 window,
22122 cx,
22123 );
22124 }
22125
22126 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22127 && !selections.is_empty()
22128 {
22129 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22130 // skip adding the initial selection to selection history
22131 self.selection_history.mode = SelectionHistoryMode::Skipping;
22132 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22133 s.select_ranges(selections.into_iter().map(|(start, end)| {
22134 snapshot.clip_offset(start, Bias::Left)
22135 ..snapshot.clip_offset(end, Bias::Right)
22136 }));
22137 });
22138 self.selection_history.mode = SelectionHistoryMode::Normal;
22139 };
22140 }
22141
22142 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22143 }
22144
22145 fn update_lsp_data(
22146 &mut self,
22147 for_buffer: Option<BufferId>,
22148 window: &mut Window,
22149 cx: &mut Context<'_, Self>,
22150 ) {
22151 self.pull_diagnostics(for_buffer, window, cx);
22152 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22153 }
22154
22155 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22156 if self.ignore_lsp_data() {
22157 return;
22158 }
22159 for (_, (visible_buffer, _, _)) in self.visible_excerpts(None, cx) {
22160 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22161 }
22162 }
22163
22164 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) -> bool {
22165 if !self.registered_buffers.contains_key(&buffer_id)
22166 && let Some(project) = self.project.as_ref()
22167 {
22168 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22169 project.update(cx, |project, cx| {
22170 self.registered_buffers.insert(
22171 buffer_id,
22172 project.register_buffer_with_language_servers(&buffer, cx),
22173 );
22174 });
22175 return true;
22176 } else {
22177 self.registered_buffers.remove(&buffer_id);
22178 }
22179 }
22180
22181 false
22182 }
22183
22184 fn ignore_lsp_data(&self) -> bool {
22185 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22186 // skip any LSP updates for it.
22187 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22188 }
22189}
22190
22191fn edit_for_markdown_paste<'a>(
22192 buffer: &MultiBufferSnapshot,
22193 range: Range<usize>,
22194 to_insert: &'a str,
22195 url: Option<url::Url>,
22196) -> (Range<usize>, Cow<'a, str>) {
22197 if url.is_none() {
22198 return (range, Cow::Borrowed(to_insert));
22199 };
22200
22201 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22202
22203 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22204 Cow::Borrowed(to_insert)
22205 } else {
22206 Cow::Owned(format!("[{old_text}]({to_insert})"))
22207 };
22208 (range, new_text)
22209}
22210
22211fn vim_enabled(cx: &App) -> bool {
22212 vim_mode_setting::VimModeSetting::try_get(cx)
22213 .map(|vim_mode| vim_mode.0)
22214 .unwrap_or(false)
22215}
22216
22217fn process_completion_for_edit(
22218 completion: &Completion,
22219 intent: CompletionIntent,
22220 buffer: &Entity<Buffer>,
22221 cursor_position: &text::Anchor,
22222 cx: &mut Context<Editor>,
22223) -> CompletionEdit {
22224 let buffer = buffer.read(cx);
22225 let buffer_snapshot = buffer.snapshot();
22226 let (snippet, new_text) = if completion.is_snippet() {
22227 let mut snippet_source = completion.new_text.clone();
22228 // Workaround for typescript language server issues so that methods don't expand within
22229 // strings and functions with type expressions. The previous point is used because the query
22230 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22231 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22232 let previous_point = if previous_point.column > 0 {
22233 cursor_position.to_previous_offset(&buffer_snapshot)
22234 } else {
22235 cursor_position.to_offset(&buffer_snapshot)
22236 };
22237 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22238 && scope.prefers_label_for_snippet_in_completion()
22239 && let Some(label) = completion.label()
22240 && matches!(
22241 completion.kind(),
22242 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22243 )
22244 {
22245 snippet_source = label;
22246 }
22247 match Snippet::parse(&snippet_source).log_err() {
22248 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22249 None => (None, completion.new_text.clone()),
22250 }
22251 } else {
22252 (None, completion.new_text.clone())
22253 };
22254
22255 let mut range_to_replace = {
22256 let replace_range = &completion.replace_range;
22257 if let CompletionSource::Lsp {
22258 insert_range: Some(insert_range),
22259 ..
22260 } = &completion.source
22261 {
22262 debug_assert_eq!(
22263 insert_range.start, replace_range.start,
22264 "insert_range and replace_range should start at the same position"
22265 );
22266 debug_assert!(
22267 insert_range
22268 .start
22269 .cmp(cursor_position, &buffer_snapshot)
22270 .is_le(),
22271 "insert_range should start before or at cursor position"
22272 );
22273 debug_assert!(
22274 replace_range
22275 .start
22276 .cmp(cursor_position, &buffer_snapshot)
22277 .is_le(),
22278 "replace_range should start before or at cursor position"
22279 );
22280
22281 let should_replace = match intent {
22282 CompletionIntent::CompleteWithInsert => false,
22283 CompletionIntent::CompleteWithReplace => true,
22284 CompletionIntent::Complete | CompletionIntent::Compose => {
22285 let insert_mode =
22286 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22287 .completions
22288 .lsp_insert_mode;
22289 match insert_mode {
22290 LspInsertMode::Insert => false,
22291 LspInsertMode::Replace => true,
22292 LspInsertMode::ReplaceSubsequence => {
22293 let mut text_to_replace = buffer.chars_for_range(
22294 buffer.anchor_before(replace_range.start)
22295 ..buffer.anchor_after(replace_range.end),
22296 );
22297 let mut current_needle = text_to_replace.next();
22298 for haystack_ch in completion.label.text.chars() {
22299 if let Some(needle_ch) = current_needle
22300 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22301 {
22302 current_needle = text_to_replace.next();
22303 }
22304 }
22305 current_needle.is_none()
22306 }
22307 LspInsertMode::ReplaceSuffix => {
22308 if replace_range
22309 .end
22310 .cmp(cursor_position, &buffer_snapshot)
22311 .is_gt()
22312 {
22313 let range_after_cursor = *cursor_position..replace_range.end;
22314 let text_after_cursor = buffer
22315 .text_for_range(
22316 buffer.anchor_before(range_after_cursor.start)
22317 ..buffer.anchor_after(range_after_cursor.end),
22318 )
22319 .collect::<String>()
22320 .to_ascii_lowercase();
22321 completion
22322 .label
22323 .text
22324 .to_ascii_lowercase()
22325 .ends_with(&text_after_cursor)
22326 } else {
22327 true
22328 }
22329 }
22330 }
22331 }
22332 };
22333
22334 if should_replace {
22335 replace_range.clone()
22336 } else {
22337 insert_range.clone()
22338 }
22339 } else {
22340 replace_range.clone()
22341 }
22342 };
22343
22344 if range_to_replace
22345 .end
22346 .cmp(cursor_position, &buffer_snapshot)
22347 .is_lt()
22348 {
22349 range_to_replace.end = *cursor_position;
22350 }
22351
22352 CompletionEdit {
22353 new_text,
22354 replace_range: range_to_replace.to_offset(buffer),
22355 snippet,
22356 }
22357}
22358
22359struct CompletionEdit {
22360 new_text: String,
22361 replace_range: Range<usize>,
22362 snippet: Option<Snippet>,
22363}
22364
22365fn insert_extra_newline_brackets(
22366 buffer: &MultiBufferSnapshot,
22367 range: Range<usize>,
22368 language: &language::LanguageScope,
22369) -> bool {
22370 let leading_whitespace_len = buffer
22371 .reversed_chars_at(range.start)
22372 .take_while(|c| c.is_whitespace() && *c != '\n')
22373 .map(|c| c.len_utf8())
22374 .sum::<usize>();
22375 let trailing_whitespace_len = buffer
22376 .chars_at(range.end)
22377 .take_while(|c| c.is_whitespace() && *c != '\n')
22378 .map(|c| c.len_utf8())
22379 .sum::<usize>();
22380 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22381
22382 language.brackets().any(|(pair, enabled)| {
22383 let pair_start = pair.start.trim_end();
22384 let pair_end = pair.end.trim_start();
22385
22386 enabled
22387 && pair.newline
22388 && buffer.contains_str_at(range.end, pair_end)
22389 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22390 })
22391}
22392
22393fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22394 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22395 [(buffer, range, _)] => (*buffer, range.clone()),
22396 _ => return false,
22397 };
22398 let pair = {
22399 let mut result: Option<BracketMatch> = None;
22400
22401 for pair in buffer
22402 .all_bracket_ranges(range.clone())
22403 .filter(move |pair| {
22404 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22405 })
22406 {
22407 let len = pair.close_range.end - pair.open_range.start;
22408
22409 if let Some(existing) = &result {
22410 let existing_len = existing.close_range.end - existing.open_range.start;
22411 if len > existing_len {
22412 continue;
22413 }
22414 }
22415
22416 result = Some(pair);
22417 }
22418
22419 result
22420 };
22421 let Some(pair) = pair else {
22422 return false;
22423 };
22424 pair.newline_only
22425 && buffer
22426 .chars_for_range(pair.open_range.end..range.start)
22427 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22428 .all(|c| c.is_whitespace() && c != '\n')
22429}
22430
22431fn update_uncommitted_diff_for_buffer(
22432 editor: Entity<Editor>,
22433 project: &Entity<Project>,
22434 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22435 buffer: Entity<MultiBuffer>,
22436 cx: &mut App,
22437) -> Task<()> {
22438 let mut tasks = Vec::new();
22439 project.update(cx, |project, cx| {
22440 for buffer in buffers {
22441 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22442 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22443 }
22444 }
22445 });
22446 cx.spawn(async move |cx| {
22447 let diffs = future::join_all(tasks).await;
22448 if editor
22449 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22450 .unwrap_or(false)
22451 {
22452 return;
22453 }
22454
22455 buffer
22456 .update(cx, |buffer, cx| {
22457 for diff in diffs.into_iter().flatten() {
22458 buffer.add_diff(diff, cx);
22459 }
22460 })
22461 .ok();
22462 })
22463}
22464
22465fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22466 let tab_size = tab_size.get() as usize;
22467 let mut width = offset;
22468
22469 for ch in text.chars() {
22470 width += if ch == '\t' {
22471 tab_size - (width % tab_size)
22472 } else {
22473 1
22474 };
22475 }
22476
22477 width - offset
22478}
22479
22480#[cfg(test)]
22481mod tests {
22482 use super::*;
22483
22484 #[test]
22485 fn test_string_size_with_expanded_tabs() {
22486 let nz = |val| NonZeroU32::new(val).unwrap();
22487 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22488 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22489 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22490 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22491 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22492 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22493 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22494 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22495 }
22496}
22497
22498/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22499struct WordBreakingTokenizer<'a> {
22500 input: &'a str,
22501}
22502
22503impl<'a> WordBreakingTokenizer<'a> {
22504 fn new(input: &'a str) -> Self {
22505 Self { input }
22506 }
22507}
22508
22509fn is_char_ideographic(ch: char) -> bool {
22510 use unicode_script::Script::*;
22511 use unicode_script::UnicodeScript;
22512 matches!(ch.script(), Han | Tangut | Yi)
22513}
22514
22515fn is_grapheme_ideographic(text: &str) -> bool {
22516 text.chars().any(is_char_ideographic)
22517}
22518
22519fn is_grapheme_whitespace(text: &str) -> bool {
22520 text.chars().any(|x| x.is_whitespace())
22521}
22522
22523fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22524 text.chars()
22525 .next()
22526 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22527}
22528
22529#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22530enum WordBreakToken<'a> {
22531 Word { token: &'a str, grapheme_len: usize },
22532 InlineWhitespace { token: &'a str, grapheme_len: usize },
22533 Newline,
22534}
22535
22536impl<'a> Iterator for WordBreakingTokenizer<'a> {
22537 /// Yields a span, the count of graphemes in the token, and whether it was
22538 /// whitespace. Note that it also breaks at word boundaries.
22539 type Item = WordBreakToken<'a>;
22540
22541 fn next(&mut self) -> Option<Self::Item> {
22542 use unicode_segmentation::UnicodeSegmentation;
22543 if self.input.is_empty() {
22544 return None;
22545 }
22546
22547 let mut iter = self.input.graphemes(true).peekable();
22548 let mut offset = 0;
22549 let mut grapheme_len = 0;
22550 if let Some(first_grapheme) = iter.next() {
22551 let is_newline = first_grapheme == "\n";
22552 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22553 offset += first_grapheme.len();
22554 grapheme_len += 1;
22555 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22556 if let Some(grapheme) = iter.peek().copied()
22557 && should_stay_with_preceding_ideograph(grapheme)
22558 {
22559 offset += grapheme.len();
22560 grapheme_len += 1;
22561 }
22562 } else {
22563 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22564 let mut next_word_bound = words.peek().copied();
22565 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22566 next_word_bound = words.next();
22567 }
22568 while let Some(grapheme) = iter.peek().copied() {
22569 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22570 break;
22571 };
22572 if is_grapheme_whitespace(grapheme) != is_whitespace
22573 || (grapheme == "\n") != is_newline
22574 {
22575 break;
22576 };
22577 offset += grapheme.len();
22578 grapheme_len += 1;
22579 iter.next();
22580 }
22581 }
22582 let token = &self.input[..offset];
22583 self.input = &self.input[offset..];
22584 if token == "\n" {
22585 Some(WordBreakToken::Newline)
22586 } else if is_whitespace {
22587 Some(WordBreakToken::InlineWhitespace {
22588 token,
22589 grapheme_len,
22590 })
22591 } else {
22592 Some(WordBreakToken::Word {
22593 token,
22594 grapheme_len,
22595 })
22596 }
22597 } else {
22598 None
22599 }
22600 }
22601}
22602
22603#[test]
22604fn test_word_breaking_tokenizer() {
22605 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22606 ("", &[]),
22607 (" ", &[whitespace(" ", 2)]),
22608 ("Ʒ", &[word("Ʒ", 1)]),
22609 ("Ǽ", &[word("Ǽ", 1)]),
22610 ("⋑", &[word("⋑", 1)]),
22611 ("⋑⋑", &[word("⋑⋑", 2)]),
22612 (
22613 "原理,进而",
22614 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22615 ),
22616 (
22617 "hello world",
22618 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22619 ),
22620 (
22621 "hello, world",
22622 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22623 ),
22624 (
22625 " hello world",
22626 &[
22627 whitespace(" ", 2),
22628 word("hello", 5),
22629 whitespace(" ", 1),
22630 word("world", 5),
22631 ],
22632 ),
22633 (
22634 "这是什么 \n 钢笔",
22635 &[
22636 word("这", 1),
22637 word("是", 1),
22638 word("什", 1),
22639 word("么", 1),
22640 whitespace(" ", 1),
22641 newline(),
22642 whitespace(" ", 1),
22643 word("钢", 1),
22644 word("笔", 1),
22645 ],
22646 ),
22647 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22648 ];
22649
22650 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22651 WordBreakToken::Word {
22652 token,
22653 grapheme_len,
22654 }
22655 }
22656
22657 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22658 WordBreakToken::InlineWhitespace {
22659 token,
22660 grapheme_len,
22661 }
22662 }
22663
22664 fn newline() -> WordBreakToken<'static> {
22665 WordBreakToken::Newline
22666 }
22667
22668 for (input, result) in tests {
22669 assert_eq!(
22670 WordBreakingTokenizer::new(input)
22671 .collect::<Vec<_>>()
22672 .as_slice(),
22673 *result,
22674 );
22675 }
22676}
22677
22678fn wrap_with_prefix(
22679 first_line_prefix: String,
22680 subsequent_lines_prefix: String,
22681 unwrapped_text: String,
22682 wrap_column: usize,
22683 tab_size: NonZeroU32,
22684 preserve_existing_whitespace: bool,
22685) -> String {
22686 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22687 let subsequent_lines_prefix_len =
22688 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22689 let mut wrapped_text = String::new();
22690 let mut current_line = first_line_prefix;
22691 let mut is_first_line = true;
22692
22693 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22694 let mut current_line_len = first_line_prefix_len;
22695 let mut in_whitespace = false;
22696 for token in tokenizer {
22697 let have_preceding_whitespace = in_whitespace;
22698 match token {
22699 WordBreakToken::Word {
22700 token,
22701 grapheme_len,
22702 } => {
22703 in_whitespace = false;
22704 let current_prefix_len = if is_first_line {
22705 first_line_prefix_len
22706 } else {
22707 subsequent_lines_prefix_len
22708 };
22709 if current_line_len + grapheme_len > wrap_column
22710 && current_line_len != current_prefix_len
22711 {
22712 wrapped_text.push_str(current_line.trim_end());
22713 wrapped_text.push('\n');
22714 is_first_line = false;
22715 current_line = subsequent_lines_prefix.clone();
22716 current_line_len = subsequent_lines_prefix_len;
22717 }
22718 current_line.push_str(token);
22719 current_line_len += grapheme_len;
22720 }
22721 WordBreakToken::InlineWhitespace {
22722 mut token,
22723 mut grapheme_len,
22724 } => {
22725 in_whitespace = true;
22726 if have_preceding_whitespace && !preserve_existing_whitespace {
22727 continue;
22728 }
22729 if !preserve_existing_whitespace {
22730 // Keep a single whitespace grapheme as-is
22731 if let Some(first) =
22732 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22733 {
22734 token = first;
22735 } else {
22736 token = " ";
22737 }
22738 grapheme_len = 1;
22739 }
22740 let current_prefix_len = if is_first_line {
22741 first_line_prefix_len
22742 } else {
22743 subsequent_lines_prefix_len
22744 };
22745 if current_line_len + grapheme_len > wrap_column {
22746 wrapped_text.push_str(current_line.trim_end());
22747 wrapped_text.push('\n');
22748 is_first_line = false;
22749 current_line = subsequent_lines_prefix.clone();
22750 current_line_len = subsequent_lines_prefix_len;
22751 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22752 current_line.push_str(token);
22753 current_line_len += grapheme_len;
22754 }
22755 }
22756 WordBreakToken::Newline => {
22757 in_whitespace = true;
22758 let current_prefix_len = if is_first_line {
22759 first_line_prefix_len
22760 } else {
22761 subsequent_lines_prefix_len
22762 };
22763 if preserve_existing_whitespace {
22764 wrapped_text.push_str(current_line.trim_end());
22765 wrapped_text.push('\n');
22766 is_first_line = false;
22767 current_line = subsequent_lines_prefix.clone();
22768 current_line_len = subsequent_lines_prefix_len;
22769 } else if have_preceding_whitespace {
22770 continue;
22771 } else if current_line_len + 1 > wrap_column
22772 && current_line_len != current_prefix_len
22773 {
22774 wrapped_text.push_str(current_line.trim_end());
22775 wrapped_text.push('\n');
22776 is_first_line = false;
22777 current_line = subsequent_lines_prefix.clone();
22778 current_line_len = subsequent_lines_prefix_len;
22779 } else if current_line_len != current_prefix_len {
22780 current_line.push(' ');
22781 current_line_len += 1;
22782 }
22783 }
22784 }
22785 }
22786
22787 if !current_line.is_empty() {
22788 wrapped_text.push_str(¤t_line);
22789 }
22790 wrapped_text
22791}
22792
22793#[test]
22794fn test_wrap_with_prefix() {
22795 assert_eq!(
22796 wrap_with_prefix(
22797 "# ".to_string(),
22798 "# ".to_string(),
22799 "abcdefg".to_string(),
22800 4,
22801 NonZeroU32::new(4).unwrap(),
22802 false,
22803 ),
22804 "# abcdefg"
22805 );
22806 assert_eq!(
22807 wrap_with_prefix(
22808 "".to_string(),
22809 "".to_string(),
22810 "\thello world".to_string(),
22811 8,
22812 NonZeroU32::new(4).unwrap(),
22813 false,
22814 ),
22815 "hello\nworld"
22816 );
22817 assert_eq!(
22818 wrap_with_prefix(
22819 "// ".to_string(),
22820 "// ".to_string(),
22821 "xx \nyy zz aa bb cc".to_string(),
22822 12,
22823 NonZeroU32::new(4).unwrap(),
22824 false,
22825 ),
22826 "// xx yy zz\n// aa bb cc"
22827 );
22828 assert_eq!(
22829 wrap_with_prefix(
22830 String::new(),
22831 String::new(),
22832 "这是什么 \n 钢笔".to_string(),
22833 3,
22834 NonZeroU32::new(4).unwrap(),
22835 false,
22836 ),
22837 "这是什\n么 钢\n笔"
22838 );
22839 assert_eq!(
22840 wrap_with_prefix(
22841 String::new(),
22842 String::new(),
22843 format!("foo{}bar", '\u{2009}'), // thin space
22844 80,
22845 NonZeroU32::new(4).unwrap(),
22846 false,
22847 ),
22848 format!("foo{}bar", '\u{2009}')
22849 );
22850}
22851
22852pub trait CollaborationHub {
22853 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22854 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22855 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22856}
22857
22858impl CollaborationHub for Entity<Project> {
22859 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22860 self.read(cx).collaborators()
22861 }
22862
22863 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22864 self.read(cx).user_store().read(cx).participant_indices()
22865 }
22866
22867 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22868 let this = self.read(cx);
22869 let user_ids = this.collaborators().values().map(|c| c.user_id);
22870 this.user_store().read(cx).participant_names(user_ids, cx)
22871 }
22872}
22873
22874pub trait SemanticsProvider {
22875 fn hover(
22876 &self,
22877 buffer: &Entity<Buffer>,
22878 position: text::Anchor,
22879 cx: &mut App,
22880 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22881
22882 fn inline_values(
22883 &self,
22884 buffer_handle: Entity<Buffer>,
22885 range: Range<text::Anchor>,
22886 cx: &mut App,
22887 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22888
22889 fn inlay_hints(
22890 &self,
22891 buffer_handle: Entity<Buffer>,
22892 range: Range<text::Anchor>,
22893 cx: &mut App,
22894 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22895
22896 fn resolve_inlay_hint(
22897 &self,
22898 hint: InlayHint,
22899 buffer_handle: Entity<Buffer>,
22900 server_id: LanguageServerId,
22901 cx: &mut App,
22902 ) -> Option<Task<anyhow::Result<InlayHint>>>;
22903
22904 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22905
22906 fn document_highlights(
22907 &self,
22908 buffer: &Entity<Buffer>,
22909 position: text::Anchor,
22910 cx: &mut App,
22911 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22912
22913 fn definitions(
22914 &self,
22915 buffer: &Entity<Buffer>,
22916 position: text::Anchor,
22917 kind: GotoDefinitionKind,
22918 cx: &mut App,
22919 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
22920
22921 fn range_for_rename(
22922 &self,
22923 buffer: &Entity<Buffer>,
22924 position: text::Anchor,
22925 cx: &mut App,
22926 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
22927
22928 fn perform_rename(
22929 &self,
22930 buffer: &Entity<Buffer>,
22931 position: text::Anchor,
22932 new_name: String,
22933 cx: &mut App,
22934 ) -> Option<Task<Result<ProjectTransaction>>>;
22935}
22936
22937pub trait CompletionProvider {
22938 fn completions(
22939 &self,
22940 excerpt_id: ExcerptId,
22941 buffer: &Entity<Buffer>,
22942 buffer_position: text::Anchor,
22943 trigger: CompletionContext,
22944 window: &mut Window,
22945 cx: &mut Context<Editor>,
22946 ) -> Task<Result<Vec<CompletionResponse>>>;
22947
22948 fn resolve_completions(
22949 &self,
22950 _buffer: Entity<Buffer>,
22951 _completion_indices: Vec<usize>,
22952 _completions: Rc<RefCell<Box<[Completion]>>>,
22953 _cx: &mut Context<Editor>,
22954 ) -> Task<Result<bool>> {
22955 Task::ready(Ok(false))
22956 }
22957
22958 fn apply_additional_edits_for_completion(
22959 &self,
22960 _buffer: Entity<Buffer>,
22961 _completions: Rc<RefCell<Box<[Completion]>>>,
22962 _completion_index: usize,
22963 _push_to_history: bool,
22964 _cx: &mut Context<Editor>,
22965 ) -> Task<Result<Option<language::Transaction>>> {
22966 Task::ready(Ok(None))
22967 }
22968
22969 fn is_completion_trigger(
22970 &self,
22971 buffer: &Entity<Buffer>,
22972 position: language::Anchor,
22973 text: &str,
22974 trigger_in_words: bool,
22975 menu_is_open: bool,
22976 cx: &mut Context<Editor>,
22977 ) -> bool;
22978
22979 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
22980
22981 fn sort_completions(&self) -> bool {
22982 true
22983 }
22984
22985 fn filter_completions(&self) -> bool {
22986 true
22987 }
22988}
22989
22990pub trait CodeActionProvider {
22991 fn id(&self) -> Arc<str>;
22992
22993 fn code_actions(
22994 &self,
22995 buffer: &Entity<Buffer>,
22996 range: Range<text::Anchor>,
22997 window: &mut Window,
22998 cx: &mut App,
22999 ) -> Task<Result<Vec<CodeAction>>>;
23000
23001 fn apply_code_action(
23002 &self,
23003 buffer_handle: Entity<Buffer>,
23004 action: CodeAction,
23005 excerpt_id: ExcerptId,
23006 push_to_history: bool,
23007 window: &mut Window,
23008 cx: &mut App,
23009 ) -> Task<Result<ProjectTransaction>>;
23010}
23011
23012impl CodeActionProvider for Entity<Project> {
23013 fn id(&self) -> Arc<str> {
23014 "project".into()
23015 }
23016
23017 fn code_actions(
23018 &self,
23019 buffer: &Entity<Buffer>,
23020 range: Range<text::Anchor>,
23021 _window: &mut Window,
23022 cx: &mut App,
23023 ) -> Task<Result<Vec<CodeAction>>> {
23024 self.update(cx, |project, cx| {
23025 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23026 let code_actions = project.code_actions(buffer, range, None, cx);
23027 cx.background_spawn(async move {
23028 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23029 Ok(code_lens_actions
23030 .context("code lens fetch")?
23031 .into_iter()
23032 .flatten()
23033 .chain(
23034 code_actions
23035 .context("code action fetch")?
23036 .into_iter()
23037 .flatten(),
23038 )
23039 .collect())
23040 })
23041 })
23042 }
23043
23044 fn apply_code_action(
23045 &self,
23046 buffer_handle: Entity<Buffer>,
23047 action: CodeAction,
23048 _excerpt_id: ExcerptId,
23049 push_to_history: bool,
23050 _window: &mut Window,
23051 cx: &mut App,
23052 ) -> Task<Result<ProjectTransaction>> {
23053 self.update(cx, |project, cx| {
23054 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23055 })
23056 }
23057}
23058
23059fn snippet_completions(
23060 project: &Project,
23061 buffer: &Entity<Buffer>,
23062 buffer_position: text::Anchor,
23063 cx: &mut App,
23064) -> Task<Result<CompletionResponse>> {
23065 let languages = buffer.read(cx).languages_at(buffer_position);
23066 let snippet_store = project.snippets().read(cx);
23067
23068 let scopes: Vec<_> = languages
23069 .iter()
23070 .filter_map(|language| {
23071 let language_name = language.lsp_id();
23072 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23073
23074 if snippets.is_empty() {
23075 None
23076 } else {
23077 Some((language.default_scope(), snippets))
23078 }
23079 })
23080 .collect();
23081
23082 if scopes.is_empty() {
23083 return Task::ready(Ok(CompletionResponse {
23084 completions: vec![],
23085 display_options: CompletionDisplayOptions::default(),
23086 is_incomplete: false,
23087 }));
23088 }
23089
23090 let snapshot = buffer.read(cx).text_snapshot();
23091 let executor = cx.background_executor().clone();
23092
23093 cx.background_spawn(async move {
23094 let mut is_incomplete = false;
23095 let mut completions: Vec<Completion> = Vec::new();
23096 for (scope, snippets) in scopes.into_iter() {
23097 let classifier =
23098 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23099
23100 const MAX_WORD_PREFIX_LEN: usize = 128;
23101 let last_word: String = snapshot
23102 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23103 .take(MAX_WORD_PREFIX_LEN)
23104 .take_while(|c| classifier.is_word(*c))
23105 .collect::<String>()
23106 .chars()
23107 .rev()
23108 .collect();
23109
23110 if last_word.is_empty() {
23111 return Ok(CompletionResponse {
23112 completions: vec![],
23113 display_options: CompletionDisplayOptions::default(),
23114 is_incomplete: true,
23115 });
23116 }
23117
23118 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23119 let to_lsp = |point: &text::Anchor| {
23120 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23121 point_to_lsp(end)
23122 };
23123 let lsp_end = to_lsp(&buffer_position);
23124
23125 let candidates = snippets
23126 .iter()
23127 .enumerate()
23128 .flat_map(|(ix, snippet)| {
23129 snippet
23130 .prefix
23131 .iter()
23132 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23133 })
23134 .collect::<Vec<StringMatchCandidate>>();
23135
23136 const MAX_RESULTS: usize = 100;
23137 let mut matches = fuzzy::match_strings(
23138 &candidates,
23139 &last_word,
23140 last_word.chars().any(|c| c.is_uppercase()),
23141 true,
23142 MAX_RESULTS,
23143 &Default::default(),
23144 executor.clone(),
23145 )
23146 .await;
23147
23148 if matches.len() >= MAX_RESULTS {
23149 is_incomplete = true;
23150 }
23151
23152 // Remove all candidates where the query's start does not match the start of any word in the candidate
23153 if let Some(query_start) = last_word.chars().next() {
23154 matches.retain(|string_match| {
23155 split_words(&string_match.string).any(|word| {
23156 // Check that the first codepoint of the word as lowercase matches the first
23157 // codepoint of the query as lowercase
23158 word.chars()
23159 .flat_map(|codepoint| codepoint.to_lowercase())
23160 .zip(query_start.to_lowercase())
23161 .all(|(word_cp, query_cp)| word_cp == query_cp)
23162 })
23163 });
23164 }
23165
23166 let matched_strings = matches
23167 .into_iter()
23168 .map(|m| m.string)
23169 .collect::<HashSet<_>>();
23170
23171 completions.extend(snippets.iter().filter_map(|snippet| {
23172 let matching_prefix = snippet
23173 .prefix
23174 .iter()
23175 .find(|prefix| matched_strings.contains(*prefix))?;
23176 let start = as_offset - last_word.len();
23177 let start = snapshot.anchor_before(start);
23178 let range = start..buffer_position;
23179 let lsp_start = to_lsp(&start);
23180 let lsp_range = lsp::Range {
23181 start: lsp_start,
23182 end: lsp_end,
23183 };
23184 Some(Completion {
23185 replace_range: range,
23186 new_text: snippet.body.clone(),
23187 source: CompletionSource::Lsp {
23188 insert_range: None,
23189 server_id: LanguageServerId(usize::MAX),
23190 resolved: true,
23191 lsp_completion: Box::new(lsp::CompletionItem {
23192 label: snippet.prefix.first().unwrap().clone(),
23193 kind: Some(CompletionItemKind::SNIPPET),
23194 label_details: snippet.description.as_ref().map(|description| {
23195 lsp::CompletionItemLabelDetails {
23196 detail: Some(description.clone()),
23197 description: None,
23198 }
23199 }),
23200 insert_text_format: Some(InsertTextFormat::SNIPPET),
23201 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23202 lsp::InsertReplaceEdit {
23203 new_text: snippet.body.clone(),
23204 insert: lsp_range,
23205 replace: lsp_range,
23206 },
23207 )),
23208 filter_text: Some(snippet.body.clone()),
23209 sort_text: Some(char::MAX.to_string()),
23210 ..lsp::CompletionItem::default()
23211 }),
23212 lsp_defaults: None,
23213 },
23214 label: CodeLabel::plain(matching_prefix.clone(), None),
23215 icon_path: None,
23216 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23217 single_line: snippet.name.clone().into(),
23218 plain_text: snippet
23219 .description
23220 .clone()
23221 .map(|description| description.into()),
23222 }),
23223 insert_text_mode: None,
23224 confirm: None,
23225 })
23226 }))
23227 }
23228
23229 Ok(CompletionResponse {
23230 completions,
23231 display_options: CompletionDisplayOptions::default(),
23232 is_incomplete,
23233 })
23234 })
23235}
23236
23237impl CompletionProvider for Entity<Project> {
23238 fn completions(
23239 &self,
23240 _excerpt_id: ExcerptId,
23241 buffer: &Entity<Buffer>,
23242 buffer_position: text::Anchor,
23243 options: CompletionContext,
23244 _window: &mut Window,
23245 cx: &mut Context<Editor>,
23246 ) -> Task<Result<Vec<CompletionResponse>>> {
23247 self.update(cx, |project, cx| {
23248 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23249 let project_completions = project.completions(buffer, buffer_position, options, cx);
23250 cx.background_spawn(async move {
23251 let mut responses = project_completions.await?;
23252 let snippets = snippets.await?;
23253 if !snippets.completions.is_empty() {
23254 responses.push(snippets);
23255 }
23256 Ok(responses)
23257 })
23258 })
23259 }
23260
23261 fn resolve_completions(
23262 &self,
23263 buffer: Entity<Buffer>,
23264 completion_indices: Vec<usize>,
23265 completions: Rc<RefCell<Box<[Completion]>>>,
23266 cx: &mut Context<Editor>,
23267 ) -> Task<Result<bool>> {
23268 self.update(cx, |project, cx| {
23269 project.lsp_store().update(cx, |lsp_store, cx| {
23270 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23271 })
23272 })
23273 }
23274
23275 fn apply_additional_edits_for_completion(
23276 &self,
23277 buffer: Entity<Buffer>,
23278 completions: Rc<RefCell<Box<[Completion]>>>,
23279 completion_index: usize,
23280 push_to_history: bool,
23281 cx: &mut Context<Editor>,
23282 ) -> Task<Result<Option<language::Transaction>>> {
23283 self.update(cx, |project, cx| {
23284 project.lsp_store().update(cx, |lsp_store, cx| {
23285 lsp_store.apply_additional_edits_for_completion(
23286 buffer,
23287 completions,
23288 completion_index,
23289 push_to_history,
23290 cx,
23291 )
23292 })
23293 })
23294 }
23295
23296 fn is_completion_trigger(
23297 &self,
23298 buffer: &Entity<Buffer>,
23299 position: language::Anchor,
23300 text: &str,
23301 trigger_in_words: bool,
23302 menu_is_open: bool,
23303 cx: &mut Context<Editor>,
23304 ) -> bool {
23305 let mut chars = text.chars();
23306 let char = if let Some(char) = chars.next() {
23307 char
23308 } else {
23309 return false;
23310 };
23311 if chars.next().is_some() {
23312 return false;
23313 }
23314
23315 let buffer = buffer.read(cx);
23316 let snapshot = buffer.snapshot();
23317 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23318 return false;
23319 }
23320 let classifier = snapshot
23321 .char_classifier_at(position)
23322 .scope_context(Some(CharScopeContext::Completion));
23323 if trigger_in_words && classifier.is_word(char) {
23324 return true;
23325 }
23326
23327 buffer.completion_triggers().contains(text)
23328 }
23329}
23330
23331impl SemanticsProvider for Entity<Project> {
23332 fn hover(
23333 &self,
23334 buffer: &Entity<Buffer>,
23335 position: text::Anchor,
23336 cx: &mut App,
23337 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23338 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23339 }
23340
23341 fn document_highlights(
23342 &self,
23343 buffer: &Entity<Buffer>,
23344 position: text::Anchor,
23345 cx: &mut App,
23346 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23347 Some(self.update(cx, |project, cx| {
23348 project.document_highlights(buffer, position, cx)
23349 }))
23350 }
23351
23352 fn definitions(
23353 &self,
23354 buffer: &Entity<Buffer>,
23355 position: text::Anchor,
23356 kind: GotoDefinitionKind,
23357 cx: &mut App,
23358 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23359 Some(self.update(cx, |project, cx| match kind {
23360 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23361 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23362 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23363 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23364 }))
23365 }
23366
23367 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23368 self.update(cx, |project, cx| {
23369 if project
23370 .active_debug_session(cx)
23371 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23372 {
23373 return true;
23374 }
23375
23376 buffer.update(cx, |buffer, cx| {
23377 project.any_language_server_supports_inlay_hints(buffer, cx)
23378 })
23379 })
23380 }
23381
23382 fn inline_values(
23383 &self,
23384 buffer_handle: Entity<Buffer>,
23385 range: Range<text::Anchor>,
23386 cx: &mut App,
23387 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23388 self.update(cx, |project, cx| {
23389 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23390
23391 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23392 })
23393 }
23394
23395 fn inlay_hints(
23396 &self,
23397 buffer_handle: Entity<Buffer>,
23398 range: Range<text::Anchor>,
23399 cx: &mut App,
23400 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23401 Some(self.update(cx, |project, cx| {
23402 project.inlay_hints(buffer_handle, range, cx)
23403 }))
23404 }
23405
23406 fn resolve_inlay_hint(
23407 &self,
23408 hint: InlayHint,
23409 buffer_handle: Entity<Buffer>,
23410 server_id: LanguageServerId,
23411 cx: &mut App,
23412 ) -> Option<Task<anyhow::Result<InlayHint>>> {
23413 Some(self.update(cx, |project, cx| {
23414 project.resolve_inlay_hint(hint, buffer_handle, server_id, cx)
23415 }))
23416 }
23417
23418 fn range_for_rename(
23419 &self,
23420 buffer: &Entity<Buffer>,
23421 position: text::Anchor,
23422 cx: &mut App,
23423 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23424 Some(self.update(cx, |project, cx| {
23425 let buffer = buffer.clone();
23426 let task = project.prepare_rename(buffer.clone(), position, cx);
23427 cx.spawn(async move |_, cx| {
23428 Ok(match task.await? {
23429 PrepareRenameResponse::Success(range) => Some(range),
23430 PrepareRenameResponse::InvalidPosition => None,
23431 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23432 // Fallback on using TreeSitter info to determine identifier range
23433 buffer.read_with(cx, |buffer, _| {
23434 let snapshot = buffer.snapshot();
23435 let (range, kind) = snapshot.surrounding_word(position, None);
23436 if kind != Some(CharKind::Word) {
23437 return None;
23438 }
23439 Some(
23440 snapshot.anchor_before(range.start)
23441 ..snapshot.anchor_after(range.end),
23442 )
23443 })?
23444 }
23445 })
23446 })
23447 }))
23448 }
23449
23450 fn perform_rename(
23451 &self,
23452 buffer: &Entity<Buffer>,
23453 position: text::Anchor,
23454 new_name: String,
23455 cx: &mut App,
23456 ) -> Option<Task<Result<ProjectTransaction>>> {
23457 Some(self.update(cx, |project, cx| {
23458 project.perform_rename(buffer.clone(), position, new_name, cx)
23459 }))
23460 }
23461}
23462
23463fn inlay_hint_settings(
23464 location: Anchor,
23465 snapshot: &MultiBufferSnapshot,
23466 cx: &mut Context<Editor>,
23467) -> InlayHintSettings {
23468 let file = snapshot.file_at(location);
23469 let language = snapshot.language_at(location).map(|l| l.name());
23470 language_settings(language, file, cx).inlay_hints
23471}
23472
23473fn consume_contiguous_rows(
23474 contiguous_row_selections: &mut Vec<Selection<Point>>,
23475 selection: &Selection<Point>,
23476 display_map: &DisplaySnapshot,
23477 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23478) -> (MultiBufferRow, MultiBufferRow) {
23479 contiguous_row_selections.push(selection.clone());
23480 let start_row = starting_row(selection, display_map);
23481 let mut end_row = ending_row(selection, display_map);
23482
23483 while let Some(next_selection) = selections.peek() {
23484 if next_selection.start.row <= end_row.0 {
23485 end_row = ending_row(next_selection, display_map);
23486 contiguous_row_selections.push(selections.next().unwrap().clone());
23487 } else {
23488 break;
23489 }
23490 }
23491 (start_row, end_row)
23492}
23493
23494fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23495 if selection.start.column > 0 {
23496 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23497 } else {
23498 MultiBufferRow(selection.start.row)
23499 }
23500}
23501
23502fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23503 if next_selection.end.column > 0 || next_selection.is_empty() {
23504 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23505 } else {
23506 MultiBufferRow(next_selection.end.row)
23507 }
23508}
23509
23510impl EditorSnapshot {
23511 pub fn remote_selections_in_range<'a>(
23512 &'a self,
23513 range: &'a Range<Anchor>,
23514 collaboration_hub: &dyn CollaborationHub,
23515 cx: &'a App,
23516 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23517 let participant_names = collaboration_hub.user_names(cx);
23518 let participant_indices = collaboration_hub.user_participant_indices(cx);
23519 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23520 let collaborators_by_replica_id = collaborators_by_peer_id
23521 .values()
23522 .map(|collaborator| (collaborator.replica_id, collaborator))
23523 .collect::<HashMap<_, _>>();
23524 self.buffer_snapshot()
23525 .selections_in_range(range, false)
23526 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23527 if replica_id == ReplicaId::AGENT {
23528 Some(RemoteSelection {
23529 replica_id,
23530 selection,
23531 cursor_shape,
23532 line_mode,
23533 collaborator_id: CollaboratorId::Agent,
23534 user_name: Some("Agent".into()),
23535 color: cx.theme().players().agent(),
23536 })
23537 } else {
23538 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23539 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23540 let user_name = participant_names.get(&collaborator.user_id).cloned();
23541 Some(RemoteSelection {
23542 replica_id,
23543 selection,
23544 cursor_shape,
23545 line_mode,
23546 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23547 user_name,
23548 color: if let Some(index) = participant_index {
23549 cx.theme().players().color_for_participant(index.0)
23550 } else {
23551 cx.theme().players().absent()
23552 },
23553 })
23554 }
23555 })
23556 }
23557
23558 pub fn hunks_for_ranges(
23559 &self,
23560 ranges: impl IntoIterator<Item = Range<Point>>,
23561 ) -> Vec<MultiBufferDiffHunk> {
23562 let mut hunks = Vec::new();
23563 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23564 HashMap::default();
23565 for query_range in ranges {
23566 let query_rows =
23567 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23568 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23569 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23570 ) {
23571 // Include deleted hunks that are adjacent to the query range, because
23572 // otherwise they would be missed.
23573 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23574 if hunk.status().is_deleted() {
23575 intersects_range |= hunk.row_range.start == query_rows.end;
23576 intersects_range |= hunk.row_range.end == query_rows.start;
23577 }
23578 if intersects_range {
23579 if !processed_buffer_rows
23580 .entry(hunk.buffer_id)
23581 .or_default()
23582 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23583 {
23584 continue;
23585 }
23586 hunks.push(hunk);
23587 }
23588 }
23589 }
23590
23591 hunks
23592 }
23593
23594 fn display_diff_hunks_for_rows<'a>(
23595 &'a self,
23596 display_rows: Range<DisplayRow>,
23597 folded_buffers: &'a HashSet<BufferId>,
23598 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23599 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23600 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23601
23602 self.buffer_snapshot()
23603 .diff_hunks_in_range(buffer_start..buffer_end)
23604 .filter_map(|hunk| {
23605 if folded_buffers.contains(&hunk.buffer_id) {
23606 return None;
23607 }
23608
23609 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23610 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23611
23612 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23613 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23614
23615 let display_hunk = if hunk_display_start.column() != 0 {
23616 DisplayDiffHunk::Folded {
23617 display_row: hunk_display_start.row(),
23618 }
23619 } else {
23620 let mut end_row = hunk_display_end.row();
23621 if hunk_display_end.column() > 0 {
23622 end_row.0 += 1;
23623 }
23624 let is_created_file = hunk.is_created_file();
23625 DisplayDiffHunk::Unfolded {
23626 status: hunk.status(),
23627 diff_base_byte_range: hunk.diff_base_byte_range,
23628 display_row_range: hunk_display_start.row()..end_row,
23629 multi_buffer_range: Anchor::range_in_buffer(
23630 hunk.excerpt_id,
23631 hunk.buffer_id,
23632 hunk.buffer_range,
23633 ),
23634 is_created_file,
23635 }
23636 };
23637
23638 Some(display_hunk)
23639 })
23640 }
23641
23642 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23643 self.display_snapshot
23644 .buffer_snapshot()
23645 .language_at(position)
23646 }
23647
23648 pub fn is_focused(&self) -> bool {
23649 self.is_focused
23650 }
23651
23652 pub fn placeholder_text(&self) -> Option<String> {
23653 self.placeholder_display_snapshot
23654 .as_ref()
23655 .map(|display_map| display_map.text())
23656 }
23657
23658 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23659 self.scroll_anchor.scroll_position(&self.display_snapshot)
23660 }
23661
23662 fn gutter_dimensions(
23663 &self,
23664 font_id: FontId,
23665 font_size: Pixels,
23666 max_line_number_width: Pixels,
23667 cx: &App,
23668 ) -> Option<GutterDimensions> {
23669 if !self.show_gutter {
23670 return None;
23671 }
23672
23673 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23674 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23675
23676 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23677 matches!(
23678 ProjectSettings::get_global(cx).git.git_gutter,
23679 GitGutterSetting::TrackedFiles
23680 )
23681 });
23682 let gutter_settings = EditorSettings::get_global(cx).gutter;
23683 let show_line_numbers = self
23684 .show_line_numbers
23685 .unwrap_or(gutter_settings.line_numbers);
23686 let line_gutter_width = if show_line_numbers {
23687 // Avoid flicker-like gutter resizes when the line number gains another digit by
23688 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23689 let min_width_for_number_on_gutter =
23690 ch_advance * gutter_settings.min_line_number_digits as f32;
23691 max_line_number_width.max(min_width_for_number_on_gutter)
23692 } else {
23693 0.0.into()
23694 };
23695
23696 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23697 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23698
23699 let git_blame_entries_width =
23700 self.git_blame_gutter_max_author_length
23701 .map(|max_author_length| {
23702 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23703 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23704
23705 /// The number of characters to dedicate to gaps and margins.
23706 const SPACING_WIDTH: usize = 4;
23707
23708 let max_char_count = max_author_length.min(renderer.max_author_length())
23709 + ::git::SHORT_SHA_LENGTH
23710 + MAX_RELATIVE_TIMESTAMP.len()
23711 + SPACING_WIDTH;
23712
23713 ch_advance * max_char_count
23714 });
23715
23716 let is_singleton = self.buffer_snapshot().is_singleton();
23717
23718 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23719 left_padding += if !is_singleton {
23720 ch_width * 4.0
23721 } else if show_runnables || show_breakpoints {
23722 ch_width * 3.0
23723 } else if show_git_gutter && show_line_numbers {
23724 ch_width * 2.0
23725 } else if show_git_gutter || show_line_numbers {
23726 ch_width
23727 } else {
23728 px(0.)
23729 };
23730
23731 let shows_folds = is_singleton && gutter_settings.folds;
23732
23733 let right_padding = if shows_folds && show_line_numbers {
23734 ch_width * 4.0
23735 } else if shows_folds || (!is_singleton && show_line_numbers) {
23736 ch_width * 3.0
23737 } else if show_line_numbers {
23738 ch_width
23739 } else {
23740 px(0.)
23741 };
23742
23743 Some(GutterDimensions {
23744 left_padding,
23745 right_padding,
23746 width: line_gutter_width + left_padding + right_padding,
23747 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23748 git_blame_entries_width,
23749 })
23750 }
23751
23752 pub fn render_crease_toggle(
23753 &self,
23754 buffer_row: MultiBufferRow,
23755 row_contains_cursor: bool,
23756 editor: Entity<Editor>,
23757 window: &mut Window,
23758 cx: &mut App,
23759 ) -> Option<AnyElement> {
23760 let folded = self.is_line_folded(buffer_row);
23761 let mut is_foldable = false;
23762
23763 if let Some(crease) = self
23764 .crease_snapshot
23765 .query_row(buffer_row, self.buffer_snapshot())
23766 {
23767 is_foldable = true;
23768 match crease {
23769 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23770 if let Some(render_toggle) = render_toggle {
23771 let toggle_callback =
23772 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23773 if folded {
23774 editor.update(cx, |editor, cx| {
23775 editor.fold_at(buffer_row, window, cx)
23776 });
23777 } else {
23778 editor.update(cx, |editor, cx| {
23779 editor.unfold_at(buffer_row, window, cx)
23780 });
23781 }
23782 });
23783 return Some((render_toggle)(
23784 buffer_row,
23785 folded,
23786 toggle_callback,
23787 window,
23788 cx,
23789 ));
23790 }
23791 }
23792 }
23793 }
23794
23795 is_foldable |= self.starts_indent(buffer_row);
23796
23797 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23798 Some(
23799 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23800 .toggle_state(folded)
23801 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23802 if folded {
23803 this.unfold_at(buffer_row, window, cx);
23804 } else {
23805 this.fold_at(buffer_row, window, cx);
23806 }
23807 }))
23808 .into_any_element(),
23809 )
23810 } else {
23811 None
23812 }
23813 }
23814
23815 pub fn render_crease_trailer(
23816 &self,
23817 buffer_row: MultiBufferRow,
23818 window: &mut Window,
23819 cx: &mut App,
23820 ) -> Option<AnyElement> {
23821 let folded = self.is_line_folded(buffer_row);
23822 if let Crease::Inline { render_trailer, .. } = self
23823 .crease_snapshot
23824 .query_row(buffer_row, self.buffer_snapshot())?
23825 {
23826 let render_trailer = render_trailer.as_ref()?;
23827 Some(render_trailer(buffer_row, folded, window, cx))
23828 } else {
23829 None
23830 }
23831 }
23832}
23833
23834impl Deref for EditorSnapshot {
23835 type Target = DisplaySnapshot;
23836
23837 fn deref(&self) -> &Self::Target {
23838 &self.display_snapshot
23839 }
23840}
23841
23842#[derive(Clone, Debug, PartialEq, Eq)]
23843pub enum EditorEvent {
23844 InputIgnored {
23845 text: Arc<str>,
23846 },
23847 InputHandled {
23848 utf16_range_to_replace: Option<Range<isize>>,
23849 text: Arc<str>,
23850 },
23851 ExcerptsAdded {
23852 buffer: Entity<Buffer>,
23853 predecessor: ExcerptId,
23854 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23855 },
23856 ExcerptsRemoved {
23857 ids: Vec<ExcerptId>,
23858 removed_buffer_ids: Vec<BufferId>,
23859 },
23860 BufferFoldToggled {
23861 ids: Vec<ExcerptId>,
23862 folded: bool,
23863 },
23864 ExcerptsEdited {
23865 ids: Vec<ExcerptId>,
23866 },
23867 ExcerptsExpanded {
23868 ids: Vec<ExcerptId>,
23869 },
23870 BufferEdited,
23871 Edited {
23872 transaction_id: clock::Lamport,
23873 },
23874 Reparsed(BufferId),
23875 Focused,
23876 FocusedIn,
23877 Blurred,
23878 DirtyChanged,
23879 Saved,
23880 TitleChanged,
23881 SelectionsChanged {
23882 local: bool,
23883 },
23884 ScrollPositionChanged {
23885 local: bool,
23886 autoscroll: bool,
23887 },
23888 TransactionUndone {
23889 transaction_id: clock::Lamport,
23890 },
23891 TransactionBegun {
23892 transaction_id: clock::Lamport,
23893 },
23894 CursorShapeChanged,
23895 BreadcrumbsChanged,
23896 PushedToNavHistory {
23897 anchor: Anchor,
23898 is_deactivate: bool,
23899 },
23900}
23901
23902impl EventEmitter<EditorEvent> for Editor {}
23903
23904impl Focusable for Editor {
23905 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23906 self.focus_handle.clone()
23907 }
23908}
23909
23910impl Render for Editor {
23911 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23912 let settings = ThemeSettings::get_global(cx);
23913
23914 let mut text_style = match self.mode {
23915 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23916 color: cx.theme().colors().editor_foreground,
23917 font_family: settings.ui_font.family.clone(),
23918 font_features: settings.ui_font.features.clone(),
23919 font_fallbacks: settings.ui_font.fallbacks.clone(),
23920 font_size: rems(0.875).into(),
23921 font_weight: settings.ui_font.weight,
23922 line_height: relative(settings.buffer_line_height.value()),
23923 ..Default::default()
23924 },
23925 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23926 color: cx.theme().colors().editor_foreground,
23927 font_family: settings.buffer_font.family.clone(),
23928 font_features: settings.buffer_font.features.clone(),
23929 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23930 font_size: settings.buffer_font_size(cx).into(),
23931 font_weight: settings.buffer_font.weight,
23932 line_height: relative(settings.buffer_line_height.value()),
23933 ..Default::default()
23934 },
23935 };
23936 if let Some(text_style_refinement) = &self.text_style_refinement {
23937 text_style.refine(text_style_refinement)
23938 }
23939
23940 let background = match self.mode {
23941 EditorMode::SingleLine => cx.theme().system().transparent,
23942 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23943 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23944 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23945 };
23946
23947 EditorElement::new(
23948 &cx.entity(),
23949 EditorStyle {
23950 background,
23951 border: cx.theme().colors().border,
23952 local_player: cx.theme().players().local(),
23953 text: text_style,
23954 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23955 syntax: cx.theme().syntax().clone(),
23956 status: cx.theme().status().clone(),
23957 inlay_hints_style: make_inlay_hints_style(cx),
23958 edit_prediction_styles: make_suggestion_styles(cx),
23959 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
23960 show_underlines: self.diagnostics_enabled(),
23961 },
23962 )
23963 }
23964}
23965
23966impl EntityInputHandler for Editor {
23967 fn text_for_range(
23968 &mut self,
23969 range_utf16: Range<usize>,
23970 adjusted_range: &mut Option<Range<usize>>,
23971 _: &mut Window,
23972 cx: &mut Context<Self>,
23973 ) -> Option<String> {
23974 let snapshot = self.buffer.read(cx).read(cx);
23975 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
23976 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
23977 if (start.0..end.0) != range_utf16 {
23978 adjusted_range.replace(start.0..end.0);
23979 }
23980 Some(snapshot.text_for_range(start..end).collect())
23981 }
23982
23983 fn selected_text_range(
23984 &mut self,
23985 ignore_disabled_input: bool,
23986 _: &mut Window,
23987 cx: &mut Context<Self>,
23988 ) -> Option<UTF16Selection> {
23989 // Prevent the IME menu from appearing when holding down an alphabetic key
23990 // while input is disabled.
23991 if !ignore_disabled_input && !self.input_enabled {
23992 return None;
23993 }
23994
23995 let selection = self
23996 .selections
23997 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
23998 let range = selection.range();
23999
24000 Some(UTF16Selection {
24001 range: range.start.0..range.end.0,
24002 reversed: selection.reversed,
24003 })
24004 }
24005
24006 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24007 let snapshot = self.buffer.read(cx).read(cx);
24008 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24009 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24010 }
24011
24012 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24013 self.clear_highlights::<InputComposition>(cx);
24014 self.ime_transaction.take();
24015 }
24016
24017 fn replace_text_in_range(
24018 &mut self,
24019 range_utf16: Option<Range<usize>>,
24020 text: &str,
24021 window: &mut Window,
24022 cx: &mut Context<Self>,
24023 ) {
24024 if !self.input_enabled {
24025 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24026 return;
24027 }
24028
24029 self.transact(window, cx, |this, window, cx| {
24030 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24031 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24032 Some(this.selection_replacement_ranges(range_utf16, cx))
24033 } else {
24034 this.marked_text_ranges(cx)
24035 };
24036
24037 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24038 let newest_selection_id = this.selections.newest_anchor().id;
24039 this.selections
24040 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24041 .iter()
24042 .zip(ranges_to_replace.iter())
24043 .find_map(|(selection, range)| {
24044 if selection.id == newest_selection_id {
24045 Some(
24046 (range.start.0 as isize - selection.head().0 as isize)
24047 ..(range.end.0 as isize - selection.head().0 as isize),
24048 )
24049 } else {
24050 None
24051 }
24052 })
24053 });
24054
24055 cx.emit(EditorEvent::InputHandled {
24056 utf16_range_to_replace: range_to_replace,
24057 text: text.into(),
24058 });
24059
24060 if let Some(new_selected_ranges) = new_selected_ranges {
24061 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24062 selections.select_ranges(new_selected_ranges)
24063 });
24064 this.backspace(&Default::default(), window, cx);
24065 }
24066
24067 this.handle_input(text, window, cx);
24068 });
24069
24070 if let Some(transaction) = self.ime_transaction {
24071 self.buffer.update(cx, |buffer, cx| {
24072 buffer.group_until_transaction(transaction, cx);
24073 });
24074 }
24075
24076 self.unmark_text(window, cx);
24077 }
24078
24079 fn replace_and_mark_text_in_range(
24080 &mut self,
24081 range_utf16: Option<Range<usize>>,
24082 text: &str,
24083 new_selected_range_utf16: Option<Range<usize>>,
24084 window: &mut Window,
24085 cx: &mut Context<Self>,
24086 ) {
24087 if !self.input_enabled {
24088 return;
24089 }
24090
24091 let transaction = self.transact(window, cx, |this, window, cx| {
24092 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24093 let snapshot = this.buffer.read(cx).read(cx);
24094 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24095 for marked_range in &mut marked_ranges {
24096 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24097 marked_range.start.0 += relative_range_utf16.start;
24098 marked_range.start =
24099 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24100 marked_range.end =
24101 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24102 }
24103 }
24104 Some(marked_ranges)
24105 } else if let Some(range_utf16) = range_utf16 {
24106 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24107 Some(this.selection_replacement_ranges(range_utf16, cx))
24108 } else {
24109 None
24110 };
24111
24112 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24113 let newest_selection_id = this.selections.newest_anchor().id;
24114 this.selections
24115 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24116 .iter()
24117 .zip(ranges_to_replace.iter())
24118 .find_map(|(selection, range)| {
24119 if selection.id == newest_selection_id {
24120 Some(
24121 (range.start.0 as isize - selection.head().0 as isize)
24122 ..(range.end.0 as isize - selection.head().0 as isize),
24123 )
24124 } else {
24125 None
24126 }
24127 })
24128 });
24129
24130 cx.emit(EditorEvent::InputHandled {
24131 utf16_range_to_replace: range_to_replace,
24132 text: text.into(),
24133 });
24134
24135 if let Some(ranges) = ranges_to_replace {
24136 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24137 s.select_ranges(ranges)
24138 });
24139 }
24140
24141 let marked_ranges = {
24142 let snapshot = this.buffer.read(cx).read(cx);
24143 this.selections
24144 .disjoint_anchors_arc()
24145 .iter()
24146 .map(|selection| {
24147 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24148 })
24149 .collect::<Vec<_>>()
24150 };
24151
24152 if text.is_empty() {
24153 this.unmark_text(window, cx);
24154 } else {
24155 this.highlight_text::<InputComposition>(
24156 marked_ranges.clone(),
24157 HighlightStyle {
24158 underline: Some(UnderlineStyle {
24159 thickness: px(1.),
24160 color: None,
24161 wavy: false,
24162 }),
24163 ..Default::default()
24164 },
24165 cx,
24166 );
24167 }
24168
24169 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24170 let use_autoclose = this.use_autoclose;
24171 let use_auto_surround = this.use_auto_surround;
24172 this.set_use_autoclose(false);
24173 this.set_use_auto_surround(false);
24174 this.handle_input(text, window, cx);
24175 this.set_use_autoclose(use_autoclose);
24176 this.set_use_auto_surround(use_auto_surround);
24177
24178 if let Some(new_selected_range) = new_selected_range_utf16 {
24179 let snapshot = this.buffer.read(cx).read(cx);
24180 let new_selected_ranges = marked_ranges
24181 .into_iter()
24182 .map(|marked_range| {
24183 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24184 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24185 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24186 snapshot.clip_offset_utf16(new_start, Bias::Left)
24187 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24188 })
24189 .collect::<Vec<_>>();
24190
24191 drop(snapshot);
24192 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24193 selections.select_ranges(new_selected_ranges)
24194 });
24195 }
24196 });
24197
24198 self.ime_transaction = self.ime_transaction.or(transaction);
24199 if let Some(transaction) = self.ime_transaction {
24200 self.buffer.update(cx, |buffer, cx| {
24201 buffer.group_until_transaction(transaction, cx);
24202 });
24203 }
24204
24205 if self.text_highlights::<InputComposition>(cx).is_none() {
24206 self.ime_transaction.take();
24207 }
24208 }
24209
24210 fn bounds_for_range(
24211 &mut self,
24212 range_utf16: Range<usize>,
24213 element_bounds: gpui::Bounds<Pixels>,
24214 window: &mut Window,
24215 cx: &mut Context<Self>,
24216 ) -> Option<gpui::Bounds<Pixels>> {
24217 let text_layout_details = self.text_layout_details(window);
24218 let CharacterDimensions {
24219 em_width,
24220 em_advance,
24221 line_height,
24222 } = self.character_dimensions(window);
24223
24224 let snapshot = self.snapshot(window, cx);
24225 let scroll_position = snapshot.scroll_position();
24226 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24227
24228 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24229 let x = Pixels::from(
24230 ScrollOffset::from(
24231 snapshot.x_for_display_point(start, &text_layout_details)
24232 + self.gutter_dimensions.full_width(),
24233 ) - scroll_left,
24234 );
24235 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24236
24237 Some(Bounds {
24238 origin: element_bounds.origin + point(x, y),
24239 size: size(em_width, line_height),
24240 })
24241 }
24242
24243 fn character_index_for_point(
24244 &mut self,
24245 point: gpui::Point<Pixels>,
24246 _window: &mut Window,
24247 _cx: &mut Context<Self>,
24248 ) -> Option<usize> {
24249 let position_map = self.last_position_map.as_ref()?;
24250 if !position_map.text_hitbox.contains(&point) {
24251 return None;
24252 }
24253 let display_point = position_map.point_for_position(point).previous_valid;
24254 let anchor = position_map
24255 .snapshot
24256 .display_point_to_anchor(display_point, Bias::Left);
24257 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24258 Some(utf16_offset.0)
24259 }
24260}
24261
24262trait SelectionExt {
24263 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24264 fn spanned_rows(
24265 &self,
24266 include_end_if_at_line_start: bool,
24267 map: &DisplaySnapshot,
24268 ) -> Range<MultiBufferRow>;
24269}
24270
24271impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24272 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24273 let start = self
24274 .start
24275 .to_point(map.buffer_snapshot())
24276 .to_display_point(map);
24277 let end = self
24278 .end
24279 .to_point(map.buffer_snapshot())
24280 .to_display_point(map);
24281 if self.reversed {
24282 end..start
24283 } else {
24284 start..end
24285 }
24286 }
24287
24288 fn spanned_rows(
24289 &self,
24290 include_end_if_at_line_start: bool,
24291 map: &DisplaySnapshot,
24292 ) -> Range<MultiBufferRow> {
24293 let start = self.start.to_point(map.buffer_snapshot());
24294 let mut end = self.end.to_point(map.buffer_snapshot());
24295 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24296 end.row -= 1;
24297 }
24298
24299 let buffer_start = map.prev_line_boundary(start).0;
24300 let buffer_end = map.next_line_boundary(end).0;
24301 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24302 }
24303}
24304
24305impl<T: InvalidationRegion> InvalidationStack<T> {
24306 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24307 where
24308 S: Clone + ToOffset,
24309 {
24310 while let Some(region) = self.last() {
24311 let all_selections_inside_invalidation_ranges =
24312 if selections.len() == region.ranges().len() {
24313 selections
24314 .iter()
24315 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24316 .all(|(selection, invalidation_range)| {
24317 let head = selection.head().to_offset(buffer);
24318 invalidation_range.start <= head && invalidation_range.end >= head
24319 })
24320 } else {
24321 false
24322 };
24323
24324 if all_selections_inside_invalidation_ranges {
24325 break;
24326 } else {
24327 self.pop();
24328 }
24329 }
24330 }
24331}
24332
24333impl<T> Default for InvalidationStack<T> {
24334 fn default() -> Self {
24335 Self(Default::default())
24336 }
24337}
24338
24339impl<T> Deref for InvalidationStack<T> {
24340 type Target = Vec<T>;
24341
24342 fn deref(&self) -> &Self::Target {
24343 &self.0
24344 }
24345}
24346
24347impl<T> DerefMut for InvalidationStack<T> {
24348 fn deref_mut(&mut self) -> &mut Self::Target {
24349 &mut self.0
24350 }
24351}
24352
24353impl InvalidationRegion for SnippetState {
24354 fn ranges(&self) -> &[Range<Anchor>] {
24355 &self.ranges[self.active_index]
24356 }
24357}
24358
24359fn edit_prediction_edit_text(
24360 current_snapshot: &BufferSnapshot,
24361 edits: &[(Range<Anchor>, String)],
24362 edit_preview: &EditPreview,
24363 include_deletions: bool,
24364 cx: &App,
24365) -> HighlightedText {
24366 let edits = edits
24367 .iter()
24368 .map(|(anchor, text)| {
24369 (
24370 anchor.start.text_anchor..anchor.end.text_anchor,
24371 text.clone(),
24372 )
24373 })
24374 .collect::<Vec<_>>();
24375
24376 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24377}
24378
24379fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, String)], cx: &App) -> HighlightedText {
24380 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24381 // Just show the raw edit text with basic styling
24382 let mut text = String::new();
24383 let mut highlights = Vec::new();
24384
24385 let insertion_highlight_style = HighlightStyle {
24386 color: Some(cx.theme().colors().text),
24387 ..Default::default()
24388 };
24389
24390 for (_, edit_text) in edits {
24391 let start_offset = text.len();
24392 text.push_str(edit_text);
24393 let end_offset = text.len();
24394
24395 if start_offset < end_offset {
24396 highlights.push((start_offset..end_offset, insertion_highlight_style));
24397 }
24398 }
24399
24400 HighlightedText {
24401 text: text.into(),
24402 highlights,
24403 }
24404}
24405
24406pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24407 match severity {
24408 lsp::DiagnosticSeverity::ERROR => colors.error,
24409 lsp::DiagnosticSeverity::WARNING => colors.warning,
24410 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24411 lsp::DiagnosticSeverity::HINT => colors.info,
24412 _ => colors.ignored,
24413 }
24414}
24415
24416pub fn styled_runs_for_code_label<'a>(
24417 label: &'a CodeLabel,
24418 syntax_theme: &'a theme::SyntaxTheme,
24419) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24420 let fade_out = HighlightStyle {
24421 fade_out: Some(0.35),
24422 ..Default::default()
24423 };
24424
24425 let mut prev_end = label.filter_range.end;
24426 label
24427 .runs
24428 .iter()
24429 .enumerate()
24430 .flat_map(move |(ix, (range, highlight_id))| {
24431 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24432 style
24433 } else {
24434 return Default::default();
24435 };
24436 let muted_style = style.highlight(fade_out);
24437
24438 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24439 if range.start >= label.filter_range.end {
24440 if range.start > prev_end {
24441 runs.push((prev_end..range.start, fade_out));
24442 }
24443 runs.push((range.clone(), muted_style));
24444 } else if range.end <= label.filter_range.end {
24445 runs.push((range.clone(), style));
24446 } else {
24447 runs.push((range.start..label.filter_range.end, style));
24448 runs.push((label.filter_range.end..range.end, muted_style));
24449 }
24450 prev_end = cmp::max(prev_end, range.end);
24451
24452 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24453 runs.push((prev_end..label.text.len(), fade_out));
24454 }
24455
24456 runs
24457 })
24458}
24459
24460pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24461 let mut prev_index = 0;
24462 let mut prev_codepoint: Option<char> = None;
24463 text.char_indices()
24464 .chain([(text.len(), '\0')])
24465 .filter_map(move |(index, codepoint)| {
24466 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24467 let is_boundary = index == text.len()
24468 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24469 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24470 if is_boundary {
24471 let chunk = &text[prev_index..index];
24472 prev_index = index;
24473 Some(chunk)
24474 } else {
24475 None
24476 }
24477 })
24478}
24479
24480pub trait RangeToAnchorExt: Sized {
24481 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24482
24483 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24484 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24485 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24486 }
24487}
24488
24489impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24490 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24491 let start_offset = self.start.to_offset(snapshot);
24492 let end_offset = self.end.to_offset(snapshot);
24493 if start_offset == end_offset {
24494 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24495 } else {
24496 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24497 }
24498 }
24499}
24500
24501pub trait RowExt {
24502 fn as_f64(&self) -> f64;
24503
24504 fn next_row(&self) -> Self;
24505
24506 fn previous_row(&self) -> Self;
24507
24508 fn minus(&self, other: Self) -> u32;
24509}
24510
24511impl RowExt for DisplayRow {
24512 fn as_f64(&self) -> f64 {
24513 self.0 as _
24514 }
24515
24516 fn next_row(&self) -> Self {
24517 Self(self.0 + 1)
24518 }
24519
24520 fn previous_row(&self) -> Self {
24521 Self(self.0.saturating_sub(1))
24522 }
24523
24524 fn minus(&self, other: Self) -> u32 {
24525 self.0 - other.0
24526 }
24527}
24528
24529impl RowExt for MultiBufferRow {
24530 fn as_f64(&self) -> f64 {
24531 self.0 as _
24532 }
24533
24534 fn next_row(&self) -> Self {
24535 Self(self.0 + 1)
24536 }
24537
24538 fn previous_row(&self) -> Self {
24539 Self(self.0.saturating_sub(1))
24540 }
24541
24542 fn minus(&self, other: Self) -> u32 {
24543 self.0 - other.0
24544 }
24545}
24546
24547trait RowRangeExt {
24548 type Row;
24549
24550 fn len(&self) -> usize;
24551
24552 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24553}
24554
24555impl RowRangeExt for Range<MultiBufferRow> {
24556 type Row = MultiBufferRow;
24557
24558 fn len(&self) -> usize {
24559 (self.end.0 - self.start.0) as usize
24560 }
24561
24562 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24563 (self.start.0..self.end.0).map(MultiBufferRow)
24564 }
24565}
24566
24567impl RowRangeExt for Range<DisplayRow> {
24568 type Row = DisplayRow;
24569
24570 fn len(&self) -> usize {
24571 (self.end.0 - self.start.0) as usize
24572 }
24573
24574 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24575 (self.start.0..self.end.0).map(DisplayRow)
24576 }
24577}
24578
24579/// If select range has more than one line, we
24580/// just point the cursor to range.start.
24581fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24582 if range.start.row == range.end.row {
24583 range
24584 } else {
24585 range.start..range.start
24586 }
24587}
24588pub struct KillRing(ClipboardItem);
24589impl Global for KillRing {}
24590
24591const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24592
24593enum BreakpointPromptEditAction {
24594 Log,
24595 Condition,
24596 HitCondition,
24597}
24598
24599struct BreakpointPromptEditor {
24600 pub(crate) prompt: Entity<Editor>,
24601 editor: WeakEntity<Editor>,
24602 breakpoint_anchor: Anchor,
24603 breakpoint: Breakpoint,
24604 edit_action: BreakpointPromptEditAction,
24605 block_ids: HashSet<CustomBlockId>,
24606 editor_margins: Arc<Mutex<EditorMargins>>,
24607 _subscriptions: Vec<Subscription>,
24608}
24609
24610impl BreakpointPromptEditor {
24611 const MAX_LINES: u8 = 4;
24612
24613 fn new(
24614 editor: WeakEntity<Editor>,
24615 breakpoint_anchor: Anchor,
24616 breakpoint: Breakpoint,
24617 edit_action: BreakpointPromptEditAction,
24618 window: &mut Window,
24619 cx: &mut Context<Self>,
24620 ) -> Self {
24621 let base_text = match edit_action {
24622 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24623 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24624 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24625 }
24626 .map(|msg| msg.to_string())
24627 .unwrap_or_default();
24628
24629 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24630 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24631
24632 let prompt = cx.new(|cx| {
24633 let mut prompt = Editor::new(
24634 EditorMode::AutoHeight {
24635 min_lines: 1,
24636 max_lines: Some(Self::MAX_LINES as usize),
24637 },
24638 buffer,
24639 None,
24640 window,
24641 cx,
24642 );
24643 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24644 prompt.set_show_cursor_when_unfocused(false, cx);
24645 prompt.set_placeholder_text(
24646 match edit_action {
24647 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24648 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24649 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24650 },
24651 window,
24652 cx,
24653 );
24654
24655 prompt
24656 });
24657
24658 Self {
24659 prompt,
24660 editor,
24661 breakpoint_anchor,
24662 breakpoint,
24663 edit_action,
24664 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24665 block_ids: Default::default(),
24666 _subscriptions: vec![],
24667 }
24668 }
24669
24670 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24671 self.block_ids.extend(block_ids)
24672 }
24673
24674 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24675 if let Some(editor) = self.editor.upgrade() {
24676 let message = self
24677 .prompt
24678 .read(cx)
24679 .buffer
24680 .read(cx)
24681 .as_singleton()
24682 .expect("A multi buffer in breakpoint prompt isn't possible")
24683 .read(cx)
24684 .as_rope()
24685 .to_string();
24686
24687 editor.update(cx, |editor, cx| {
24688 editor.edit_breakpoint_at_anchor(
24689 self.breakpoint_anchor,
24690 self.breakpoint.clone(),
24691 match self.edit_action {
24692 BreakpointPromptEditAction::Log => {
24693 BreakpointEditAction::EditLogMessage(message.into())
24694 }
24695 BreakpointPromptEditAction::Condition => {
24696 BreakpointEditAction::EditCondition(message.into())
24697 }
24698 BreakpointPromptEditAction::HitCondition => {
24699 BreakpointEditAction::EditHitCondition(message.into())
24700 }
24701 },
24702 cx,
24703 );
24704
24705 editor.remove_blocks(self.block_ids.clone(), None, cx);
24706 cx.focus_self(window);
24707 });
24708 }
24709 }
24710
24711 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24712 self.editor
24713 .update(cx, |editor, cx| {
24714 editor.remove_blocks(self.block_ids.clone(), None, cx);
24715 window.focus(&editor.focus_handle);
24716 })
24717 .log_err();
24718 }
24719
24720 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24721 let settings = ThemeSettings::get_global(cx);
24722 let text_style = TextStyle {
24723 color: if self.prompt.read(cx).read_only(cx) {
24724 cx.theme().colors().text_disabled
24725 } else {
24726 cx.theme().colors().text
24727 },
24728 font_family: settings.buffer_font.family.clone(),
24729 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24730 font_size: settings.buffer_font_size(cx).into(),
24731 font_weight: settings.buffer_font.weight,
24732 line_height: relative(settings.buffer_line_height.value()),
24733 ..Default::default()
24734 };
24735 EditorElement::new(
24736 &self.prompt,
24737 EditorStyle {
24738 background: cx.theme().colors().editor_background,
24739 local_player: cx.theme().players().local(),
24740 text: text_style,
24741 ..Default::default()
24742 },
24743 )
24744 }
24745}
24746
24747impl Render for BreakpointPromptEditor {
24748 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24749 let editor_margins = *self.editor_margins.lock();
24750 let gutter_dimensions = editor_margins.gutter;
24751 h_flex()
24752 .key_context("Editor")
24753 .bg(cx.theme().colors().editor_background)
24754 .border_y_1()
24755 .border_color(cx.theme().status().info_border)
24756 .size_full()
24757 .py(window.line_height() / 2.5)
24758 .on_action(cx.listener(Self::confirm))
24759 .on_action(cx.listener(Self::cancel))
24760 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24761 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24762 }
24763}
24764
24765impl Focusable for BreakpointPromptEditor {
24766 fn focus_handle(&self, cx: &App) -> FocusHandle {
24767 self.prompt.focus_handle(cx)
24768 }
24769}
24770
24771fn all_edits_insertions_or_deletions(
24772 edits: &Vec<(Range<Anchor>, String)>,
24773 snapshot: &MultiBufferSnapshot,
24774) -> bool {
24775 let mut all_insertions = true;
24776 let mut all_deletions = true;
24777
24778 for (range, new_text) in edits.iter() {
24779 let range_is_empty = range.to_offset(snapshot).is_empty();
24780 let text_is_empty = new_text.is_empty();
24781
24782 if range_is_empty != text_is_empty {
24783 if range_is_empty {
24784 all_deletions = false;
24785 } else {
24786 all_insertions = false;
24787 }
24788 } else {
24789 return false;
24790 }
24791
24792 if !all_insertions && !all_deletions {
24793 return false;
24794 }
24795 }
24796 all_insertions || all_deletions
24797}
24798
24799struct MissingEditPredictionKeybindingTooltip;
24800
24801impl Render for MissingEditPredictionKeybindingTooltip {
24802 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24803 ui::tooltip_container(cx, |container, cx| {
24804 container
24805 .flex_shrink_0()
24806 .max_w_80()
24807 .min_h(rems_from_px(124.))
24808 .justify_between()
24809 .child(
24810 v_flex()
24811 .flex_1()
24812 .text_ui_sm(cx)
24813 .child(Label::new("Conflict with Accept Keybinding"))
24814 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24815 )
24816 .child(
24817 h_flex()
24818 .pb_1()
24819 .gap_1()
24820 .items_end()
24821 .w_full()
24822 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24823 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24824 }))
24825 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24826 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24827 })),
24828 )
24829 })
24830 }
24831}
24832
24833#[derive(Debug, Clone, Copy, PartialEq)]
24834pub struct LineHighlight {
24835 pub background: Background,
24836 pub border: Option<gpui::Hsla>,
24837 pub include_gutter: bool,
24838 pub type_id: Option<TypeId>,
24839}
24840
24841struct LineManipulationResult {
24842 pub new_text: String,
24843 pub line_count_before: usize,
24844 pub line_count_after: usize,
24845}
24846
24847fn render_diff_hunk_controls(
24848 row: u32,
24849 status: &DiffHunkStatus,
24850 hunk_range: Range<Anchor>,
24851 is_created_file: bool,
24852 line_height: Pixels,
24853 editor: &Entity<Editor>,
24854 _window: &mut Window,
24855 cx: &mut App,
24856) -> AnyElement {
24857 h_flex()
24858 .h(line_height)
24859 .mr_1()
24860 .gap_1()
24861 .px_0p5()
24862 .pb_1()
24863 .border_x_1()
24864 .border_b_1()
24865 .border_color(cx.theme().colors().border_variant)
24866 .rounded_b_lg()
24867 .bg(cx.theme().colors().editor_background)
24868 .gap_1()
24869 .block_mouse_except_scroll()
24870 .shadow_md()
24871 .child(if status.has_secondary_hunk() {
24872 Button::new(("stage", row as u64), "Stage")
24873 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24874 .tooltip({
24875 let focus_handle = editor.focus_handle(cx);
24876 move |window, cx| {
24877 Tooltip::for_action_in(
24878 "Stage Hunk",
24879 &::git::ToggleStaged,
24880 &focus_handle,
24881 window,
24882 cx,
24883 )
24884 }
24885 })
24886 .on_click({
24887 let editor = editor.clone();
24888 move |_event, _window, cx| {
24889 editor.update(cx, |editor, cx| {
24890 editor.stage_or_unstage_diff_hunks(
24891 true,
24892 vec![hunk_range.start..hunk_range.start],
24893 cx,
24894 );
24895 });
24896 }
24897 })
24898 } else {
24899 Button::new(("unstage", row as u64), "Unstage")
24900 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24901 .tooltip({
24902 let focus_handle = editor.focus_handle(cx);
24903 move |window, cx| {
24904 Tooltip::for_action_in(
24905 "Unstage Hunk",
24906 &::git::ToggleStaged,
24907 &focus_handle,
24908 window,
24909 cx,
24910 )
24911 }
24912 })
24913 .on_click({
24914 let editor = editor.clone();
24915 move |_event, _window, cx| {
24916 editor.update(cx, |editor, cx| {
24917 editor.stage_or_unstage_diff_hunks(
24918 false,
24919 vec![hunk_range.start..hunk_range.start],
24920 cx,
24921 );
24922 });
24923 }
24924 })
24925 })
24926 .child(
24927 Button::new(("restore", row as u64), "Restore")
24928 .tooltip({
24929 let focus_handle = editor.focus_handle(cx);
24930 move |window, cx| {
24931 Tooltip::for_action_in(
24932 "Restore Hunk",
24933 &::git::Restore,
24934 &focus_handle,
24935 window,
24936 cx,
24937 )
24938 }
24939 })
24940 .on_click({
24941 let editor = editor.clone();
24942 move |_event, window, cx| {
24943 editor.update(cx, |editor, cx| {
24944 let snapshot = editor.snapshot(window, cx);
24945 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
24946 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
24947 });
24948 }
24949 })
24950 .disabled(is_created_file),
24951 )
24952 .when(
24953 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
24954 |el| {
24955 el.child(
24956 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
24957 .shape(IconButtonShape::Square)
24958 .icon_size(IconSize::Small)
24959 // .disabled(!has_multiple_hunks)
24960 .tooltip({
24961 let focus_handle = editor.focus_handle(cx);
24962 move |window, cx| {
24963 Tooltip::for_action_in(
24964 "Next Hunk",
24965 &GoToHunk,
24966 &focus_handle,
24967 window,
24968 cx,
24969 )
24970 }
24971 })
24972 .on_click({
24973 let editor = editor.clone();
24974 move |_event, window, cx| {
24975 editor.update(cx, |editor, cx| {
24976 let snapshot = editor.snapshot(window, cx);
24977 let position =
24978 hunk_range.end.to_point(&snapshot.buffer_snapshot());
24979 editor.go_to_hunk_before_or_after_position(
24980 &snapshot,
24981 position,
24982 Direction::Next,
24983 window,
24984 cx,
24985 );
24986 editor.expand_selected_diff_hunks(cx);
24987 });
24988 }
24989 }),
24990 )
24991 .child(
24992 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
24993 .shape(IconButtonShape::Square)
24994 .icon_size(IconSize::Small)
24995 // .disabled(!has_multiple_hunks)
24996 .tooltip({
24997 let focus_handle = editor.focus_handle(cx);
24998 move |window, cx| {
24999 Tooltip::for_action_in(
25000 "Previous Hunk",
25001 &GoToPreviousHunk,
25002 &focus_handle,
25003 window,
25004 cx,
25005 )
25006 }
25007 })
25008 .on_click({
25009 let editor = editor.clone();
25010 move |_event, window, cx| {
25011 editor.update(cx, |editor, cx| {
25012 let snapshot = editor.snapshot(window, cx);
25013 let point =
25014 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25015 editor.go_to_hunk_before_or_after_position(
25016 &snapshot,
25017 point,
25018 Direction::Prev,
25019 window,
25020 cx,
25021 );
25022 editor.expand_selected_diff_hunks(cx);
25023 });
25024 }
25025 }),
25026 )
25027 },
25028 )
25029 .into_any_element()
25030}
25031
25032pub fn multibuffer_context_lines(cx: &App) -> u32 {
25033 EditorSettings::try_get(cx)
25034 .map(|settings| settings.excerpt_context_lines)
25035 .unwrap_or(2)
25036 .min(32)
25037}